前言

        本项目非原创,我也是作为一名初学者跟着一起学习。项目来源于:代码随想录-知识星球。

代码随想录https://link.csdn.net/?from_id=159158927&target=https%3A%2F%2Fwx.zsxq.com%2Fgroup%2F88511825151142

在知识星球里看到卡哥分享这个项目 ,感觉还不错,于是想要学习一下这个项目怎么写。项目日记也会同步更新。(本人不分享本项目源码,支持项目付费

本文由我学习该项目并结合AI整理总结而来,分享出来学习过程中的心得体会,由浅入深,用于日后的回顾,同时也希望能给你带来帮助。


目录

前言

从聊天到做事,差的是什么

V2 相比 V1,多了哪些关键组件

Agent Loop 的执行流程控制

Agent Loop 的三大核心模块

Think 阶段:让模型决定下一步干什么

ToolCall 出现时,为什么不立刻写入 memory

Execute 阶段:系统真正动手的地方

Step:Think + Execute 的最小循环单元

chat() 方法,不再是一次调用

到这里,我们已经真正拥有了一个 Agent Loop

测试工具调用能力



【JchatMind智能体 | 第五天】实现带记忆的聊天功能https://blog.csdn.net/h52412224/article/details/159158927

【JchatMind智能体 | 第四天】Spring AI 集成与多模型支持https://blog.csdn.net/h52412224/article/details/159078281【JchatMind智能体 | 第三天】数据模型设计https://blog.csdn.net/h52412224/article/details/159043756


在上一章里,我们用 JChatMindV1 实现了一个能聊天的最小 Agent。它已经具备了几个关键特征:

  • 通过 ChatClient 接入模型能力

  • 用 ChatMemory 显式维护对话上下文

  • 对话是有 session、有状态的

但 V1 有个很明显的局限:它只能说话,不能做事。

一旦用户的问题需要和外部世界交互,比如查天气、查日期或者调用数据库,V1 就无能为力了。

这一章的目标,就是在不推翻 V1 原有结构的基础上,引入 Agent Loop,让 Agent 真正开始具备做事的能力:

  • 判断什么时候需要调用外部工具

  • 主动发起工具调用

  • 根据工具返回的结果继续推进任务

从聊天到做事,差的是什么

这一次模型的回复,是不是最终答案?

如果不是,系统就必须介入,推动对话进入下一个阶段。

JChatMindV2 的全部设计,都是围绕这个问题展开的。

V2 相比 V1,多了哪些关键组件

我们先看看 V2 在类结构上的变化:

public class JChatMindV2 extends JChatMindV1 {
    protected List<ToolCallback> availableTools;
    protected ToolCallingManager toolCallingManager;
    protected ChatOptions chatOptions;
    protected ChatResponse lastChatResponse;
}

相比 V1,新增的这几个字段是关键:

  • availableTools:当前 Agent 可以使用的所有外部工具列表

  • ToolCallingManager:统一管理和执行所有工具调用的协调器

  • ChatOptions:显式控制模型的行为,比如是否自动执行工具

  • lastChatResponse:保存最近一次模型的完整输出,作为后续执行阶段的输入

Agent Loop 的执行流程控制

在 V2 的构造函数里,我们做了一个重要的设置:

this.chatOptions = DefaultToolCallingChatOptions.builder()
        .internalToolExecutionEnabled(false)
        .build();

Spring AI 原生支持模型自动执行工具并回填结果的一条龙流程,但我们在这里选择了手动接管。

原因很简单,Agent Loop 的核心不是工具调用,而是对执行过程的控制。

如果让 Spring AI 自动执行工具,Agent 就会失去很多关键能力:

  • 记录完整执行轨迹的能力

  • 插入中断、回滚和重试的机会

  • 判断是否需要继续执行的决策权

所以在 V2 中,我们宁愿多写一些代码,也要把执行的控制权牢牢握在自己手里。

Agent Loop 的三大核心模块

整个 Agent Loop 可以清晰地分为三个功能模块:

  • Think 决策模块:分析当前对话上下文,决定下一步该做什么,是直接回答还是调用工具

  • LLMs 大语言模型:根据决策模块的要求,生成结构化的推理结果,可能是普通回复,也可能是工具调用指令

  • Execute 执行器:解析并执行模型生成的工具调用请求,把真实的执行结果回写到对话历史中

这三个模块会形成一个循环:

Think → LLMs → Execute → Think,直到任务完成或者达到最大循环次数。

Think 阶段:让模型决定下一步干什么

Agent Loop 的第一步,就是 Think 阶段。

protected boolean think() {
    String thinkPrompt = """
        现在你是一个智能的决策模块。
        请根据当前对话上下文,决定下一步的动作。
        如果需要调用工具来完成任务,请调用相应的工具。
        """;
    // ... 模型调用代码
}

这里有个非常关键的区别:Think 阶段的目标不是直接给用户答案,而是做决策。

它要判断,当前这个问题,是靠模型自己就能回答,还是必须调用外部工具。

在调用模型时,我们做了三件事:

  1. 把完整的对话上下文(来自 ChatMemory)都发给模型

  2. 向模型暴露当前 Agent 所有可用的工具

  3. 获取完整的 ChatResponse 对象,而不是只拿文本,因为里面包含了工具调用的指令

ToolCall 出现时,为什么不立刻写入 memory

在 Think 阶段结束后,我们做了一个预防 bug 的处理:

if (toolCalls.isEmpty()) {
    chatMemory.add(sessionId, output);
}

也就是说:

  • 如果模型没有生成任何工具调用,说明这就是最终回复,直接写入 memory

  • 如果有工具调用,就先不写入,等工具执行完后再统一处理

这一点非常重要。如果提前把包含工具调用的消息写入 memory,万一后面工具执行失败,就会导致上下文信息不一致。

这个坑只有在真正写代码的时候才会遇到。

Execute 阶段:系统真正动手的地方

Execute 阶段,是 Agent 从会想走向会做的关键一步。

ToolExecutionResult toolExecutionResult =
        toolCallingManager.executeToolCalls(prompt, this.lastChatResponse);

ToolResponseMessage toolResponseMessage = (ToolResponseMessage) toolExecutionResult
        .conversationHistory()
        .get(toolExecutionResult.conversationHistory().size() - 1);

这几行代码背后,其实是一整套完整的工具调用流程:

  1. 从 lastChatResponse 中解析出工具调用的指令

  2. 根据工具名称和参数,调用对应的外部工具

  3. 把工具的返回结果包装成标准化的 ToolResponseMessage

  4. 返回更新后的对话历史

Step:Think + Execute 的最小循环单元

我们把一次 Think 和一次可选的 Execute 封装成了一个最小的循环单元,叫做 step:

protected void step() {
    if (think()) {
        execute();
    } else {
        agentState = AgentState.FINISHED;
    }
}

一次 step,就是 Agent 完整的一次思考和行动。

chat() 方法,不再是一次调用

在 V2 中,chat() 方法的语义已经完全变了:

for (int i = 0; i < MAX_STEPS && agentState != AgentState.FINISHED; i++) {
    step();
}

它不再是简单的问一句答一句,而是变成了:

给 Agent 一个目标,让它自己通过循环跑完整个流程。

MAX_STEPS 的存在,也体现了一个重要的工程原则:

  • Agent 不能无限循环下去

  • 模型的推理深度是有上限的

  • 系统必须有兜底机制

到这里,我们已经真正拥有了一个 Agent Loop

到 JChatMindV2 为止,我们完成了 Agent 系统中最关键的一步:

  • 模型不再直接回答问题

  • 模型开始参与下一步该做什么的决策

  • 系统和模型有了明确的职责分工

有了这个基础,后续无论是引入更复杂的规划、加入失败重试机制、接入 RAG 知识库,还是做执行轨迹的可视化,都只是在这个 Loop 上不断叠加功能而已。

测试工具调用能力

最后,我们写个测试函数,验证一下 Agent Loop 和工具调用是否正常工作:


上述内容也同步在我的飞书,欢迎访问

https://my.feishu.cn/wiki/QLauws6lWif1pnkhB8IcAvkhncc?from=from_copylink

如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,你们的支持就是我坚持下去的动力!

Logo

脑启社区是一个专注类脑智能领域的开发者社区。欢迎加入社区,共建类脑智能生态。社区为开发者提供了丰富的开源类脑工具软件、类脑算法模型及数据集、类脑知识库、类脑技术培训课程以及类脑应用案例等资源。

更多推荐