Skip to content

Commit

Permalink
Add support for Project API Keys
Browse files Browse the repository at this point in the history
  • Loading branch information
StefanBratanov committed Aug 29, 2024
1 parent b477db1 commit 702e55f
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 5 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ ChatCompletion chatCompletion = chatClient.createChatCompletion(createChatComple
| [Projects](https://platform.openai.com/docs/api-reference/projects) | ✔️ |
| [Project Users](https://platform.openai.com/docs/api-reference/project-users) | ✔️ |
| [Project Service Accounts](https://platform.openai.com/docs/api-reference/project-service-accounts) | ✔️ |
| [Project API Keys](https://platform.openai.com/docs/api-reference/project-api-keys) | |
| [Project API Keys](https://platform.openai.com/docs/api-reference/project-api-keys) | ✔️ |
| [Audit Logs](https://platform.openai.com/docs/api-reference/audit-logs) | |

> **_NOTE:_** Legacy APIs are not supported
Expand Down
15 changes: 13 additions & 2 deletions src/main/java/io/github/stefanbratanov/jvm/openai/OpenAI.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
import java.util.Optional;

/**
* A class which when created using the {@link #newBuilder(String)} can be used to create clients
* based on the endpoints defined at <a href="https://platform.openai.com/docs/api-reference">API
* A class which when created using the {@link OpenAI.Builder} can be used to create clients based
* on the endpoints defined at <a href="https://platform.openai.com/docs/api-reference">API
* Reference - OpenAI API</a>
*/
public final class OpenAI {
Expand Down Expand Up @@ -37,6 +37,7 @@ public final class OpenAI {
private final ProjectsClient projectsClient;
private final ProjectUsersClient projectUsersClient;
private final ProjectServiceAccountsClient projectServiceAccountsClient;
private final ProjectApiKeysClient projectApiKeysClient;

private OpenAI(
URI baseUrl,
Expand Down Expand Up @@ -86,6 +87,8 @@ private OpenAI(
projectServiceAccountsClient =
new ProjectServiceAccountsClient(
baseUrl, adminAuthenticationHeaders, httpClient, requestTimeout);
projectApiKeysClient =
new ProjectApiKeysClient(baseUrl, adminAuthenticationHeaders, httpClient, requestTimeout);
}

/**
Expand Down Expand Up @@ -276,6 +279,14 @@ public ProjectServiceAccountsClient projectServiceAccountsClient() {
return projectServiceAccountsClient;
}

/**
* @return a client based on <a
* href="https://platform.openai.com/docs/api-reference/project-api-keys">Project API Keys</a>
*/
public ProjectApiKeysClient projectApiKeysClient() {
return projectApiKeysClient;
}

private String[] createAuthenticationHeaders(
Optional<String> apiKey, Optional<String> organization, Optional<String> project) {
List<String> authHeaders = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.github.stefanbratanov.jvm.openai;

/** Represents an individual API key in a project. */
public record ProjectApiKey(
String redactedValue, String name, long createdAt, String id, Owner owner) {

public record Owner(String type, ProjectUser user, ProjectServiceAccount serviceAccount) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package io.github.stefanbratanov.jvm.openai;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
* Manage API keys for a given project. Supports listing and deleting keys for users. This API does
* not allow issuing keys for users, as users need to authorize themselves to generate keys.
*
* <p>Based on <a href="https://platform.openai.com/docs/api-reference/project-api-keys">Project API
* Keys</a>
*/
public final class ProjectApiKeysClient extends OpenAIClient {

private static final String API_KEYS_SEGMENT = "/api_keys";

private final URI baseUrl;

ProjectApiKeysClient(
URI baseUrl,
String[] authenticationHeaders,
HttpClient httpClient,
Optional<Duration> requestTimeout) {
super(authenticationHeaders, httpClient, requestTimeout);
this.baseUrl = baseUrl;
}

/**
* Returns a list of API keys in the project.
*
* @param projectId The ID of the project.
* @param after A cursor for use in pagination. after is an object ID that defines your place in
* the list.
* @param limit A limit on the number of objects to be returned.
* @throws OpenAIException in case of API errors
*/
public PaginatedProjectApiKeys listProjectApiKeys(
String projectId, Optional<String> after, Optional<Integer> limit) {
String queryParameters =
createQueryParameters(
Map.of(Constants.LIMIT_QUERY_PARAMETER, limit, Constants.AFTER_QUERY_PARAMETER, after));
HttpRequest httpRequest =
newHttpRequestBuilder()
.uri(
baseUrl.resolve(
Endpoint.PROJECTS.getPath()
+ "/"
+ projectId
+ API_KEYS_SEGMENT
+ queryParameters))
.GET()
.build();
HttpResponse<byte[]> httpResponse = sendHttpRequest(httpRequest);
return deserializeResponse(httpResponse.body(), PaginatedProjectApiKeys.class);
}

public record PaginatedProjectApiKeys(
List<ProjectApiKey> data, String firstId, String lastId, boolean hasMore) {}

/**
* Retrieves an API key in the project.
*
* @param projectId The ID of the project.
* @param keyId The ID of the API key.
* @throws OpenAIException in case of API errors
*/
public ProjectApiKey retrieveProjectApiKey(String projectId, String keyId) {
HttpRequest httpRequest =
newHttpRequestBuilder()
.uri(
baseUrl.resolve(
Endpoint.PROJECTS.getPath() + "/" + projectId + API_KEYS_SEGMENT + "/" + keyId))
.GET()
.build();
HttpResponse<byte[]> httpResponse = sendHttpRequest(httpRequest);
return deserializeResponse(httpResponse.body(), ProjectApiKey.class);
}

/**
* Deletes an API key from the project.
*
* @param projectId The ID of the project.
* @param keyId The ID of the API key.
* @throws OpenAIException in case of API errors
*/
public DeletionStatus deleteProjectApiKey(String projectId, String keyId) {
HttpRequest httpRequest =
newHttpRequestBuilder()
.uri(
baseUrl.resolve(
Endpoint.PROJECTS.getPath() + "/" + projectId + API_KEYS_SEGMENT + "/" + keyId))
.DELETE()
.build();
HttpResponse<byte[]> httpResponse = sendHttpRequest(httpRequest);
return deserializeResponse(httpResponse.body(), DeletionStatus.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
* deleted from a project.
*
* <p>Based on <a
* href="https://platform.openai.com/docs/api-reference/project-service-accounts/list">Project
* Service Accounts</a>
* href="https://platform.openai.com/docs/api-reference/project-service-accounts">Project Service
* Accounts</a>
*/
public final class ProjectServiceAccountsClient extends OpenAIClient {

Expand All @@ -37,6 +37,7 @@ public final class ProjectServiceAccountsClient extends OpenAIClient {
/**
* Returns a list of service accounts in the project.
*
* @param projectId The ID of the project.
* @param after A cursor for use in pagination. after is an object ID that defines your place in
* the list.
* @param limit A limit on the number of objects to be returned.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,27 @@ void testProjectServiceAccountsClient() {
assertThat(deletionStatus.deleted()).isTrue();
}

@Test
void testProjectApiKeysClient() {
ProjectApiKeysClient projectApiKeysClient = openAI.projectApiKeysClient();

Project project = retrieveProject();

List<ProjectApiKey> projectApiKeys =
projectApiKeysClient
.listProjectApiKeys(project.id(), Optional.empty(), Optional.empty())
.data();

assertThat(projectApiKeys).isNotEmpty();

ProjectApiKey projectApiKey = projectApiKeys.get(0);

ProjectApiKey retrievedProjectApiKey =
projectApiKeysClient.retrieveProjectApiKey(project.id(), projectApiKey.id());

assertThat(retrievedProjectApiKey).isEqualTo(projectApiKey);
}

private Project retrieveProject() {
return openAI
.projectsClient()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.atlassian.oai.validator.report.ValidationReport;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.stefanbratanov.jvm.openai.ProjectApiKeysClient.PaginatedProjectApiKeys;
import io.github.stefanbratanov.jvm.openai.ProjectServiceAccountsClient.ProjectServiceAccountCreateResponse;
import io.github.stefanbratanov.jvm.openai.RunStepsClient.PaginatedThreadRunSteps;
import io.swagger.v3.oas.models.OpenAPI;
Expand Down Expand Up @@ -526,6 +527,20 @@ void validateProjectServiceAccounts() {
validate(request, response);
}

@RepeatedTest(25)
void validateProjectApiKeys() {
Request request =
new SimpleRequest.Builder(
Method.GET, "/" + Endpoint.PROJECTS.getPath() + "/{project_id}/api_keys")
.build();

PaginatedProjectApiKeys paginatedProjectApiKeys = testDataUtil.randomPaginatedProjectApiKeys();

Response response = createResponseWithBody(serializeObject(paginatedProjectApiKeys));

validate(request, response);
}

private void validate(Request request, Response response, String... reportMessagesToIgnore) {
ValidationReport report = validator.validate(request, response);
validateReport(report, reportMessagesToIgnore);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import io.github.stefanbratanov.jvm.openai.CreateChatCompletionRequest.StreamOptions;
import io.github.stefanbratanov.jvm.openai.FineTuningJobIntegration.Wandb;
import io.github.stefanbratanov.jvm.openai.ProjectApiKey.Owner;
import io.github.stefanbratanov.jvm.openai.ProjectApiKeysClient.PaginatedProjectApiKeys;
import io.github.stefanbratanov.jvm.openai.ProjectServiceAccountsClient.ApiKey;
import io.github.stefanbratanov.jvm.openai.ProjectServiceAccountsClient.ProjectServiceAccountCreateResponse;
import io.github.stefanbratanov.jvm.openai.RunStepsClient.PaginatedThreadRunSteps;
Expand Down Expand Up @@ -791,6 +793,24 @@ public ProjectServiceAccountCreateResponse randomProjectServiceAccountCreateResp
randomString(5), randomString(7), randomString(12), randomLong(9999, 1_000_000)));
}

public PaginatedProjectApiKeys randomPaginatedProjectApiKeys() {
return new PaginatedProjectApiKeys(
listOf(randomInt(1, 5), this::randomProjectApiKey),
randomString(5),
randomString(5),
randomBoolean());
}

private ProjectApiKey randomProjectApiKey() {
return new ProjectApiKey(
randomString(8),
randomString(5),
randomLong(10_000, 99_999),
randomString(6),
new Owner(
oneOf("user", "service_account"), randomProjectUser(), randomProjectServiceAccount()));
}

private ProjectServiceAccount randomProjectServiceAccount() {
return new ProjectServiceAccount(
randomString(5), randomString(7), "member", randomLong(10_000, 99_999));
Expand Down

0 comments on commit 702e55f

Please sign in to comment.