fix callback merging in AzureOpenAiChatModel constructor

and minor fixes

Signed-off-by: jitokim <pigberger70@gmail.com>
This commit is contained in:
jitokim
2024-11-19 02:13:05 +09:00
committed by Christian Tzolov
parent f5b00a027c
commit d8583271c7
6 changed files with 96 additions and 15 deletions

View File

@@ -80,7 +80,7 @@ public class AnthropicApi {
private final StreamHelper streamHelper = new StreamHelper();
private WebClient webClient;
private final WebClient webClient;
/**
* Create a new client api with DEFAULT_BASE_URL
@@ -261,7 +261,7 @@ public class AnthropicApi {
/**
* The CLAUDE_INSTANT_1_2
*/
CLAUDE_INSTANT_1_2("claude-instant-1.2");
@Deprecated CLAUDE_INSTANT_1_2("claude-instant-1.2");
// @formatter:on
private final String value;
@@ -366,7 +366,7 @@ public class AnthropicApi {
/**
* Artificially created event to aggregate tool use events.
*/
TOOL_USE_AGGREATE
TOOL_USE_AGGREGATE
}
@@ -889,7 +889,7 @@ public class AnthropicApi {
@Override
public EventType type() {
return EventType.TOOL_USE_AGGREATE;
return EventType.TOOL_USE_AGGREGATE;
}
/**

View File

@@ -48,6 +48,7 @@ import org.springframework.util.StringUtils;
*
* @author Mariusz Bernacki
* @author Christian Tzolov
* @author Jihoon Kim
* @since 1.0.0
*/
public class StreamHelper {
@@ -85,10 +86,10 @@ public class StreamHelper {
}
}
else if (event.type() == EventType.CONTENT_BLOCK_DELTA) {
ContentBlockDeltaEvent contentBolckDelta = (ContentBlockDeltaEvent) event;
if (ContentBlock.Type.INPUT_JSON_DELTA.getValue().equals(contentBolckDelta.delta().type())) {
ContentBlockDeltaEvent contentBlockDelta = (ContentBlockDeltaEvent) event;
if (ContentBlock.Type.INPUT_JSON_DELTA.getValue().equals(contentBlockDelta.delta().type())) {
return eventAggregator
.appendPartialJson(((ContentBlockDeltaJson) contentBolckDelta.delta()).partialJson());
.appendPartialJson(((ContentBlockDeltaJson) contentBlockDelta.delta()).partialJson());
}
}
else if (event.type() == EventType.CONTENT_BLOCK_STOP) {
@@ -119,7 +120,7 @@ public class StreamHelper {
.withUsage(messageStartEvent.message().usage())
.withContent(new ArrayList<>());
}
else if (event.type().equals(EventType.TOOL_USE_AGGREATE)) {
else if (event.type().equals(EventType.TOOL_USE_AGGREGATE)) {
ToolUseAggregationEvent eventToolUseBuilder = (ToolUseAggregationEvent) event;
if (!CollectionUtils.isEmpty(eventToolUseBuilder.getToolContentBlocks())) {

View File

@@ -104,6 +104,7 @@ import org.springframework.util.CollectionUtils;
* @author luocongqiu
* @author timostark
* @author Soby Chacko
* @author Jihoon Kim
* @see ChatModel
* @see com.azure.ai.openai.OpenAIClient
* @since 1.0.0
@@ -160,7 +161,7 @@ public class AzureOpenAiChatModel extends AbstractToolCallSupport implements Cha
public AzureOpenAiChatModel(OpenAIClientBuilder openAIClientBuilder, AzureOpenAiChatOptions options,
FunctionCallbackContext functionCallbackContext, List<FunctionCallback> toolFunctionCallbacks) {
this(openAIClientBuilder, options, functionCallbackContext, List.of(), ObservationRegistry.NOOP);
this(openAIClientBuilder, options, functionCallbackContext, toolFunctionCallbacks, ObservationRegistry.NOOP);
}
public AzureOpenAiChatModel(OpenAIClientBuilder openAIClientBuilder, AzureOpenAiChatOptions options,
@@ -235,10 +236,6 @@ public class AzureOpenAiChatModel extends AbstractToolCallSupport implements Cha
Flux<ChatCompletions> chatCompletionsStream = this.openAIAsyncClient
.getChatCompletionsStream(options.getModel(), options);
// For chunked responses, only the first chunk contains the choice role.
// The rest of the chunks with same ID share the same role.
ConcurrentHashMap<String, String> roleMap = new ConcurrentHashMap<>();
ChatModelObservationContext observationContext = ChatModelObservationContext.builder()
.prompt(prompt)
.provider(AiProvider.AZURE_OPENAI.value())

View File

@@ -0,0 +1,82 @@
package org.springframework.ai.azure.openai;
import com.azure.ai.openai.OpenAIClientBuilder;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.model.function.FunctionCallbackContext;
/**
* @author Jihoon Kim
*/
@ExtendWith(MockitoExtension.class)
public class AzureOpenAiChatModelTests {
@Mock
OpenAIClientBuilder mockClient;
@Mock
FunctionCallbackContext functionCallbackContext;
@Test
public void createAzureOpenAiChatModelTest() {
String callbackFromChatOptions = "callbackFromChatOptions";
String callbackFromConstructorParam = "callbackFromConstructorParam";
AzureOpenAiChatOptions chatOptions = AzureOpenAiChatOptions.builder()
.withFunctionCallbacks(List.of(new TestFunctionCallback(callbackFromChatOptions)))
.build();
List<FunctionCallback> functionCallbacks = List.of(new TestFunctionCallback(callbackFromConstructorParam));
AzureOpenAiChatModel openAiChatModel = new AzureOpenAiChatModel(mockClient, chatOptions,
functionCallbackContext, functionCallbacks);
assert 2 == openAiChatModel.getFunctionCallbackRegister().size();
assert callbackFromChatOptions == openAiChatModel.getFunctionCallbackRegister()
.get(callbackFromChatOptions)
.getName();
assert callbackFromConstructorParam == openAiChatModel.getFunctionCallbackRegister()
.get(callbackFromConstructorParam)
.getName();
}
private class TestFunctionCallback implements FunctionCallback {
private final String name;
public TestFunctionCallback(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public String getDescription() {
return null;
}
@Override
public String getInputTypeSchema() {
return null;
}
@Override
public String call(String functionInput) {
return null;
}
}
}

View File

@@ -41,6 +41,7 @@ import org.springframework.util.CollectionUtils;
* @author Christian Tzolov
* @author Grogdunn
* @author Thomas Vitale
* @author Jihoon Kim
* @since 1.0.0
*/
public abstract class AbstractToolCallSupport {
@@ -85,7 +86,7 @@ public abstract class AbstractToolCallSupport {
if (!CollectionUtils.isEmpty(functionOptions.getFunctionCallbacks())) {
toolFunctionCallbacksCopy.addAll(functionOptions.getFunctionCallbacks());
// Make sure that that function callbacks are are registered directly to the
// Make sure that that function callbacks are registered directly to the
// functionCallbackRegister and not passed in the default options.
functionOptions.setFunctionCallbacks(List.of());
}

View File

@@ -112,7 +112,7 @@ public record Request(String location, Unit unit) {}
It is a best practice to annotate the request object with information such that the generated JSON schema of that function is as descriptive as possible to help the AI model pick the correct function to invoke.
The link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/anthropic/tool/FunctionCallWithFunctionBeanIT.java.java[FunctionCallWithFunctionBeanIT.java] demonstrates this approach.
The link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/anthropic/tool/FunctionCallWithFunctionBeanIT.java[FunctionCallWithFunctionBeanIT.java] demonstrates this approach.
==== FunctionCallback