From bc46a98a018073531c2903c73b4f8fdad9f9a702 Mon Sep 17 00:00:00 2001 From: liangtao <547670718@qq.com> Date: Fri, 10 May 2024 09:16:35 +0800 Subject: [PATCH 1/6] add release config --- .github/release.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/release.yml diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000..f3f9b28 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,23 @@ +changelog: + exclude: + labels: + - ignore-for-release + categories: + - title: 🏕 Features + labels: + - enhancement + - title: 🐛 Bug Fixes + labels: + - bug + - title: 👋 Deprecated + labels: + - deprecation + - title: 📄 documentation + labels: + - documentation + - title: 👒 Dependencies + labels: + - dependencies + - title: 'Other Changes' + labels: + - "*" From 3883c933e9b9de1266436e8d829af062e594310d Mon Sep 17 00:00:00 2001 From: liangtao <547670718@qq.com> Date: Fri, 10 May 2024 14:19:41 +0800 Subject: [PATCH 2/6] =?UTF-8?q?function=20=E5=AE=9A=E4=B9=89=E9=87=8D?= =?UTF-8?q?=E6=9E=84;=E7=BB=9F=E4=B8=80=E4=BD=BF=E7=94=A8=E6=96=B9?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/pom.xml | 11 ++ .../assistant/AssistantFunction.java | 27 ---- .../assistants/assistant/FunctionTool.java | 3 + .../openai/assistants/run/ToolCall.java | 1 + .../chat/ChatCompletionRequest.java | 10 +- .../openai/completion/chat/ChatFunction.java | 6 + .../completion/chat/ChatFunctionDynamic.java | 6 + .../chat/ChatFunctionParameters.java | 5 + .../completion/chat/ChatFunctionProperty.java | 1 + .../openai/completion/chat/ChatTool.java | 17 ++- .../completion/chat/FunctionMessage.java | 2 + .../openai/function/FunctionDefinition.java | 116 ++++++++++++++ .../function/FunctionExecutorManager.java | 143 ++++++++++++++++++ .../FunctionParametersSerializer.java | 31 ++++ .../example/FunctionOrToolCreateExample.java | 92 +++++++++++ service/pom.xml | 11 -- .../openai/service/ChatFunctionMixIn.java | 4 + .../ChatFunctionParametersSerializer.java | 1 + .../service/assistants/AssistantTest.java | 40 +++-- 19 files changed, 459 insertions(+), 68 deletions(-) delete mode 100644 api/src/main/java/com/theokanning/openai/assistants/assistant/AssistantFunction.java create mode 100644 api/src/main/java/com/theokanning/openai/function/FunctionDefinition.java create mode 100644 api/src/main/java/com/theokanning/openai/function/FunctionExecutorManager.java create mode 100644 api/src/main/java/com/theokanning/openai/function/FunctionParametersSerializer.java create mode 100644 example/src/main/java/example/FunctionOrToolCreateExample.java diff --git a/api/pom.xml b/api/pom.xml index cc138d8..f76ca05 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -33,6 +33,17 @@ jtokkit 0.5.1 + + com.kjetland + mbknor-jackson-jsonschema_2.12 + 1.0.39 + + + jackson-databind + com.fasterxml.jackson.core + + + diff --git a/api/src/main/java/com/theokanning/openai/assistants/assistant/AssistantFunction.java b/api/src/main/java/com/theokanning/openai/assistants/assistant/AssistantFunction.java deleted file mode 100644 index 4f83ce5..0000000 --- a/api/src/main/java/com/theokanning/openai/assistants/assistant/AssistantFunction.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.theokanning.openai.assistants.assistant; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - * @description: - * @author: vacuity - * @create: 2023-11-20 10:09 - **/ - - -@Builder -@NoArgsConstructor -@AllArgsConstructor -@Data -public class AssistantFunction { - - private String description; - - private String name; - - private Object parameters; - -} diff --git a/api/src/main/java/com/theokanning/openai/assistants/assistant/FunctionTool.java b/api/src/main/java/com/theokanning/openai/assistants/assistant/FunctionTool.java index 4a22009..e36ba02 100644 --- a/api/src/main/java/com/theokanning/openai/assistants/assistant/FunctionTool.java +++ b/api/src/main/java/com/theokanning/openai/assistants/assistant/FunctionTool.java @@ -16,6 +16,9 @@ public class FunctionTool implements Tool { /** * Function definition, only used if type is "function" + * recommend to use {@link com.theokanning.openai.function.FunctionDefinition} or custom class + * + * @since 0.20.5 {@link com.theokanning.openai.completion.chat.ChatFunction} {@link com.theokanning.openai.completion.chat.ChatFunctionDynamic}will be deprecated */ Object function; } diff --git a/api/src/main/java/com/theokanning/openai/assistants/run/ToolCall.java b/api/src/main/java/com/theokanning/openai/assistants/run/ToolCall.java index 65b0c54..e341544 100644 --- a/api/src/main/java/com/theokanning/openai/assistants/run/ToolCall.java +++ b/api/src/main/java/com/theokanning/openai/assistants/run/ToolCall.java @@ -20,6 +20,7 @@ @NoArgsConstructor @AllArgsConstructor public class ToolCall { + //may be need @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) Integer index; diff --git a/api/src/main/java/com/theokanning/openai/completion/chat/ChatCompletionRequest.java b/api/src/main/java/com/theokanning/openai/completion/chat/ChatCompletionRequest.java index 4c876b0..743df14 100644 --- a/api/src/main/java/com/theokanning/openai/completion/chat/ChatCompletionRequest.java +++ b/api/src/main/java/com/theokanning/openai/completion/chat/ChatCompletionRequest.java @@ -114,7 +114,9 @@ public class ChatCompletionRequest { String user; /** - * A list of the available functions. + * @deprecated Replaced by {@link #tools} + * recommend to use {@link com.theokanning.openai.function.FunctionDefinition} or custom class + * @since 0.20.5 {@link com.theokanning.openai.completion.chat.ChatFunction} {@link com.theokanning.openai.completion.chat.ChatFunctionDynamic}will be deprecated */ @Deprecated List functions; @@ -146,8 +148,12 @@ public class ChatCompletionRequest { Integer topLogprobs; + /** - * A list of tools the model may call. Currently, only functions are supported as a tool. + * Function definition, only used if type is "function" + * recommend to use {@link com.theokanning.openai.function.FunctionDefinition} or custom class + * + * @since 0.20.5 {@link com.theokanning.openai.completion.chat.ChatFunction} {@link com.theokanning.openai.completion.chat.ChatFunctionDynamic}will be deprecated */ List tools; diff --git a/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunction.java b/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunction.java index da215c2..9319efb 100644 --- a/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunction.java +++ b/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunction.java @@ -8,8 +8,13 @@ import java.util.function.Function; +/** + * @deprecated This class is no longer used and will be removed in a future release. + * Please use {@link com.theokanning.openai.function.FunctionDefinition} instead. + */ @Data @NoArgsConstructor +@Deprecated public class ChatFunction { /** @@ -29,6 +34,7 @@ public class ChatFunction { @JsonProperty("parameters") private Class parametersClass; + @JsonIgnore private Function executor; diff --git a/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunctionDynamic.java b/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunctionDynamic.java index 9b4f207..eb59b34 100644 --- a/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunctionDynamic.java +++ b/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunctionDynamic.java @@ -4,7 +4,13 @@ import lombok.NonNull; +/** + * @deprecated function json schema(https://json-schema.org/understanding-json-schema/reference) 十分复杂,很多属性都是可选的,维护这个类会变得异常复杂,目前仅仅只是支持了很简单的属性,肯定无法满足更多灵活的function定义.
+ * 现在推荐使用{@link ChatFunction}来定义function + * + */ @Data +@Deprecated public class ChatFunctionDynamic { /** diff --git a/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunctionParameters.java b/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunctionParameters.java index fee71e8..ed7094e 100644 --- a/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunctionParameters.java +++ b/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunctionParameters.java @@ -6,7 +6,12 @@ import java.util.HashMap; import java.util.List; +/** + * @deprecated function json schema(https://json-schema.org/understanding-json-schema/reference) 十分复杂,很多属性都是可选的,维护会变得异常复杂,目前仅仅只是支持了很简单的属性,肯定无法满足更多灵活的function定义.
+ * 并且将这个类放在了错误的包下,应该放在service包下.
+ */ @Data +@Deprecated public class ChatFunctionParameters { private final String type = "object"; diff --git a/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunctionProperty.java b/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunctionProperty.java index b8cd15a..73bdea7 100644 --- a/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunctionProperty.java +++ b/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunctionProperty.java @@ -18,6 +18,7 @@ public class ChatFunctionProperty { private String type; @JsonIgnore private Boolean required; + private String description; private ChatFunctionParameters items; diff --git a/api/src/main/java/com/theokanning/openai/completion/chat/ChatTool.java b/api/src/main/java/com/theokanning/openai/completion/chat/ChatTool.java index a0eb1a4..82ee081 100644 --- a/api/src/main/java/com/theokanning/openai/completion/chat/ChatTool.java +++ b/api/src/main/java/com/theokanning/openai/completion/chat/ChatTool.java @@ -1,16 +1,15 @@ package com.theokanning.openai.completion.chat; +import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.NonNull; @Data @NoArgsConstructor +@AllArgsConstructor public class ChatTool { - @NonNull - private Object function; - /** * The name of the tool being called, only function supported for now. */ @@ -18,8 +17,14 @@ public class ChatTool { private String type = "function"; - public ChatTool(@NonNull Object function) { - this.function = function; - } + /** + * recommend use {@link com.theokanning.openai.function.FunctionDefinition} . + * also you can customer your own function + */ + @NonNull + private Object function; + + + } diff --git a/api/src/main/java/com/theokanning/openai/completion/chat/FunctionMessage.java b/api/src/main/java/com/theokanning/openai/completion/chat/FunctionMessage.java index bd2a2df..6cf9fb2 100644 --- a/api/src/main/java/com/theokanning/openai/completion/chat/FunctionMessage.java +++ b/api/src/main/java/com/theokanning/openai/completion/chat/FunctionMessage.java @@ -5,11 +5,13 @@ import lombok.NoArgsConstructor; /** + * @deprecated see {@link ToolMessage} * @author LiangTao * @date 2024年04月10 10:37 **/ @Data @NoArgsConstructor +@Deprecated public class FunctionMessage implements ChatMessage { private final String role = ChatMessageRole.FUNCTION.value(); diff --git a/api/src/main/java/com/theokanning/openai/function/FunctionDefinition.java b/api/src/main/java/com/theokanning/openai/function/FunctionDefinition.java new file mode 100644 index 0000000..a6bfe25 --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/function/FunctionDefinition.java @@ -0,0 +1,116 @@ +package com.theokanning.openai.function; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.node.TextNode; +import com.kjetland.jackson.jsonSchema.JsonSchemaGenerator; +import com.theokanning.openai.completion.chat.ChatFunction; +import lombok.*; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.function.Function; + +/** + * @author LiangTao + * @date 2024年05月10 11:15 + **/ +@JsonSerialize(using = FunctionParametersSerializer.class) +@Getter +public class FunctionDefinition { + + /** + * not allow to create instance by constructor + */ + private FunctionDefinition() { + + } + + /** + * The name of the function being called. + */ + @NonNull + protected String name; + + /** + * A description of what the function does, used by the model to choose when and how to call the function. + */ + private String description; + + /** + * parameters definition by class schema ,will use {@link JsonSchemaGenerator} to generate json schema + */ + private Class parametersDefinitionClass; + + /** + * The parameters the functions accepts.Choose between this parameter and {@link #parametersDefinitionClass} + **/ + private Object parametersDefinition; + + /** + * Function executor,if set {@link #parametersDefinitionClass},the executor type must {@link #parametersDefinitionClass}.
+ * Else executor parameter type must {@link #parametersDefinition} JsonNode type + * 可以为null + */ + private Function executor; + + public static FunctionDefinition.Builder builder() { + return new FunctionDefinition.Builder<>(); + } + + public static class Builder { + private String name; + private String description; + private Class parametersDefinitionClass; + + private T parametersDefinition; + + private Function executor; + + public FunctionDefinition.Builder name(String name) { + this.name = name; + return this; + } + + public FunctionDefinition.Builder description(String description) { + this.description = description; + return this; + } + + public FunctionDefinition.Builder parametersDefinition(T parametersDefinition) { + this.parametersDefinition = parametersDefinition; + return this; + } + + public FunctionDefinition.Builder parametersDefinitionByClass(Class parametersDefinitionClass) { + this.parametersDefinitionClass = parametersDefinitionClass; + return this; + } + + public FunctionDefinition.Builder executor(Function executor) { + this.executor = executor; + return this; + } + + + @SuppressWarnings("unchecked") + public FunctionDefinition build() { + if (name == null) { + throw new IllegalArgumentException("name can't be null"); + } + if (parametersDefinitionClass == null && parametersDefinition == null) { + throw new IllegalArgumentException("parametersDefinitionClass and parametersDefinition can't be null at the same time,please set one of them"); + } + if (parametersDefinition != null && parametersDefinitionClass != null) { + throw new IllegalArgumentException("parametersDefinitionClass and parametersDefinition can't be set at the same time,please set one of them"); + } + FunctionDefinition functionDefinition = new FunctionDefinition(); + functionDefinition.name = name; + functionDefinition.description = description; + functionDefinition.parametersDefinitionClass = parametersDefinitionClass; + functionDefinition.parametersDefinition = parametersDefinition; + functionDefinition.executor = (Function) executor; + return functionDefinition; + } + } +} diff --git a/api/src/main/java/com/theokanning/openai/function/FunctionExecutorManager.java b/api/src/main/java/com/theokanning/openai/function/FunctionExecutorManager.java new file mode 100644 index 0000000..b424e8c --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/function/FunctionExecutorManager.java @@ -0,0 +1,143 @@ +package com.theokanning.openai.function; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; +import com.theokanning.openai.assistants.run.SubmitToolOutputRequestItem; +import com.theokanning.openai.completion.chat.FunctionMessage; +import com.theokanning.openai.completion.chat.ToolMessage; +import lombok.Getter; + +import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.function.Function; + +/** + * @author LiangTao + * @date 2024年05月10 13:30 + **/ +public class FunctionExecutorManager { + @Getter + private final ObjectMapper mapper; + private final Map functionHolderMap; + + private final ExecutorService executorService; + + public FunctionExecutorManager() { + this(new ObjectMapper(), Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()), Collections.emptyList()); + } + + public FunctionExecutorManager(List functionDefinitionList) { + this(new ObjectMapper(), Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()), functionDefinitionList); + } + + public FunctionExecutorManager(ObjectMapper mapper, ExecutorService executorService) { + this(mapper, executorService, Collections.emptyList()); + } + + public FunctionExecutorManager(ObjectMapper mapper, ExecutorService executorService, List functionDefinitionList) { + this.functionHolderMap = new HashMap<>(); + for (FunctionDefinition functionDefinition : functionDefinitionList) { + if (functionDefinition.getExecutor() == null) { + throw new IllegalArgumentException("FunctionDefinition must have executor"); + } + functionHolderMap.put(functionDefinition.getName(), functionDefinition); + } + this.mapper = mapper; + this.executorService = executorService; + } + + public FunctionDefinition getFunctionDefinition(String functionName) { + return functionHolderMap.get(functionName); + } + + public void addFunctionDefinition(FunctionDefinition functionDefinition) { + if (functionDefinition.getExecutor() == null) { + throw new IllegalArgumentException("FunctionDefinition must have executor"); + } + functionHolderMap.put(functionDefinition.getName(), functionDefinition); + } + + public void clear() { + functionHolderMap.clear(); + } + + public boolean remove(String functionName) { + return functionHolderMap.remove(functionName) != null; + } + + public boolean contains(String functionName) { + return functionHolderMap.containsKey(functionName); + } + + @SuppressWarnings("unchecked") + public T execute(String name, JsonNode arguments) { + FunctionDefinition functionDefinition = functionHolderMap.get(name); + if (functionDefinition == null) { + throw new IllegalArgumentException("FunctionDefinition not found"); + } + Object functionArg = functionDefinition.getParametersDefinitionClass() == null ? arguments : mapper.convertValue(arguments, functionDefinition.getParametersDefinitionClass()); + return (T) functionDefinition.getExecutor().apply(functionArg); + } + + public Future executeAsync(String name, JsonNode arguments) { + return executorService.submit(() -> execute(name, arguments)); + } + + public JsonNode executeAndConvertToJson(String funName, JsonNode arguments) { + return mapper.convertValue(execute(funName, arguments), JsonNode.class); + } + + /** + * chat-completion toolMessage + */ + public ToolMessage executeAndConvertToToolMessage(String funName, JsonNode arguments, String toolId) { + return new ToolMessage(executeAndConvertToJson(funName, arguments).toPrettyString(), toolId); + } + + public Future executeAndConvertToToolMessageAsync(String funName, JsonNode arguments, String toolId) { + return executorService.submit(() -> executeAndConvertToToolMessage(funName, arguments, toolId)); + } + + + /** + * assistant stream toolMessage + */ + public SubmitToolOutputRequestItem executeAndConvertToSubmitToolOutputRequestItem(String funName, JsonNode arguments, String toolId) { + return new SubmitToolOutputRequestItem(executeAndConvertToJson(funName, arguments).toPrettyString(), toolId); + } + + public Future executeAndConvertToSubmitToolOutputRequestItemAsync(String funName, JsonNode arguments, String toolId) { + return executorService.submit(() -> executeAndConvertToSubmitToolOutputRequestItem(funName, arguments, toolId)); + } + + /** + * @deprecated see {@link ToolMessage}{@link #executeAndConvertToToolMessage(String, JsonNode, String)} + */ + @Deprecated + public FunctionMessage executeAndConvertToFunctionMessage(String funName, JsonNode arguments) { + return new FunctionMessage(executeAndConvertToJson(funName, arguments).toPrettyString()); + } + + @Deprecated + public Future executeAndConvertToFunctionMessageAsync(String funName, JsonNode arguments) { + return executorService.submit(() -> executeAndConvertToFunctionMessage(funName, arguments)); + } + + + + + // public static void main(String[] args) { + // FunctionExecutorManager functionExecutorManager = new FunctionExecutorManager(); + // functionExecutorManager.addFunctionDefinition(FunctionDefinition.builder() + // .name("test") + // .parametersDefinitionByClass(String.class) + // .executor((Function) s -> s + "test2") + // .build()); + // System.out.println(functionExecutorManager.executeAndConvertToJson("test", new TextNode("test"))); + // } + +} diff --git a/api/src/main/java/com/theokanning/openai/function/FunctionParametersSerializer.java b/api/src/main/java/com/theokanning/openai/function/FunctionParametersSerializer.java new file mode 100644 index 0000000..2e7443d --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/function/FunctionParametersSerializer.java @@ -0,0 +1,31 @@ +package com.theokanning.openai.function; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.kjetland.jackson.jsonSchema.JsonSchemaConfig; +import com.kjetland.jackson.jsonSchema.JsonSchemaGenerator; + +import java.io.IOException; + +public class FunctionParametersSerializer extends JsonSerializer> { + + private final ObjectMapper mapper = new ObjectMapper(); + private final JsonSchemaConfig config = JsonSchemaConfig.vanillaJsonSchemaDraft4(); + private final JsonSchemaGenerator jsonSchemaGenerator = new JsonSchemaGenerator(mapper, config); + + @Override + public void serialize(Class value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + + } + + + +} + + + + + diff --git a/example/src/main/java/example/FunctionOrToolCreateExample.java b/example/src/main/java/example/FunctionOrToolCreateExample.java new file mode 100644 index 0000000..f1fe3a4 --- /dev/null +++ b/example/src/main/java/example/FunctionOrToolCreateExample.java @@ -0,0 +1,92 @@ +package example; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.kjetland.jackson.jsonSchema.JsonSchemaConfig; +import com.kjetland.jackson.jsonSchema.JsonSchemaGenerator; +import com.theokanning.openai.assistants.assistant.FunctionTool; +import com.theokanning.openai.completion.chat.ChatFunction; +import com.theokanning.openai.completion.chat.ChatFunctionDynamic; +import com.theokanning.openai.completion.chat.ChatFunctionProperty; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; + +/** + * severalWaysToCreateFunctionTool + * + * @author LiangTao + * @date 2024年05月10 10:30 + **/ +public class FunctionOrToolCreateExample { + static ObjectMapper mapper = new ObjectMapper(); + + public static void main(String[] args) throws JsonProcessingException { + // createByJsonString(); + createByDynamic(); + } + + /** + * create function by dynamic + */ + static void createByDynamic() throws JsonProcessingException { + //assistant function + ChatFunctionDynamic dynamicFun = ChatFunctionDynamic.builder() + .name("weather_reporter") + .description("Get the current weather of a location") + .addProperty(ChatFunctionProperty.builder() + .name("location") + .type("string") + .description("The city and state, e.g. San Francisco, CA") + .build()) + .addProperty(ChatFunctionProperty.builder() + .name("unit") + .type("string") + .enumValues(new HashSet<>(Arrays.asList("celsius", "fahrenheit"))) + .build()) + .build(); + + final ObjectMapper mapper = new ObjectMapper(); + final JsonSchemaConfig config = JsonSchemaConfig.vanillaJsonSchemaDraft4(); + final JsonSchemaGenerator jsonSchemaGenerator = new JsonSchemaGenerator(mapper, config); + System.out.println(jsonSchemaGenerator.generateJsonSchema(ToolUtil.Weather.class)); + System.out.println("----"); + + FunctionTool functionTool = new FunctionTool(dynamicFun); + System.out.println(mapper.writeValueAsString(functionTool)); + } + + /** + * create functionTool by ChatFunctionDynamic + */ + static void createByJsonString() throws JsonProcessingException { + //this function definition is not recommended, it is recommended to use the ToolUtil class to define the function + String funcDefJson = "{\n" + + " \"type\": \"object\",\n" + + " \"properties\": {\n" + + " \"location\": {\n" + + " \"type\": \"string\",\n" + + " \"description\": \"The city and state, e.g. San Francisco, CA\"\n" + + " },\n" + + " \"unit\": {\n" + + " \"type\": \"string\",\n" + + " \"enum\": [\"celsius\", \"fahrenheit\"]\n" + + " }\n" + + " },\n" + + " \"required\": [\"location\"]\n" + + "}"; + Map funcParameters = mapper.readValue(funcDefJson, new TypeReference>() { + }); + + //assistant function + FunctionTool functionTool = new FunctionTool(funcParameters); + System.out.println(mapper.writeValueAsString(functionTool)); + + + + + } + +} diff --git a/service/pom.xml b/service/pom.xml index 3371ecb..fac017d 100644 --- a/service/pom.xml +++ b/service/pom.xml @@ -41,17 +41,6 @@ - - com.kjetland - mbknor-jackson-jsonschema_2.12 - 1.0.34 - - - jackson-databind - com.fasterxml.jackson.core - - - org.mockito diff --git a/service/src/main/java/com/theokanning/openai/service/ChatFunctionMixIn.java b/service/src/main/java/com/theokanning/openai/service/ChatFunctionMixIn.java index d94a917..b601610 100644 --- a/service/src/main/java/com/theokanning/openai/service/ChatFunctionMixIn.java +++ b/service/src/main/java/com/theokanning/openai/service/ChatFunctionMixIn.java @@ -2,6 +2,10 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; +/** + * @deprecated This class is deprecated and will be removed in a future version + */ +@Deprecated public abstract class ChatFunctionMixIn { @JsonSerialize(using = ChatFunctionParametersSerializer.class) diff --git a/service/src/main/java/com/theokanning/openai/service/ChatFunctionParametersSerializer.java b/service/src/main/java/com/theokanning/openai/service/ChatFunctionParametersSerializer.java index c027391..5f340c7 100644 --- a/service/src/main/java/com/theokanning/openai/service/ChatFunctionParametersSerializer.java +++ b/service/src/main/java/com/theokanning/openai/service/ChatFunctionParametersSerializer.java @@ -10,6 +10,7 @@ import java.io.IOException; +@Deprecated public class ChatFunctionParametersSerializer extends JsonSerializer> { private final ObjectMapper mapper = new ObjectMapper(); diff --git a/service/src/test/java/com/theokanning/openai/service/assistants/AssistantTest.java b/service/src/test/java/com/theokanning/openai/service/assistants/AssistantTest.java index 916f60e..9a4fd11 100644 --- a/service/src/test/java/com/theokanning/openai/service/assistants/AssistantTest.java +++ b/service/src/test/java/com/theokanning/openai/service/assistants/AssistantTest.java @@ -7,12 +7,16 @@ import com.theokanning.openai.ListSearchParameters; import com.theokanning.openai.OpenAiResponse; import com.theokanning.openai.assistants.assistant.*; +import com.theokanning.openai.completion.chat.ChatFunctionDynamic; +import com.theokanning.openai.completion.chat.ChatFunctionParameters; +import com.theokanning.openai.completion.chat.ChatFunctionProperty; import com.theokanning.openai.completion.chat.ChatResponseFormat; import com.theokanning.openai.service.OpenAiService; import com.theokanning.openai.utils.TikTokensUtil; import org.junit.jupiter.api.*; import java.util.Arrays; +import java.util.HashSet; import java.util.Map; import static org.junit.jupiter.api.Assertions.*; @@ -45,29 +49,21 @@ static void teardown() { @Test @Order(1) void createAssistant() throws JsonProcessingException { - - //this function definition is not recommended, it is recommended to use the ToolUtil class to define the function - String funcDef = "{\n" + - " \"type\": \"object\",\n" + - " \"properties\": {\n" + - " \"location\": {\n" + - " \"type\": \"string\",\n" + - " \"description\": \"The city and state, e.g. San Francisco, CA\"\n" + - " },\n" + - " \"unit\": {\n" + - " \"type\": \"string\",\n" + - " \"enum\": [\"celsius\", \"fahrenheit\"]\n" + - " }\n" + - " },\n" + - " \"required\": [\"location\"]\n" + - "}"; - Map funcParameters = mapper.readValue(funcDef, new TypeReference>() { - }); - AssistantFunction function = AssistantFunction.builder() + ChatFunctionDynamic dynamicFun = ChatFunctionDynamic.builder() .name("weather_reporter") .description("Get the current weather of a location") - .parameters(funcParameters) - .build(); + .addProperty(ChatFunctionProperty.builder() + .name("location") + .type("string") + .description("The city and state, e.g. San Francisco, CA") + .build()) + .addProperty(ChatFunctionProperty.builder() + .name("unit") + .type("string") + .enumValues(new HashSet<>(Arrays.asList("celsius", "fahrenheit"))) + .description("The temperature unit, can be 'celsius' or 'fahrenheit") + .required(true) + .build()).build(); AssistantRequest assistantRequest = AssistantRequest.builder() .model("gpt-3.5-turbo") @@ -76,7 +72,7 @@ void createAssistant() throws JsonProcessingException { .instructions("You are a personal Math Tutor.") .tools(Arrays.asList( new CodeInterpreterTool(), - new FunctionTool(function) + new FunctionTool(dynamicFun) )) .responseFormat(ChatResponseFormat.AUTO) .temperature(0.2D) From 309393511a3ff04afc0441390c182c15ca3418b4 Mon Sep 17 00:00:00 2001 From: liangtao <547670718@qq.com> Date: Fri, 10 May 2024 14:54:22 +0800 Subject: [PATCH 3/6] =?UTF-8?q?deprecated=20example=20code=20=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../openai/completion/chat/ChatTool.java | 3 + .../openai/function/FunctionDefinition.java | 8 +- .../function/FunctionExecutorManager.java | 36 +++---- .../FunctionParametersSerializer.java | 1 - .../main/java/example/AssistantExample.java | 6 +- .../src/main/java/example/ChatExample.java | 18 ++-- .../example/FunctionOrToolCreateExample.java | 1 - .../main/java/example/FunctionsExample.java | 30 ++---- .../example/FunctionsWithStreamExample.java | 15 +-- example/src/main/java/example/ToolUtil.java | 9 +- .../openai/service/FunctionExecutor.java | 5 + .../openai/service/ChatCompletionTest.java | 99 +++++++++++-------- .../service/assistants/AssistantTest.java | 3 - .../service/assistants/AssistantToolTest.java | 8 +- .../openai/service/util/ToolUtil.java | 9 +- 15 files changed, 120 insertions(+), 131 deletions(-) diff --git a/api/src/main/java/com/theokanning/openai/completion/chat/ChatTool.java b/api/src/main/java/com/theokanning/openai/completion/chat/ChatTool.java index 82ee081..d193582 100644 --- a/api/src/main/java/com/theokanning/openai/completion/chat/ChatTool.java +++ b/api/src/main/java/com/theokanning/openai/completion/chat/ChatTool.java @@ -9,6 +9,9 @@ @NoArgsConstructor @AllArgsConstructor public class ChatTool { + public ChatTool(@NonNull Object function) { + this.function = function; + } /** * The name of the tool being called, only function supported for now. diff --git a/api/src/main/java/com/theokanning/openai/function/FunctionDefinition.java b/api/src/main/java/com/theokanning/openai/function/FunctionDefinition.java index a6bfe25..5294599 100644 --- a/api/src/main/java/com/theokanning/openai/function/FunctionDefinition.java +++ b/api/src/main/java/com/theokanning/openai/function/FunctionDefinition.java @@ -1,14 +1,10 @@ package com.theokanning.openai.function; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.node.TextNode; import com.kjetland.jackson.jsonSchema.JsonSchemaGenerator; -import com.theokanning.openai.completion.chat.ChatFunction; -import lombok.*; +import lombok.Getter; +import lombok.NonNull; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; import java.util.function.Function; /** diff --git a/api/src/main/java/com/theokanning/openai/function/FunctionExecutorManager.java b/api/src/main/java/com/theokanning/openai/function/FunctionExecutorManager.java index b424e8c..60093bd 100644 --- a/api/src/main/java/com/theokanning/openai/function/FunctionExecutorManager.java +++ b/api/src/main/java/com/theokanning/openai/function/FunctionExecutorManager.java @@ -2,8 +2,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.fasterxml.jackson.databind.node.TextNode; import com.theokanning.openai.assistants.run.SubmitToolOutputRequestItem; import com.theokanning.openai.completion.chat.FunctionMessage; import com.theokanning.openai.completion.chat.ToolMessage; @@ -13,7 +11,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; -import java.util.function.Function; /** * @author LiangTao @@ -54,6 +51,10 @@ public FunctionDefinition getFunctionDefinition(String functionName) { return functionHolderMap.get(functionName); } + public List getFunctionDefinitions() { + return new ArrayList<>(functionHolderMap.values()); + } + public void addFunctionDefinition(FunctionDefinition functionDefinition) { if (functionDefinition.getExecutor() == null) { throw new IllegalArgumentException("FunctionDefinition must have executor"); @@ -94,15 +95,14 @@ public JsonNode executeAndConvertToJson(String funName, JsonNode arguments) { /** * chat-completion toolMessage */ - public ToolMessage executeAndConvertToToolMessage(String funName, JsonNode arguments, String toolId) { + public ToolMessage executeAndConvertToChatMessage(String funName, JsonNode arguments, String toolId) { return new ToolMessage(executeAndConvertToJson(funName, arguments).toPrettyString(), toolId); } - public Future executeAndConvertToToolMessageAsync(String funName, JsonNode arguments, String toolId) { - return executorService.submit(() -> executeAndConvertToToolMessage(funName, arguments, toolId)); + public Future executeAndConvertToChatMessageAsync(String funName, JsonNode arguments, String toolId) { + return executorService.submit(() -> executeAndConvertToChatMessage(funName, arguments, toolId)); } - /** * assistant stream toolMessage */ @@ -115,29 +115,15 @@ public Future executeAndConvertToSubmitToolOutputRe } /** - * @deprecated see {@link ToolMessage}{@link #executeAndConvertToToolMessage(String, JsonNode, String)} + * @deprecated see {@link ToolMessage}{@link #executeAndConvertToChatMessage(String, JsonNode, String)} */ @Deprecated - public FunctionMessage executeAndConvertToFunctionMessage(String funName, JsonNode arguments) { + public FunctionMessage executeAndConvertToChatMessage(String funName, JsonNode arguments) { return new FunctionMessage(executeAndConvertToJson(funName, arguments).toPrettyString()); } @Deprecated - public Future executeAndConvertToFunctionMessageAsync(String funName, JsonNode arguments) { - return executorService.submit(() -> executeAndConvertToFunctionMessage(funName, arguments)); + public Future executeAndConvertToChatMessageAsync(String funName, JsonNode arguments) { + return executorService.submit(() -> executeAndConvertToChatMessage(funName, arguments)); } - - - - - // public static void main(String[] args) { - // FunctionExecutorManager functionExecutorManager = new FunctionExecutorManager(); - // functionExecutorManager.addFunctionDefinition(FunctionDefinition.builder() - // .name("test") - // .parametersDefinitionByClass(String.class) - // .executor((Function) s -> s + "test2") - // .build()); - // System.out.println(functionExecutorManager.executeAndConvertToJson("test", new TextNode("test"))); - // } - } diff --git a/api/src/main/java/com/theokanning/openai/function/FunctionParametersSerializer.java b/api/src/main/java/com/theokanning/openai/function/FunctionParametersSerializer.java index 2e7443d..5be2449 100644 --- a/api/src/main/java/com/theokanning/openai/function/FunctionParametersSerializer.java +++ b/api/src/main/java/com/theokanning/openai/function/FunctionParametersSerializer.java @@ -1,7 +1,6 @@ package com.theokanning.openai.function; import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializerProvider; diff --git a/example/src/main/java/example/AssistantExample.java b/example/src/main/java/example/AssistantExample.java index b7e2f2d..f228ac7 100644 --- a/example/src/main/java/example/AssistantExample.java +++ b/example/src/main/java/example/AssistantExample.java @@ -15,7 +15,7 @@ import com.theokanning.openai.assistants.thread.Thread; import com.theokanning.openai.assistants.thread.ThreadRequest; import com.theokanning.openai.file.File; -import com.theokanning.openai.service.FunctionExecutor; +import com.theokanning.openai.function.FunctionExecutorManager; import com.theokanning.openai.service.OpenAiService; import com.theokanning.openai.service.assistant_stream.AssistantSSE; import io.reactivex.Flowable; @@ -44,7 +44,7 @@ public static void main(String[] args) { static void assistantToolCall() { OpenAiService service = new OpenAiService(); - FunctionExecutor executor = new FunctionExecutor(Collections.singletonList(ToolUtil.weatherFunction())); + FunctionExecutorManager executor = new FunctionExecutorManager(Collections.singletonList(ToolUtil.weatherFunction())); AssistantRequest assistantRequest = AssistantRequest.builder() .model("gpt-3.5-turbo").name("weather assistant") .instructions("You are a weather assistant responsible for calling the weather API to return weather information based on the location entered by the user") @@ -82,7 +82,7 @@ static void assistantToolCall() { ToolCallFunction function = toolCall.getFunction(); String toolCallId = toolCall.getId(); - SubmitToolOutputsRequest submitToolOutputsRequest = SubmitToolOutputsRequest.ofSingletonToolOutput(toolCallId, executor.executeAndConvertToJson(function).toPrettyString()); + SubmitToolOutputsRequest submitToolOutputsRequest = SubmitToolOutputsRequest.ofSingletonToolOutput(toolCallId, executor.executeAndConvertToJson(function.getName(),function.getArguments()).toPrettyString()); retrievedRun = service.submitToolOutputs(threadId, retrievedRun.getId(), submitToolOutputsRequest); while (!(retrievedRun.getStatus().equals("completed")) diff --git a/example/src/main/java/example/ChatExample.java b/example/src/main/java/example/ChatExample.java index 188cdab..4352ca3 100644 --- a/example/src/main/java/example/ChatExample.java +++ b/example/src/main/java/example/ChatExample.java @@ -2,7 +2,8 @@ import com.theokanning.openai.assistants.run.ToolChoice; import com.theokanning.openai.completion.chat.*; -import com.theokanning.openai.service.FunctionExecutor; +import com.theokanning.openai.function.FunctionDefinition; +import com.theokanning.openai.function.FunctionExecutorManager; import com.theokanning.openai.service.OpenAiService; import java.time.Duration; @@ -154,12 +155,13 @@ static void gptVision() { static void streamChatWithTool() { OpenAiService service = new OpenAiService(Duration.ofSeconds(30)); - final List functions = Arrays.asList( + final List functions = Arrays.asList( //1. 天气查询 - ChatFunction.builder() + FunctionDefinition.builder() .name("get_weather") .description("Get the current weather in a given location") - .executor(ToolUtil.Weather.class, w -> { + .parametersDefinitionByClass(ToolUtil.Weather.class) + .executor(w -> { switch (w.location) { case "tokyo": return new ToolUtil.WeatherResponse(w.location, w.unit, 10, "cloudy"); @@ -172,9 +174,9 @@ static void streamChatWithTool() { } }).build(), //2. 城市查询 - ChatFunction.builder().name("getCities").description("Get a list of cities by time").executor(ToolUtil.City.class, v -> Arrays.asList("tokyo", "paris")).build() + FunctionDefinition.builder().name("getCities").description("Get a list of cities by time").parametersDefinitionByClass(ToolUtil.City.class).executor(v -> Arrays.asList("tokyo", "paris")).build() ); - final FunctionExecutor toolExecutor = new FunctionExecutor(functions); + FunctionExecutorManager toolExecutor = new FunctionExecutorManager(functions); List tools = new ArrayList<>(); tools.add(new ChatTool(functions.get(0))); @@ -203,7 +205,7 @@ static void streamChatWithTool() { List toolCalls = accumulatedMessage.getToolCalls(); ChatToolCall toolCall = toolCalls.get(0); - ToolMessage toolMessage = toolExecutor.executeAndConvertToMessageHandlingExceptions(toolCall.getFunction(), toolCall.getId()); + ToolMessage toolMessage = toolExecutor.executeAndConvertToChatMessage(toolCall.getFunction().getName(),toolCall.getFunction().getArguments(), toolCall.getId()); messages.add(accumulatedMessage); messages.add(toolMessage); @@ -229,7 +231,7 @@ static void streamChatWithTool() { messages.add(accumulatedMessage2); for (ChatToolCall weatherToolCall : accumulatedMessage2.getToolCalls()) { - messages.add(toolExecutor.executeAndConvertToMessage(weatherToolCall.getFunction(), weatherToolCall.getId())); + messages.add(toolExecutor.executeAndConvertToChatMessage(weatherToolCall.getFunction().getName(),weatherToolCall.getFunction().getArguments(), weatherToolCall.getId())); } ChatCompletionRequest chatCompletionRequest3 = ChatCompletionRequest diff --git a/example/src/main/java/example/FunctionOrToolCreateExample.java b/example/src/main/java/example/FunctionOrToolCreateExample.java index f1fe3a4..5c1899b 100644 --- a/example/src/main/java/example/FunctionOrToolCreateExample.java +++ b/example/src/main/java/example/FunctionOrToolCreateExample.java @@ -6,7 +6,6 @@ import com.kjetland.jackson.jsonSchema.JsonSchemaConfig; import com.kjetland.jackson.jsonSchema.JsonSchemaGenerator; import com.theokanning.openai.assistants.assistant.FunctionTool; -import com.theokanning.openai.completion.chat.ChatFunction; import com.theokanning.openai.completion.chat.ChatFunctionDynamic; import com.theokanning.openai.completion.chat.ChatFunctionProperty; diff --git a/example/src/main/java/example/FunctionsExample.java b/example/src/main/java/example/FunctionsExample.java index e6dcd94..04904b4 100644 --- a/example/src/main/java/example/FunctionsExample.java +++ b/example/src/main/java/example/FunctionsExample.java @@ -1,7 +1,8 @@ package example; import com.theokanning.openai.completion.chat.*; -import com.theokanning.openai.service.FunctionExecutor; +import com.theokanning.openai.function.FunctionDefinition; +import com.theokanning.openai.function.FunctionExecutorManager; import com.theokanning.openai.service.OpenAiService; import java.util.*; @@ -12,7 +13,8 @@ class FunctionsExample { public static void main(String... args) { OpenAiService service = new OpenAiService(); - FunctionExecutor functionExecutor = new FunctionExecutor(Collections.singletonList(ToolUtil.weatherFunction())); + FunctionDefinition fd = ToolUtil.weatherFunction(); + FunctionExecutorManager functionExecutor = new FunctionExecutorManager(Collections.singletonList(fd)); List messages = new ArrayList<>(); @@ -29,7 +31,7 @@ public static void main(String... args) { .builder() .model("gpt-3.5-turbo-0613") .messages(messages) - .functions(functionExecutor.getFunctions()) + .functions(functionExecutor.getFunctionDefinitions()) .functionCall("auto") .n(1) .maxTokens(100) @@ -41,25 +43,9 @@ public static void main(String... args) { ChatFunctionCall functionCall = responseMessage.getFunctionCall(); if (functionCall != null) { System.out.println("Trying to execute " + functionCall.getName() + "..."); - Optional message = functionExecutor.executeAndConvertToMessageSafely(functionCall); - /* You can also try 'executeAndConvertToMessage' inside a try-catch block, and add the following line inside the catch: - "message = executor.handleException(exception);" - The content of the message will be the exception itself, so the flow of the conversation will not be interrupted, and you will still be able to log the issue. */ - - if (message.isPresent()) { - /* At this point: - 1. The function requested was found - 2. The request was converted to its specified object for execution (Weather.class in this case) - 3. It was executed - 4. The response was finally converted to a ChatMessage object. */ - - System.out.println("Executed " + functionCall.getName() + "."); - messages.add(message.get()); - continue; - } else { - System.out.println("Something went wrong with the execution of " + functionCall.getName() + "..."); - break; - } + FunctionMessage message = functionExecutor.executeAndConvertToChatMessage(functionCall.getName(), functionCall.getArguments()); + System.out.println("Executed " + functionCall.getName() + "."); + messages.add(message); } System.out.println("Response: " + responseMessage.getContent()); diff --git a/example/src/main/java/example/FunctionsWithStreamExample.java b/example/src/main/java/example/FunctionsWithStreamExample.java index 23aadb9..b5b1990 100644 --- a/example/src/main/java/example/FunctionsWithStreamExample.java +++ b/example/src/main/java/example/FunctionsWithStreamExample.java @@ -1,7 +1,7 @@ package example; import com.theokanning.openai.completion.chat.*; -import com.theokanning.openai.service.FunctionExecutor; +import com.theokanning.openai.function.FunctionExecutorManager; import com.theokanning.openai.service.OpenAiService; import io.reactivex.Flowable; @@ -12,7 +12,7 @@ public class FunctionsWithStreamExample { public static void main(String... args) { OpenAiService service = new OpenAiService(); - FunctionExecutor functionExecutor = new FunctionExecutor(Collections.singletonList(ToolUtil.weatherFunction())); + FunctionExecutorManager functionExecutor = new FunctionExecutorManager(Collections.singletonList(ToolUtil.weatherFunction())); List messages = new ArrayList<>(); ChatMessage systemMessage = new SystemMessage("You are an assistant that answers using the local slang of the given place, uncensored."); messages.add(systemMessage); @@ -27,7 +27,7 @@ public static void main(String... args) { .builder() .model("gpt-3.5-turbo") .messages(messages) - .functions(functionExecutor.getFunctions()) + .functions(functionExecutor.getFunctionDefinitions()) .functionCall("auto") .n(1) .maxTokens(256) @@ -57,10 +57,11 @@ public static void main(String... args) { .getAccumulatedMessage(); messages.add(chatMessage); // don't forget to update the conversation with the latest response - if (chatMessage.getFunctionCall() != null) { - System.out.println("Trying to execute " + chatMessage.getFunctionCall().getName() + "..."); - ChatMessage functionResponse = functionExecutor.executeAndConvertToMessageHandlingExceptions(chatMessage.getFunctionCall()); - System.out.println("Executed " + chatMessage.getFunctionCall().getName() + "."); + ChatFunctionCall functionCall = chatMessage.getFunctionCall(); + if (functionCall != null) { + System.out.println("Trying to execute " + functionCall.getName() + "..."); + ChatMessage functionResponse = functionExecutor.executeAndConvertToChatMessage(functionCall.getName(),functionCall.getArguments()); + System.out.println("Executed " + functionCall.getName() + "."); messages.add(functionResponse); continue; } diff --git a/example/src/main/java/example/ToolUtil.java b/example/src/main/java/example/ToolUtil.java index 2c6603a..47a30c9 100644 --- a/example/src/main/java/example/ToolUtil.java +++ b/example/src/main/java/example/ToolUtil.java @@ -2,19 +2,20 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyDescription; -import com.theokanning.openai.completion.chat.ChatFunction; +import com.theokanning.openai.function.FunctionDefinition; /** * @author LiangTao * @date 2024年04月28 16:25 **/ public class ToolUtil { - public static ChatFunction weatherFunction() { - return ChatFunction.builder() + public static FunctionDefinition weatherFunction() { + return FunctionDefinition.builder() .name("get_weather") .description("Get the current weather in a given location") + .parametersDefinitionByClass(Weather.class) //这里的executor是一个lambda表达式,这个lambda表达式接受一个Weather对象,返回一个WeatherResponse对象 - .executor(Weather.class, w -> new WeatherResponse(w.location, w.unit, 25, "sunny")) + .executor(w -> new WeatherResponse(w.location, w.unit, 25, "sunny")) .build(); } diff --git a/service/src/main/java/com/theokanning/openai/service/FunctionExecutor.java b/service/src/main/java/com/theokanning/openai/service/FunctionExecutor.java index 9d3f873..6e8cb5b 100644 --- a/service/src/main/java/com/theokanning/openai/service/FunctionExecutor.java +++ b/service/src/main/java/com/theokanning/openai/service/FunctionExecutor.java @@ -13,6 +13,11 @@ import java.util.*; +/** + * @deprecated This class is deprecated and will be removed in a future version + * replaced by {@link com.theokanning.openai.function.FunctionExecutorManager} + */ +@Deprecated public class FunctionExecutor { private ObjectMapper MAPPER = new ObjectMapper(); diff --git a/service/src/test/java/com/theokanning/openai/service/ChatCompletionTest.java b/service/src/test/java/com/theokanning/openai/service/ChatCompletionTest.java index 6a8f002..ea07b3d 100644 --- a/service/src/test/java/com/theokanning/openai/service/ChatCompletionTest.java +++ b/service/src/test/java/com/theokanning/openai/service/ChatCompletionTest.java @@ -7,6 +7,8 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.theokanning.openai.assistants.run.ToolChoice; import com.theokanning.openai.completion.chat.*; +import com.theokanning.openai.function.FunctionDefinition; +import com.theokanning.openai.function.FunctionExecutorManager; import com.theokanning.openai.service.util.ToolUtil; import org.junit.jupiter.api.Test; @@ -116,8 +118,9 @@ private boolean isValidJson(String jsonString) { @Test void createChatCompletionWithFunctions() { - final List functions = Collections.singletonList(ToolUtil.weatherFunction()); - final FunctionExecutor functionExecutor = new FunctionExecutor(functions); + final List functions = Collections.singletonList(ToolUtil.weatherFunction()); + // final FunctionExecutor functionExecutor = new FunctionExecutor(functions); + final FunctionExecutorManager functionExecutor = new FunctionExecutorManager(functions); final List messages = new ArrayList<>(); final ChatMessage systemMessage = new SystemMessage("You are a helpful assistant."); @@ -129,7 +132,7 @@ void createChatCompletionWithFunctions() { .builder() .model("gpt-3.5-turbo-0613") .messages(messages) - .functions(functionExecutor.getFunctions()) + .functions(functions) .n(1) .maxTokens(100) .logitBias(new HashMap<>()) @@ -137,19 +140,20 @@ void createChatCompletionWithFunctions() { ChatCompletionChoice choice = service.createChatCompletion(chatCompletionRequest).getChoices().get(0); assertEquals("function_call", choice.getFinishReason()); - assertNotNull(choice.getMessage().getFunctionCall()); - assertEquals("get_weather", choice.getMessage().getFunctionCall().getName()); - assertInstanceOf(ObjectNode.class, choice.getMessage().getFunctionCall().getArguments()); + ChatFunctionCall functionCall = choice.getMessage().getFunctionCall(); + assertNotNull(functionCall); + assertEquals("get_weather", functionCall.getName()); + assertInstanceOf(ObjectNode.class, functionCall.getArguments()); - ChatMessage callResponse = functionExecutor.executeAndConvertToMessageHandlingExceptions(choice.getMessage().getFunctionCall()); + ChatMessage callResponse = functionExecutor.executeAndConvertToChatMessage(functionCall.getName(),functionCall.getArguments()); assertNotEquals("error", callResponse.getName()); // this performs an unchecked cast - ToolUtil.WeatherResponse functionExecutionResponse = functionExecutor.execute(choice.getMessage().getFunctionCall()); + ToolUtil.WeatherResponse functionExecutionResponse = functionExecutor.execute(functionCall.getName(),functionCall.getArguments()); assertInstanceOf(ToolUtil.WeatherResponse.class, functionExecutionResponse); assertEquals(25, functionExecutionResponse.temperature); - JsonNode jsonFunctionExecutionResponse = functionExecutor.executeAndConvertToJson(choice.getMessage().getFunctionCall()); + JsonNode jsonFunctionExecutionResponse = functionExecutor.executeAndConvertToJson(functionCall.getName(),functionCall.getArguments()); assertInstanceOf(ObjectNode.class, jsonFunctionExecutionResponse); assertEquals("25", jsonFunctionExecutionResponse.get("temperature").asText()); @@ -160,7 +164,7 @@ void createChatCompletionWithFunctions() { .builder() .model("gpt-3.5-turbo-0613") .messages(messages) - .functions(functionExecutor.getFunctions()) + .functions(functions) .n(1) .maxTokens(100) .logitBias(new HashMap<>()) @@ -219,8 +223,8 @@ void createChatCompletionWithDynamicFunctions() { @Test void streamChatCompletionWithFunctions() { - final List functions = Collections.singletonList(ToolUtil.weatherFunction()); - final FunctionExecutor functionExecutor = new FunctionExecutor(functions); + final List functions = Collections.singletonList(ToolUtil.weatherFunction()); + final FunctionExecutorManager functionExecutor = new FunctionExecutorManager(functions); final List messages = new ArrayList<>(); final ChatMessage systemMessage = new SystemMessage("You are a helpful assistant."); @@ -232,7 +236,7 @@ void streamChatCompletionWithFunctions() { .builder() .model("gpt-3.5-turbo-0613") .messages(messages) - .functions(functionExecutor.getFunctions()) + .functions(functions) .n(1) .maxTokens(100) .logitBias(new HashMap<>()) @@ -241,19 +245,20 @@ void streamChatCompletionWithFunctions() { AssistantMessage accumulatedMessage = service.mapStreamToAccumulator(service.streamChatCompletion(chatCompletionRequest)) .blockingLast() .getAccumulatedMessage(); - assertNotNull(accumulatedMessage.getFunctionCall()); - assertEquals("get_weather", accumulatedMessage.getFunctionCall().getName()); - assertInstanceOf(ObjectNode.class, accumulatedMessage.getFunctionCall().getArguments()); + ChatFunctionCall functionCall = accumulatedMessage.getFunctionCall(); + assertNotNull(functionCall); + assertEquals("get_weather", functionCall.getName()); + assertInstanceOf(ObjectNode.class, functionCall.getArguments()); - ChatMessage callResponse = functionExecutor.executeAndConvertToMessageHandlingExceptions(accumulatedMessage.getFunctionCall()); + ChatMessage callResponse = functionExecutor.executeAndConvertToChatMessage(functionCall.getName(),functionCall.getArguments()); assertNotEquals("error", callResponse.getName()); // this performs an unchecked cast - ToolUtil.WeatherResponse functionExecutionResponse = functionExecutor.execute(accumulatedMessage.getFunctionCall()); + ToolUtil.WeatherResponse functionExecutionResponse = functionExecutor.execute(functionCall.getName(),functionCall.getArguments()); assertInstanceOf(ToolUtil.WeatherResponse.class, functionExecutionResponse); assertEquals(25, functionExecutionResponse.temperature); - JsonNode jsonFunctionExecutionResponse = functionExecutor.executeAndConvertToJson(accumulatedMessage.getFunctionCall()); + JsonNode jsonFunctionExecutionResponse = functionExecutor.executeAndConvertToJson(functionCall.getName(),functionCall.getArguments()); assertInstanceOf(ObjectNode.class, jsonFunctionExecutionResponse); assertEquals("25", jsonFunctionExecutionResponse.get("temperature").asText()); @@ -265,7 +270,7 @@ void streamChatCompletionWithFunctions() { .builder() .model("gpt-3.5-turbo-0613") .messages(messages) - .functions(functionExecutor.getFunctions()) + .functions(functions) .n(1) .maxTokens(100) .logitBias(new HashMap<>()) @@ -352,16 +357,17 @@ void createChatCompletionWithToolFunctions() { ChatToolCall toolCall = choice.getMessage().getToolCalls().get(0); - FunctionExecutor toolExecutor = new FunctionExecutor(Arrays.asList(ToolUtil.weatherFunction())); - Object functionExecutionResponse = toolExecutor.execute(toolCall.getFunction()); + FunctionExecutorManager toolExecutor = new FunctionExecutorManager(Arrays.asList(ToolUtil.weatherFunction())); + ChatFunctionCall function = toolCall.getFunction(); + Object functionExecutionResponse = toolExecutor.execute(function.getName(), function.getArguments()); assertInstanceOf(ToolUtil.WeatherResponse.class, functionExecutionResponse); assertEquals(25, ((ToolUtil.WeatherResponse) functionExecutionResponse).temperature); - JsonNode jsonFunctionExecutionResponse = toolExecutor.executeAndConvertToJson(toolCall.getFunction()); + JsonNode jsonFunctionExecutionResponse = toolExecutor.executeAndConvertToJson(function.getName(), function.getArguments()); assertInstanceOf(ObjectNode.class, jsonFunctionExecutionResponse); assertEquals("25", jsonFunctionExecutionResponse.get("temperature").asText()); - ToolMessage chatMessageTool = toolExecutor.executeAndConvertToMessageHandlingExceptions(toolCall.getFunction(), toolCall.getId()); + ToolMessage chatMessageTool = toolExecutor.executeAndConvertToChatMessage(function.getName(),function.getArguments(), toolCall.getId()); //确保不是异常的返回 assertNotEquals("error", chatMessageTool.getName()); @@ -387,10 +393,11 @@ void createChatCompletionWithToolFunctions() { @Test void createChatCompletionWithMultipleToolCalls() { - final List functions = Arrays.asList(ChatFunction.builder() + final List functions = Arrays.asList(FunctionDefinition.builder() .name("get_weather") .description("Get the current weather in a given location") - .executor(ToolUtil.Weather.class, w -> { + .parametersDefinitionByClass(ToolUtil.Weather.class) + .executor( w -> { switch (w.location) { case "tokyo": return new ToolUtil.WeatherResponse(w.location, w.unit, 10, "cloudy"); @@ -402,12 +409,14 @@ void createChatCompletionWithMultipleToolCalls() { return new ToolUtil.WeatherResponse(w.location, w.unit, 0, "unknown"); } }).build(), - ChatFunction.builder().name("getCities").description("Get a list of cities by time").executor(ToolUtil.City.class, v -> { + FunctionDefinition.builder().name("getCities").description("Get a list of cities by time") + .parametersDefinitionByClass(ToolUtil.City.class) + .executor(v -> { assertEquals("2022-12-01", v.time); return Arrays.asList("tokyo", "paris"); }).build() ); - final FunctionExecutor toolExecutor = new FunctionExecutor(functions); + final FunctionExecutorManager toolExecutor = new FunctionExecutorManager(functions); List tools = new ArrayList<>(); tools.add(new ChatTool(functions.get(0))); @@ -438,15 +447,16 @@ void createChatCompletionWithMultipleToolCalls() { assertInstanceOf(ObjectNode.class, choice.getMessage().getToolCalls().get(0).getFunction().getArguments()); ChatToolCall toolCall = choice.getMessage().getToolCalls().get(0); - Object execute = toolExecutor.execute(toolCall.getFunction()); + ChatFunctionCall function = toolCall.getFunction(); + Object execute = toolExecutor.execute(function.getName(), function.getArguments()); assertInstanceOf(List.class, execute); - JsonNode jsonNode = toolExecutor.executeAndConvertToJson(toolCall.getFunction()); + JsonNode jsonNode = toolExecutor.executeAndConvertToJson(function.getName(),function.getArguments()); assertInstanceOf(ArrayNode.class, jsonNode); - ToolMessage toolMessage = toolExecutor.executeAndConvertToMessageHandlingExceptions(toolCall.getFunction(), toolCall.getId()); + ToolMessage toolMessage = toolExecutor.executeAndConvertToChatMessage(function.getName(),function.getArguments(), toolCall.getId()); assertNotEquals("error", toolMessage.getName()); messages.add(choice.getMessage()); @@ -476,9 +486,9 @@ void createChatCompletionWithMultipleToolCalls() { messages.add(choice2.getMessage()); for (ChatToolCall weatherToolCall : choice2.getMessage().getToolCalls()) { - Object itemResult = toolExecutor.execute(weatherToolCall.getFunction()); + Object itemResult = toolExecutor.execute(weatherToolCall.getFunction().getName(), weatherToolCall.getFunction().getArguments()); assertInstanceOf(ToolUtil.WeatherResponse.class, itemResult); - messages.add(toolExecutor.executeAndConvertToMessage(weatherToolCall.getFunction(), weatherToolCall.getId())); + messages.add(toolExecutor.executeAndConvertToChatMessage(weatherToolCall.getFunction().getName(),weatherToolCall.getFunction().getArguments(), weatherToolCall.getId())); } ChatCompletionRequest chatCompletionRequest3 = ChatCompletionRequest @@ -530,12 +540,13 @@ void createImageChatCompletion() { */ @Test void streamChatMultipleToolCalls() { - final List functions = Arrays.asList( + final List functions = Arrays.asList( //1. 天气查询 - ChatFunction.builder() + FunctionDefinition.builder() .name("get_weather") .description("Get the current weather in a given location") - .executor(ToolUtil.Weather.class, w -> { + .parametersDefinitionByClass(ToolUtil.Weather.class) + .executor( w -> { switch (w.location) { case "tokyo": return new ToolUtil.WeatherResponse(w.location, w.unit, 10, "cloudy"); @@ -548,9 +559,9 @@ void streamChatMultipleToolCalls() { } }).build(), //2. 城市查询 - ChatFunction.builder().name("getCities").description("Get a list of cities by time").executor(ToolUtil.City.class, v -> Arrays.asList("tokyo", "paris")).build() + FunctionDefinition.builder().name("getCities").description("Get a list of cities by time").parametersDefinitionByClass(ToolUtil.City.class).executor(v -> Arrays.asList("tokyo", "paris")).build() ); - final FunctionExecutor toolExecutor = new FunctionExecutor(functions); + final FunctionExecutorManager toolExecutor = new FunctionExecutorManager(functions); List tools = new ArrayList<>(); tools.add(new ChatTool(functions.get(0))); @@ -584,14 +595,15 @@ void streamChatMultipleToolCalls() { assertInstanceOf(ObjectNode.class, toolCalls.get(0).getFunction().getArguments()); ChatToolCall toolCall = toolCalls.get(0); - Object execute = toolExecutor.execute(toolCall.getFunction()); + ChatFunctionCall function = toolCall.getFunction(); + Object execute = toolExecutor.execute(function.getName(), function.getArguments()); assertInstanceOf(List.class, execute); - JsonNode jsonNode = toolExecutor.executeAndConvertToJson(toolCall.getFunction()); + JsonNode jsonNode = toolExecutor.executeAndConvertToJson(function.getName(), function.getArguments()); assertInstanceOf(ArrayNode.class, jsonNode); - ToolMessage toolMessage = toolExecutor.executeAndConvertToMessageHandlingExceptions(toolCall.getFunction(), toolCall.getId()); + ToolMessage toolMessage = toolExecutor.executeAndConvertToChatMessage(function.getName(),function.getArguments(), toolCall.getId()); assertNotEquals("error", toolMessage.getName()); messages.add(accumulatedMessage); @@ -622,9 +634,10 @@ void streamChatMultipleToolCalls() { messages.add(accumulatedMessage2); for (ChatToolCall weatherToolCall : accumulatedMessage2.getToolCalls()) { - Object itemResult = toolExecutor.execute(weatherToolCall.getFunction()); + ChatFunctionCall call2 = weatherToolCall.getFunction(); + Object itemResult = toolExecutor.execute(call2.getName(), call2.getArguments()); assertInstanceOf(ToolUtil.WeatherResponse.class, itemResult); - messages.add(toolExecutor.executeAndConvertToMessage(weatherToolCall.getFunction(), weatherToolCall.getId())); + messages.add(toolExecutor.executeAndConvertToChatMessage(call2.getName(),call2.getArguments(), weatherToolCall.getId())); } ChatCompletionRequest chatCompletionRequest3 = ChatCompletionRequest diff --git a/service/src/test/java/com/theokanning/openai/service/assistants/AssistantTest.java b/service/src/test/java/com/theokanning/openai/service/assistants/AssistantTest.java index 9a4fd11..930b936 100644 --- a/service/src/test/java/com/theokanning/openai/service/assistants/AssistantTest.java +++ b/service/src/test/java/com/theokanning/openai/service/assistants/AssistantTest.java @@ -1,14 +1,12 @@ package com.theokanning.openai.service.assistants; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.theokanning.openai.DeleteResult; import com.theokanning.openai.ListSearchParameters; import com.theokanning.openai.OpenAiResponse; import com.theokanning.openai.assistants.assistant.*; import com.theokanning.openai.completion.chat.ChatFunctionDynamic; -import com.theokanning.openai.completion.chat.ChatFunctionParameters; import com.theokanning.openai.completion.chat.ChatFunctionProperty; import com.theokanning.openai.completion.chat.ChatResponseFormat; import com.theokanning.openai.service.OpenAiService; @@ -17,7 +15,6 @@ import java.util.Arrays; import java.util.HashSet; -import java.util.Map; import static org.junit.jupiter.api.Assertions.*; diff --git a/service/src/test/java/com/theokanning/openai/service/assistants/AssistantToolTest.java b/service/src/test/java/com/theokanning/openai/service/assistants/AssistantToolTest.java index 27963e6..f5e307f 100644 --- a/service/src/test/java/com/theokanning/openai/service/assistants/AssistantToolTest.java +++ b/service/src/test/java/com/theokanning/openai/service/assistants/AssistantToolTest.java @@ -10,7 +10,7 @@ import com.theokanning.openai.assistants.run.*; import com.theokanning.openai.assistants.thread.Thread; import com.theokanning.openai.assistants.thread.ThreadRequest; -import com.theokanning.openai.service.FunctionExecutor; +import com.theokanning.openai.function.FunctionExecutorManager; import com.theokanning.openai.service.OpenAiService; import com.theokanning.openai.service.util.ToolUtil; import org.junit.jupiter.api.*; @@ -36,11 +36,11 @@ class AssistantToolTest { static String threadId; - static FunctionExecutor executor; + static FunctionExecutorManager executor; @BeforeAll static void initial() { - executor = new FunctionExecutor(Collections.singletonList(ToolUtil.weatherFunction())); + executor = new FunctionExecutorManager(Collections.singletonList(ToolUtil.weatherFunction())); AssistantRequest assistantRequest = AssistantRequest.builder() .model("gpt-3.5-turbo").name("weather assistant") .instructions("You are a weather assistant responsible for calling the weather API to return weather information based on the location entered by the user") @@ -98,7 +98,7 @@ void weatherFunctionCallTest() { SubmitToolOutputRequestItem toolOutputRequestItem = SubmitToolOutputRequestItem.builder() .toolCallId(toolCallId) - .output(executor.executeAndConvertToJson(function).toPrettyString()) + .output(executor.executeAndConvertToJson(function.getName(),function.getArguments()).toPrettyString()) .build(); List toolOutputRequestItems = Collections.singletonList(toolOutputRequestItem); SubmitToolOutputsRequest submitToolOutputsRequest = SubmitToolOutputsRequest.builder() diff --git a/service/src/test/java/com/theokanning/openai/service/util/ToolUtil.java b/service/src/test/java/com/theokanning/openai/service/util/ToolUtil.java index b1d0c4b..e0657d0 100644 --- a/service/src/test/java/com/theokanning/openai/service/util/ToolUtil.java +++ b/service/src/test/java/com/theokanning/openai/service/util/ToolUtil.java @@ -2,19 +2,20 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyDescription; -import com.theokanning.openai.completion.chat.ChatFunction; +import com.theokanning.openai.function.FunctionDefinition; /** * @author LiangTao * @date 2024年04月28 16:25 **/ public class ToolUtil { - public static ChatFunction weatherFunction() { - return ChatFunction.builder() + public static FunctionDefinition weatherFunction() { + return FunctionDefinition.builder() .name("get_weather") .description("Get the current weather in a given location") + .parametersDefinitionByClass(Weather.class) //这里的executor是一个lambda表达式,这个lambda表达式接受一个Weather对象,返回一个WeatherResponse对象 - .executor(Weather.class, w -> new WeatherResponse(w.location, w.unit, 25, "sunny")) + .executor(w -> new WeatherResponse(w.location, w.unit, 25, "sunny")) .build(); } From 5d23368d9431c7c3d08423190fe684c9672437a2 Mon Sep 17 00:00:00 2001 From: liangtao <547670718@qq.com> Date: Fri, 10 May 2024 15:01:12 +0800 Subject: [PATCH 4/6] =?UTF-8?q?bug:=20fix=20stream=5Foption=20=E5=BA=8F?= =?UTF-8?q?=E5=88=97=E5=8C=96=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../openai/completion/chat/StreamOption.java | 7 ++++--- .../function/FunctionParametersSerializer.java | 16 +++++++++++++--- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/api/src/main/java/com/theokanning/openai/completion/chat/StreamOption.java b/api/src/main/java/com/theokanning/openai/completion/chat/StreamOption.java index 916175a..e36ecac 100644 --- a/api/src/main/java/com/theokanning/openai/completion/chat/StreamOption.java +++ b/api/src/main/java/com/theokanning/openai/completion/chat/StreamOption.java @@ -1,13 +1,17 @@ package com.theokanning.openai.completion.chat; import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; /** * @author LiangTao * @date 2024年05月07 22:46 **/ @Data +@NoArgsConstructor +@AllArgsConstructor public class StreamOption { public static final StreamOption INCLUDE = new StreamOption(true); @@ -21,7 +25,4 @@ public class StreamOption { @JsonProperty("include_usage") Boolean includeUsage; - private StreamOption(Boolean includeUsage) { - this.includeUsage = includeUsage; - } } diff --git a/api/src/main/java/com/theokanning/openai/function/FunctionParametersSerializer.java b/api/src/main/java/com/theokanning/openai/function/FunctionParametersSerializer.java index 5be2449..9caa962 100644 --- a/api/src/main/java/com/theokanning/openai/function/FunctionParametersSerializer.java +++ b/api/src/main/java/com/theokanning/openai/function/FunctionParametersSerializer.java @@ -9,15 +9,25 @@ import java.io.IOException; -public class FunctionParametersSerializer extends JsonSerializer> { +public class FunctionParametersSerializer extends JsonSerializer { private final ObjectMapper mapper = new ObjectMapper(); private final JsonSchemaConfig config = JsonSchemaConfig.vanillaJsonSchemaDraft4(); private final JsonSchemaGenerator jsonSchemaGenerator = new JsonSchemaGenerator(mapper, config); @Override - public void serialize(Class value, JsonGenerator gen, SerializerProvider serializers) throws IOException { - + public void serialize(FunctionDefinition value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writeStartObject(); + gen.writeStringField("name", value.getName()); + gen.writeStringField("description", value.getDescription()); + if (value.getParametersDefinitionClass() != null) { + gen.writeFieldName("parameters"); + gen.writeRawValue(mapper.writeValueAsString(jsonSchemaGenerator.generateJsonSchema(value.getParametersDefinitionClass()))); + } else { + gen.writeFieldName("parameters"); + gen.writeRawValue(mapper.writeValueAsString(value.getParametersDefinition())); + } + gen.writeEndObject(); } From ec30caa20279a085c8772cc1ec2a88347930e946 Mon Sep 17 00:00:00 2001 From: liangtao <547670718@qq.com> Date: Fri, 10 May 2024 15:04:42 +0800 Subject: [PATCH 5/6] =?UTF-8?q?bug:=20function=20message=20=E6=9E=84?= =?UTF-8?q?=E9=80=A0=E7=BC=BA=E5=B0=91name=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../theokanning/openai/function/FunctionExecutorManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/com/theokanning/openai/function/FunctionExecutorManager.java b/api/src/main/java/com/theokanning/openai/function/FunctionExecutorManager.java index 60093bd..8e10c68 100644 --- a/api/src/main/java/com/theokanning/openai/function/FunctionExecutorManager.java +++ b/api/src/main/java/com/theokanning/openai/function/FunctionExecutorManager.java @@ -119,7 +119,7 @@ public Future executeAndConvertToSubmitToolOutputRe */ @Deprecated public FunctionMessage executeAndConvertToChatMessage(String funName, JsonNode arguments) { - return new FunctionMessage(executeAndConvertToJson(funName, arguments).toPrettyString()); + return new FunctionMessage(executeAndConvertToJson(funName, arguments).toPrettyString(),funName); } @Deprecated From e3767aee2ae1c9bc6bf22dafddfaef7eedbec468 Mon Sep 17 00:00:00 2001 From: liangtao <547670718@qq.com> Date: Fri, 10 May 2024 15:20:28 +0800 Subject: [PATCH 6/6] =?UTF-8?q?feature:=20readme=20=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README-zh.md | 385 ++++++++-------- README.md | 428 ++++++++---------- .../example/FunctionOrToolCreateExample.java | 91 ---- 3 files changed, 380 insertions(+), 524 deletions(-) delete mode 100644 example/src/main/java/example/FunctionOrToolCreateExample.java diff --git a/README-zh.md b/README-zh.md index 915822c..329c32a 100644 --- a/README-zh.md +++ b/README-zh.md @@ -188,55 +188,57 @@ public static class WeatherResponse { 接下来,我们声明该函数并将其与执行器相关联,在这里模拟API响应: ```java -//首先,获取天气的函数 -ChatFunction function = ChatFunction.builder() - .name("get_weather") - .description("Get the current weather in a specified location") - //executor是一个lambda表达式,它接受Weather对象并返回WeatherResponse - .executor(Weather.class, w -> new WeatherResponse(w.location, w.unit, 25, "sunny")) - .build(); +//First, a function to fetch the weather +public static FunctionDefinition weatherFunction() { + return FunctionDefinition.builder() + .name("get_weather") + .description("Get the current weather in a given location") + .parametersDefinitionByClass(Weather.class) + //The executor here is a lambda expression that accepts a Weather object and returns a Weather Response object + .executor(w -> new WeatherResponse(w.location, w.unit, 25, "sunny")) + .build(); +} ``` 然后,该服务用于聊天完成请求,包含以下工具: ```java static void toolChat() { - OpenAiService service = new OpenAiService(Duration.ofSeconds(30)); - //ToolUtil是一个简化工具创建的实用程序类,里面就是前面的函数创建代码 - final ChatTool tool = new ChatTool(ToolUtil.weatherFunction()); - final List messages = new ArrayList<>(); - final ChatMessage systemMessage = new SystemMessage("You are a helpful assistant."); - final ChatMessage userMessage = new UserMessage("What is the weather in BeiJin?"); - messages.add(systemMessage); - messages.add(userMessage); - - ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder() - .model("gpt-3.5-turbo") - .messages(messages) - //工具是一个列表;可以包括多个工具 - .tools(Collections.singletonList(tool)) - .toolChoice(ToolChoice.AUTO) - .n(1) - .maxTokens(100) - .build(); - //Request is sent - ChatCompletionChoice choice = service.createChatCompletion(chatCompletionRequest).getChoices().get(0); - AssistantMessage toolCallMsg = choice.getMessage(); - ChatToolCall toolCall = toolCallMsg.getToolCalls().get(0); - System.out.println(toolCall.getFunction()); - - messages.add(toolCallMsg); - messages.add(new ToolMessage("the weather is fine today.", toolCall.getId())); - - //submit tool call - ChatCompletionRequest toolCallRequest = ChatCompletionRequest.builder() - .model("gpt-3.5-turbo") - .messages(messages) - .n(1) - .maxTokens(100) - .build(); - ChatCompletionChoice toolCallChoice = service.createChatCompletion(toolCallRequest).getChoices().get(0); - System.out.println(toolCallChoice.getMessage().getContent()); + OpenAiService service = new OpenAiService(Duration.ofSeconds(30)); + final ChatTool tool = new ChatTool(ToolUtil.weatherFunction()); + final List messages = new ArrayList<>(); + final ChatMessage systemMessage = new SystemMessage("You are a helpful assistant."); + final ChatMessage userMessage = new UserMessage("What is the weather in BeiJin?"); + messages.add(systemMessage); + messages.add(userMessage); + + ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder() + .model("gpt-3.5-turbo") + .messages(messages) + //Tools is a list; multiple tools can be included + .tools(Collections.singletonList(tool)) + .toolChoice(ToolChoice.AUTO) + .n(1) + .maxTokens(100) + .build(); + //Request is sent + ChatCompletionChoice choice = service.createChatCompletion(chatCompletionRequest).getChoices().get(0); + AssistantMessage toolCallMsg = choice.getMessage(); + ChatToolCall toolCall = toolCallMsg.getToolCalls().get(0); + System.out.println(toolCall.getFunction()); + + messages.add(toolCallMsg); + messages.add(new ToolMessage("the weather is fine today.", toolCall.getId())); + + //submit tool call + ChatCompletionRequest toolCallRequest = ChatCompletionRequest.builder() + .model("gpt-3.5-turbo") + .messages(messages) + .n(1) + .maxTokens(100) + .build(); + ChatCompletionChoice toolCallChoice = service.createChatCompletion(toolCallRequest).getChoices().get(0); + System.out.println(toolCallChoice.getMessage().getContent()); } ``` @@ -290,99 +292,98 @@ static void functionChat() { ```java void streamChatMultipleToolCalls() { - final List functions = Arrays.asList( - //1. 天气查询 - ChatFunction.builder() - .name("get_weather") - .description("Get the current weather in a given location") - .executor(Weather.class, w -> { - switch (w.location) { - case "tokyo": - return new WeatherResponse(w.location, w.unit, 10, "cloudy"); - case "san francisco": - return new WeatherResponse(w.location, w.unit, 72, "sunny"); - case "paris": - return new WeatherResponse(w.location, w.unit, 22, "sunny"); - default: - return new WeatherResponse(w.location, w.unit, 0, "unknown"); - } - }).build(), - //2. 城市查询 - ChatFunction.builder().name("getCities").description("Get a list of cities by time").executor(City.class, v -> Arrays.asList("tokyo", "paris")).build() - ); - final FunctionExecutor toolExecutor = new FunctionExecutor(functions); - - List tools = new ArrayList<>(); - tools.add(new ChatTool<>(functions.get(0))); - tools.add(new ChatTool<>(functions.get(1))); - - final List messages = new ArrayList<>(); - final ChatMessage systemMessage = new SystemMessage("You are a helpful assistant."); - final ChatMessage userMessage = new UserMessage("What is the weather like in cities with weather on 2022-12-01 ?"); - messages.add(systemMessage); - messages.add(userMessage); + final List functions = Arrays.asList( + //1. weather query + FunctionDefinition.builder() + .name("get_weather") + .description("Get the current weather in a given location") + .parametersDefinitionByClass(ToolUtil.Weather.class) + .executor( w -> { + switch (w.location) { + case "tokyo": + return new ToolUtil.WeatherResponse(w.location, w.unit, 10, "cloudy"); + case "san francisco": + return new ToolUtil.WeatherResponse(w.location, w.unit, 72, "sunny"); + case "paris": + return new ToolUtil.WeatherResponse(w.location, w.unit, 22, "sunny"); + default: + return new ToolUtil.WeatherResponse(w.location, w.unit, 0, "unknown"); + } + }).build(), + //2. city query + FunctionDefinition.builder().name("getCities").description("Get a list of cities by time").parametersDefinitionByClass(ToolUtil.City.class).executor(v -> Arrays.asList("tokyo", "paris")).build() + ); + final FunctionExecutorManager toolExecutor = new FunctionExecutorManager(functions); + + List tools = new ArrayList<>(); + tools.add(new ChatTool(functions.get(0))); + tools.add(new ChatTool(functions.get(1))); + + final List messages = new ArrayList<>(); + final ChatMessage systemMessage = new SystemMessage("You are a helpful assistant."); + final ChatMessage userMessage = new UserMessage("What is the weather like in cities with weather on 2022-12-01 ?"); + messages.add(systemMessage); + messages.add(userMessage); + + ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest + .builder() + .model("gpt-3.5-turbo") + .messages(messages) + .tools(tools) + .toolChoice(ToolChoice.AUTO) + .n(1) + .maxTokens(200) + .build(); - ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest - .builder() - .model("gpt-3.5-turbo-0613") - .messages(messages) - .tools(tools) - .toolChoice("auto") - .n(1) - .maxTokens(200) - .build(); + AssistantMessage accumulatedMessage = service.mapStreamToAccumulator(service.streamChatCompletion(chatCompletionRequest)) + .blockingLast() + .getAccumulatedMessage(); + + List toolCalls = accumulatedMessage.getToolCalls(); + + ChatToolCall toolCall = toolCalls.get(0); + ChatFunctionCall function = toolCall.getFunction(); + JsonNode jsonNode = toolExecutor.executeAndConvertToJson(function.getName(), function.getArguments()); + ToolMessage toolMessage = toolExecutor.executeAndConvertToChatMessage(function.getName(),function.getArguments(), toolCall.getId()); + messages.add(accumulatedMessage); + messages.add(toolMessage); + ChatCompletionRequest chatCompletionRequest2 = ChatCompletionRequest + .builder() + //3.5 there may be logical issues + .model("gpt-3.5-turbo-0125") + .messages(messages) + .tools(tools) + .toolChoice(ToolChoice.AUTO) + .n(1) + .maxTokens(100) + .logitBias(new HashMap<>()) + .build(); - AssistantMessage accumulatedMessage = service.mapStreamToAccumulator(service.streamChatCompletion(chatCompletionRequest)) - .blockingLast() - .getAccumulatedMessage(); - - List toolCalls = accumulatedMessage.getToolCalls(); - ChatToolCall toolCall = toolCalls.get(0); - Object execute = toolExecutor.execute(toolCall.getFunction()); - JsonNode jsonNode = toolExecutor.executeAndConvertToJson(toolCall.getFunction()); - ToolMessage toolMessage = toolExecutor.executeAndConvertToMessageHandlingExceptions(toolCall.getFunction(), toolCall.getId()); - messages.add(accumulatedMessage); - messages.add(toolMessage); - - ChatCompletionRequest chatCompletionRequest2 = ChatCompletionRequest - .builder() - //3.5 there may be logical issues - .model("gpt-3.5-turbo-0125") - .messages(messages) - .tools(tools) - .toolChoice("auto") - .n(1) - .maxTokens(100) - .logitBias(new HashMap<>()) - .build(); + // ChatCompletionChoice choice2 = service.createChatCompletion(chatCompletionRequest2).getChoices().get(0); + AssistantMessage accumulatedMessage2 = service.mapStreamToAccumulator(service.streamChatCompletion(chatCompletionRequest2)) + .blockingLast() + .getAccumulatedMessage(); + messages.add(accumulatedMessage2); + for (ChatToolCall weatherToolCall : accumulatedMessage2.getToolCalls()) { + ChatFunctionCall call2 = weatherToolCall.getFunction(); + Object itemResult = toolExecutor.execute(call2.getName(), call2.getArguments()); + messages.add(toolExecutor.executeAndConvertToChatMessage(call2.getName(),call2.getArguments(), weatherToolCall.getId())); + } - // ChatCompletionChoice choice2 = service.createChatCompletion(chatCompletionRequest2).getChoices().get(0); - AssistantMessage accumulatedMessage2 = service.mapStreamToAccumulator(service.streamChatCompletion(chatCompletionRequest2)) - .blockingLast() - .getAccumulatedMessage(); - //这里应该有两个工具调用 - messages.add(accumulatedMessage2); - - for (ChatToolCall weatherToolCall : accumulatedMessage2.getToolCalls()) { - Object itemResult = toolExecutor.execute(weatherToolCall.getFunction()); - assertInstanceOf(WeatherResponse.class, itemResult); - messages.add(toolExecutor.executeAndConvertToMessage(weatherToolCall.getFunction(), weatherToolCall.getId())); - } - - ChatCompletionRequest chatCompletionRequest3 = ChatCompletionRequest - .builder() - .model("gpt-3.5-turbo-0613") - .messages(messages) - .tools(tools) - .toolChoice("auto") - .n(1) - .maxTokens(100) - .logitBias(new HashMap<>()) - .build(); + ChatCompletionRequest chatCompletionRequest3 = ChatCompletionRequest + .builder() + .model("gpt-3.5-turbo") + .messages(messages) + .tools(tools) + .toolChoice(ToolChoice.AUTO) + .n(1) + .maxTokens(100) + .logitBias(new HashMap<>()) + .build(); - AssistantMessage accumulatedMessage3 = service.mapStreamToAccumulator(service.streamChatCompletion(chatCompletionRequest3)) - .blockingLast() - .getAccumulatedMessage(); + AssistantMessage accumulatedMessage3 = service.mapStreamToAccumulator(service.streamChatCompletion(chatCompletionRequest3)) + .blockingLast() + .getAccumulatedMessage(); } ``` @@ -410,71 +411,65 @@ public static void main(String... args) { ```java static void assistantToolCall() { - OpenAiService service = new OpenAiService(); - FunctionExecutor executor = new FunctionExecutor(Collections.singletonList(ToolUtil.weatherFunction())); - //create assistant - AssistantRequest assistantRequest = AssistantRequest.builder() - .model("gpt-3.5-turbo").name("weather assistant") - .instructions("You are a weather assistant responsible for calling the weather API to return weather information based on the location entered by the user") - .tools(Collections.singletonList(new FunctionTool(ToolUtil.weatherFunction()))) - .temperature(0D) - .build(); - Assistant assistant = service.createAssistant(assistantRequest); - String assistantId = assistant.getId(); + OpenAiService service = new OpenAiService(); + FunctionExecutorManager executor = new FunctionExecutorManager(Collections.singletonList(ToolUtil.weatherFunction())); + AssistantRequest assistantRequest = AssistantRequest.builder() + .model("gpt-3.5-turbo").name("weather assistant") + .instructions("You are a weather assistant responsible for calling the weather API to return weather information based on the location entered by the user") + .tools(Collections.singletonList(new FunctionTool(ToolUtil.weatherFunction()))) + .temperature(0D) + .build(); + Assistant assistant = service.createAssistant(assistantRequest); + String assistantId = assistant.getId(); + ThreadRequest threadRequest = ThreadRequest.builder().build(); + Thread thread = service.createThread(threadRequest); + String threadId = thread.getId(); + + MessageRequest messageRequest = MessageRequest.builder() + .content("What's the weather of Xiamen?") + .build(); + //add message to thread + service.createMessage(threadId, messageRequest); + RunCreateRequest runCreateRequest = RunCreateRequest.builder().assistantId(assistantId).build(); - //create thread - ThreadRequest threadRequest = ThreadRequest.builder().build(); - Thread thread = service.createThread(threadRequest); - String threadId = thread.getId(); + Run run = service.createRun(threadId, runCreateRequest); + + Run retrievedRun = service.retrieveRun(threadId, run.getId()); + while (!(retrievedRun.getStatus().equals("completed")) + && !(retrievedRun.getStatus().equals("failed")) + && !(retrievedRun.getStatus().equals("expired")) + && !(retrievedRun.getStatus().equals("incomplete")) + && !(retrievedRun.getStatus().equals("requires_action"))) { + retrievedRun = service.retrieveRun(threadId, run.getId()); + } + System.out.println(retrievedRun); + + RequiredAction requiredAction = retrievedRun.getRequiredAction(); + List toolCalls = requiredAction.getSubmitToolOutputs().getToolCalls(); + ToolCall toolCall = toolCalls.get(0); + ToolCallFunction function = toolCall.getFunction(); + String toolCallId = toolCall.getId(); + + SubmitToolOutputsRequest submitToolOutputsRequest = SubmitToolOutputsRequest.ofSingletonToolOutput(toolCallId, executor.executeAndConvertToJson(function.getName(),function.getArguments()).toPrettyString()); + retrievedRun = service.submitToolOutputs(threadId, retrievedRun.getId(), submitToolOutputsRequest); + + while (!(retrievedRun.getStatus().equals("completed")) + && !(retrievedRun.getStatus().equals("failed")) + && !(retrievedRun.getStatus().equals("expired")) + && !(retrievedRun.getStatus().equals("incomplete")) + && !(retrievedRun.getStatus().equals("requires_action"))) { + retrievedRun = service.retrieveRun(threadId, run.getId()); + } + + System.out.println(retrievedRun); + + OpenAiResponse response = service.listMessages(threadId, MessageListSearchParameters.builder() + .runId(retrievedRun.getId()).build()); + List messages = response.getData(); + messages.forEach(message -> { + System.out.println(message.getContent()); + }); - MessageRequest messageRequest = MessageRequest.builder() - .content("What's the weather of Xiamen?") - .build(); - //add message to thread - service.createMessage(threadId, messageRequest); - RunCreateRequest runCreateRequest = RunCreateRequest.builder().assistantId(assistantId).build(); - - Run run = service.createRun(threadId, runCreateRequest); - - //wait for the run to complete - Run retrievedRun = service.retrieveRun(threadId, run.getId()); - while (!(retrievedRun.getStatus().equals("completed")) - && !(retrievedRun.getStatus().equals("failed")) - && !(retrievedRun.getStatus().equals("expired")) - && !(retrievedRun.getStatus().equals("incomplete")) - && !(retrievedRun.getStatus().equals("requires_action"))) { - retrievedRun = service.retrieveRun(threadId, run.getId()); - } - //print the result - System.out.println(retrievedRun); - - RequiredAction requiredAction = retrievedRun.getRequiredAction(); - List toolCalls = requiredAction.getSubmitToolOutputs().getToolCalls(); - ToolCall toolCall = toolCalls.get(0); - ToolCallFunction function = toolCall.getFunction(); - String toolCallId = toolCall.getId(); - - //submit tool output with get_weather function - SubmitToolOutputsRequest submitToolOutputsRequest = SubmitToolOutputsRequest.ofSingletonToolOutput(toolCallId, executor.executeAndConvertToJson(function).toPrettyString()); - retrievedRun = service.submitToolOutputs(threadId, retrievedRun.getId(), submitToolOutputsRequest); - - while (!(retrievedRun.getStatus().equals("completed")) - && !(retrievedRun.getStatus().equals("failed")) - && !(retrievedRun.getStatus().equals("expired")) - && !(retrievedRun.getStatus().equals("incomplete")) - && !(retrievedRun.getStatus().equals("requires_action"))) { - retrievedRun = service.retrieveRun(threadId, run.getId()); - } - - //print the result with tool call - System.out.println(retrievedRun); - - //get result message list - OpenAiResponse response = service.listMessages(threadId, new ListSearchParameters()); - List messages = response.getData(); - messages.forEach(message -> { - System.out.println(message.getContent()); - }); } ``` diff --git a/README.md b/README.md index 8c9b5a3..63eec17 100644 --- a/README.md +++ b/README.md @@ -195,97 +195,56 @@ Next, we declare the function and associate it with an executor, here simulating ```java //First, a function to fetch the weather -ChatFunction function = ChatFunction.builder() - .name("get_weather") - .description("Get the current weather in a specified location") - //The executor is a lambda expression that takes a Weather object and returns a WeatherResponse - .executor(Weather.class, w -> new WeatherResponse(w.location, w.unit, 25, "sunny")) - .build(); +public static FunctionDefinition weatherFunction() { + return FunctionDefinition.builder() + .name("get_weather") + .description("Get the current weather in a given location") + .parametersDefinitionByClass(Weather.class) + //The executor here is a lambda expression that accepts a Weather object and returns a Weather Response object + .executor(w -> new WeatherResponse(w.location, w.unit, 25, "sunny")) + .build(); +} ``` Then, the service is used for a chatCompletion request, incorporating the tool: ```java static void toolChat() { - OpenAiService service = new OpenAiService(Duration.ofSeconds(30)); - //ToolUtil is a utility class that simplifies the creation of tools - final ChatTool tool = new ChatTool(ToolUtil.weatherFunction()); - final List messages = new ArrayList<>(); - final ChatMessage systemMessage = new SystemMessage("You are a helpful assistant."); - final ChatMessage userMessage = new UserMessage("What is the weather in BeiJin?"); - messages.add(systemMessage); - messages.add(userMessage); - - ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder() - .model("gpt-3.5-turbo") - .messages(messages) - //Tools is a list; multiple tools can be included - .tools(Collections.singletonList(tool)) - .toolChoice(ToolChoice.AUTO) - .n(1) - .maxTokens(100) - .build(); - //Request is sent - ChatCompletionChoice choice = service.createChatCompletion(chatCompletionRequest).getChoices().get(0); - AssistantMessage toolCallMsg = choice.getMessage(); - ChatToolCall toolCall = toolCallMsg.getToolCalls().get(0); - System.out.println(toolCall.getFunction()); - - messages.add(toolCallMsg); - messages.add(new ToolMessage("the weather is fine today.", toolCall.getId())); - - //submit tool call - ChatCompletionRequest toolCallRequest = ChatCompletionRequest.builder() - .model("gpt-3.5-turbo") - .messages(messages) - .n(1) - .maxTokens(100) - .build(); - ChatCompletionChoice toolCallChoice = service.createChatCompletion(toolCallRequest).getChoices().get(0); - System.out.println(toolCallChoice.getMessage().getContent()); -} -``` - - - -
-function(deprecated) - -```java -static void functionChat() { - OpenAiService service = new OpenAiService(Duration.ofSeconds(30)); - final List messages = new ArrayList<>(); - final ChatMessage systemMessage = new SystemMessage("You are a helpful assistant."); - final ChatMessage userMessage = new UserMessage("What is the weather in BeiJin?"); - messages.add(systemMessage); - messages.add(userMessage); - - ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder() - .model("gpt-3.5-turbo") - .messages(messages) - .functions(Collections.singletonList(ToolUtil.weatherFunction())) - .functionCall("auto") - .n(1) - .maxTokens(100) - .build(); - //Request is sent - ChatCompletionChoice choice = service.createChatCompletion(chatCompletionRequest).getChoices().get(0); - AssistantMessage functionCallMsg = choice.getMessage(); - ChatFunctionCall functionCall = functionCallMsg.getFunctionCall(); - System.out.println(functionCall); - - messages.add(functionCallMsg); - messages.add(new FunctionMessage("the weather is fine today.", "get_weather")); - - //submit tool call - ChatCompletionRequest toolCallRequest = ChatCompletionRequest.builder() - .model("gpt-3.5-turbo") - .messages(messages) - .n(1) - .maxTokens(100) - .build(); - ChatCompletionChoice toolCallChoice = service.createChatCompletion(toolCallRequest).getChoices().get(0); - System.out.println(toolCallChoice.getMessage().getContent()); + OpenAiService service = new OpenAiService(Duration.ofSeconds(30)); + final ChatTool tool = new ChatTool(ToolUtil.weatherFunction()); + final List messages = new ArrayList<>(); + final ChatMessage systemMessage = new SystemMessage("You are a helpful assistant."); + final ChatMessage userMessage = new UserMessage("What is the weather in BeiJin?"); + messages.add(systemMessage); + messages.add(userMessage); + + ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder() + .model("gpt-3.5-turbo") + .messages(messages) + //Tools is a list; multiple tools can be included + .tools(Collections.singletonList(tool)) + .toolChoice(ToolChoice.AUTO) + .n(1) + .maxTokens(100) + .build(); + //Request is sent + ChatCompletionChoice choice = service.createChatCompletion(chatCompletionRequest).getChoices().get(0); + AssistantMessage toolCallMsg = choice.getMessage(); + ChatToolCall toolCall = toolCallMsg.getToolCalls().get(0); + System.out.println(toolCall.getFunction()); + + messages.add(toolCallMsg); + messages.add(new ToolMessage("the weather is fine today.", toolCall.getId())); + + //submit tool call + ChatCompletionRequest toolCallRequest = ChatCompletionRequest.builder() + .model("gpt-3.5-turbo") + .messages(messages) + .n(1) + .maxTokens(100) + .build(); + ChatCompletionChoice toolCallChoice = service.createChatCompletion(toolCallRequest).getChoices().get(0); + System.out.println(toolCallChoice.getMessage().getContent()); } ``` @@ -296,99 +255,98 @@ static void functionChat() { ```java void streamChatMultipleToolCalls() { - final List functions = Arrays.asList( - //1. weather query - ChatFunction.builder() - .name("get_weather") - .description("Get the current weather in a given location") - .executor(Weather.class, w -> { - switch (w.location) { - case "tokyo": - return new WeatherResponse(w.location, w.unit, 10, "cloudy"); - case "san francisco": - return new WeatherResponse(w.location, w.unit, 72, "sunny"); - case "paris": - return new WeatherResponse(w.location, w.unit, 22, "sunny"); - default: - return new WeatherResponse(w.location, w.unit, 0, "unknown"); - } - }).build(), - //2. city query - ChatFunction.builder().name("getCities").description("Get a list of cities by time").executor(City.class, v -> Arrays.asList("tokyo", "paris")).build() - ); - final FunctionExecutor toolExecutor = new FunctionExecutor(functions); - - List tools = new ArrayList<>(); - tools.add(new ChatTool<>(functions.get(0))); - tools.add(new ChatTool<>(functions.get(1))); - - final List messages = new ArrayList<>(); - final ChatMessage systemMessage = new SystemMessage("You are a helpful assistant."); - final ChatMessage userMessage = new UserMessage("What is the weather like in cities with weather on 2022-12-01 ?"); - messages.add(systemMessage); - messages.add(userMessage); + final List functions = Arrays.asList( + //1. weather query + FunctionDefinition.builder() + .name("get_weather") + .description("Get the current weather in a given location") + .parametersDefinitionByClass(ToolUtil.Weather.class) + .executor( w -> { + switch (w.location) { + case "tokyo": + return new ToolUtil.WeatherResponse(w.location, w.unit, 10, "cloudy"); + case "san francisco": + return new ToolUtil.WeatherResponse(w.location, w.unit, 72, "sunny"); + case "paris": + return new ToolUtil.WeatherResponse(w.location, w.unit, 22, "sunny"); + default: + return new ToolUtil.WeatherResponse(w.location, w.unit, 0, "unknown"); + } + }).build(), + //2. city query + FunctionDefinition.builder().name("getCities").description("Get a list of cities by time").parametersDefinitionByClass(ToolUtil.City.class).executor(v -> Arrays.asList("tokyo", "paris")).build() + ); + final FunctionExecutorManager toolExecutor = new FunctionExecutorManager(functions); + + List tools = new ArrayList<>(); + tools.add(new ChatTool(functions.get(0))); + tools.add(new ChatTool(functions.get(1))); + + final List messages = new ArrayList<>(); + final ChatMessage systemMessage = new SystemMessage("You are a helpful assistant."); + final ChatMessage userMessage = new UserMessage("What is the weather like in cities with weather on 2022-12-01 ?"); + messages.add(systemMessage); + messages.add(userMessage); + + ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest + .builder() + .model("gpt-3.5-turbo") + .messages(messages) + .tools(tools) + .toolChoice(ToolChoice.AUTO) + .n(1) + .maxTokens(200) + .build(); - ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest - .builder() - .model("gpt-3.5-turbo-0613") - .messages(messages) - .tools(tools) - .toolChoice("auto") - .n(1) - .maxTokens(200) - .build(); + AssistantMessage accumulatedMessage = service.mapStreamToAccumulator(service.streamChatCompletion(chatCompletionRequest)) + .blockingLast() + .getAccumulatedMessage(); + + List toolCalls = accumulatedMessage.getToolCalls(); + + ChatToolCall toolCall = toolCalls.get(0); + ChatFunctionCall function = toolCall.getFunction(); + JsonNode jsonNode = toolExecutor.executeAndConvertToJson(function.getName(), function.getArguments()); + ToolMessage toolMessage = toolExecutor.executeAndConvertToChatMessage(function.getName(),function.getArguments(), toolCall.getId()); + messages.add(accumulatedMessage); + messages.add(toolMessage); + ChatCompletionRequest chatCompletionRequest2 = ChatCompletionRequest + .builder() + //3.5 there may be logical issues + .model("gpt-3.5-turbo-0125") + .messages(messages) + .tools(tools) + .toolChoice(ToolChoice.AUTO) + .n(1) + .maxTokens(100) + .logitBias(new HashMap<>()) + .build(); - AssistantMessage accumulatedMessage = service.mapStreamToAccumulator(service.streamChatCompletion(chatCompletionRequest)) - .blockingLast() - .getAccumulatedMessage(); - - List toolCalls = accumulatedMessage.getToolCalls(); - ChatToolCall toolCall = toolCalls.get(0); - Object execute = toolExecutor.execute(toolCall.getFunction()); - JsonNode jsonNode = toolExecutor.executeAndConvertToJson(toolCall.getFunction()); - ToolMessage toolMessage = toolExecutor.executeAndConvertToMessageHandlingExceptions(toolCall.getFunction(), toolCall.getId()); - messages.add(accumulatedMessage); - messages.add(toolMessage); - - ChatCompletionRequest chatCompletionRequest2 = ChatCompletionRequest - .builder() - //3.5 there may be logical issues - .model("gpt-3.5-turbo-0125") - .messages(messages) - .tools(tools) - .toolChoice("auto") - .n(1) - .maxTokens(100) - .logitBias(new HashMap<>()) - .build(); + // ChatCompletionChoice choice2 = service.createChatCompletion(chatCompletionRequest2).getChoices().get(0); + AssistantMessage accumulatedMessage2 = service.mapStreamToAccumulator(service.streamChatCompletion(chatCompletionRequest2)) + .blockingLast() + .getAccumulatedMessage(); + messages.add(accumulatedMessage2); + for (ChatToolCall weatherToolCall : accumulatedMessage2.getToolCalls()) { + ChatFunctionCall call2 = weatherToolCall.getFunction(); + Object itemResult = toolExecutor.execute(call2.getName(), call2.getArguments()); + messages.add(toolExecutor.executeAndConvertToChatMessage(call2.getName(),call2.getArguments(), weatherToolCall.getId())); + } - // ChatCompletionChoice choice2 = service.createChatCompletion(chatCompletionRequest2).getChoices().get(0); - AssistantMessage accumulatedMessage2 = service.mapStreamToAccumulator(service.streamChatCompletion(chatCompletionRequest2)) - .blockingLast() - .getAccumulatedMessage(); - //There should be two tool calls here - messages.add(accumulatedMessage2); - - for (ChatToolCall weatherToolCall : accumulatedMessage2.getToolCalls()) { - Object itemResult = toolExecutor.execute(weatherToolCall.getFunction()); - assertInstanceOf(WeatherResponse.class, itemResult); - messages.add(toolExecutor.executeAndConvertToMessage(weatherToolCall.getFunction(), weatherToolCall.getId())); - } - - ChatCompletionRequest chatCompletionRequest3 = ChatCompletionRequest - .builder() - .model("gpt-3.5-turbo-0613") - .messages(messages) - .tools(tools) - .toolChoice("auto") - .n(1) - .maxTokens(100) - .logitBias(new HashMap<>()) - .build(); + ChatCompletionRequest chatCompletionRequest3 = ChatCompletionRequest + .builder() + .model("gpt-3.5-turbo") + .messages(messages) + .tools(tools) + .toolChoice(ToolChoice.AUTO) + .n(1) + .maxTokens(100) + .logitBias(new HashMap<>()) + .build(); - AssistantMessage accumulatedMessage3 = service.mapStreamToAccumulator(service.streamChatCompletion(chatCompletionRequest3)) - .blockingLast() - .getAccumulatedMessage(); + AssistantMessage accumulatedMessage3 = service.mapStreamToAccumulator(service.streamChatCompletion(chatCompletionRequest3)) + .blockingLast() + .getAccumulatedMessage(); } ``` @@ -416,71 +374,65 @@ public static void main(String... args) { ```java static void assistantToolCall() { - OpenAiService service = new OpenAiService(); - FunctionExecutor executor = new FunctionExecutor(Collections.singletonList(ToolUtil.weatherFunction())); - //create assistant - AssistantRequest assistantRequest = AssistantRequest.builder() - .model("gpt-3.5-turbo").name("weather assistant") - .instructions("You are a weather assistant responsible for calling the weather API to return weather information based on the location entered by the user") - .tools(Collections.singletonList(new FunctionTool(ToolUtil.weatherFunction()))) - .temperature(0D) - .build(); - Assistant assistant = service.createAssistant(assistantRequest); - String assistantId = assistant.getId(); + OpenAiService service = new OpenAiService(); + FunctionExecutorManager executor = new FunctionExecutorManager(Collections.singletonList(ToolUtil.weatherFunction())); + AssistantRequest assistantRequest = AssistantRequest.builder() + .model("gpt-3.5-turbo").name("weather assistant") + .instructions("You are a weather assistant responsible for calling the weather API to return weather information based on the location entered by the user") + .tools(Collections.singletonList(new FunctionTool(ToolUtil.weatherFunction()))) + .temperature(0D) + .build(); + Assistant assistant = service.createAssistant(assistantRequest); + String assistantId = assistant.getId(); + ThreadRequest threadRequest = ThreadRequest.builder().build(); + Thread thread = service.createThread(threadRequest); + String threadId = thread.getId(); - //create thread - ThreadRequest threadRequest = ThreadRequest.builder().build(); - Thread thread = service.createThread(threadRequest); - String threadId = thread.getId(); + MessageRequest messageRequest = MessageRequest.builder() + .content("What's the weather of Xiamen?") + .build(); + //add message to thread + service.createMessage(threadId, messageRequest); + RunCreateRequest runCreateRequest = RunCreateRequest.builder().assistantId(assistantId).build(); + + Run run = service.createRun(threadId, runCreateRequest); + + Run retrievedRun = service.retrieveRun(threadId, run.getId()); + while (!(retrievedRun.getStatus().equals("completed")) + && !(retrievedRun.getStatus().equals("failed")) + && !(retrievedRun.getStatus().equals("expired")) + && !(retrievedRun.getStatus().equals("incomplete")) + && !(retrievedRun.getStatus().equals("requires_action"))) { + retrievedRun = service.retrieveRun(threadId, run.getId()); + } + System.out.println(retrievedRun); + + RequiredAction requiredAction = retrievedRun.getRequiredAction(); + List toolCalls = requiredAction.getSubmitToolOutputs().getToolCalls(); + ToolCall toolCall = toolCalls.get(0); + ToolCallFunction function = toolCall.getFunction(); + String toolCallId = toolCall.getId(); + + SubmitToolOutputsRequest submitToolOutputsRequest = SubmitToolOutputsRequest.ofSingletonToolOutput(toolCallId, executor.executeAndConvertToJson(function.getName(),function.getArguments()).toPrettyString()); + retrievedRun = service.submitToolOutputs(threadId, retrievedRun.getId(), submitToolOutputsRequest); + + while (!(retrievedRun.getStatus().equals("completed")) + && !(retrievedRun.getStatus().equals("failed")) + && !(retrievedRun.getStatus().equals("expired")) + && !(retrievedRun.getStatus().equals("incomplete")) + && !(retrievedRun.getStatus().equals("requires_action"))) { + retrievedRun = service.retrieveRun(threadId, run.getId()); + } + + System.out.println(retrievedRun); + + OpenAiResponse response = service.listMessages(threadId, MessageListSearchParameters.builder() + .runId(retrievedRun.getId()).build()); + List messages = response.getData(); + messages.forEach(message -> { + System.out.println(message.getContent()); + }); - MessageRequest messageRequest = MessageRequest.builder() - .content("What's the weather of Xiamen?") - .build(); - //add message to thread - service.createMessage(threadId, messageRequest); - RunCreateRequest runCreateRequest = RunCreateRequest.builder().assistantId(assistantId).build(); - - Run run = service.createRun(threadId, runCreateRequest); - - //wait for the run to complete - Run retrievedRun = service.retrieveRun(threadId, run.getId()); - while (!(retrievedRun.getStatus().equals("completed")) - && !(retrievedRun.getStatus().equals("failed")) - && !(retrievedRun.getStatus().equals("expired")) - && !(retrievedRun.getStatus().equals("incomplete")) - && !(retrievedRun.getStatus().equals("requires_action"))) { - retrievedRun = service.retrieveRun(threadId, run.getId()); - } - //print the result - System.out.println(retrievedRun); - - RequiredAction requiredAction = retrievedRun.getRequiredAction(); - List toolCalls = requiredAction.getSubmitToolOutputs().getToolCalls(); - ToolCall toolCall = toolCalls.get(0); - ToolCallFunction function = toolCall.getFunction(); - String toolCallId = toolCall.getId(); - - //submit tool output with get_weather function - SubmitToolOutputsRequest submitToolOutputsRequest = SubmitToolOutputsRequest.ofSingletonToolOutput(toolCallId, executor.executeAndConvertToJson(function).toPrettyString()); - retrievedRun = service.submitToolOutputs(threadId, retrievedRun.getId(), submitToolOutputsRequest); - - while (!(retrievedRun.getStatus().equals("completed")) - && !(retrievedRun.getStatus().equals("failed")) - && !(retrievedRun.getStatus().equals("expired")) - && !(retrievedRun.getStatus().equals("incomplete")) - && !(retrievedRun.getStatus().equals("requires_action"))) { - retrievedRun = service.retrieveRun(threadId, run.getId()); - } - - //print the result with tool call - System.out.println(retrievedRun); - - //get result message list - OpenAiResponse response = service.listMessages(threadId, new ListSearchParameters()); - List messages = response.getData(); - messages.forEach(message -> { - System.out.println(message.getContent()); - }); } ``` @@ -532,7 +484,7 @@ static void assistantStream() throws JsonProcessingException { // Function call stream threadId = runStep.getThreadId(); - service.createMessage(threadId, MessageRequest.builder().content("Please help me check the weather in Beijing").build()); + service.createMessage(threadId, MessageRequest.builder().content("Please help me check the weather in Beijing").build()); Flowable getWeatherFlowable = service.createRunStream(threadId, RunCreateRequest.builder() //Force the use of the get weather function here .assistantId(assistantId) diff --git a/example/src/main/java/example/FunctionOrToolCreateExample.java b/example/src/main/java/example/FunctionOrToolCreateExample.java deleted file mode 100644 index 5c1899b..0000000 --- a/example/src/main/java/example/FunctionOrToolCreateExample.java +++ /dev/null @@ -1,91 +0,0 @@ -package example; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.kjetland.jackson.jsonSchema.JsonSchemaConfig; -import com.kjetland.jackson.jsonSchema.JsonSchemaGenerator; -import com.theokanning.openai.assistants.assistant.FunctionTool; -import com.theokanning.openai.completion.chat.ChatFunctionDynamic; -import com.theokanning.openai.completion.chat.ChatFunctionProperty; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.Map; - -/** - * severalWaysToCreateFunctionTool - * - * @author LiangTao - * @date 2024年05月10 10:30 - **/ -public class FunctionOrToolCreateExample { - static ObjectMapper mapper = new ObjectMapper(); - - public static void main(String[] args) throws JsonProcessingException { - // createByJsonString(); - createByDynamic(); - } - - /** - * create function by dynamic - */ - static void createByDynamic() throws JsonProcessingException { - //assistant function - ChatFunctionDynamic dynamicFun = ChatFunctionDynamic.builder() - .name("weather_reporter") - .description("Get the current weather of a location") - .addProperty(ChatFunctionProperty.builder() - .name("location") - .type("string") - .description("The city and state, e.g. San Francisco, CA") - .build()) - .addProperty(ChatFunctionProperty.builder() - .name("unit") - .type("string") - .enumValues(new HashSet<>(Arrays.asList("celsius", "fahrenheit"))) - .build()) - .build(); - - final ObjectMapper mapper = new ObjectMapper(); - final JsonSchemaConfig config = JsonSchemaConfig.vanillaJsonSchemaDraft4(); - final JsonSchemaGenerator jsonSchemaGenerator = new JsonSchemaGenerator(mapper, config); - System.out.println(jsonSchemaGenerator.generateJsonSchema(ToolUtil.Weather.class)); - System.out.println("----"); - - FunctionTool functionTool = new FunctionTool(dynamicFun); - System.out.println(mapper.writeValueAsString(functionTool)); - } - - /** - * create functionTool by ChatFunctionDynamic - */ - static void createByJsonString() throws JsonProcessingException { - //this function definition is not recommended, it is recommended to use the ToolUtil class to define the function - String funcDefJson = "{\n" + - " \"type\": \"object\",\n" + - " \"properties\": {\n" + - " \"location\": {\n" + - " \"type\": \"string\",\n" + - " \"description\": \"The city and state, e.g. San Francisco, CA\"\n" + - " },\n" + - " \"unit\": {\n" + - " \"type\": \"string\",\n" + - " \"enum\": [\"celsius\", \"fahrenheit\"]\n" + - " }\n" + - " },\n" + - " \"required\": [\"location\"]\n" + - "}"; - Map funcParameters = mapper.readValue(funcDefJson, new TypeReference>() { - }); - - //assistant function - FunctionTool functionTool = new FunctionTool(funcParameters); - System.out.println(mapper.writeValueAsString(functionTool)); - - - - - } - -}