当前位置: 首页 > news >正文

Ai Agent 项目

这是一个基于 Spring AI 和 LangChain4j 开发的 AI agent项目, 涉及  Embeddings(文本向量化),MCP协议 ,ReAct (思考->行动),查询增强与查询重写(Query Augmentation / Rewriting),文档预处理与分块(Chunking / Tokenization),向量数据库与检索(Vector Store / RAG 基础) ,Prompt 工程。本项目参考鱼皮大哥的思想

Spring Ai 相关


Spring AI 的核心抽象

EmbeddingModel:embedding 生成器(LoveAppVectorStoreConfig 的 dashscopeEmbeddingModel)。
Chat/Completion Model:对话/生成模型抽象(项目 demo 中的多种 AiInvoke 实现)。
Document:带元数据的文本单元(用于向量化与检索)。
VectorStore:统一向量存取接口(SimpleVectorStore、PgVector 实现)。


RAG 集成模式

Spring AI 支持将检索结果作为 context 注入到 prompt 中的模式。


Agent / Tool 调用支持
Spring AI 可配合 LangChain-like agent 逻辑,封装工具调用。
在项目中:Agent 抽象(BaseAgent、ReActAgent、ToolCallAgent),工具注册(ToolRegistration)。

对话记忆(Memory)与持久化
Spring AI 提供对话记忆模式,项目用 FileBasedChatMemory 实现持久化。

Advisor / 拦截器模式
用于在调用链中注入额外逻辑(日志、重读、上下文改写)。
在项目中:MyLoggerAdvisor、ReReadingAdvisor、LoveAppRagCustomAdvisorFactory。
学习建议:掌握如何在请求前/后增强或变换 prompt 与响应。
Streaming / SSE 支持

PgVectorVectorStoreConfig 体现数据库后端实现,包含索引与查询实现细节。
学习建议:了解向量索引在 PostgreSQL 中的实现与限制(index 类型、扩展)。

代码

Agent部分

BaseAgent.java


/*** 抽象基础代理类,用于管理代理状态和执行流程。* <p>* 提供状态转换、内存管理和基于步骤的执行循环的基础功能。* 子类必须实现step方法。*/
@Data
@Slf4jpublic abstract class BaseAgent {// 核心属性private String name;// 提示词private String systemPrompt;private String nextStepPrompt;// 代理状态private AgentState state = AgentState.IDLE;// 执行步骤控制private int currentStep = 0;private int maxSteps = 10;// LLM 大模型private ChatClient chatClient;// Memory 记忆(需要自主维护会话上下文)private List<Message> messageList = new ArrayList<>();/*** 运行代理** @param userPrompt 用户提示词* @return 执行结果*/public String run(String userPrompt) {// 1、基础校验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");}// 2、执行,更改状态this.state = AgentState.RUNNING;// 记录消息上下文messageList.add(new UserMessage(userPrompt));// 保存结果列表List<String> 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 {// 3、清理资源this.cleanup();}}/*** 运行代理(流式输出)** @param userPrompt 用户提示词* @return 执行结果*/public SseEmitter runStream(String userPrompt) {// 创建一个超时时间较长的 SseEmitterSseEmitter sseEmitter = new SseEmitter(300000L); // 5 分钟超时// 使用线程异步处理,避免阻塞主线程CompletableFuture.runAsync(() -> {// 1、基础校验try {if (this.state != AgentState.IDLE) {sseEmitter.send("错误:无法从状态运行代理:" + this.state);sseEmitter.complete();return;}if (StrUtil.isBlank(userPrompt)) {sseEmitter.send("错误:不能使用空提示词运行代理");sseEmitter.complete();return;}} catch (Exception e) {sseEmitter.completeWithError(e);}// 2、执行,更改状态this.state = AgentState.RUNNING;// 记录消息上下文messageList.add(new UserMessage(userPrompt));// 保存结果列表List<String> 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);// 输出当前每一步的结果到 SSEsseEmitter.send(result);}// 检查是否超出步骤限制if (currentStep >= maxSteps) {state = AgentState.FINISHED;results.add("Terminated: Reached max steps (" + maxSteps + ")");sseEmitter.send("执行结束:达到最大步骤(" + maxSteps + ")");}// 正常完成sseEmitter.complete();} catch (Exception e) {state = AgentState.ERROR;log.error("error executing agent", e);try {sseEmitter.send("执行错误:" + e.getMessage());sseEmitter.complete();} catch (IOException ex) {sseEmitter.completeWithError(ex);}} finally {// 3、清理资源this.cleanup();}});// 设置超时回调sseEmitter.onTimeout(() -> {this.state = AgentState.ERROR;this.cleanup();log.warn("SSE connection timeout");});// 设置完成回调sseEmitter.onCompletion(() -> {if (this.state == AgentState.RUNNING) {this.state = AgentState.FINISHED;}this.cleanup();log.info("SSE connection completed");});return sseEmitter;}/*** 定义单个步骤** @return*/public abstract String step();/*** 清理资源*/protected void cleanup() {// 子类可以重写此方法来清理资源}
}

BaseAgent 是所有智能体(Agent)的基础父类,定义了智能体的核心属性、状态管理、执行流程和生命周期。

子类(如 ReActAgent、ToolCallAgent)会继承它,并实现自己的推理/执行逻辑。


2. 主要成员变量
  • name:智能体名称。
  • systemPrompt、nextStepPrompt:系统提示词和下一步提示词(用于大模型推理)。
  • state:当前智能体状态(枚举类型 AgentState,如IDLE、RUNNING、FINISHED、ERROR)。
  • currentStep、maxSteps:当前执行到第几步、最多允许多少步(防止死循环)。
  • chatClient:大模型客户端(如Spring AI的ChatClient)。
  • messageList:消息上下文(保存对话历史,便于多轮推理)。

3. 核心方法
3.1 run(String userPrompt)
  • 入口方法,同步执行智能体任务。
  • 步骤:
  1. 校验当前状态和用户输入。
  2. 状态切换为RUNNING,记录用户消息。
  3. 循环执行 step() 方法(由子类实现),每次为一步推理。
  4. 达到最大步数或子类将状态设为FINISHED时结束。
  5. 捕获异常,切换为ERROR状态。
  6. 最后调用 cleanup() 清理资源。
3.2 runStream(String userPrompt)
  • 支持流式输出(Server-Sent Events,SSE),适合前端实时展示推理过程。
  • 步骤与 run 类似,但每一步结果都会通过SSE实时推送给前端。
  • 异步执行,防止阻塞主线程。
  • 包含超时和完成回调,保证资源释放。
3.3 step()
  • 抽象方法,必须由子类实现。
  • 定义了“单步”推理/执行的具体逻辑。
  • 每次循环都会调用一次,直到达到终止条件。
3.4 cleanup()
  • 钩子方法,子类可重写用于资源释放、状态重置等。

4. 设计
  • 状态机思想:通过 AgentState 管理生命周期,防止并发/重复执行。
  • 多步推理:支持多轮step,便于实现复杂的推理链(如ReAct、Tool-Use等)。
  • 上下文记忆:messageList 记录历史消息,支持多轮对话和上下文感知。
  • 流式输出:便于前端实时展示AI推理过程,提升用户体验。
  • 高可扩展性:子类只需实现 step(),即可自定义任意智能体逻辑。

ReActAgent.java


/*** ReAct (Reasoning and Acting) 模式的代理抽象类* 实现了思考-行动的循环模式*/
@EqualsAndHashCode(callSuper = true)
@Data
@Slf4j
public abstract class ReActAgent extends BaseAgent {/*** 处理当前状态并决定下一步行动** @return 是否需要执行行动,true表示需要执行,false表示不需要执行*/public abstract boolean think();/*** 执行决定的行动** @return 行动执行结果*/public abstract String act();/*** 执行单个步骤:思考和行动** @return 步骤执行结果*/@Overridepublic String step() {try {// 先思考boolean shouldAct = think();if (!shouldAct) {return "思考完成 - 无需行动";}// 再行动return act();} catch (Exception e) {// 记录异常日志e.printStackTrace();return "步骤执行失败:" + e.getMessage();}}}

1. 作用与定位
  • 定位: ReActAgent 是基于 ReAct(Reason + Act)范式的抽象智能体,继承 BaseAgent,将每个 step() 拆分为“先思考,再行动”的固定流程。
  • 职责: 约束子类必须提供“思考”与“行动”的实现,让 BaseAgent 的多步循环具备清晰的推理-执行结构。
2. 主要成员(继承 + 注解)
  • 无新增字段:全部字段来自 BaseAgent(如 state、currentStep、messageList 等)。
  • 注解:
  • @EqualsAndHashCode(callSuper = true):包含父类字段进行相等性与哈希计算。
  • @Data:Lombok 生成 getter/setter 等。
  • @Slf4j:日志记录。
3. 核心方法
  • public abstract boolean think():思考阶段,产出“是否需要行动”的布尔决策。
  • public abstract String act():执行阶段,完成具体行动并返回文本结果。
  • @Override public String step():封装单步逻辑,先 think(),若不需行动返回“思考完成 - 无需行动”;否则执行 act()。异常会捕获并返回“步骤执行失败:{错误信息}”。

ToolCAllAgent.java


/*** 处理工具调用的基础代理类,具体实现了 think 和 act 方法,可以用作创建实例的父类*/
@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().withInternalToolExecutionEnabled(false).build();}/*** 处理当前状态并决定下一步行动** @return 是否需要执行行动*/@Overridepublic boolean think() {// 1、校验提示词,拼接用户提示词if (StrUtil.isNotBlank(getNextStepPrompt())) {UserMessage userMessage = new UserMessage(getNextStepPrompt());getMessageList().add(userMessage);}// 2、调用 AI 大模型,获取工具调用结果List<Message> messageList = getMessageList();Prompt prompt = new Prompt(messageList, this.chatOptions);try {ChatResponse chatResponse = getChatClient().prompt(prompt).system(getSystemPrompt()).tools(availableTools).call().chatResponse();// 记录响应,用于等下 Actthis.toolCallChatResponse = chatResponse;// 3、解析工具调用结果,获取要调用的工具// 助手消息AssistantMessage assistantMessage = chatResponse.getResult().getOutput();// 获取要调用的工具列表List<AssistantMessage.ToolCall> 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);// 如果不需要调用工具,返回 falseif (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 执行结果*/@Overridepublic 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 -> response.name().equals("doTerminate"));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;}
}

1.作用与定位
  • 定位: ToolCallAgent 基于 ReAct 范式的“工具调用型智能体”,继承自 ReActAgent,实现了 think() 和 act(),可直接实例化使用。
  • 职责: 在“思考”阶段调用大模型获取工具调用计划;在“行动”阶段通过工具管理器执行工具并处理结果,必要时结束任务。

2. 主要成员(字段与依赖)
  • availableTools: 可用工具列表(ToolCallback[]),供模型选择调用。
  • toolCallChatResponse: 保存大模型返回的包含工具调用计划的响应。
  • toolCallingManager: 工具调用管理器(ToolCallingManager),负责根据模型计划实际执行工具。
  • chatOptions: DashScopeChatOptions,显式设置 withInternalToolExecutionEnabled(false),禁用 Spring AI 的内置自动工具执行,改为手动控制消息与调用。
  • 其余通用属性继承自 BaseAgent(如 state、messageList、chatClient 等)。
3. 核心方法
1.think()    (决定是否需要执行行动)
  • 若 nextStepPrompt 非空,先以 UserMessage 追加到上下文。
  • 构造 Prompt(messageList, chatOptions),通过 getChatClient().prompt(...).system(getSystemPrompt()).tools(availableTools).call().chatResponse() 调用模型。
  • 解析 AssistantMessage 的工具调用列表:
  • 若为空:将该助手消息追加进上下文,返回 false(无需行动)。
  • 若非空:返回 true(进入行动阶段),并将响应保存到 toolCallChatResponse,不立即写入助手消息(由工具执行流程统一记录)。
  • 异常:记录错误,并把错误信息作为助手消息追加,返回 false。

2.act()     (执行工具调用并处理结果)
  • 若 toolCallChatResponse 无工具调用,直接返回“没有工具需要调用”。
  • 使用 toolCallingManager.executeToolCalls(prompt, toolCallChatResponse) 执行工具;将返回的 conversationHistory 覆盖回消息上下文。
  • 取最后一条 ToolResponseMessage,汇总各工具的 responseData() 作为本步结果。
  • 若检测到名为 doTerminate 的终止工具,则将 state 置为 FINISHED。
4.设计亮点
  • 外部化工具编排:禁用内置自动工具执行,手动管理 Prompt、会话历史与执行流程,可控性强、便于调试与扩展。
  • ReAct 明确分层:think() 专注“是否调用工具”的决策,act() 专注“如何调用和汇总结果”。
  • 终止协议内置:检测 doTerminate 工具即结束状态,便于在工具层关闭任务。
  • 上下文完整闭环:工具调用后的会话历史统一回写到 messageList,保证对话记忆连续性。
  • 已完成:读取并解析 ToolCallAgent.java,按 1-4 点给出职责、字段、方法与亮点,并引用关键实现片段。

ShanDianManus.java


/*** 闪电的 AI 超级智能体(拥有自主规划能力,可以直接使用)*/
@Component
public class ShandianManus extends ToolCallAgent {public ShandianManus(ToolCallback[] allTools, ChatModel dashscopeChatModel) {super(allTools);this.setName("shandianManus");String SYSTEM_PROMPT = """You are shandianManus, 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(20);// 初始化 AI 对话客户端ChatClient chatClient = ChatClient.builder(dashscopeChatModel).defaultAdvisors(new MyLoggerAdvisor()).build();this.setChatClient(chatClient);}
}

1. 作用与定位
  • 定位: shandianManus 是一个可直接使用的“超级智能体”,继承自 ToolCallAgent,具备通用的任务解决与工具编排能力。
  • 场景: 作为项目内默认的全能型 Agent,结合系统提示词与步骤提示词,自动选择并调用工具完成复杂任务。
2. 主要成员(字段与依赖)
  • 无新增字段:全部属性来自 BaseAgent / ReActAgent / ToolCallAgent。
  • 依赖注入:
  • 构造参数:ToolCallback[] allTools(工具集合)、ChatModel dashscopeChatModel(大模型实现)。
  • 使用 ChatClient.builder(dashscopeChatModel) 构造对话客户端,并配置 MyLoggerAdvisor 作为默认 Advisor。
  • 关键配置(在构造器中完成):
  • name: 设为 shandianManus
  • systemPrompt: 角色定位为“全能 AI 助手”
  • nextStepPrompt: 指导“按需选择/组合工具、复杂任务分解、每步说明结果与建议、需要时可终止”
  • maxSteps: 设为 20(支持更长的多步推理)
3. 核心方法
  • 构造器:完成全部初始化与配置。
  • 传入工具与模型 → 父类注册工具 → 设定名称/提示词/步数 → 构建 ChatClient 并注入日志 Advisor → 挂载到 Agent。
4. 设计
  • 即插即用:通过构造器集中注入工具与模型,开箱即用的通用 Agent。
  • 强指令工程:systemPrompt + nextStepPrompt 明确角色与执行策略,鼓励拆解问题与多工具协作。
  • 长链路任务支持:步数上限 20,适配复杂流程。
  • 可观测性:默认添加 MyLoggerAdvisor,便于对话与工具调用过程的记录与排查。
  • 与 ToolCallAgent 协同:沿用其 ReAct 分层与手动工具编排模式,增强可控性。

tools部分

toolRigistration.java

/*** 集中的工具注册类*/
@Configuration
public class ToolRegistration {@Value("${search-api.api-key}")private String searchApiKey;@Beanpublic ToolCallback[] allTools() {FileOperationTool fileOperationTool = new FileOperationTool();WebSearchTool webSearchTool = new WebSearchTool(searchApiKey);WebScrapingTool webScrapingTool = new WebScrapingTool();ResourceDownloadTool resourceDownloadTool = new ResourceDownloadTool();TerminalOperationTool terminalOperationTool = new TerminalOperationTool();PDFGenerationTool pdfGenerationTool = new PDFGenerationTool();TerminateTool terminateTool = new TerminateTool();return ToolCallbacks.from(fileOperationTool,     //读写本地文件webSearchTool,         //调用 SearchAPI 做百度搜索,返回前 5 条自然结果(JSON 字符串拼接)。webScrapingTool,       //抓取网页并返回 HTML。resourceDownloadTool,  //从 URL 下载资源到本地terminalOperationTool, //在系统终端执行命令并返回输出pdfGenerationTool,     //把给定文本生成 PDF 文件terminateTool          //终止任务(被上层识别为“结束交互”的信号));}
}
  • 作用与定位: 集中注册所有工具,向上游提供 ToolCallback[] 供智能体选择与调用。
  • 主要成员变量:     searchApiKey:从配置读取的 SearchAPI 密钥(search-api.api-key)。
  • 核心方法:allTools():实例化各工具并通过 ToolCallbacks.from(...) 汇总为 ToolCallback[] Bean
  • 设计:统一装配与可插拔扩展;外部化 apiKey;与 IOC 集成简洁。

FileOperationTool.java


/*** 文件操作工具类(提供文件读写功能)*/
public class FileOperationTool {private final String FILE_DIR = FileConstant.FILE_SAVE_DIR + "/file";@Tool(description = "Read content from a file")public String readFile(@ToolParam(description = "Name of a file to read") String fileName) {String filePath = FILE_DIR + "/" + fileName;try {return FileUtil.readUtf8String(filePath);} catch (Exception e) {return "Error reading file: " + e.getMessage();}}@Tool(description = "Write content to a file")public String writeFile(@ToolParam(description = "Name of the file to write") String fileName,@ToolParam(description = "Content to write to the file") String content) {String filePath = FILE_DIR + "/" + fileName;try {// 创建目录FileUtil.mkdir(FILE_DIR);FileUtil.writeUtf8String(content, filePath);return "File written successfully to: " + filePath;} catch (Exception e) {return "Error writing to file: " + e.getMessage();}}
}
  • 作用与定位: 文件读写工具,支持把内容读/写到项目指定目录。
  • 主要成员变量:    FILE_DIR  :文件根目录,FileConstant.FILE_SAVE_DIR + "/file"。
  • 核心方法:   readFile(fileName) :读取 UTF-8 内容。
  • writeFile(fileName, content) :确保目录存在后写入内容。
  • 设计:目录前置创建;异常 → 友好文案;与 Agent 结合可做临时持久化/中间结果保存。

pdfGenerationtool.java


/*** PDF 生成工具*/
public class PDFGenerationTool {@Tool(description = "Generate a PDF file with given content", returnDirect = false)public String generatePDF(@ToolParam(description = "Name of the file to save the generated PDF") String fileName,@ToolParam(description = "Content to be included in the PDF") String content) {String fileDir = FileConstant.FILE_SAVE_DIR + "/pdf";String filePath = fileDir + "/" + fileName;try {// 创建目录FileUtil.mkdir(fileDir);// 创建 PdfWriter 和 PdfDocument 对象try (PdfWriter writer = new PdfWriter(filePath);PdfDocument pdf = new PdfDocument(writer);Document document = new Document(pdf)) {// 自定义字体(需要人工下载字体文件到特定目录)
//                String fontPath = Paths.get("src/main/resources/static/fonts/simsun.ttf")
//                        .toAbsolutePath().toString();
//                PdfFont font = PdfFontFactory.createFont(fontPath,
//                        PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED);// 使用内置中文字体PdfFont font = PdfFontFactory.createFont("STSongStd-Light", "UniGB-UCS2-H");document.setFont(font);// 创建段落Paragraph paragraph = new Paragraph(content);// 添加段落并关闭文档document.add(paragraph);}return "PDF generated successfully to: " + filePath;} catch (IOException e) {return "Error generating PDF: " + e.getMessage();}}
}

TerminalOperationTool

/*** 终端操作工具*/
public class TerminalOperationTool {@Tool(description = "Execute a command in the terminal")public String executeTerminalCommand(@ToolParam(description = "Command to execute in the terminal") String command) {StringBuilder output = new StringBuilder();   //用来收集子进程的标准输出(stdout)文本,最后拼成一个字符串返回。try {ProcessBuilder builder = new ProcessBuilder("cmd.exe", "/c", command); //构造子进程的“启动配置”。
//            Process process = Runtime.getRuntime().exec(command);Process process = builder.start();//代表已经启动的“操作系统子进程”。你可以通过它拿到:
//标准输出流:process.getInputStream()(被当作“输入流”是站在父进程角度来看)
//标准错误流:process.getErrorStream()
//标准输入流:process.getOutputStream()(对需要交互的命令,可以往里写)
//还可以调用 waitFor() 等待它结束,destroy() 结束它等。try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {String line;while ((line = reader.readLine()) != null) {output.append(line).append("\n");}}int exitCode = process.waitFor();if (exitCode != 0) {output.append("Command execution failed with exit code: ").append(exitCode);}} catch (IOException | InterruptedException e) {output.append("Error executing command: ").append(e.getMessage());}return output.toString();}
}

是一个“让智能体能执行系统命令”的工具类。它把“在操作系统终端执行命令”的能力,封装为可被大模型调用的工具方法,供 ToolCallAgent 通过工具调用机制使用。

  • 使用 ProcessBuilder("cmd.exe", "/c", command) 启动一个 Windows 终端执行指定命令。
  • 持续读取子进程的标准输出,拼成字符串。
  • 调用 waitFor() 等待命令执行完成,检查退出码,若非 0 附加失败提示。
  • 捕获异常并返回错误信息。
  • 最终返回标准输出(附带可能的失败/错误描述)。

是用“进程”在执行命令的。

  • ProcessBuilder builder = new ProcessBuilder("cmd.exe", "/c", command); 会让操作系统启动一个子进程:cmd.exe,并让它执行 /c command。
  • Process process = builder.start(); 真正启动了这个“操作系统子进程”,返回的就是子进程句柄(java.lang.Process 实例)。
  • process.getInputStream() / getErrorStream() 是这个“子进程”的标准输出/标准错误;waitFor() 等的是“子进程退出”。

webSearchTool.java


/*** 网页搜索工具*/
public class WebSearchTool {// SearchAPI 的搜索接口地址private static final String SEARCH_API_URL = "https://www.searchapi.io/api/v1/search";private final String apiKey;public WebSearchTool(String apiKey) {this.apiKey = apiKey;}@Tool(description = "Search for information from Baidu Search Engine")public String searchWeb(@ToolParam(description = "Search query keyword") String query) {Map<String, Object> paramMap = new HashMap<>();paramMap.put("q", query);paramMap.put("api_key", apiKey);paramMap.put("engine", "baidu");try {String response = HttpUtil.get(SEARCH_API_URL, paramMap);// 取出返回结果的前 5 条JSONObject jsonObject = JSONUtil.parseObj(response);// 提取 organic_results 部分JSONArray organicResults = jsonObject.getJSONArray("organic_results");List<Object> objects = organicResults.subList(0, 5);// 拼接搜索结果为字符串String result = objects.stream().map(obj -> {JSONObject tmpJSONObject = (JSONObject) obj;return tmpJSONObject.toString();}).collect(Collectors.joining(","));return result;} catch (Exception e) {return "Error searching Baidu: " + e.getMessage();}}
}

  • 参数准备: 组装请求参数 q(查询词)、api_key(密钥)、engine=baidu(搜索引擎)。
  • HTTP 请求: 使用 Hutool HttpUtil.get(SEARCH_API_URL, paramMap) 发送 GET 请求,自动拼接查询参数。
  • JSON 解析: 使用 JSONUtil.parseObj 解析响应,取 organic_results 数组;截取前 5 条并转为字符串(每条是一个 JSON 对象的字符串),用逗号连接。
  • 返回值: 成功时返回“前 5 条自然结果的 JSON 字符串拼接”;异常时返回 "Error searching Baidu: ..."。

与系统的关系

  • 在 ToolRegistration.allTools() 中被实例化并注册到工具集合,最终由 ToolCallAgent 在 think() 中通过 .tools(availableTools) 暴露给大模型规划使用;act() 中由 ToolCallingManager 真实执行。

Rag 部分

DocumentLoader.java


/*** 应用文档加载器*/
@Component
@Slf4j
public class DocumentLoader {private final ResourcePatternResolver resourcePatternResolver;public LoveAppDocumentLoader(ResourcePatternResolver resourcePatternResolver) {this.resourcePatternResolver = resourcePatternResolver;}/*** 加载多篇 Markdown 文档* @return*/public List<Document> loadMarkdowns() {List<Document> allDocuments = new ArrayList<>();try {Resource[] resources = resourcePatternResolver.getResources("classpath:document/*.md");for (Resource resource : resources) {String filename = resource.getFilename();// 提取文档倒数第 3 和第 2 个字作为标签String status = filename.substring(filename.length() - 6, filename.length() - 4);MarkdownDocumentReaderConfig config = MarkdownDocumentReaderConfig.builder().withHorizontalRuleCreateDocument(true).withIncludeCodeBlock(false).withIncludeBlockquote(false).withAdditionalMetadata("filename", filename).withAdditionalMetadata("status", status).build();MarkdownDocumentReader markdownDocumentReader = new MarkdownDocumentReader(resource, config);allDocuments.addAll(markdownDocumentReader.get());}} catch (IOException e) {log.error("Markdown 文档加载失败", e);}return allDocuments;}
}

该类负责从类路径下的 document/*.md 加载多篇 Markdown 文档,解析为 Document 对象,并在每个文档的 metadata 中补充 filename 和 status,返回所有文档的列表供后续向量化/检索使用。

  • 构建 MarkdownDocumentReaderConfig 并设置:
  • withHorizontalRuleCreateDocument(true):以水平线分隔创建新文档。
  • withIncludeCodeBlock(false)、withIncludeBlockquote(false):不包含代码块和引用内容。
  • withAdditionalMetadata("filename", filename)、withAdditionalMetadata("status", status):为读取到的 Document 增加额外元信息。
  • 用 MarkdownDocumentReader 解析 resource,并把解析得到的 Document 列表 addAll 到 allDocuments。
  • 捕获 IOException 并通过 log.error(...) 记录错误。
  • 返回 allDocuments(即所有解析并带元信息的文档)。

MyTokenTextSplitter.java

/*** 自定义基于 Token 的切词器*/
@Component
class MyTokenTextSplitter {public List<Document> splitDocuments(List<Document> documents) {TokenTextSplitter splitter = new TokenTextSplitter();return splitter.apply(documents);}public List<Document> splitCustomized(List<Document> documents) {TokenTextSplitter splitter = new TokenTextSplitter(200, 100, 10, 5000, true);return splitter.apply(documents);}
}
  • 作用:提供基于 token 的文本切分工具,把长文本 Document 切成适合检索/向量化的小片段。
  • 参数含义(按顺序)
    defaultChunkSize = 200:目标每片文本的 token 数(以模型 tokenizer 的 token 为单位),会尽量把文本按此大小切分。
    minChunkSizeChars = 100:在按 token 切分之后,尝试在距离当前断点至少此字符数后寻找合适的断句/断行位置,以避免在句子中间随意截断(单位为字符)。minChunkLengthToEmbed = 10:最终接收并用于向量化/存储的最小块长度(字符数),比这个短的块会被丢弃。
    maxNumChunks = 5000:对单个输入文本允许生成的最大块数量,超过则停止生成更多块(防止爆炸性切分)。
    keepSeparator = true:是否在分片中保留分隔符(如换行、段落分隔符);true 会保留这些分隔符,false 会去掉。


    大致工作流程
  1. 用模型对应的 tokenizer 将文本编码为 token 序列。
  2. 根据 defaultChunkSize 拆成若干 token 段。
  3. 对每个 token 段解码回文本,然后尝试在满足 minChunkSizeChars 后的位置寻找合适断点(优先句号、问号、换行等自然断点)进行截断。
  4. 根据 keepSeparator 决定是否保留断点处的分隔符。
  5. 若截断后的块长度 >= minChunkLengthToEmbed,则加入输出;否则丢弃。
  6. 重复直到处理完或达到 maxNumChunks。
  • 简单举例
  • 一个大约 1000 token 的文档,defaultChunkSize=200 时大致会产生 ~5 个块;但实际边界会向后/向前调整以对齐句子或段落(受 minChunkSizeChars 影响)。
  • 如果文本很短(小于 minChunkLengthToEmbed),则不会被加入到向量库。
  • “token” 取决于所用模型/分词器(不同模型 token 长度划分会不同),因此切分结果会随 embedding/chat 模型而变。
  • 如果希望更细或更粗的切分,可调整 defaultChunkSize 与 minChunkSizeChars;若想保留段落信息,keepSeparator=true 有帮助。

MyKeywordEnricher.java

/*** 基于 AI 的文档元信息增强器(为文档补充元信息)*/
@Component
public class MyKeywordEnricher {@Resourceprivate ChatModel dashscopeChatModel;public List<Document> enrichDocuments(List<Document> documents) {KeywordMetadataEnricher keywordMetadataEnricher = new KeywordMetadataEnricher(dashscopeChatModel, 5);return  keywordMetadataEnricher.apply(documents);}
}

VectorStoreConfig.java

/*** 向量数据库配置(初始化基于内存的向量数据库 Bean)*/
@Configuration
public class VectorStoreConfig {@Resourceprivate LoveAppDocumentLoader loveAppDocumentLoader;@Resourceprivate MyTokenTextSplitter myTokenTextSplitter;@Resourceprivate MyKeywordEnricher myKeywordEnricher;@BeanVectorStore VectorStore(EmbeddingModel dashscopeEmbeddingModel) {SimpleVectorStore simpleVectorStore = SimpleVectorStore.builder(dashscopeEmbeddingModel).build();//创建内存向量库实例并绑定一个 EmbeddingModel”,该 EmbeddingModel 用来把文本转为向量。// 加载文档List<Document> documentList = loveAppDocumentLoader.loadMarkdowns();// 自主切分文档
//        List<Document> splitDocuments = myTokenTextSplitter.splitCustomized(documentList);// 自动补充关键词元信息List<Document> enrichedDocuments = myKeywordEnricher.enrichDocuments(documentList);simpleVectorStore.add(enrichedDocuments);return simpleVectorStore;}

RagCustomAdvisorFactory.java


/*** 创建自定义的 RAG 检索增强顾问的工厂*/
public class RagCustomAdvisorFactory {/*** 创建自定义的 RAG 检索增强顾问** @param vectorStore 向量存储* @param status      状态* @return 自定义的 RAG 检索增强顾问*/public static Advisor createLoveAppRagCustomAdvisor(VectorStore vectorStore, String status) {// 过滤特定状态的文档Filter.Expression expression = new FilterExpressionBuilder().eq("status", status).build();// 创建文档检索器DocumentRetriever documentRetriever = VectorStoreDocumentRetriever.builder().vectorStore(vectorStore).filterExpression(expression) // 过滤条件.similarityThreshold(0.5) // 相似度阈值.topK(3) // 返回文档数量.build();return RetrievalAugmentationAdvisor.builder().documentRetriever(documentRetriever).queryAugmenter(LoveAppContextualQueryAugmenterFactory.createInstance()).build();}
}
  • 这是一个静态工厂类,提供一个方法来生成一个“检索增强的 Advisor”(RetrievalAugmentationAdvisor),用于把向量检索结果作为上下文喂给上层的聊天/生成模型。

ContextualQueryAugmenterFactory.java

/*** 创建上下文查询增强器的工厂*/
public class ContextualQueryAugmenterFactory {public static ContextualQueryAugmenter createInstance() {PromptTemplate emptyContextPromptTemplate = new PromptTemplate("""你应该输出下面的内容:抱歉,我只能回答---相关的问题,别的不会喵,有问题可以查询 https://mp.csdn.net/mp_blog/creation/editor/150224340""");return ContextualQueryAugmenter.builder().allowEmptyContext(false).emptyContextPromptTemplate(emptyContextPromptTemplate).build();}
}
  • 这个工厂保证:如果检索不到相关知识库内容,系统不会随意生成无关答案,而是以指定的模板文本作为安全/友好的默认回应。

QueryRewriter


/*** 查询重写器*/
@Component
public class QueryRewriter {private final QueryTransformer queryTransformer;public QueryRewriter(ChatModel dashscopeChatModel) {ChatClient.Builder builder = ChatClient.builder(dashscopeChatModel);// 创建查询重写转换器queryTransformer = RewriteQueryTransformer.builder().chatClientBuilder(builder).build();}/*** 执行查询重写** @param prompt* @return*/public String doQueryRewrite(String prompt) {Query query = new Query(prompt);// 执行查询重写Query transformedQuery = queryTransformer.transform(query);// 输出重写后的查询return transformedQuery.text();}
}

用来在检索前把用户的原始查询改写为更利于检索的文本(pre-retrieval query rewriting)

QueryRewriter 是用 Spring AI 的 RewriteQueryTransformer 基于 ChatModel 去做改写。RewriteQueryTransformer(来自 Spring AI 的 RAG 工具)封装了一个改写逻辑:它内部有固定/可配置的 prompt 模板,调用 ChatClient(由传入的 ChatModel 构建)向 LLM 发起一次聊天/完成请求,得到改写后的文本。

http://www.lryc.cn/news/625138.html

相关文章:

  • 项目延期的主要原因分析,以及应对策略
  • 摔倒检测数据集:1w+图像,yolo标注
  • 深度学习-计算机视觉-微调 Fine-tune
  • 【完整源码+数据集+部署教程】织物缺陷检测系统源码和数据集:改进yolo11-RevCol
  • STL库——string(类函数学习)
  • steal tsoding‘s pastebeam code as go server
  • CMake指令:查找文件(find_file)、查找目录(find_path)、查找库文件(find_library)
  • npm设置了镜像 pnpm还需要设置镜像吗
  • Esp32基础(③旋转编码器)
  • wait / notify、单例模式
  • 在openEuler系统中如何查看文件夹下每个文件的大小
  • AVB(Android Verified Boot)中vbmeta结构浅析
  • C/C++ 中 str、str、*str 在指针语境下的具体含义(以 char* str 为例):
  • Android输入框文字不垂直居中
  • Linux下的软件编程——IPC机制
  • Java发送企业微信通知
  • Vue2篇——第五章 Vue.js 自定义指令与插槽核心
  • (第十八期)图像标签的三个常用属性:width、height、border
  • minio安装和配置
  • 【DL学习笔记】交叉熵损失函数详解
  • 之前说的要写的TCP高性能服务器,今天来了
  • 给linux的root磁盘扩容
  • Ansible 部署LNMP
  • 每日AI要闻【20250818】
  • 自回归图像生成新突破!140亿参数自回归模型NextStep-1开源,图像生成无需扩散模型
  • 基于SFM的三维重建MATLAB程序
  • MBTI职业规划指南:发掘你的人格潜能,照亮职业发展之路
  • Elasticsearch查询中的track_total_hits参数
  • 力扣hot100:移动零问题的巧妙解决:双指针与原地交换策略(283)
  • 构建高效智能语音代理:技术架构、实现细节与API服务推荐