ChatClient register functions with explicit input type

The Lambda functions do not retain the type information, so we need to provide the input type explicitly.

 Resolves #1052

 Co-authored-by: liuzhifei <2679431923@qq.com>
This commit is contained in:
Christian Tzolov
2024-07-23 14:58:48 +02:00
parent 03d1d50130
commit 6270d627f1
3 changed files with 55 additions and 0 deletions

View File

@@ -17,7 +17,9 @@ package org.springframework.ai.openai.chat.client;
import static org.assertj.core.api.Assertions.assertThat;
import java.lang.reflect.Method;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
@@ -25,6 +27,7 @@ import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.DefaultChatClient;
import org.springframework.ai.openai.OpenAiTestConfiguration;
import org.springframework.ai.openai.api.tool.MockWeatherService;
import org.springframework.ai.openai.testutils.AbstractIT;
@@ -123,4 +126,47 @@ class OpenAiChatClientMultipleFunctionCallsIT extends AbstractIT {
}
@Test
void functionCallWithExplicitInputType() throws NoSuchMethodException {
var chatClient = ChatClient.create(chatModel);
Method currentTemp = MyFunction.class.getMethod("getCurrentTemp", MyFunction.Req.class);
// NOTE: Lambda functions do not retain the type information, so we need to
// provide the input type explicitly.
MyFunction myFunction = new MyFunction();
Function<MyFunction.Req, Object> function = createFunction(myFunction, currentTemp);
ChatClient.ChatClientRequestSpec chatClientRequestSpec = chatClient.prompt()
.user("What's the weather like in Shanghai?")
.function("currentTemp", "get current temp", MyFunction.Req.class, function);
String content = chatClientRequestSpec.call().content();
assertThat(content).contains("23");
}
public static <T, R> Function<T, R> createFunction(Object obj, Method method) {
return (T t) -> {
try {
return (R) method.invoke(obj, t);
}
catch (Exception e) {
throw new RuntimeException(e);
}
};
}
public static class MyFunction {
public record Req(String city) {
}
public String getCurrentTemp(Req req) {
return "23";
}
}
}

View File

@@ -189,6 +189,9 @@ public interface ChatClient {
<I, O> ChatClientRequestSpec function(String name, String description,
java.util.function.Function<I, O> function);
<I, O> ChatClientRequestSpec function(String name, String description, Class<I> inputType,
java.util.function.Function<I, O> function);
ChatClientRequestSpec functions(String... functionBeanNames);
ChatClientRequestSpec system(String text);

View File

@@ -610,6 +610,11 @@ public class DefaultChatClient implements ChatClient {
public <I, O> ChatClientRequestSpec function(String name, String description,
java.util.function.Function<I, O> function) {
return this.function(name, description, null, function);
}
public <I, O> ChatClientRequestSpec function(String name, String description, Class<I> inputType,
java.util.function.Function<I, O> function) {
Assert.hasText(name, "the name must be non-null and non-empty");
Assert.hasText(description, "the description must be non-null and non-empty");
@@ -618,6 +623,7 @@ public class DefaultChatClient implements ChatClient {
var fcw = FunctionCallbackWrapper.builder(function)
.withDescription(description)
.withName(name)
.withInputType(inputType)
.withResponseConverter(Object::toString)
.build();
this.functionCallbacks.add(fcw);