初始化
This commit is contained in:
30
easy-agents-chat/easy-agents-chat-deepseek/pom.xml
Normal file
30
easy-agents-chat/easy-agents-chat-deepseek/pom.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.easyagents</groupId>
|
||||
<artifactId>easy-agents-chat</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
<name>easy-agents-chat-deepseek</name>
|
||||
<artifactId>easy-agents-chat-deepseek</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.easyagents</groupId>
|
||||
<artifactId>easy-agents-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2023-2026, Easy-Agents (fuhai999@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.easyagents.llm.deepseek;
|
||||
|
||||
import com.easyagents.core.model.chat.OpenAICompatibleChatModel;
|
||||
import com.easyagents.core.model.chat.ChatInterceptor;
|
||||
import com.easyagents.core.model.chat.GlobalChatInterceptors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author huangjf
|
||||
* @version : v1.0
|
||||
*/
|
||||
public class DeepseekChatModel extends OpenAICompatibleChatModel<DeepseekConfig> {
|
||||
|
||||
|
||||
/**
|
||||
* 构造一个聊天模型实例,不使用实例级拦截器。
|
||||
*
|
||||
* @param config 聊天模型配置
|
||||
*/
|
||||
public DeepseekChatModel(DeepseekConfig config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造一个聊天模型实例,并指定实例级拦截器。
|
||||
* <p>
|
||||
* 实例级拦截器会与全局拦截器(通过 {@link GlobalChatInterceptors} 注册)合并,
|
||||
* 执行顺序为:可观测性拦截器 → 全局拦截器 → 实例拦截器。
|
||||
*
|
||||
* @param config 聊天模型配置
|
||||
* @param userInterceptors 实例级拦截器列表
|
||||
*/
|
||||
public DeepseekChatModel(DeepseekConfig config, List<ChatInterceptor> userInterceptors) {
|
||||
super(config, userInterceptors);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2023-2026, Easy-Agents (fuhai999@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.easyagents.llm.deepseek;
|
||||
|
||||
import com.easyagents.core.model.chat.ChatConfig;
|
||||
|
||||
/**
|
||||
* @author huangjf
|
||||
* @version : v1.0
|
||||
*/
|
||||
public class DeepseekConfig extends ChatConfig {
|
||||
|
||||
private static final String DEFAULT_MODEL = "deepseek-chat";
|
||||
private static final String DEFAULT_ENDPOINT = "https://api.deepseek.com";
|
||||
private static final String DEFAULT_REQUEST_PATH = "/chat/completions";
|
||||
|
||||
public DeepseekConfig() {
|
||||
setEndpoint(DEFAULT_ENDPOINT);
|
||||
setRequestPath(DEFAULT_REQUEST_PATH);
|
||||
setModel(DEFAULT_MODEL);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.easyagents.llm.deepseek;
|
||||
|
||||
import com.easyagents.core.message.UserMessage;
|
||||
import com.easyagents.core.model.client.StreamContext;
|
||||
import com.easyagents.core.model.chat.ChatModel;
|
||||
import com.easyagents.core.model.chat.StreamResponseListener;
|
||||
import com.easyagents.core.model.chat.tool.annotation.ToolDef;
|
||||
import com.easyagents.core.model.chat.tool.annotation.ToolParam;
|
||||
import com.easyagents.core.model.chat.response.AiMessageResponse;
|
||||
import com.easyagents.core.message.SystemMessage;
|
||||
import com.easyagents.core.prompt.MemoryPrompt;
|
||||
import com.easyagents.core.prompt.SimplePrompt;
|
||||
import com.easyagents.core.util.StringUtil;
|
||||
|
||||
import java.util.Scanner;
|
||||
|
||||
public class DeepseekTest {
|
||||
|
||||
|
||||
@ToolDef(name = "get_the_weather_info", description = "get the weather info")
|
||||
public static String getWeatherInfo(@ToolParam(name = "city", description = "城市名称") String name) {
|
||||
//在这里,我们应该通过第三方接口调用 api 信息
|
||||
return name + "的天气是阴转多云。 ";
|
||||
}
|
||||
|
||||
@ToolDef(name = "get_holiday_balance", description = "获取假期余额")
|
||||
public static String getHolidayBalance() {
|
||||
//在这里,我们应该通过第三方接口调用 api 信息
|
||||
String username = "michael";
|
||||
return username + "你的年假还剩余3天,有效期至26年1月。调休假剩余1天,长期有效。 ";
|
||||
}
|
||||
|
||||
public static ChatModel getLLM() {
|
||||
DeepseekConfig deepseekConfig = new DeepseekConfig();
|
||||
deepseekConfig.setEndpoint("https://api.siliconflow.cn/v1");
|
||||
deepseekConfig.setApiKey("*********************");
|
||||
deepseekConfig.setModel("Pro/deepseek-ai/DeepSeek-V3");
|
||||
deepseekConfig.setLogEnabled(true);
|
||||
return new DeepseekChatModel(deepseekConfig);
|
||||
}
|
||||
|
||||
public static void chatHr() {
|
||||
ChatModel chatModel = getLLM();
|
||||
MemoryPrompt prompt = new MemoryPrompt();
|
||||
// 加入system
|
||||
prompt.addMessage(new SystemMessage("你是一个人事助手小智,专注于为用户提供高效、精准的信息查询和问题解答服务。"));
|
||||
System.out.println("我是小智,你的人事小助手!请尽情吩咐小智!");
|
||||
Scanner scanner = new Scanner(System.in);
|
||||
String userInput = scanner.nextLine();
|
||||
while (userInput != null) {
|
||||
// 第二步:创建 HumanMessage,并添加方法调用
|
||||
UserMessage userMessage = new UserMessage(userInput);
|
||||
userMessage.addToolsFromClass(DeepseekTest.class);
|
||||
// 第三步:将 HumanMessage 添加到 HistoriesPrompt 中
|
||||
prompt.addMessage(userMessage);
|
||||
// 第四步:调用 chatStream 方法,进行对话
|
||||
chatModel.chatStream(prompt, new StreamResponseListener() {
|
||||
@Override
|
||||
public void onMessage(StreamContext context, AiMessageResponse response) {
|
||||
if (StringUtil.hasText(response.getMessage().getContent())) {
|
||||
System.out.print(response.getMessage().getContent());
|
||||
}
|
||||
if (response.getMessage().isFinalDelta()) {
|
||||
System.out.println(response);
|
||||
System.out.println("------");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop(StreamContext context) {
|
||||
System.out.println("stop!!!------");
|
||||
}
|
||||
});
|
||||
userInput = scanner.nextLine();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static void functionCall() {
|
||||
ChatModel chatModel = getLLM();
|
||||
SimplePrompt prompt = new SimplePrompt("今天北京的天气怎么样");
|
||||
prompt.addToolsFromClass(DeepseekTest.class);
|
||||
AiMessageResponse response = chatModel.chat(prompt);
|
||||
System.out.println(response.executeToolCallsAndGetResults());
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
// functionCall();
|
||||
chatHr();
|
||||
}
|
||||
}
|
||||
34
easy-agents-chat/easy-agents-chat-ollama/pom.xml
Normal file
34
easy-agents-chat/easy-agents-chat-ollama/pom.xml
Normal file
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.easyagents</groupId>
|
||||
<artifactId>easy-agents-chat</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<name>easy-agents-chat-ollama</name>
|
||||
<artifactId>easy-agents-chat-ollama</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.easyagents</groupId>
|
||||
<artifactId>easy-agents-core</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2023-2026, Easy-Agents (fuhai999@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.easyagents.llm.ollama;
|
||||
|
||||
import com.easyagents.core.model.chat.ChatConfig;
|
||||
|
||||
public class OllamaChatConfig extends ChatConfig {
|
||||
|
||||
private static final String DEFAULT_PROVIDER = "ollama";
|
||||
private static final String DEFAULT_ENDPOINT = "https://localhost:11434";
|
||||
private static final String DEFAULT_REQUEST_PATH = "/v1/chat/completions";
|
||||
|
||||
public OllamaChatConfig() {
|
||||
setProvider(DEFAULT_PROVIDER);
|
||||
setEndpoint(DEFAULT_ENDPOINT);
|
||||
setRequestPath(DEFAULT_REQUEST_PATH);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) 2023-2026, Easy-Agents (fuhai999@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.easyagents.llm.ollama;
|
||||
|
||||
import com.easyagents.core.model.chat.OpenAICompatibleChatModel;
|
||||
import com.easyagents.core.model.chat.ChatInterceptor;
|
||||
import com.easyagents.core.model.chat.GlobalChatInterceptors;
|
||||
import com.easyagents.core.model.client.ChatRequestSpecBuilder;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class OllamaChatModel extends OpenAICompatibleChatModel<OllamaChatConfig> {
|
||||
|
||||
/**
|
||||
* 构造一个聊天模型实例,不使用实例级拦截器。
|
||||
*
|
||||
* @param config 聊天模型配置
|
||||
*/
|
||||
public OllamaChatModel(OllamaChatConfig config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造一个聊天模型实例,并指定实例级拦截器。
|
||||
* <p>
|
||||
* 实例级拦截器会与全局拦截器(通过 {@link GlobalChatInterceptors} 注册)合并,
|
||||
* 执行顺序为:可观测性拦截器 → 全局拦截器 → 实例拦截器。
|
||||
*
|
||||
* @param config 聊天模型配置
|
||||
* @param userInterceptors 实例级拦截器列表
|
||||
*/
|
||||
public OllamaChatModel(OllamaChatConfig config, List<ChatInterceptor> userInterceptors) {
|
||||
super(config, userInterceptors);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ChatRequestSpecBuilder getChatRequestSpecBuilder() {
|
||||
return new OllamaRequestSpecBuilder();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.easyagents.llm.ollama;
|
||||
|
||||
import com.easyagents.core.model.chat.ChatConfig;
|
||||
import com.easyagents.core.model.chat.ChatOptions;
|
||||
import com.easyagents.core.model.client.OpenAIChatRequestSpecBuilder;
|
||||
import com.easyagents.core.prompt.Prompt;
|
||||
import com.easyagents.core.util.Maps;
|
||||
|
||||
public class OllamaRequestSpecBuilder extends OpenAIChatRequestSpecBuilder {
|
||||
protected Maps buildBaseParamsOfRequestBody(Prompt prompt, ChatOptions options, ChatConfig config) {
|
||||
Maps params = super.buildBaseParamsOfRequestBody(prompt, options, config);
|
||||
params.setIf(!options.isStreaming(), "stream", false);
|
||||
|
||||
// 支持思考
|
||||
if (config.isSupportThinking()) {
|
||||
params.setIf(options.getThinkingEnabled() != null, "thinking", options.getThinkingEnabled());
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.easyagents.llm.ollama;
|
||||
|
||||
import com.easyagents.core.message.AiMessage;
|
||||
import com.easyagents.core.model.chat.ChatModel;
|
||||
import com.easyagents.core.model.chat.response.AiMessageResponse;
|
||||
import com.easyagents.core.model.exception.ModelException;
|
||||
import com.easyagents.core.prompt.SimplePrompt;
|
||||
import org.junit.Test;
|
||||
|
||||
public class OllamaChatModelTest {
|
||||
|
||||
@Test(expected = ModelException.class)
|
||||
public void testChat() {
|
||||
OllamaChatConfig config = new OllamaChatConfig();
|
||||
config.setEndpoint("http://localhost:11434");
|
||||
config.setModel("llama3");
|
||||
config.setLogEnabled(true);
|
||||
|
||||
ChatModel chatModel = new OllamaChatModel(config);
|
||||
String chat = chatModel.chat("Why is the sky blue?");
|
||||
System.out.println(">>>" + chat);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testChatStream() throws InterruptedException {
|
||||
OllamaChatConfig config = new OllamaChatConfig();
|
||||
config.setEndpoint("http://localhost:11434");
|
||||
config.setModel("llama3");
|
||||
config.setLogEnabled(true);
|
||||
|
||||
ChatModel chatModel = new OllamaChatModel(config);
|
||||
chatModel.chatStream("Why is the sky blue?", (context, response) -> System.out.println(response.getMessage().getContent()));
|
||||
|
||||
Thread.sleep(2000);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testFunctionCall1() throws InterruptedException {
|
||||
OllamaChatConfig config = new OllamaChatConfig();
|
||||
config.setEndpoint("http://localhost:11434");
|
||||
config.setModel("llama3.1");
|
||||
config.setLogEnabled(true);
|
||||
|
||||
ChatModel chatModel = new OllamaChatModel(config);
|
||||
|
||||
SimplePrompt prompt = new SimplePrompt("What's the weather like in Beijing?");
|
||||
prompt.addToolsFromClass(WeatherFunctions.class);
|
||||
AiMessageResponse response = chatModel.chat(prompt);
|
||||
|
||||
System.out.println(response.executeToolCallsAndGetResults());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testFunctionCall2() throws InterruptedException {
|
||||
OllamaChatConfig config = new OllamaChatConfig();
|
||||
config.setEndpoint("http://localhost:11434");
|
||||
config.setModel("llama3.1");
|
||||
config.setLogEnabled(true);
|
||||
|
||||
ChatModel chatModel = new OllamaChatModel(config);
|
||||
|
||||
SimplePrompt prompt = new SimplePrompt("What's the weather like in Beijing?");
|
||||
prompt.addToolsFromClass(WeatherFunctions.class);
|
||||
AiMessageResponse response = chatModel.chat(prompt);
|
||||
|
||||
if (response.hasToolCalls()) {
|
||||
prompt.setToolMessages(response.executeToolCallsAndGetToolMessages());
|
||||
AiMessageResponse response1 = chatModel.chat(prompt);
|
||||
System.out.println(response1.getMessage().getContent());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testVisionModel() {
|
||||
OllamaChatConfig config = new OllamaChatConfig();
|
||||
config.setEndpoint("http://localhost:11434");
|
||||
config.setModel("llava");
|
||||
config.setLogEnabled(true);
|
||||
|
||||
ChatModel chatModel = new OllamaChatModel(config);
|
||||
|
||||
SimplePrompt imagePrompt = new SimplePrompt("What's in the picture?");
|
||||
imagePrompt.addImageUrl("https://agentsflex.com/assets/images/logo.png");
|
||||
|
||||
AiMessageResponse response = chatModel.chat(imagePrompt);
|
||||
AiMessage message = response == null ? null : response.getMessage();
|
||||
System.out.println(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.easyagents.llm.ollama;
|
||||
|
||||
import com.easyagents.core.model.chat.tool.annotation.ToolDef;
|
||||
import com.easyagents.core.model.chat.tool.annotation.ToolParam;
|
||||
|
||||
public class WeatherFunctions {
|
||||
|
||||
@ToolDef(name = "get_the_weather_info", description = "get the weather info")
|
||||
public static String getWeatherInfo(
|
||||
@ToolParam(name = "city", description = "the city name") String name
|
||||
) {
|
||||
return "Snowy days";
|
||||
}
|
||||
|
||||
|
||||
@ToolDef(name = "get_the_temperature", description = "get the temperature")
|
||||
public static String getTemperature(
|
||||
@ToolParam(name = "city", description = "the city name") String name
|
||||
) {
|
||||
return "The temperature in " + name + " is 15°C";
|
||||
}
|
||||
}
|
||||
32
easy-agents-chat/easy-agents-chat-openai/pom.xml
Normal file
32
easy-agents-chat/easy-agents-chat-openai/pom.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.easyagents</groupId>
|
||||
<artifactId>easy-agents-chat</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<name>easy-agents-chat-openai</name>
|
||||
<artifactId>easy-agents-chat-openai</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.easyagents</groupId>
|
||||
<artifactId>easy-agents-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* Copyright (c) 2023-2026, Easy-Agents (fuhai999@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.easyagents.llm.openai;
|
||||
|
||||
import com.easyagents.core.model.chat.ChatConfig;
|
||||
import com.easyagents.core.model.chat.ChatInterceptor;
|
||||
import com.easyagents.core.util.StringUtil;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* OpenAI 聊天模型的配置类,支持通过 Builder 模式创建配置或直接构建 {@link OpenAIChatModel}。
|
||||
* <p>
|
||||
* 默认值:
|
||||
* <ul>
|
||||
* <li>provider: {@code "openai"}</li>
|
||||
* <li>model: {@code "gpt-3.5-turbo"}</li>
|
||||
* <li>endpoint: {@code "https://api.openai.com"}</li>
|
||||
* <li>requestPath: {@code "/v1/chat/completions"}</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* 该配置类专为 OpenAI 兼容 API 设计,适用于 OpenAI 官方、Azure OpenAI 或其他兼容服务。
|
||||
*/
|
||||
public class OpenAIChatConfig extends ChatConfig {
|
||||
|
||||
private static final String DEFAULT_PROVIDER = "openai";
|
||||
private static final String DEFAULT_MODEL = "gpt-3.5-turbo";
|
||||
private static final String DEFAULT_ENDPOINT = "https://api.openai.com";
|
||||
private static final String DEFAULT_REQUEST_PATH = "/v1/chat/completions";
|
||||
|
||||
public OpenAIChatConfig() {
|
||||
setProvider(DEFAULT_PROVIDER);
|
||||
setEndpoint(DEFAULT_ENDPOINT);
|
||||
setRequestPath(DEFAULT_REQUEST_PATH);
|
||||
setModel(DEFAULT_MODEL);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个 {@link OpenAIChatModel} 实例,使用当前配置。
|
||||
*
|
||||
* @return 新的 {@link OpenAIChatModel} 实例
|
||||
*/
|
||||
public final OpenAIChatModel toChatModel() {
|
||||
return new OpenAIChatModel(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个 {@link OpenAIChatModel} 实例,使用当前配置和指定的实例级拦截器。
|
||||
*
|
||||
* @param interceptors 实例级拦截器列表,可为 {@code null} 或空列表
|
||||
* @return 新的 {@link OpenAIChatModel} 实例
|
||||
*/
|
||||
public final OpenAIChatModel toChatModel(List<ChatInterceptor> interceptors) {
|
||||
return new OpenAIChatModel(this, interceptors);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构建器类,用于流畅地创建 {@link OpenAIChatConfig} 或直接构建 {@link OpenAIChatModel}。
|
||||
*/
|
||||
public static class Builder {
|
||||
private final OpenAIChatConfig config = new OpenAIChatConfig();
|
||||
|
||||
// --- BaseModelConfig fields ---
|
||||
|
||||
public Builder apiKey(String apiKey) {
|
||||
config.setApiKey(apiKey);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder provider(String provider) {
|
||||
config.setProvider(provider);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder endpoint(String endpoint) {
|
||||
config.setEndpoint(endpoint);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder requestPath(String requestPath) {
|
||||
config.setRequestPath(requestPath);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder model(String model) {
|
||||
config.setModel(model);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加单个自定义属性(会进行深拷贝,不会持有外部引用)。
|
||||
*/
|
||||
public Builder customProperty(String key, Object value) {
|
||||
config.putCustomProperty(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置自定义属性映射(会进行深拷贝,不会持有外部 map 引用)。
|
||||
*/
|
||||
public Builder customProperties(Map<String, Object> customProperties) {
|
||||
config.setCustomProperties(customProperties);
|
||||
return this;
|
||||
}
|
||||
|
||||
// --- ChatConfig fields ---
|
||||
|
||||
public Builder supportImage(Boolean supportImage) {
|
||||
config.setSupportImage(supportImage);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder supportImageBase64Only(Boolean supportImageBase64Only) {
|
||||
config.setSupportImageBase64Only(supportImageBase64Only);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder supportAudio(Boolean supportAudio) {
|
||||
config.setSupportAudio(supportAudio);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder supportVideo(Boolean supportVideo) {
|
||||
config.setSupportVideo(supportVideo);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder supportTool(Boolean supportTool) {
|
||||
config.setSupportTool(supportTool);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder supportThinking(Boolean supportThinking) {
|
||||
config.setSupportThinking(supportThinking);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder thinkingEnabled(boolean thinkingEnabled) {
|
||||
config.setThinkingEnabled(thinkingEnabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder observabilityEnabled(boolean observabilityEnabled) {
|
||||
config.setObservabilityEnabled(observabilityEnabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder logEnabled(boolean logEnabled) {
|
||||
config.setLogEnabled(logEnabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构建 {@link OpenAIChatConfig} 配置对象。
|
||||
* <p>
|
||||
* 该方法会校验必要字段(如 {@code apiKey}),若缺失将抛出异常。
|
||||
*
|
||||
* @return 构建完成的配置对象
|
||||
* @throws IllegalStateException 如果 {@code apiKey} 未设置或为空
|
||||
*/
|
||||
public OpenAIChatConfig build() {
|
||||
if (StringUtil.noText(config.getApiKey())) {
|
||||
throw new IllegalStateException("apiKey must be set for OpenAIChatConfig");
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接构建 {@link OpenAIChatModel} 实例,使用默认(全局)拦截器。
|
||||
*
|
||||
* @return 新的聊天模型实例
|
||||
* @throws IllegalStateException 如果 {@code apiKey} 未设置或为空
|
||||
*/
|
||||
public OpenAIChatModel buildModel() {
|
||||
return new OpenAIChatModel(build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接构建 {@link OpenAIChatModel} 实例,并指定实例级拦截器。
|
||||
*
|
||||
* @param interceptors 实例级拦截器列表,可为 {@code null} 或空
|
||||
* @return 新的聊天模型实例
|
||||
* @throws IllegalStateException 如果 {@code apiKey} 未设置或为空
|
||||
*/
|
||||
public OpenAIChatModel buildModel(List<ChatInterceptor> interceptors) {
|
||||
return new OpenAIChatModel(build(), interceptors);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个新的构建器实例,用于链式配置。
|
||||
*
|
||||
* @return {@link Builder} 实例
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (c) 2023-2026, Easy-Agents (fuhai999@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.easyagents.llm.openai;
|
||||
|
||||
import com.easyagents.core.model.chat.BaseChatModel;
|
||||
import com.easyagents.core.model.chat.OpenAICompatibleChatModel;
|
||||
import com.easyagents.core.model.chat.ChatInterceptor;
|
||||
import com.easyagents.core.model.chat.GlobalChatInterceptors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* OpenAI 聊天模型实现。
|
||||
* <p>
|
||||
* 该类封装了 OpenAI API 的具体调用细节,包括:
|
||||
* <ul>
|
||||
* <li>请求体构建(支持同步/流式)</li>
|
||||
* <li>HTTP 客户端管理</li>
|
||||
* <li>解析器配置(同步/流式使用不同解析器)</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* 所有横切逻辑(监控、日志、拦截)由 {@link BaseChatModel} 的责任链处理,
|
||||
* 本类只关注 OpenAI 协议特有的实现细节。
|
||||
*/
|
||||
public class OpenAIChatModel extends OpenAICompatibleChatModel<OpenAIChatConfig> {
|
||||
|
||||
|
||||
/**
|
||||
* 构造一个聊天模型实例,不使用实例级拦截器。
|
||||
*
|
||||
* @param config 聊天模型配置
|
||||
*/
|
||||
public OpenAIChatModel(OpenAIChatConfig config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造一个聊天模型实例,并指定实例级拦截器。
|
||||
* <p>
|
||||
* 实例级拦截器会与全局拦截器(通过 {@link GlobalChatInterceptors} 注册)合并,
|
||||
* 执行顺序为:可观测性拦截器 → 全局拦截器 → 实例拦截器。
|
||||
*
|
||||
* @param config 聊天模型配置
|
||||
* @param userInterceptors 实例级拦截器列表
|
||||
*/
|
||||
public OpenAIChatModel(OpenAIChatConfig config, List<ChatInterceptor> userInterceptors) {
|
||||
super(config, userInterceptors);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.easyagents.llm.openai;
|
||||
|
||||
import com.easyagents.core.model.chat.ChatModel;
|
||||
import com.easyagents.core.model.chat.ChatOptions;
|
||||
import com.easyagents.core.model.chat.StreamResponseListener;
|
||||
import com.easyagents.core.model.chat.response.AiMessageResponse;
|
||||
import com.easyagents.core.model.client.StreamContext;
|
||||
import com.easyagents.core.prompt.Prompt;
|
||||
import com.easyagents.core.prompt.SimplePrompt;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ChatModelTestUtils {
|
||||
|
||||
public static void waitForStream(
|
||||
ChatModel model,
|
||||
String prompt,
|
||||
StreamResponseListener listener) {
|
||||
waitForStream(model, new SimplePrompt(prompt), listener, Integer.MAX_VALUE, null);
|
||||
}
|
||||
|
||||
public static void waitForStream(
|
||||
ChatModel model,
|
||||
String prompt,
|
||||
StreamResponseListener listener,
|
||||
ChatOptions options) {
|
||||
waitForStream(model, new SimplePrompt(prompt), listener, Integer.MAX_VALUE, options);
|
||||
}
|
||||
|
||||
public static void waitForStream(
|
||||
ChatModel model,
|
||||
Prompt prompt,
|
||||
StreamResponseListener listener) {
|
||||
waitForStream(model, prompt, listener, Integer.MAX_VALUE, null);
|
||||
}
|
||||
|
||||
public static void waitForStream(
|
||||
ChatModel model,
|
||||
Prompt prompt,
|
||||
StreamResponseListener listener,
|
||||
ChatOptions options) {
|
||||
waitForStream(model, prompt, listener, Integer.MAX_VALUE, options);
|
||||
}
|
||||
|
||||
public static void waitForStream(
|
||||
ChatModel model,
|
||||
Prompt prompt,
|
||||
StreamResponseListener listener,
|
||||
long timeoutSeconds, ChatOptions options) {
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
StreamResponseListener wrapped = new StreamResponseListener() {
|
||||
@Override
|
||||
public void onStart(StreamContext context) {
|
||||
listener.onStart(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(StreamContext ctx, AiMessageResponse resp) {
|
||||
listener.onMessage(ctx, resp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop(StreamContext ctx) {
|
||||
listener.onStop(ctx);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(StreamContext context, Throwable throwable) {
|
||||
listener.onFailure(context, throwable);
|
||||
}
|
||||
};
|
||||
|
||||
model.chatStream(prompt, wrapped, options);
|
||||
try {
|
||||
if (!latch.await(timeoutSeconds, TimeUnit.SECONDS)) {
|
||||
throw new RuntimeException("Stream did not complete within " + timeoutSeconds + "s");
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.easyagents.llm.openai;
|
||||
|
||||
import com.easyagents.core.model.chat.ChatModel;
|
||||
import com.easyagents.core.model.chat.response.AiMessageResponse;
|
||||
import com.easyagents.core.prompt.SimplePrompt;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.Test;
|
||||
|
||||
public class GiteeAiImageTest {
|
||||
|
||||
|
||||
@NotNull
|
||||
private static OpenAIChatConfig getOpenAIChatConfig() {
|
||||
OpenAIChatConfig config = new OpenAIChatConfig();
|
||||
config.setApiKey("PXW1GXE******L7D12");
|
||||
// config.setModel("InternVL3-78B");
|
||||
config.setModel("Qwen3-32B");
|
||||
config.setEndpoint("https://ai.gitee.com");
|
||||
config.setLogEnabled(true);
|
||||
return config;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImage() {
|
||||
OpenAIChatConfig config = getOpenAIChatConfig();
|
||||
ChatModel chatModel = new OpenAIChatModel(config);
|
||||
|
||||
SimplePrompt prompt = new SimplePrompt("请识别并输入 markdown,请用中文输出");
|
||||
prompt.addImageUrl("http://www.codeformat.cn/static/images/logo.png");
|
||||
|
||||
AiMessageResponse response = chatModel.chat(prompt);
|
||||
if (!response.isError()) {
|
||||
System.out.println(response.getMessage().getContent());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChat() {
|
||||
OpenAIChatConfig config = getOpenAIChatConfig();
|
||||
config.setSupportImage(false);
|
||||
ChatModel chatModel = new OpenAIChatModel(config);
|
||||
|
||||
SimplePrompt prompt = new SimplePrompt("你叫什么名字");
|
||||
prompt.addImageUrl("http://www.codeformat.cn/static/images/logo.png");
|
||||
|
||||
AiMessageResponse response = chatModel.chat(prompt);
|
||||
if (!response.isError()) {
|
||||
System.out.println(response.getMessage().getContent());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,500 @@
|
||||
package com.easyagents.llm.openai;
|
||||
|
||||
import com.easyagents.core.agent.react.ReActAgent;
|
||||
import com.easyagents.core.agent.react.ReActAgentListener;
|
||||
import com.easyagents.core.agent.react.ReActAgentState;
|
||||
import com.easyagents.core.agent.react.ReActStep;
|
||||
import com.easyagents.core.memory.ChatMemory;
|
||||
import com.easyagents.core.message.ToolCall;
|
||||
import com.easyagents.core.message.ToolMessage;
|
||||
import com.easyagents.core.message.UserMessage;
|
||||
import com.easyagents.core.model.chat.ChatModel;
|
||||
import com.easyagents.core.model.chat.ChatOptions;
|
||||
import com.easyagents.core.model.chat.StreamResponseListener;
|
||||
import com.easyagents.core.model.chat.response.AiMessageResponse;
|
||||
import com.easyagents.core.model.chat.tool.Tool;
|
||||
import com.easyagents.core.model.chat.tool.ToolScanner;
|
||||
import com.easyagents.core.model.client.StreamContext;
|
||||
import com.easyagents.core.model.exception.ModelException;
|
||||
import com.easyagents.core.prompt.SimplePrompt;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class OpenAIChatModelTest {
|
||||
|
||||
@Test(expected = ModelException.class)
|
||||
public void testChat() {
|
||||
|
||||
String output = OpenAIChatConfig.builder()
|
||||
.endpoint("https://ai.gitee.com")
|
||||
.provider("GiteeAI")
|
||||
.model("Qwen3-32B")
|
||||
.apiKey("PXW1****D12")
|
||||
.buildModel()
|
||||
.chat("你叫什么名字");
|
||||
|
||||
System.out.println(output);
|
||||
}
|
||||
|
||||
@Test()
|
||||
public void testChatStream() {
|
||||
OpenAIChatConfig config = new OpenAIChatConfig();
|
||||
config.setApiKey("PXW1GXE***");
|
||||
config.setEndpoint("https://ai.gitee.com");
|
||||
config.setModel("Qwen3-32B");
|
||||
config.setLogEnabled(true);
|
||||
|
||||
ChatOptions options = ChatOptions.builder().thinkingEnabled(false).build();
|
||||
|
||||
ChatModel chatModel = new OpenAIChatModel(config);
|
||||
|
||||
ChatModelTestUtils.waitForStream(chatModel, "你叫什么名字", new StreamResponseListener() {
|
||||
@Override
|
||||
public void onMessage(StreamContext context, AiMessageResponse response) {
|
||||
System.out.println(response.getMessage().getContent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(StreamContext context, Throwable throwable) {
|
||||
System.out.println("onFailure>>>>" + throwable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop(StreamContext context) {
|
||||
System.out.println("stop!!!!");
|
||||
}
|
||||
}, options);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test()
|
||||
public void testChatStreamBailian() {
|
||||
OpenAIChatConfig config = new OpenAIChatConfig();
|
||||
config.setApiKey("sk-32ab******57502");
|
||||
config.setEndpoint("https://dashscope.aliyuncs.com");
|
||||
config.setRequestPath("/compatible-mode/v1/chat/completions");
|
||||
config.setModel("qwen3-max");
|
||||
ChatModel chatModel = new OpenAIChatModel(config);
|
||||
|
||||
SimplePrompt prompt = new SimplePrompt("北京的天气如何?");
|
||||
prompt.addToolsFromClass(WeatherFunctions.class);
|
||||
|
||||
ChatModelTestUtils.waitForStream(chatModel, prompt, new StreamResponseListener() {
|
||||
@Override
|
||||
public void onFailure(StreamContext context, Throwable throwable) {
|
||||
System.out.println("onFailure>>>>" + throwable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(StreamContext context, AiMessageResponse response) {
|
||||
if (response.getMessage().getContent() == null) {
|
||||
System.out.println(response.getMessage());
|
||||
}
|
||||
|
||||
if (response.getMessage().isFinalDelta()) {
|
||||
List<ToolCall> toolCalls = response.getMessage().getToolCalls();
|
||||
System.out.println(toolCalls);
|
||||
}
|
||||
|
||||
System.out.println("onMessage >>>>>" + response.getMessage().getContent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop(StreamContext context) {
|
||||
System.out.println("stop!!!!");
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChatOllama() {
|
||||
OpenAIChatConfig config = new OpenAIChatConfig();
|
||||
config.setEndpoint("http://localhost:11434");
|
||||
config.setModel("llama3");
|
||||
config.setLogEnabled(true);
|
||||
|
||||
ChatModel chatModel = new OpenAIChatModel(config);
|
||||
chatModel.chatStream("who are you", new StreamResponseListener() {
|
||||
@Override
|
||||
public void onMessage(StreamContext context, AiMessageResponse response) {
|
||||
System.out.println(response.getMessage().getContent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop(StreamContext context) {
|
||||
System.out.println("stop!!!!");
|
||||
}
|
||||
});
|
||||
|
||||
// try {
|
||||
// Thread.sleep(2000);
|
||||
// } catch (InterruptedException e) {
|
||||
// throw new RuntimeException(e);
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
@Test()
|
||||
public void testChatWithImage() {
|
||||
OpenAIChatConfig config = new OpenAIChatConfig();
|
||||
config.setApiKey("sk-5gqOcl*****");
|
||||
config.setModel("gpt-4-turbo");
|
||||
|
||||
|
||||
ChatModel chatModel = new OpenAIChatModel(config);
|
||||
SimplePrompt prompt = new SimplePrompt("What's in this image?");
|
||||
prompt.addImageUrl("https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg");
|
||||
|
||||
|
||||
AiMessageResponse response = chatModel.chat(prompt);
|
||||
System.out.println(response);
|
||||
}
|
||||
|
||||
|
||||
@Test()
|
||||
public void testFunctionCalling1() throws InterruptedException {
|
||||
OpenAIChatConfig config = new OpenAIChatConfig();
|
||||
config.setApiKey("sk-rts5NF6n*******");
|
||||
|
||||
OpenAIChatModel llm = new OpenAIChatModel(config);
|
||||
|
||||
SimplePrompt prompt = new SimplePrompt("今天北京的天气怎么样");
|
||||
prompt.addToolsFromClass(WeatherFunctions.class);
|
||||
AiMessageResponse response = llm.chat(prompt);
|
||||
|
||||
System.out.println(response.executeToolCallsAndGetResults());
|
||||
// 阴转多云
|
||||
}
|
||||
|
||||
@Test()
|
||||
public void testFunctionCalling2() throws InterruptedException {
|
||||
OpenAIChatConfig config = new OpenAIChatConfig();
|
||||
config.setApiKey("sk-rts5NF6n*******");
|
||||
|
||||
OpenAIChatModel llm = new OpenAIChatModel(config);
|
||||
|
||||
SimplePrompt prompt = new SimplePrompt("今天北京的天气怎么样");
|
||||
prompt.addToolsFromClass(WeatherFunctions.class);
|
||||
AiMessageResponse response = llm.chat(prompt);
|
||||
|
||||
if (response.hasToolCalls()) {
|
||||
prompt.setToolMessages(response.executeToolCallsAndGetToolMessages());
|
||||
AiMessageResponse response1 = llm.chat(prompt);
|
||||
System.out.println(response1.getMessage().getContent());
|
||||
} else {
|
||||
System.out.println(response);
|
||||
}
|
||||
}
|
||||
|
||||
@Test()
|
||||
public void testFunctionCalling3() throws InterruptedException {
|
||||
OpenAIChatConfig config = new OpenAIChatConfig();
|
||||
config.setLogEnabled(true);
|
||||
config.setEndpoint("https://ark.cn-beijing.volces.com");
|
||||
config.setRequestPath("/api/v3/chat/completions");
|
||||
config.setModel("deepseek-v3-250324");
|
||||
config.setApiKey("2d57a");
|
||||
|
||||
OpenAIChatModel llm = new OpenAIChatModel(config);
|
||||
|
||||
SimplePrompt prompt = new SimplePrompt("今天北京的天气怎么样");
|
||||
prompt.addToolsFromClass(WeatherFunctions.class);
|
||||
AiMessageResponse response = llm.chat(prompt);
|
||||
|
||||
if (response.hasToolCalls()) {
|
||||
prompt.setToolMessages(response.executeToolCallsAndGetToolMessages());
|
||||
AiMessageResponse response1 = llm.chat(prompt);
|
||||
System.out.println(response1.getMessage().getContent());
|
||||
} else {
|
||||
System.out.println(response);
|
||||
}
|
||||
}
|
||||
|
||||
@Test()
|
||||
public void testFunctionCalling4() throws InterruptedException {
|
||||
OpenAIChatConfig config = new OpenAIChatConfig();
|
||||
config.setLogEnabled(true);
|
||||
config.setEndpoint("https://ark.cn-beijing.volces.com");
|
||||
config.setRequestPath("/api/v3/chat/completions");
|
||||
config.setModel("deepseek-v3-250324");
|
||||
config.setApiKey("2d57aa75");
|
||||
|
||||
OpenAIChatModel llm = new OpenAIChatModel(config);
|
||||
|
||||
SimplePrompt prompt = new SimplePrompt("今天北京的天气怎么样");
|
||||
prompt.addToolsFromClass(WeatherFunctions.class);
|
||||
llm.chatStream(prompt, new StreamResponseListener() {
|
||||
@Override
|
||||
public void onMessage(StreamContext context, AiMessageResponse response) {
|
||||
System.out.println(" onMessage >>>>>" + response.hasToolCalls());
|
||||
}
|
||||
});
|
||||
|
||||
TimeUnit.SECONDS.sleep(5);
|
||||
}
|
||||
|
||||
@Test()
|
||||
public void testFunctionCalling44() throws InterruptedException {
|
||||
OpenAIChatConfig config = new OpenAIChatConfig();
|
||||
config.setLogEnabled(true);
|
||||
config.setEndpoint("https://ark.cn-beijing.volces.com");
|
||||
config.setRequestPath("/api/v3/chat/completions");
|
||||
config.setModel("deepseek-v3-250324");
|
||||
config.setApiKey("2d5");
|
||||
|
||||
OpenAIChatModel llm = new OpenAIChatModel(config);
|
||||
|
||||
SimplePrompt prompt = new SimplePrompt("今天北京的天气怎么样");
|
||||
prompt.addToolsFromClass(WeatherFunctions.class);
|
||||
llm.chatStream(prompt, new StreamResponseListener() {
|
||||
@Override
|
||||
public void onMessage(StreamContext context, AiMessageResponse response) {
|
||||
System.out.println(" onMessage >>>>>" + response.hasToolCalls());
|
||||
}
|
||||
});
|
||||
|
||||
TimeUnit.SECONDS.sleep(5);
|
||||
}
|
||||
|
||||
@Test()
|
||||
public void testFunctionCalling444() throws InterruptedException {
|
||||
OpenAIChatConfig config = new OpenAIChatConfig();
|
||||
config.setLogEnabled(true);
|
||||
config.setEndpoint("https://ai.gitee.com");
|
||||
// config.setRequestPath("/api/v3/chat/completions");
|
||||
// config.setModel("Qwen3-32B");
|
||||
config.setModel("DeepSeek-V3.2");
|
||||
config.setApiKey("PXW1G***L7D12");
|
||||
// config.setLogEnabled(false);
|
||||
|
||||
OpenAIChatModel llm = new OpenAIChatModel(config);
|
||||
|
||||
|
||||
SimplePrompt prompt = new SimplePrompt("北京和上海的天气怎么样");
|
||||
prompt.addToolsFromClass(WeatherFunctions.class);
|
||||
llm.chatStream(prompt, new StreamResponseListener() {
|
||||
@Override
|
||||
public void onMessage(StreamContext context, AiMessageResponse response) {
|
||||
|
||||
// System.out.println("onMessage11 >>>>>" + response);
|
||||
if (response.getMessage().isFinalDelta() && response.hasToolCalls()) {
|
||||
System.out.println(":::::::: start....");
|
||||
List<ToolMessage> toolMessages = response.executeToolCallsAndGetToolMessages();
|
||||
prompt.setAiMessage(response.getMessage());
|
||||
prompt.setToolMessages(toolMessages);
|
||||
llm.chatStream(prompt, new StreamResponseListener() {
|
||||
@Override
|
||||
public void onMessage(StreamContext context, AiMessageResponse response) {
|
||||
String msg = response.getMessage().getContent() != null ? response.getMessage().getContent() : response.getMessage().getReasoningContent();
|
||||
System.out.println(":::22" + msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop(StreamContext context) {
|
||||
System.out.println("onStop >>>>>");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
String msg = response.getMessage().getContent() != null ? response.getMessage().getContent() : response.getMessage().getReasoningContent();
|
||||
System.out.println(">>>" + msg);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
TimeUnit.SECONDS.sleep(25);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test()
|
||||
public void testFunctionCalling5() throws InterruptedException {
|
||||
OpenAIChatConfig config = new OpenAIChatConfig();
|
||||
config.setLogEnabled(true);
|
||||
config.setEndpoint("https://ai.gitee.com");
|
||||
config.setModel("Qwen3-32B");
|
||||
config.setApiKey("PXW1G*********D12");
|
||||
|
||||
OpenAIChatModel llm = new OpenAIChatModel(config);
|
||||
|
||||
SimplePrompt prompt = new SimplePrompt("北京和上海的天气怎么样");
|
||||
prompt.addToolsFromClass(WeatherFunctions.class);
|
||||
llm.chatStream(prompt, new StreamResponseListener() {
|
||||
@Override
|
||||
public void onMessage(StreamContext context, AiMessageResponse response) {
|
||||
// System.out.println("onMessage >>>>>" + response);
|
||||
}
|
||||
});
|
||||
|
||||
TimeUnit.SECONDS.sleep(25);
|
||||
}
|
||||
|
||||
|
||||
@Test()
|
||||
public void testFunctionCalling55() throws InterruptedException {
|
||||
OpenAIChatConfig config = new OpenAIChatConfig();
|
||||
config.setLogEnabled(true);
|
||||
config.setEndpoint("https://ai.gitee.com");
|
||||
config.setModel("Qwen3-32B");
|
||||
// config.setModel("DeepSeek-V3");
|
||||
// config.setSupportToolMessage(false);
|
||||
config.setApiKey("PXW1");
|
||||
|
||||
|
||||
OpenAIChatModel llm = new OpenAIChatModel(config);
|
||||
|
||||
SimplePrompt prompt = new SimplePrompt("/no_think 北京和上海的天气怎么样");
|
||||
prompt.addToolsFromClass(WeatherFunctions.class);
|
||||
|
||||
|
||||
llm.chatStream(prompt, new StreamResponseListener() {
|
||||
@Override
|
||||
public void onMessage(StreamContext context, AiMessageResponse response) {
|
||||
if (response.getMessage().isFinalDelta() && response.hasToolCalls()) {
|
||||
System.out.println(":::::::: start....");
|
||||
prompt.setAiMessage(response.getMessage());
|
||||
prompt.setToolMessages(response.executeToolCallsAndGetToolMessages());
|
||||
llm.chatStream(prompt, new StreamResponseListener() {
|
||||
@Override
|
||||
public void onMessage(StreamContext context, AiMessageResponse response) {
|
||||
String msg = response.getMessage().getContent() != null ? response.getMessage().getContent() : response.getMessage().getReasoningContent();
|
||||
System.out.println(":::" + msg);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
String msg = response.getMessage().getContent() != null ? response.getMessage().getContent() : response.getMessage().getReasoningContent();
|
||||
System.out.println(">>>" + msg);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
TimeUnit.SECONDS.sleep(25);
|
||||
}
|
||||
|
||||
@Test()
|
||||
public void testFunctionCalling6() throws InterruptedException {
|
||||
OpenAIChatConfig config = new OpenAIChatConfig();
|
||||
config.setLogEnabled(true);
|
||||
config.setEndpoint("https://ai.gitee.com");
|
||||
config.setModel("Qwen3-32B");
|
||||
config.setApiKey("PXW1");
|
||||
|
||||
OpenAIChatModel llm = new OpenAIChatModel(config);
|
||||
|
||||
SimplePrompt prompt = new SimplePrompt("/no_think 北京和上海的天气怎么样");
|
||||
prompt.addToolsFromClass(WeatherFunctions.class);
|
||||
AiMessageResponse response = llm.chat(prompt);
|
||||
|
||||
prompt.setToolMessages(response.executeToolCallsAndGetToolMessages());
|
||||
|
||||
System.out.println(llm.chat(prompt));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test()
|
||||
public void testReAct1() throws InterruptedException {
|
||||
OpenAIChatConfig config = new OpenAIChatConfig();
|
||||
// config.setDebug(true);
|
||||
config.setEndpoint("https://ai.gitee.com");
|
||||
config.setModel("Qwen3-32B");
|
||||
config.setApiKey("****");
|
||||
|
||||
OpenAIChatModel llm = new OpenAIChatModel(config);
|
||||
|
||||
List<Tool> tools = ToolScanner.scan(WeatherFunctions.class);
|
||||
// ReActAgent reActAgent = new ReActAgent(llm, functions, "北京和上海的天气怎么样?");
|
||||
ReActAgent reActAgent = new ReActAgent(llm, tools, "介绍一下北京");
|
||||
reActAgent.addListener(new ReActAgentListener() {
|
||||
|
||||
@Override
|
||||
public void onActionStart(ReActStep step) {
|
||||
System.out.println(">>>>>>" + step.getThought());
|
||||
System.out.println("正在调用工具 >>>>> " + step.getAction() + ":" + step.getActionInput());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActionEnd(ReActStep step, Object result) {
|
||||
System.out.println("工具调用结束 >>>>> " + step.getAction() + ":" + step.getActionInput() + ">>>>结果:" + result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinalAnswer(String finalAnswer) {
|
||||
System.out.println("onFinalAnswer >>>>>" + finalAnswer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNonActionResponse(AiMessageResponse response) {
|
||||
System.out.println("onNonActionResponse >>>>>" + response.getMessage().getContent());
|
||||
}
|
||||
});
|
||||
|
||||
reActAgent.execute();
|
||||
}
|
||||
|
||||
|
||||
@Test()
|
||||
public void testReAct2() throws InterruptedException {
|
||||
OpenAIChatConfig config = new OpenAIChatConfig();
|
||||
// config.setDebug(true);
|
||||
config.setEndpoint("https://ai.gitee.com");
|
||||
config.setModel("Qwen2-72B-Instruct");
|
||||
config.setApiKey("*****");
|
||||
|
||||
OpenAIChatModel llm = new OpenAIChatModel(config);
|
||||
|
||||
List<Tool> tools = ToolScanner.scan(WeatherFunctions.class);
|
||||
ReActAgent reActAgent = new ReActAgent(llm, tools, "今天的天气怎么样?");
|
||||
// reActAgent.setStreamable(true);
|
||||
reActAgent.addListener(new ReActAgentListener() {
|
||||
|
||||
@Override
|
||||
public void onChatResponseStream(StreamContext context, AiMessageResponse response) {
|
||||
// System.out.print(response.getMessage().getContent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestUserInput(String question) {
|
||||
System.out.println("onRequestUserInput>>>" + question);
|
||||
|
||||
ReActAgentState state = reActAgent.getState();
|
||||
state.addMessage(new UserMessage("我在北京市"));
|
||||
ReActAgent newAgent = new ReActAgent(llm, tools, state);
|
||||
newAgent.addListener(this);
|
||||
newAgent.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActionStart(ReActStep step) {
|
||||
System.out.println(">>>>>>" + step.getThought());
|
||||
System.out.println("正在调用工具 >>>>> " + step.getAction() + ":" + step.getActionInput());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActionEnd(ReActStep step, Object result) {
|
||||
System.out.println("工具调用结束 >>>>> " + step.getAction() + ":" + step.getActionInput() + ">>>>结果:" + result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinalAnswer(String finalAnswer) {
|
||||
System.out.println("onFinalAnswer >>>>>" + finalAnswer);
|
||||
ChatMemory memory = reActAgent.getMemoryPrompt().getMemory();
|
||||
System.out.println(memory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNonActionResponseStream(StreamContext context) {
|
||||
System.out.println("onNonActionResponseStream >>>>>" + context);
|
||||
}
|
||||
});
|
||||
|
||||
reActAgent.execute();
|
||||
|
||||
TimeUnit.SECONDS.sleep(20);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.easyagents.llm.openai;
|
||||
|
||||
import com.easyagents.core.model.chat.tool.annotation.ToolDef;
|
||||
import com.easyagents.core.model.chat.tool.annotation.ToolParam;
|
||||
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class WeatherFunctions {
|
||||
|
||||
private static final String[] weathers = {
|
||||
"晴", "多云", "阴", "小雨", "中雨", "大雨", "暴雨", "雷阵雨",
|
||||
"小雪", "中雪", "大雪", "暴雪", "雨夹雪", "雾", "霾", "沙尘暴",
|
||||
"冰雹", "阵雨", "冻雨", "晴间多云", "局部多云", "强对流"
|
||||
};
|
||||
|
||||
@ToolDef(name = "get_the_weather_info", description = "get the weather info")
|
||||
public static String getWeatherInfo(@ToolParam(name = "city", description = "the city name") String name) {
|
||||
String weather = weathers[ThreadLocalRandom.current().nextInt(weathers.length)];
|
||||
System.out.println(">>>>>>>>>>>>>>!!!!!!" + name + ":" + weather);
|
||||
return weather;
|
||||
}
|
||||
|
||||
}
|
||||
33
easy-agents-chat/easy-agents-chat-qwen/pom.xml
Normal file
33
easy-agents-chat/easy-agents-chat-qwen/pom.xml
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.easyagents</groupId>
|
||||
<artifactId>easy-agents-chat</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<name>easy-agents-chat-qwen</name>
|
||||
<artifactId>easy-agents-chat-qwen</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.easyagents</groupId>
|
||||
<artifactId>easy-agents-core</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2023-2026, Easy-Agents (fuhai999@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.easyagents.llm.qwen;
|
||||
|
||||
import com.easyagents.core.model.chat.ChatConfig;
|
||||
|
||||
public class QwenChatConfig extends ChatConfig {
|
||||
|
||||
private static final String DEFAULT_MODEL = "qwen-turbo";
|
||||
private static final String DEFAULT_ENDPOINT = "https://dashscope.aliyuncs.com";
|
||||
private static final String DEFAULT_REQUEST_PATH = "/compatible-mode/v1/chat/completions";
|
||||
|
||||
public QwenChatConfig() {
|
||||
setEndpoint(DEFAULT_ENDPOINT);
|
||||
setRequestPath(DEFAULT_REQUEST_PATH);
|
||||
setModel(DEFAULT_MODEL);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (c) 2023-2026, Easy-Agents (fuhai999@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.easyagents.llm.qwen;
|
||||
|
||||
import com.easyagents.core.model.chat.OpenAICompatibleChatModel;
|
||||
import com.easyagents.core.model.chat.ChatInterceptor;
|
||||
import com.easyagents.core.model.chat.GlobalChatInterceptors;
|
||||
import com.easyagents.core.model.client.ChatRequestSpecBuilder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class QwenChatModel extends OpenAICompatibleChatModel<QwenChatConfig> {
|
||||
|
||||
|
||||
/**
|
||||
* 构造一个聊天模型实例,不使用实例级拦截器。
|
||||
*
|
||||
* @param config 聊天模型配置
|
||||
*/
|
||||
public QwenChatModel(QwenChatConfig config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造一个聊天模型实例,并指定实例级拦截器。
|
||||
* <p>
|
||||
* 实例级拦截器会与全局拦截器(通过 {@link GlobalChatInterceptors} 注册)合并,
|
||||
* 执行顺序为:可观测性拦截器 → 全局拦截器 → 实例拦截器。
|
||||
*
|
||||
* @param config 聊天模型配置
|
||||
* @param userInterceptors 实例级拦截器列表
|
||||
*/
|
||||
public QwenChatModel(QwenChatConfig config, List<ChatInterceptor> userInterceptors) {
|
||||
super(config, userInterceptors);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ChatRequestSpecBuilder getChatRequestSpecBuilder() {
|
||||
return new QwenRequestSpecBuilder();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
package com.easyagents.llm.qwen;
|
||||
|
||||
import com.easyagents.core.model.chat.ChatOptions;
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <link href="https://help.aliyun.com/zh/model-studio/use-qwen-by-calling-api">通义千问API参考</link>
|
||||
*
|
||||
* @author liutf
|
||||
*/
|
||||
public class QwenChatOptions extends ChatOptions {
|
||||
/**
|
||||
* 输出数据的模态,仅支持 Qwen-Omni 模型指定。(可选)
|
||||
* 默认值为["text"]
|
||||
* 可选值:
|
||||
* ["text"]:输出文本。
|
||||
*/
|
||||
private List<String> modalities;
|
||||
|
||||
/**
|
||||
* 控制模型生成文本时的内容重复度。(可选)
|
||||
* 取值范围:[-2.0, 2.0]。正数会减少重复度,负数会增加重复度。
|
||||
* <pre>
|
||||
* 适用场景:
|
||||
* 较高的presence_penalty适用于要求多样性、趣味性或创造性的场景,如创意写作或头脑风暴。
|
||||
* 较低的presence_penalty适用于要求一致性或专业术语的场景,如技术文档或其他正式文档。
|
||||
* </pre>
|
||||
* 不建议修改QVQ模型的默认presence_penalty值。
|
||||
*/
|
||||
private Float presencePenalty;
|
||||
|
||||
/**
|
||||
* 生成响应的个数,取值范围是1-4。
|
||||
* 对于需要生成多个响应的场景(如创意写作、广告文案等),可以设置较大的 n 值。
|
||||
* <pre>
|
||||
* 当前仅支持 qwen-plus 模型,且在传入 tools 参数时固定为1。
|
||||
* 设置较大的 n 值不会增加输入 Token 消耗,会增加输出 Token 的消耗。
|
||||
* </pre>
|
||||
*/
|
||||
private Integer n;
|
||||
|
||||
/**
|
||||
* 是否开启并行工具调用。
|
||||
* 参数为true时开启,为false时不开启。
|
||||
* 并行工具调用请参见:https://help.aliyun.com/zh/model-studio/qwen-function-calling#cb6b5c484bt4x
|
||||
*/
|
||||
private Boolean parallelToolCalls;
|
||||
|
||||
/**
|
||||
* 当您使用翻译模型时需要配置的翻译参数。
|
||||
*/
|
||||
private TranslationOptions translationOptions;
|
||||
|
||||
/**
|
||||
* 用于控制模型在生成文本时是否使用互联网搜索结果进行参考。
|
||||
* 取值如下:
|
||||
* True:启用互联网搜索,模型会将搜索结果作为文本生成过程中的参考信息,但模型会基于其内部逻辑判断是否使用互联网搜索结果。
|
||||
* False(默认):关闭互联网搜索。
|
||||
* 启用互联网搜索功能可能会增加 Token 的消耗。
|
||||
* 当前支持 qwen-max、qwen-plus、qwen-turbo
|
||||
*/
|
||||
private Boolean enableSearch;
|
||||
|
||||
|
||||
/**
|
||||
* 思考模式预算,适用于 Qwen3 模型。
|
||||
* 思考过程的最大长度,只在 enable_thinking 为 true 时生效。适用于 Qwen3 的商业版与开源版模型。
|
||||
* 详情请参见限制思考长度:https://help.aliyun.com/zh/model-studio/deep-thinking#e7c0002fe4meu
|
||||
*/
|
||||
private Integer thinkingBudget;
|
||||
|
||||
/**
|
||||
* 联网搜索的策略。
|
||||
* 仅当enable_search为true时生效。
|
||||
*/
|
||||
private SearchOptions searchOptions;
|
||||
|
||||
public static class TranslationOptions {
|
||||
/**
|
||||
* (必选)
|
||||
* 源语言的英文全称
|
||||
* 您可以将source_lang设置为"auto",模型会自动判断输入文本属于哪种语言。
|
||||
* 支持的语言: https://help.aliyun.com/zh/model-studio/user-guide/machine-translation#038d2865bbydc
|
||||
*/
|
||||
@JSONField(name = "source_lang")
|
||||
private String sourceLang;
|
||||
|
||||
/**
|
||||
* (必选)
|
||||
* 目标语言的英文全称,
|
||||
* 支持的语言: https://help.aliyun.com/zh/model-studio/user-guide/machine-translation#038d2865bbydc
|
||||
*/
|
||||
@JSONField(name = "target_lang")
|
||||
private String targetLang;
|
||||
|
||||
/**
|
||||
* 在使用术语干预翻译功能时需要设置的术语数组。
|
||||
* https://help.aliyun.com/zh/model-studio/user-guide/machine-translation#2bf54a5ab5voe
|
||||
*/
|
||||
@JSONField(name = "terms")
|
||||
private List<TranslationOptionsExt> terms;
|
||||
|
||||
/**
|
||||
* 在使用翻译记忆功能时需要设置的翻译记忆数组。
|
||||
* https://help.aliyun.com/zh/model-studio/user-guide/machine-translation#17e15234e7gfp
|
||||
*/
|
||||
@JSONField(name = "tm_list")
|
||||
private List<TranslationOptionsExt> tmList;
|
||||
|
||||
/**
|
||||
* (可选)
|
||||
* 在使用领域提示功能时需要设置的领域提示语句。
|
||||
* 领域提示语句暂时只支持英文。
|
||||
* https://help.aliyun.com/zh/model-studio/user-guide/machine-translation#4af23a31db7lf
|
||||
*/
|
||||
@JSONField(name = "domains")
|
||||
private String domains;
|
||||
|
||||
public String getSourceLang() {
|
||||
return sourceLang;
|
||||
}
|
||||
|
||||
public TranslationOptions setSourceLang(String sourceLang) {
|
||||
this.sourceLang = sourceLang;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getTargetLang() {
|
||||
return targetLang;
|
||||
}
|
||||
|
||||
public TranslationOptions setTargetLang(String targetLang) {
|
||||
this.targetLang = targetLang;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<TranslationOptionsExt> getTerms() {
|
||||
return terms;
|
||||
}
|
||||
|
||||
public TranslationOptions setTerms(List<TranslationOptionsExt> terms) {
|
||||
this.terms = terms;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<TranslationOptionsExt> getTmList() {
|
||||
return tmList;
|
||||
}
|
||||
|
||||
public TranslationOptions setTmList(List<TranslationOptionsExt> tmList) {
|
||||
this.tmList = tmList;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getDomains() {
|
||||
return domains;
|
||||
}
|
||||
|
||||
public TranslationOptions setDomains(String domains) {
|
||||
this.domains = domains;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TranslationOptionsExt {
|
||||
/**
|
||||
* 源语言的术语/源语言的语句
|
||||
*/
|
||||
private String source;
|
||||
/**
|
||||
* 目标语言的术语/目标语言的语句
|
||||
*/
|
||||
private String target;
|
||||
|
||||
public String getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
public TranslationOptionsExt setSource(String source) {
|
||||
this.source = source;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
public TranslationOptionsExt setTarget(String target) {
|
||||
this.target = target;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class SearchOptions {
|
||||
/**
|
||||
* 是否强制开启搜索。(可选)默认值为false
|
||||
* 参数值:true=强制开启;false=不强制开启。
|
||||
*/
|
||||
@JSONField(name = "forced_search")
|
||||
private Boolean forcedSearch;
|
||||
|
||||
/**
|
||||
* 搜索互联网信息的数量,(可选)默认值为"standard"
|
||||
* 参数值:
|
||||
* standard:在请求时搜索5条互联网信息;
|
||||
* pro:在请求时搜索10条互联网信息。
|
||||
*/
|
||||
@JSONField(name = "search_strategy")
|
||||
private String searchStrategy;
|
||||
|
||||
public Boolean getForcedSearch() {
|
||||
return forcedSearch;
|
||||
}
|
||||
|
||||
public SearchOptions setForcedSearch(Boolean forcedSearch) {
|
||||
this.forcedSearch = forcedSearch;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getSearchStrategy() {
|
||||
return searchStrategy;
|
||||
}
|
||||
|
||||
public SearchOptions setSearchStrategy(String searchStrategy) {
|
||||
this.searchStrategy = searchStrategy;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> getModalities() {
|
||||
return modalities;
|
||||
}
|
||||
|
||||
public QwenChatOptions setModalities(List<String> modalities) {
|
||||
this.modalities = modalities;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Float getPresencePenalty() {
|
||||
return presencePenalty;
|
||||
}
|
||||
|
||||
public QwenChatOptions setPresencePenalty(Float presencePenalty) {
|
||||
this.presencePenalty = presencePenalty;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Integer getN() {
|
||||
return n;
|
||||
}
|
||||
|
||||
public QwenChatOptions setN(Integer n) {
|
||||
this.n = n;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Boolean getParallelToolCalls() {
|
||||
return parallelToolCalls;
|
||||
}
|
||||
|
||||
public QwenChatOptions setParallelToolCalls(Boolean parallelToolCalls) {
|
||||
this.parallelToolCalls = parallelToolCalls;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TranslationOptions getTranslationOptions() {
|
||||
return translationOptions;
|
||||
}
|
||||
|
||||
public QwenChatOptions setTranslationOptions(TranslationOptions translationOptions) {
|
||||
this.translationOptions = translationOptions;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Boolean getEnableSearch() {
|
||||
return enableSearch;
|
||||
}
|
||||
|
||||
public QwenChatOptions setEnableSearch(Boolean enableSearch) {
|
||||
this.enableSearch = enableSearch;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Integer getThinkingBudget() {
|
||||
return thinkingBudget;
|
||||
}
|
||||
|
||||
public void setThinkingBudget(Integer thinkingBudget) {
|
||||
this.thinkingBudget = thinkingBudget;
|
||||
}
|
||||
|
||||
public SearchOptions getSearchOptions() {
|
||||
return searchOptions;
|
||||
}
|
||||
|
||||
public QwenChatOptions setSearchOptions(SearchOptions searchOptions) {
|
||||
this.searchOptions = searchOptions;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.easyagents.llm.qwen;
|
||||
|
||||
import com.easyagents.core.model.chat.ChatConfig;
|
||||
import com.easyagents.core.model.chat.ChatOptions;
|
||||
import com.easyagents.core.model.client.OpenAIChatRequestSpecBuilder;
|
||||
import com.easyagents.core.prompt.Prompt;
|
||||
import com.easyagents.core.util.CollectionUtil;
|
||||
import com.easyagents.core.util.Maps;
|
||||
|
||||
public class QwenRequestSpecBuilder extends OpenAIChatRequestSpecBuilder {
|
||||
|
||||
@Override
|
||||
protected Maps buildBaseParamsOfRequestBody(Prompt prompt, ChatOptions options, ChatConfig config) {
|
||||
Maps params = super.buildBaseParamsOfRequestBody(prompt, options, config);
|
||||
if (options instanceof QwenChatOptions) {
|
||||
QwenChatOptions op = (QwenChatOptions) options;
|
||||
params.setIf(CollectionUtil.hasItems(op.getModalities()), "modalities", op.getModalities());
|
||||
params.setIf(op.getPresencePenalty() != null, "presence_penalty", op.getPresencePenalty());
|
||||
params.setIf(op.getResponseFormat() != null, "response_format", op.getResponseFormat());
|
||||
params.setIf(op.getN() != null, "n", op.getN());
|
||||
params.setIf(op.getParallelToolCalls() != null, "parallel_tool_calls", op.getParallelToolCalls());
|
||||
params.setIf(op.getTranslationOptions() != null, "translation_options", op.getTranslationOptions());
|
||||
params.setIf(op.getEnableSearch() != null, "enable_search", op.getEnableSearch());
|
||||
params.setIf(op.getEnableSearch() != null && op.getEnableSearch() && op.getSearchOptions() != null, "search_options", op.getSearchOptions());
|
||||
params.setIf(op.getThinkingEnabled() != null, "enable_thinking", op.getThinkingEnabled());
|
||||
params.setIf(op.getThinkingEnabled() != null && op.getThinkingEnabled() && op.getThinkingBudget() != null, "thinking_budget", op.getThinkingBudget());
|
||||
}
|
||||
return params;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package com.easyagents.llm.qwen.test;
|
||||
|
||||
import com.easyagents.core.message.AiMessage;
|
||||
import com.easyagents.core.model.chat.ChatModel;
|
||||
import com.easyagents.core.model.chat.ChatOptions;
|
||||
import com.easyagents.core.model.chat.response.AiMessageResponse;
|
||||
import com.easyagents.core.model.exception.ModelException;
|
||||
import com.easyagents.core.prompt.SimplePrompt;
|
||||
|
||||
import com.easyagents.llm.qwen.QwenChatConfig;
|
||||
import com.easyagents.llm.qwen.QwenChatModel;
|
||||
import com.easyagents.llm.qwen.QwenChatOptions;
|
||||
import com.easyagents.llm.qwen.QwenChatOptions.SearchOptions;
|
||||
import org.junit.Test;
|
||||
|
||||
public class QwenTest {
|
||||
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
QwenChatConfig config = new QwenChatConfig();
|
||||
|
||||
//https://bailian.console.aliyun.com/?apiKey=1#/api-key
|
||||
config.setApiKey("sk-28a6be3236****");
|
||||
config.setModel("qwen-plus");
|
||||
|
||||
ChatModel chatModel = new QwenChatModel(config);
|
||||
chatModel.chatStream("请写一个小兔子战胜大灰狼的故事", (context, response) -> {
|
||||
AiMessage message = response.getMessage();
|
||||
System.out.println(">>>> " + message.getContent());
|
||||
});
|
||||
|
||||
Thread.sleep(10000);
|
||||
}
|
||||
|
||||
@Test(expected = ModelException.class)
|
||||
public void testForcedSearch() throws InterruptedException {
|
||||
QwenChatConfig config = new QwenChatConfig();
|
||||
config.setApiKey("sk-28a6be3236****");
|
||||
config.setModel("qwen-max");
|
||||
|
||||
ChatModel chatModel = new QwenChatModel(config);
|
||||
QwenChatOptions options = new QwenChatOptions();
|
||||
options.setEnableSearch(true);
|
||||
options.setSearchOptions(new SearchOptions().setForcedSearch(true));
|
||||
|
||||
String responseStr = chatModel.chat("今天是几号?", options);
|
||||
|
||||
System.out.println(responseStr);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFunctionCalling() throws InterruptedException {
|
||||
QwenChatConfig config = new QwenChatConfig();
|
||||
config.setApiKey("sk-28a6be3236****");
|
||||
config.setModel("qwen-turbo");
|
||||
|
||||
ChatModel chatModel = new QwenChatModel(config);
|
||||
|
||||
SimplePrompt prompt = new SimplePrompt("今天北京的天气怎么样");
|
||||
prompt.addToolsFromClass(WeatherFunctions.class);
|
||||
AiMessageResponse response = chatModel.chat(prompt);
|
||||
|
||||
System.out.println(response.executeToolCallsAndGetResults());
|
||||
// "Today it will be dull and overcast in 北京"
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态替换模型
|
||||
*/
|
||||
@Test
|
||||
public void testDynamicModel() throws InterruptedException {
|
||||
// 默认模型
|
||||
QwenChatConfig config = new QwenChatConfig();
|
||||
config.setApiKey("sk-28a6be3236****");
|
||||
config.setModel("qwen-turbo");
|
||||
|
||||
// 运行时动态替换模型
|
||||
ChatOptions options = new QwenChatOptions();
|
||||
options.setModel("deepseek-r1");
|
||||
|
||||
ChatModel chatModel = new QwenChatModel(config);
|
||||
chatModel.chatStream("请写一个小兔子战胜大灰狼的故事", (context, response) -> {
|
||||
AiMessage message = response.getMessage();
|
||||
System.err.println(message.getReasoningContent());
|
||||
System.out.println(message.getFullContent());
|
||||
System.out.println();
|
||||
}, options);
|
||||
Thread.sleep(10000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试千问3 开启思考模式的开关
|
||||
*/
|
||||
@Test
|
||||
public void testQwen3Thinking() throws InterruptedException {
|
||||
QwenChatConfig config = new QwenChatConfig();
|
||||
config.setApiKey("sk-28a6be3236****");
|
||||
config.setModel("qwen3-235b-a22b");
|
||||
|
||||
ChatModel chatModel = new QwenChatModel(config);
|
||||
QwenChatOptions options = new QwenChatOptions();
|
||||
options.setThinkingEnabled(false);
|
||||
//options.setThinkingBudget(1024);
|
||||
|
||||
chatModel.chatStream("你是谁", (context, response) -> {
|
||||
AiMessage message = response.getMessage();
|
||||
System.err.println(message.getReasoningContent());
|
||||
System.out.println(message.getFullContent());
|
||||
System.out.println();
|
||||
}, options);
|
||||
Thread.sleep(10000);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.easyagents.llm.qwen.test;
|
||||
|
||||
import com.easyagents.core.model.chat.tool.annotation.ToolDef;
|
||||
import com.easyagents.core.model.chat.tool.annotation.ToolParam;
|
||||
|
||||
public class WeatherFunctions {
|
||||
|
||||
@ToolDef(name = "get_the_weather_info", description = "get the weather info")
|
||||
public static String getWeatherInfo(
|
||||
@ToolParam(name = "city", description = "the city name") String name
|
||||
) {
|
||||
return "Today it will be dull and overcast in " + name;
|
||||
}
|
||||
}
|
||||
29
easy-agents-chat/pom.xml
Normal file
29
easy-agents-chat/pom.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.easyagents</groupId>
|
||||
<artifactId>easy-agents-parent</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<name>easy-agents-chat</name>
|
||||
<artifactId>easy-agents-chat</artifactId>
|
||||
|
||||
<packaging>pom</packaging>
|
||||
<modules>
|
||||
<module>easy-agents-chat-openai</module>
|
||||
<module>easy-agents-chat-qwen</module>
|
||||
<module>easy-agents-chat-ollama</module>
|
||||
<module>easy-agents-chat-deepseek</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
Reference in New Issue
Block a user