Skip to content

Commit 1662ae7

Browse files
committed
Tool call full e2e unverified
1 parent 009c9fe commit 1662ae7

File tree

6 files changed

+199
-0
lines changed

6 files changed

+199
-0
lines changed

sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,17 @@ Object chatCompletionTools(
131131
return response.getContent();
132132
}
133133

134+
@GetMapping("/chatCompletionToolExecution")
135+
@Nonnull
136+
Object chatCompletionToolExecution(
137+
@Nullable @RequestParam(value = "format", required = false) final String format) {
138+
final var response = service.chatCompletionToolExecution(12);
139+
if ("json".equals(format)) {
140+
return response;
141+
}
142+
return response.getContent();
143+
}
144+
134145
@GetMapping("/embedding")
135146
@Nonnull
136147
Object embedding() {

sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OpenAiService.java

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import static com.sap.ai.sdk.foundationmodels.openai.OpenAiModel.TEXT_EMBEDDING_3_SMALL;
66
import static com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionTool.ToolType.FUNCTION;
77

8+
import com.fasterxml.jackson.databind.ObjectMapper;
89
import com.sap.ai.sdk.core.AiCoreService;
910
import com.sap.ai.sdk.foundationmodels.openai.OpenAiClient;
1011
import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionDelta;
@@ -15,6 +16,7 @@
1516
import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatMessage;
1617
import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiEmbeddingOutput;
1718
import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiEmbeddingParameters;
19+
import java.util.ArrayList;
1820
import java.util.List;
1921
import java.util.Map;
2022
import java.util.stream.Stream;
@@ -112,6 +114,73 @@ public OpenAiChatCompletionOutput chatCompletionTools(final int months) {
112114
return OpenAiClient.forModel(GPT_4O_MINI).chatCompletion(request);
113115
}
114116

117+
/**
118+
* Executes a chat completion request to OpenAI with a tool that calculates the Fibonacci number.
119+
*
120+
* @param months The number of months to be inferred in the tool.
121+
* @return The assistant message response.
122+
*/
123+
@Nonnull
124+
public OpenAiChatCompletionOutput chatCompletionToolExecution(final int months) {
125+
record Fibonacci(int N) {
126+
public long execute() {
127+
return fib(N);
128+
}
129+
130+
private static long fib(int n) {
131+
if (n < 2) {
132+
return n;
133+
}
134+
return fib(n - 1) + fib(n - 2);
135+
}
136+
}
137+
138+
final var function =
139+
new OpenAiChatCompletionFunction()
140+
.setName("fibonacci")
141+
.setDescription("Calculate the Fibonacci number for given sequence index.")
142+
.setParameters(
143+
Map.of("type", "object", "properties", Map.of("N", Map.of("type", "integer"))));
144+
final var tool = new OpenAiChatCompletionTool().setType(FUNCTION).setFunction(function);
145+
146+
final var messages = new ArrayList<OpenAiChatMessage>();
147+
messages.add(
148+
new OpenAiChatMessage.OpenAiChatUserMessage()
149+
.addText("How many rabbits will there be after %s months?".formatted(months)));
150+
151+
final var request =
152+
new OpenAiChatCompletionParameters()
153+
.addMessages(messages.toArray(OpenAiChatMessage[]::new))
154+
.setTools(List.of(tool))
155+
.setToolChoiceFunction("fibonacci");
156+
157+
OpenAiClient client = OpenAiClient.forModel(GPT_4O_MINI);
158+
var initialResponse = client.chatCompletion(request);
159+
160+
var toolCall = initialResponse.getChoices().get(0).getMessage().getToolCalls().get(0);
161+
String toolResponse;
162+
try {
163+
var fibonacci =
164+
new ObjectMapper().readValue(toolCall.getFunction().getArguments(), Fibonacci.class);
165+
toolResponse = String.valueOf(fibonacci.execute());
166+
} catch (Exception e) {
167+
throw new IllegalArgumentException("Error parsing tool call arguments", e);
168+
}
169+
170+
var assistantMessage = initialResponse.getChoices().get(0).getMessage();
171+
messages.add(assistantMessage);
172+
173+
var toolMessage =
174+
new OpenAiChatMessage.OpenAiChatToolMessage()
175+
.setToolCallId(toolCall.getId())
176+
.setContent(toolResponse);
177+
messages.add(toolMessage);
178+
179+
var finalRequest = new OpenAiChatCompletionParameters().addMessages(messages.toArray(OpenAiChatMessage[]::new));
180+
181+
return client.chatCompletion(finalRequest);
182+
}
183+
115184
/**
116185
* Get the embedding of a text
117186
*

sample-code/spring-app/src/main/resources/static/index.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,17 @@ <h5 class="mb-1">OpenAI</h5>
503503
</div>
504504
</div>
505505
</li>
506+
<li class="list-group-item">
507+
<div class="info-tooltip">
508+
<button type="submit" formaction="/chatCompletionToolExecution"
509+
class="link-offset-2-hover link-underline link-underline-opacity-0 link-underline-opacity-75-hover endpoint">
510+
<code>/chatCompletionToolExecution</code>
511+
</button>
512+
<div class="tooltip-content">
513+
Chat request to OpenAI with an executed tool call.
514+
</div>
515+
</div>
516+
</li>
506517
<li class="list-group-item">
507518
<div class="info-tooltip">
508519
<button type="submit" formaction="/chatCompletionImage"

sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/OpenAiTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,14 @@ void chatCompletionWithResource() {
101101
assertThat(message.getRole()).isEqualTo("assistant");
102102
assertThat(message.getContent()).isNotEmpty();
103103
}
104+
105+
@Test
106+
void chatCompletionToolExecution() {
107+
final var completion = service.chatCompletionToolExecution(12);
108+
109+
String content = completion.getContent();
110+
111+
assertThat(content).isNotEmpty();
112+
assertThat(content).contains("144");
113+
}
104114
}

sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/OpenAiV2Test.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,14 @@ void chatCompletionWithResource() {
102102
assertThat(completion.getChoice().getMessage().getRole()).isEqualTo(ASSISTANT);
103103
assertThat(completion.getContent()).isNotEmpty();
104104
}
105+
106+
@Test
107+
void chatCompletionToolExecution() {
108+
final var completion = service.chatCompletionToolExecution(12);
109+
110+
String content = completion.getChoices().get(0).getMessage().getContent();
111+
112+
assertThat(content).isNotEmpty();
113+
assertThat(content).contains("144");
114+
}
105115
}

sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/services/OpenAiServiceV2.java

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import static com.sap.ai.sdk.foundationmodels.openai.OpenAiModel.TEXT_EMBEDDING_3_SMALL;
66
import static com.sap.ai.sdk.foundationmodels.openai.generated.model.ChatCompletionTool.TypeEnum.FUNCTION;
77

8+
import com.fasterxml.jackson.core.JsonProcessingException;
9+
import com.fasterxml.jackson.databind.ObjectMapper;
810
import com.sap.ai.sdk.core.AiCoreService;
911
import com.sap.ai.sdk.foundationmodels.openai.OpenAiChatCompletionDelta;
1012
import com.sap.ai.sdk.foundationmodels.openai.OpenAiChatCompletionRequest;
@@ -15,7 +17,15 @@
1517
import com.sap.ai.sdk.foundationmodels.openai.OpenAiImageItem;
1618
import com.sap.ai.sdk.foundationmodels.openai.OpenAiMessage;
1719
import com.sap.ai.sdk.foundationmodels.openai.OpenAiToolChoice;
20+
import com.sap.ai.sdk.foundationmodels.openai.generated.model.ChatCompletionRequestAssistantMessage;
21+
import com.sap.ai.sdk.foundationmodels.openai.generated.model.ChatCompletionRequestAssistantMessageContent;
22+
import com.sap.ai.sdk.foundationmodels.openai.generated.model.ChatCompletionRequestToolMessage;
23+
import com.sap.ai.sdk.foundationmodels.openai.generated.model.ChatCompletionRequestToolMessageContent;
24+
import com.sap.ai.sdk.foundationmodels.openai.generated.model.ChatCompletionRequestUserMessage;
25+
import com.sap.ai.sdk.foundationmodels.openai.generated.model.ChatCompletionRequestUserMessageContent;
1826
import com.sap.ai.sdk.foundationmodels.openai.generated.model.ChatCompletionTool;
27+
import com.sap.ai.sdk.foundationmodels.openai.generated.model.CreateChatCompletionRequest;
28+
import com.sap.ai.sdk.foundationmodels.openai.generated.model.CreateChatCompletionResponse;
1929
import com.sap.ai.sdk.foundationmodels.openai.generated.model.FunctionObject;
2030
import java.util.List;
2131
import java.util.Map;
@@ -110,6 +120,84 @@ public OpenAiChatCompletionResponse chatCompletionTools(final int months) {
110120
return OpenAiClient.forModel(GPT_4O_MINI).chatCompletion(request);
111121
}
112122

123+
/**
124+
* Executes a chat completion request to OpenAI with a tool that calculates the Fibonacci number.
125+
*
126+
* @param months The number of months to be inferred in the tool.
127+
* @return The assistant message response.
128+
*/
129+
@Nonnull
130+
public CreateChatCompletionResponse chatCompletionToolExecution(final int months) {
131+
132+
record Fibonacci(int n) {
133+
public long execute() {
134+
return fib(n);
135+
}
136+
137+
private static long fib(int n) {
138+
if (n < 2) {
139+
return n;
140+
}
141+
return fib(n - 1) + fib(n - 2);
142+
}
143+
}
144+
145+
final var function =
146+
new FunctionObject()
147+
.name("fibonacci")
148+
.description("Calculate the Fibonacci number for given sequence index.")
149+
.parameters(
150+
Map.of("type", "object", "properties", Map.of("n", Map.of("type", "integer"))));
151+
152+
final var tool = new ChatCompletionTool().type(FUNCTION).function(function);
153+
154+
var userMessage =
155+
new ChatCompletionRequestUserMessage()
156+
.role(ChatCompletionRequestUserMessage.RoleEnum.USER)
157+
.content(
158+
ChatCompletionRequestUserMessageContent.create(
159+
"A pair of rabbits is placed in a field. Each month, every pair produces one new pair, starting from the second month. How many rabbits will there be after %s months?"
160+
.formatted(months)));
161+
162+
final var request =
163+
new CreateChatCompletionRequest()
164+
.addMessagesItem(userMessage)
165+
.tools(List.of(tool))
166+
.functions(null);
167+
168+
OpenAiClient client = OpenAiClient.forModel(GPT_4O_MINI);
169+
170+
var initialResponse = client.chatCompletion(request);
171+
172+
var toolCall = initialResponse.getChoices().get(0).getMessage().getToolCalls().get(0);
173+
String toolResponseContent;
174+
try {
175+
var fibonacci =
176+
new ObjectMapper().readValue(toolCall.getFunction().getArguments(), Fibonacci.class);
177+
toolResponseContent = String.valueOf(fibonacci.execute());
178+
} catch (JsonProcessingException e) {
179+
throw new IllegalArgumentException("Error parsing tool call arguments", e);
180+
}
181+
182+
var assistantMessage =
183+
new ChatCompletionRequestAssistantMessage()
184+
.role(ChatCompletionRequestAssistantMessage.RoleEnum.ASSISTANT)
185+
.content(
186+
ChatCompletionRequestAssistantMessageContent.create(
187+
initialResponse.getChoices().get(0).getMessage().getContent()))
188+
.toolCalls(List.of(toolCall));
189+
request.addMessagesItem(assistantMessage);
190+
191+
var toolMessage =
192+
new ChatCompletionRequestToolMessage()
193+
.role(ChatCompletionRequestToolMessage.RoleEnum.TOOL)
194+
.content(ChatCompletionRequestToolMessageContent.create(toolResponseContent))
195+
.toolCallId(toolCall.getId());
196+
request.addMessagesItem(toolMessage);
197+
198+
return client.chatCompletion(request);
199+
}
200+
113201
/**
114202
* Get the embedding of a text
115203
*

0 commit comments

Comments
 (0)