Skip to content

Commit b02829e

Browse files
committed
feat(openai-assistants): Integrate OpenAI Assistant API management
- Added `OpenAiAssistantManager` for high-level assistant lifecycle management (create, retrieve, modify, list, delete). - Introduced `OpenAiAssistantOptions` for configuring assistant properties. - Implemented `OpenAiAssistantApi` with API-level operations. - Added support for tools like `FunctionTool`, `FileSearchTool`, and `CodeInterpreterTool`. - Enhanced `spring-ai-spring-boot-autoconfigure` with assistant-related properties and auto-configuration. - Included integration tests (`OpenAiAssistantManagerIT` and `OpenAiAssistantApiIT`) for assistant lifecycle operations. - Updated documentation to cover assistant API features and usage examples. Partially resolves #506
1 parent f922195 commit b02829e

File tree

22 files changed

+2394
-17
lines changed

22 files changed

+2394
-17
lines changed
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.ai.openai;
18+
19+
import java.util.List;
20+
21+
import org.slf4j.Logger;
22+
import org.slf4j.LoggerFactory;
23+
24+
import org.springframework.ai.model.ModelOptionsUtils;
25+
import org.springframework.ai.openai.api.assistants.OpenAiAssistantApi;
26+
import org.springframework.ai.openai.api.assistants.OpenAiAssistantApi.AssistantResponse;
27+
import org.springframework.ai.openai.api.assistants.OpenAiAssistantApi.CreateAssistantRequest;
28+
import org.springframework.ai.openai.api.assistants.OpenAiAssistantApi.ListAssistantsResponse;
29+
import org.springframework.ai.openai.api.assistants.OpenAiAssistantApi.ModifyAssistantRequest;
30+
import org.springframework.ai.retry.RetryUtils;
31+
import org.springframework.http.ResponseEntity;
32+
import org.springframework.retry.support.RetryTemplate;
33+
import org.springframework.util.Assert;
34+
35+
/**
36+
* OpenAiAssistantModel provides a high-level abstraction for managing OpenAI assistants
37+
* by utilizing the OpenAiAssistantApi to interact with the OpenAI Assistants API.
38+
*
39+
* This model is responsible for creating, modifying, retrieving, listing, and deleting
40+
* assistants while supporting retry mechanisms and default options.
41+
*
42+
* @author Alexandros Pappas
43+
*/
44+
public class OpenAiAssistantManager {
45+
46+
private final Logger logger = LoggerFactory.getLogger(getClass());
47+
48+
private final OpenAiAssistantApi openAiAssistantApi;
49+
50+
private final RetryTemplate retryTemplate;
51+
52+
private OpenAiAssistantOptions defaultOptions;
53+
54+
public OpenAiAssistantManager(OpenAiAssistantApi openAiAssistantApi) {
55+
this(openAiAssistantApi, RetryUtils.DEFAULT_RETRY_TEMPLATE);
56+
}
57+
58+
public OpenAiAssistantManager(OpenAiAssistantApi openAiAssistantApi, RetryTemplate retryTemplate) {
59+
Assert.notNull(openAiAssistantApi, "OpenAiAssistantApi must not be null");
60+
Assert.notNull(retryTemplate, "RetryTemplate must not be null");
61+
this.openAiAssistantApi = openAiAssistantApi;
62+
this.retryTemplate = retryTemplate;
63+
}
64+
65+
public OpenAiAssistantOptions getDefaultOptions() {
66+
return this.defaultOptions;
67+
}
68+
69+
public OpenAiAssistantManager withDefaultOptions(OpenAiAssistantOptions defaultOptions) {
70+
this.defaultOptions = defaultOptions;
71+
return this;
72+
}
73+
74+
public AssistantResponse createAssistant(CreateAssistantRequest createRequest) {
75+
Assert.notNull(createRequest, "CreateAssistantRequest cannot be null.");
76+
CreateAssistantRequest finalRequest = mergeOptions(createRequest, this.defaultOptions);
77+
78+
ResponseEntity<AssistantResponse> responseEntity = this.retryTemplate
79+
.execute(ctx -> this.openAiAssistantApi.createAssistant(finalRequest));
80+
81+
AssistantResponse responseBody = responseEntity.getBody();
82+
if (responseBody == null) {
83+
logger.warn("No assistant created for request: {}", finalRequest);
84+
return AssistantResponse.builder().build();
85+
}
86+
87+
return responseBody;
88+
}
89+
90+
public AssistantResponse modifyAssistant(String assistantId, ModifyAssistantRequest modifyRequest) {
91+
Assert.hasLength(assistantId, "Assistant ID cannot be null or empty.");
92+
Assert.notNull(modifyRequest, "ModifyAssistantRequest cannot be null.");
93+
94+
ResponseEntity<AssistantResponse> responseEntity = this.retryTemplate
95+
.execute(ctx -> this.openAiAssistantApi.modifyAssistant(assistantId, modifyRequest));
96+
97+
AssistantResponse responseBody = responseEntity.getBody();
98+
if (responseBody == null) {
99+
logger.warn("No assistant modification response for assistantId={} and request: {}", assistantId,
100+
modifyRequest);
101+
return AssistantResponse.builder().build();
102+
}
103+
104+
return responseBody;
105+
}
106+
107+
public AssistantResponse retrieveAssistant(String assistantId) {
108+
Assert.hasLength(assistantId, "Assistant ID cannot be null or empty.");
109+
110+
ResponseEntity<AssistantResponse> responseEntity = this.retryTemplate
111+
.execute(ctx -> this.openAiAssistantApi.retrieveAssistant(assistantId));
112+
113+
AssistantResponse responseBody = responseEntity.getBody();
114+
if (responseBody == null) {
115+
logger.warn("No assistant retrieved for assistantId={}", assistantId);
116+
return AssistantResponse.builder().build();
117+
}
118+
119+
return responseBody;
120+
}
121+
122+
public ListAssistantsResponse listAssistants(int limit, String order, String after, String before) {
123+
ResponseEntity<ListAssistantsResponse> responseEntity = this.retryTemplate
124+
.execute(ctx -> this.openAiAssistantApi.listAssistants(limit, order, after, before));
125+
126+
ListAssistantsResponse responseBody = responseEntity.getBody();
127+
if (responseBody == null) {
128+
logger.warn("No assistants returned for list request with limit={}, order={}, after={}, before={}", limit,
129+
order, after, before);
130+
return new ListAssistantsResponse("list", List.of());
131+
}
132+
133+
return responseBody;
134+
}
135+
136+
public boolean deleteAssistant(String assistantId) {
137+
Assert.hasLength(assistantId, "Assistant ID cannot be null or empty.");
138+
139+
ResponseEntity<OpenAiAssistantApi.DeleteAssistantResponse> responseEntity = this.retryTemplate
140+
.execute(ctx -> this.openAiAssistantApi.deleteAssistant(assistantId));
141+
142+
OpenAiAssistantApi.DeleteAssistantResponse responseBody = responseEntity.getBody();
143+
if (responseBody == null || responseBody.deleted() == null) {
144+
logger.warn("No delete response or null deletion flag for assistantId={}", assistantId);
145+
return false;
146+
}
147+
148+
return Boolean.TRUE.equals(responseBody.deleted());
149+
}
150+
151+
/**
152+
* Merges the default options with the request-specific options if present.
153+
*/
154+
private <T> T mergeOptions(T request, OpenAiAssistantOptions defaultOptions) {
155+
if (defaultOptions != null) {
156+
return ModelOptionsUtils.merge(defaultOptions, request, (Class<T>) request.getClass());
157+
}
158+
return request;
159+
}
160+
161+
}

0 commit comments

Comments
 (0)