diff --git a/pom.xml b/pom.xml index 05cd2b8..46ae2c2 100644 --- a/pom.xml +++ b/pom.xml @@ -20,15 +20,6 @@ - - - - - - - - long-image-search-mcp-server - 21 diff --git a/src/main/java/com/huangge1199/aiagent/agent/BaseAgent.java b/src/main/java/com/huangge1199/aiagent/agent/BaseAgent.java new file mode 100644 index 0000000..1d637d4 --- /dev/null +++ b/src/main/java/com/huangge1199/aiagent/agent/BaseAgent.java @@ -0,0 +1,108 @@ +package com.huangge1199.aiagent.agent; + +import cn.hutool.core.util.StrUtil; +import com.huangge1199.aiagent.agent.model.AgentState; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.UserMessage; + +import java.util.ArrayList; +import java.util.List; + +/** + * BaseAgent + *

+ * 抽象基础代理类,用于管理代理状态和执行流程。 + *

+ * 提供状态转换、内存管理和基于步骤的执行循环的基础功能。 + * 子类必须实现step方法。 + * + * @author huangge1199 + * @since 2025/6/4 17:01:45 + */ +@Data +@Slf4j +public abstract class BaseAgent { + + // 核心属性 + private String name; + + // 提示 + private String systemPrompt; + private String nextStepPrompt; + + // 状态 + private AgentState state = AgentState.IDLE; + + // 执行控制 + private int maxSteps = 10; + private int currentStep = 0; + + // LLM + private ChatClient chatClient; + + // Memory(需要自主维护会话上下文) + private List messageList = new ArrayList<>(); + + /** + * 运行代理 + * + * @param userPrompt 用户提示词 + * @return 执行结果 + */ + public String run(String userPrompt) { + if (this.state != AgentState.IDLE) { + throw new RuntimeException("Cannot run agent from state: " + this.state); + } + if (StrUtil.isBlank(userPrompt)) { + throw new RuntimeException("Cannot run agent with empty user prompt"); + } + // 更改状态 + state = AgentState.RUNNING; + // 记录消息上下文 + messageList.add(new UserMessage(userPrompt)); + // 保存结果列表 + List results = new ArrayList<>(); + try { + for (int i = 0; i < maxSteps && state != AgentState.FINISHED; i++) { + int stepNumber = i + 1; + currentStep = stepNumber; + log.info("Executing step {}/{}", stepNumber, maxSteps); + // 单步执行 + String stepResult = step(); + String result = "Step " + stepNumber + ": " + stepResult; + results.add(result); + } + // 检查是否超出步骤限制 + if (currentStep >= maxSteps) { + state = AgentState.FINISHED; + results.add("Terminated: Reached max steps (" + maxSteps + ")"); + } + return String.join("\n", results); + } catch (Exception e) { + state = AgentState.ERROR; + log.error("Error executing agent", e); + return "执行错误" + e.getMessage(); + } finally { + // 清理资源 + this.cleanup(); + } + } + + /** + * 执行单个步骤 + * + * @return 步骤执行结果 + */ + public abstract String step(); + + /** + * 清理资源 + */ + protected void cleanup() { + // 子类可以重写此方法来清理资源 + } +} + diff --git a/src/main/java/com/huangge1199/aiagent/agent/LongManus.java b/src/main/java/com/huangge1199/aiagent/agent/LongManus.java new file mode 100644 index 0000000..74fff57 --- /dev/null +++ b/src/main/java/com/huangge1199/aiagent/agent/LongManus.java @@ -0,0 +1,41 @@ +package com.huangge1199.aiagent.agent; + +import com.huangge1199.aiagent.config.MyLoggerAdvisor; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.stereotype.Component; + +/** + * LongManus + * AI 超级智能体(拥有自主规划能力,可以直接使用) + * + * @author huangge1199 + * @since 2025/6/4 17:08:52 + */ +@Component +public class LongManus extends ToolCallAgent { + + public LongManus(ToolCallback[] allTools, ChatModel dashscopeChatModel) { + super(allTools); + this.setName("longManus"); + String SYSTEM_PROMPT = """ + You are LongManus, an all-capable AI assistant, aimed at solving any task presented by the user. + You have various tools at your disposal that you can call upon to efficiently complete complex requests. + """; + this.setSystemPrompt(SYSTEM_PROMPT); + String NEXT_STEP_PROMPT = """ + Based on user needs, proactively select the most appropriate tool or combination of tools. + For complex tasks, you can break down the problem and use different tools step by step to solve it. + After using each tool, clearly explain the execution results and suggest the next steps. + If you want to stop the interaction at any point, use the `terminate` tool/function call. + """; + this.setNextStepPrompt(NEXT_STEP_PROMPT); + this.setMaxSteps(5); + // 初始化 AI 对话客户端 + ChatClient chatClient = ChatClient.builder(dashscopeChatModel) + .defaultAdvisors(new MyLoggerAdvisor()) + .build(); + this.setChatClient(chatClient); + } +} diff --git a/src/main/java/com/huangge1199/aiagent/agent/ReActAgent.java b/src/main/java/com/huangge1199/aiagent/agent/ReActAgent.java new file mode 100644 index 0000000..7bc014d --- /dev/null +++ b/src/main/java/com/huangge1199/aiagent/agent/ReActAgent.java @@ -0,0 +1,57 @@ +package com.huangge1199.aiagent.agent; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.extern.slf4j.Slf4j; + +/** + * ReActAgent + * ReAct (Reasoning and Acting) 模式的代理抽象类 + * 实现了思考-行动的循环模式 + * + * @author huangge1199 + * @since 2025/6/4 17:04:56 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@Slf4j +public abstract class ReActAgent extends BaseAgent { + + /** + * 处理当前状态并决定下一步行动 + * + * @return 是否需要执行行动,true表示需要执行,false表示不需要执行 + */ + public abstract boolean think(); + + /** + * 执行决定的行动 + * + * @return 行动执行结果 + */ + public abstract String act(); + + /** + * 执行单个步骤:思考和行动 + * + * @return 步骤执行结果 + */ + @Override + public String step() { + try { + // 先思考 + boolean shouldAct = think(); + if (!shouldAct) { + return "思考完成 - 无需行动"; + } + // 再行动 + return act(); + } catch (Exception e) { + // 记录异常日志 + e.printStackTrace(); + return "步骤执行失败:" + e.getMessage(); + } + } + +} + diff --git a/src/main/java/com/huangge1199/aiagent/agent/ToolCallAgent.java b/src/main/java/com/huangge1199/aiagent/agent/ToolCallAgent.java new file mode 100644 index 0000000..a1bec00 --- /dev/null +++ b/src/main/java/com/huangge1199/aiagent/agent/ToolCallAgent.java @@ -0,0 +1,140 @@ +package com.huangge1199.aiagent.agent; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; +import com.huangge1199.aiagent.agent.model.AgentState; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.chat.messages.AssistantMessage; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.ToolResponseMessage; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.model.tool.ToolCallingManager; +import org.springframework.ai.model.tool.ToolExecutionResult; +import org.springframework.ai.tool.ToolCallback; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * ToolCallAgent + * 处理工具调用的基础代理类,具体实现了 think 和 act 方法,可以用作创建实例的父类 + * + * @author huangge1199 + * @since 2025/6/4 17:05:58 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@Slf4j +public class ToolCallAgent extends ReActAgent { + + // 可用的工具 + private final ToolCallback[] availableTools; + + // 保存工具调用信息的响应结果(要调用那些工具) + private ChatResponse toolCallChatResponse; + + // 工具调用管理者 + private final ToolCallingManager toolCallingManager; + + // 禁用 Spring AI 内置的工具调用机制,自己维护选项和消息上下文 + private final ChatOptions chatOptions; + + public ToolCallAgent(ToolCallback[] availableTools) { + super(); + this.availableTools = availableTools; + this.toolCallingManager = ToolCallingManager.builder().build(); + // 禁用 Spring AI 内置的工具调用机制,自己维护选项和消息上下文 + this.chatOptions = DashScopeChatOptions.builder() + .withProxyToolCalls(true) + .build(); + } + + /** + * 处理当前状态并决定下一步行动 + * + * @return 是否需要执行行动 + */ + @Override + public boolean think() { + // 1、校验提示词,拼接用户提示词 + if (StrUtil.isNotBlank(getNextStepPrompt())) { + UserMessage userMessage = new UserMessage(getNextStepPrompt()); + getMessageList().add(userMessage); + } + // 2、调用 AI 大模型,获取工具调用结果 + List messageList = getMessageList(); + Prompt prompt = new Prompt(messageList, this.chatOptions); + try { + ChatResponse chatResponse = getChatClient().prompt(prompt) + .system(getSystemPrompt()) + .tools(availableTools) + .call() + .chatResponse(); + // 记录响应,用于等下 Act + this.toolCallChatResponse = chatResponse; + // 3、解析工具调用结果,获取要调用的工具 + // 助手消息 + AssistantMessage assistantMessage = chatResponse.getResult().getOutput(); + // 获取要调用的工具列表 + List toolCallList = assistantMessage.getToolCalls(); + // 输出提示信息 + String result = assistantMessage.getText(); + log.info("{}的思考:{}", getName(), result); + log.info("{}选择了 {} 个工具来使用", getName(), toolCallList.size()); + String toolCallInfo = toolCallList.stream() + .map(toolCall -> String.format("工具名称:%s,参数:%s", toolCall.name(), toolCall.arguments())) + .collect(Collectors.joining("\n")); + log.info(toolCallInfo); + // 如果不需要调用工具,返回 false + if (toolCallList.isEmpty()) { + // 只有不调用工具时,才需要手动记录助手消息 + getMessageList().add(assistantMessage); + return false; + } else { + // 需要调用工具时,无需记录助手消息,因为调用工具时会自动记录 + return true; + } + } catch (Exception e) { + log.error("{}的思考过程遇到了问题:{}", getName(), e.getMessage()); + getMessageList().add(new AssistantMessage("处理时遇到了错误:" + e.getMessage())); + return false; + } + } + + /** + * 执行工具调用并处理结果 + * + * @return 执行结果 + */ + @Override + public String act() { + if (!toolCallChatResponse.hasToolCalls()) { + return "没有工具需要调用"; + } + // 调用工具 + Prompt prompt = new Prompt(getMessageList(), this.chatOptions); + ToolExecutionResult toolExecutionResult = toolCallingManager.executeToolCalls(prompt, toolCallChatResponse); + // 记录消息上下文,conversationHistory 已经包含了助手消息和工具调用返回的结果 + setMessageList(toolExecutionResult.conversationHistory()); + ToolResponseMessage toolResponseMessage = (ToolResponseMessage) CollUtil.getLast(toolExecutionResult.conversationHistory()); + // 判断是否调用了终止工具 + boolean terminateToolCalled = toolResponseMessage.getResponses().stream() + .anyMatch(response -> "doTerminate".equals(response.name())); + if (terminateToolCalled) { + // 任务结束,更改状态 + setState(AgentState.FINISHED); + } + String results = toolResponseMessage.getResponses().stream() + .map(response -> "工具 " + response.name() + " 返回的结果:" + response.responseData()) + .collect(Collectors.joining("\n")); + log.info(results); + return results; + } +} + diff --git a/src/main/java/com/huangge1199/aiagent/agent/model/AgentState.java b/src/main/java/com/huangge1199/aiagent/agent/model/AgentState.java new file mode 100644 index 0000000..dda3e8a --- /dev/null +++ b/src/main/java/com/huangge1199/aiagent/agent/model/AgentState.java @@ -0,0 +1,33 @@ +package com.huangge1199.aiagent.agent.model; + +/** + * AgentState + * + * @author huangge1199 + * @since 2025/6/4 17:00:54 + */ +/** + * 代理执行状态的枚举类 + */ +public enum AgentState { + + /** + * 空闲状态 + */ + IDLE, + + /** + * 运行中状态 + */ + RUNNING, + + /** + * 已完成状态 + */ + FINISHED, + + /** + * 错误状态 + */ + ERROR +} diff --git a/src/main/java/com/huangge1199/aiagent/config/ChatModelConfig.java b/src/main/java/com/huangge1199/aiagent/config/ChatModelConfig.java index 1ac5d5a..351c2ea 100644 --- a/src/main/java/com/huangge1199/aiagent/config/ChatModelConfig.java +++ b/src/main/java/com/huangge1199/aiagent/config/ChatModelConfig.java @@ -1,5 +1,6 @@ package com.huangge1199.aiagent.config; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.ollama.OllamaChatModel; import org.springframework.context.annotation.Bean; @@ -16,16 +17,16 @@ import org.springframework.context.annotation.Primary; public class ChatModelConfig { // 选择DashScope作为默认模型 -// @Bean -// @Primary -// public ChatModel primaryChatModel(DashScopeChatModel dashscopeChatModel) { -// return dashscopeChatModel; -// } + @Bean + @Primary + public ChatModel primaryChatModel(DashScopeChatModel dashscopeChatModel) { + return dashscopeChatModel; + } - // 或者选择Ollama: - @Bean - @Primary - public ChatModel primaryChatModel(OllamaChatModel ollamaChatModel) { - return ollamaChatModel; - } +// // 或者选择Ollama: +// @Bean +// @Primary +// public ChatModel primaryChatModel(OllamaChatModel ollamaChatModel) { +// return ollamaChatModel; +// } } diff --git a/src/main/java/com/huangge1199/aiagent/controller/ResController.java b/src/main/java/com/huangge1199/aiagent/controller/ResController.java index 122d2cf..ff220ea 100644 --- a/src/main/java/com/huangge1199/aiagent/controller/ResController.java +++ b/src/main/java/com/huangge1199/aiagent/controller/ResController.java @@ -1,5 +1,6 @@ package com.huangge1199.aiagent.controller; +import com.huangge1199.aiagent.agent.LongManus; import com.huangge1199.aiagent.common.R; import com.huangge1199.aiagent.config.MyLoggerAdvisor; import io.swagger.v3.oas.annotations.Operation; @@ -42,6 +43,9 @@ public class ResController { @Resource private ToolCallbackProvider toolCallbackProvider; + @Resource + private LongManus longManus; + @PostMapping("/chatRes") @Operation(summary = "返回 ChatResponse") public R chatResRes(@RequestBody String question) { @@ -108,4 +112,11 @@ public class ResController { String content = chatResponse.getResult().getOutput().getText(); return R.ok(content); } + + @PostMapping("/manus") + @Operation(summary = "智能体测试") + public R manus(@RequestBody String question) { + String content = longManus.run(question); + return R.ok(content); + } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 315066d..6ed6ce3 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -9,7 +9,7 @@ spring: options: model: qwen-plus ollama: - base-url: http://192.168.188.2:11435 + base-url: http://192.168.188.2:11434 chat: model: llama3.2:3b vectorstore: