导读 :当你使用豆包、Copilot 这类 AI 助手时,有没有想过------它为什么比普通聊天机器人"聪明"得多?答案就藏在 Agent 这个概念里。本文将从三个维度拆解 Agent 的核心原理:首先搞清楚 Agent 的本质,理解它与普通 Function Call 的根本区别;然后深入 ReAct 框架,看看模型是如何通过"思考-行动-观察"的循环完成复杂任务的;最后聚焦工具设计的最佳实践,一个好的
description往往决定了 Agent 能不能跑通。无论你是刚接触 Agent 开发,还是已经在用 LangChain、Spring AI 等框架,理解这些底层原理都能让你写出更好的 Agent 应用。
一、Agent 的本质:从工具调用到自主决策的跨越
1.1 一个场景看懂区别
假设用户说:"帮我分析一下上月销售数据,找出问题并给出建议。"
普通 Chat + Function Call 模式:
用户提问 → 模型判断要用工具 → 调用工具拿到数据 → 分析后返回文字
整个过程一轮就结束了,工具调用的逻辑是固定的、被动的。模型只是一只"取数据的手",没有自己的思考过程和自主行为。
Agent 模式:
用户提问 → Agent 收到任务
→ 思考:我需要哪些步骤?
1. 查上个月数据
2. 对比历史数据
3. 分析异常原因
4. 生成报告
→ 执行:调用工具查数据
→ 观察:发现销售额下滑
→ 思考:为什么下滑这么多?
→ 执行:调用工具查库存、查竞品价格
→ 观察:发现品类库存积压、竞品降价
→ 思考:信息已经足够了
→ 生成最终报告
关键区别 在于:调用顺序不是人写死的,而是模型根据每一步的观察结果动态决定下一步做什么。这就是 Agent 与 Function Call 套壳的本质区别。
1.2 Agent 的三大核心要素
一个完整的 Agent 至少需要三个东西:工具 、记忆 、规划。
工具------Agent 的手
工具就是 Agent 的"手"。没有工具的 Agent 只会说话,不会做事。
就像一个人如果没有手,你可以说话,但让你去做具体的事情,就做不了了。
这里有一个非常关键的避坑点:工具的质量决定了 Agent 能力的上限 。即便你的大模型再聪明,如果工具的 description 写得简陋,模型看不懂、不知道什么时候用,那大模型的能力就被白白浪费了。
在我们的项目中,以 CustomerServiceAgent 为例,工具通过 Spring AI 的 .defaultTools() 注册到 ChatClient 中:
// com.jichi.agent.service.CustomerServiceAgent
@Service
public class CustomerServiceAgent {
private final ChatClient chatClient;
public CustomerServiceAgent(@Qualifier("dashScopeChatModel") ChatModel chatModel,
CustomerServiceTools tools) {
this.chatClient = ChatClient.builder(chatModel)
.defaultSystem("""
你是一个专业的电商客服助手,服务于一家耳机电商平台。
你可以:
- 根据订单号查询物流状态(getOrderTracking)
- 搜索商品、查询库存和价格(searchProducts)
工作原则:
- 需要数据时调用工具,绝对不要猜测或编造数据
- 如果用户提供的订单号格式不对(不是 ORD 开头),先提示用户确认
- 回答简洁友好,语气专业但不生硬
""")
.defaultTools(tools) // 注册工具
.build();
}
public String chat(String message) {
return chatClient.prompt()
.user(message)
.call()
.content();
}
}
注意看 System Prompt 的设计------不仅告诉模型"你是谁",还明确列出了可用工具的名称和使用场景,甚至写了"不要猜测或编造数据"这种约束规则。
记忆------Agent 的上下文
Agent 的记忆分为两种:
| 类型 | 作用 | 示例 |
|---|---|---|
| 短期记忆 | 当前任务执行过程中每步的结果 | 上一步查到的城市名、价格数据 |
| 长期记忆 | 跨任务的信息持久化 | 用户的偏好设置、历史对话记录 |
没有记忆会怎样?Agent 每执行完一步就忘了前面干了什么。比如写一个查天气助手,查完一个城市的天气后,第二轮它就忘了之前查的是哪个城市------这就是记忆没做好导致的"翻车"。
规划------Agent 的大脑
当面对一个复杂的大任务时,Agent 需要有能力将其拆解成多个小任务,然后逐步执行。规划能力本质上来自大模型的推理能力:好的模型推理强,规划就靠谱;差的模型可能会陷入循环或者跑偏。
1.3 Agent Loop:核心运行机制
Agent 的运行模式本质上就是一个循环:
观察 → 思考 → 执行 → 观察 → 思考 → 执行 → ... → 任务完成(退出循环)
退出条件非常重要。早期很多国产模型的 Agent 实现有一个通病------停不下来。模型不知道什么时候该结束,一直在循环。因此我们必须给 Agent 设计"刹车系统",比如设置最大循环次数、检测终止标志等。
1.4 Function Call vs Agent:一张表说清楚
| 维度 | Function Call | Agent |
|---|---|---|
| 执行轮次 | 通常一轮 | 多轮循环 |
| 决策方式 | 固定逻辑 / 用户触发 | 模型自主决定下一步 |
| 任务复杂度 | 简单查询 | 复杂多步骤任务 |
| 有无规划 | 无 | 有(思考阶段) |
| Token 消耗 | 少 | 多(每步都在推理) |
一句话总结:Function Call 是 Agent 的手,Agent Loop 才是 Agent 的大脑。有手没大脑,那就只是工具调用;有大脑加上手,那才是 Agent。
1.5 什么场景适合用 Agent?
适合的场景 :
-
任务需要多步骤、多工具协作
-
执行路径不固定,需要根据中间结果动态决策
-
任务描述本身比较模糊,需要 AI 自己判断
不适合的场景 :
-
简单问答,一次对话就搞定
-
固定流程,可以写成 if-else 决策树
-
延迟要求很高(Agent 推理可能几秒到几十秒)
-
金融交易、合同签署等需要确定性流程控制的高风险场景
判断标准:如果你能把业务流程写成一棵 if-else 决策树,那大概率不需要 Agent。但如果分支本身不确定、无法穷举,就必须靠模型实时判断------这时候就需要 Agent 了。
二、ReAct 框架详解:思考-行动-观察循环
2.1 什么是 ReAct?
ReAct = Re asoning + Acting(推理 + 行动)。
这个概念源自 2022 年 Google 的一篇论文,核心思路只有一句话:让模型在行动之前,先说出它的推理过程。
与传统 Function Call 的对比:
传统 Function Call:
用户问题 → 模型直接输出工具调用 → 返回结果
ReAct:
用户问题
→ Thought: 我需要先查当前价格
→ Action: 调用 get_price("iPhone")
→ Observation: 当前价格 5999 元
→ Thought: 价格拿到了,还需要看库存
→ Action: 调用 get_stock("iPhone")
→ Observation: 库存 23 台
→ Thought: 价格和库存都有了,可以回答了
→ Final Answer: 当前 iPhone 售价 5999 元,库存充足(23台)
2.2 为什么要先思考再行动?
三个核心原因:
1. 准确率更高
就像做数学题先打草稿一样,思考过程清楚了再动手,出错概率会低很多。如果不思考直接行动,很可能一顿操作猛如虎,结果全是错的。
2. 方便调试
我们可以直接看到模型为什么做了某个决策。出了问题不用猜,看 Thought 的输出就能精准定位问题在哪。
3. 支持自我修正
思考过程让模型可以在执行中重新评估。比如查到的数据看起来不对,模型可以在 Thought 中说"这个结果有点奇怪,需要再验证一下",然后调用其他工具进行核实。这种自我修正能力是传统 Function Call 做不到的。
2.3 ReAct 的三个阶段
阶段一:Thought(思考)
模型根据用户的自然语言描述,分析当前情况,制定下一步计划。这段文字一般不展示给用户,但对整个流程能否跑好至关重要。
思考的质量很大程度上取决于 System Prompt 的设计。如果你发现模型的思考过程经常写废话,很可能是 System Prompt 没写好。
阶段二:Action(行动)
模型输出要调用的工具名和参数。注意,模型本身输出的是文本而不是函数调用------框架(如 Spring AI、LangChain4j)负责解析这段文本,找到对应函数并执行。
阶段三:Observation(观察)
将工具的执行结果作为下一轮思考的输入。观察结果会被追加到消息历史中,这样模型在下一轮就能看到之前所有的结果。
重要提醒 :工具的反馈要精简!不要把第三方 API 的原始 JSON 全丢给模型,几十个字段只会让模型"懵圈"。
2.4 手写一个最简单的 ReAct Agent
下面是项目中 SimpleReActAgent 的完整实现------不依赖任何 Agent 框架,纯手写 ReAct 循环,基于 Spring AI 的 ChatClient:
// com.jichi.agent.service.SimpleReActAgent
@Service
public class SimpleReActAgent {
private final ChatClient chatClient;
// 工具注册表:工具名 → 工具实现
// 模型输出 "Action: getWeather",就直接拿名字去这里找对应函数
private final Map<String, AgentTool> tools;
public SimpleReActAgent(@Qualifier("dashScopeChatModel") ChatModel chatModel) {
this.chatClient = ChatClient.builder(chatModel).build();
this.tools = Map.of(
"getWeather", this::getWeather,
"getDate", this::getDate
);
}
/**
* 执行 ReAct 循环
*
* @param userTask 用户任务描述
* @param maxIterations 最大循环次数(防止死循环)
*/
public String run(String userTask, int maxIterations) {
List<Message> messages = new ArrayList<>();
String systemPrompt = buildSystemPrompt();
messages.add(new UserMessage(userTask));
for (int i = 0; i < maxIterations; i++) {
// 每轮都把完整消息历史传给模型
// 模型是无状态的,它"记得"上下文靠的就是这个列表
String modelOutput = chatClient.prompt()
.system(systemPrompt)
.messages(messages)
.call()
.content();
messages.add(new AssistantMessage(modelOutput));
System.out.println("=== 第 " + (i + 1) + " 轮 ===");
System.out.println(modelOutput);
// 检测是否输出了最终答案
if (modelOutput.contains("Final Answer:")) {
return extractFinalAnswer(modelOutput);
}
// 解析模型想调用哪个工具、参数是什么
String toolName = extractAction(modelOutput);
String toolInput = extractActionInput(modelOutput);
if (toolName == null) {
return "模型输出格式异常,无法继续执行";
}
AgentTool tool = tools.get(toolName);
String observation;
if (tool == null) {
observation = "工具 " + toolName + " 不存在,请换一个";
} else {
observation = tool.execute(toolInput);
}
System.out.println("Observation: " + observation);
// 把观察结果加回消息历史,下一轮模型就能看到这个结果
messages.add(new UserMessage("Observation: " + observation));
}
return "超过最大迭代次数(" + maxIterations + "),任务未完成";
}
@FunctionalInterface
interface AgentTool {
String execute(String input);
}
}
再来看 System Prompt 怎么写------这是整个 ReAct 能跑通的关键:
private String buildSystemPrompt() {
return """
你是一个智能助手,按照以下格式严格输出,每次只做一个动作:
可用工具:
- getWeather(input: JSON {"city": "城市名", "date": "today/tomorrow"}):查询天气
- getDate(input: 无):获取今天的日期
输出格式(严格遵守):
Thought: [你的分析和下一步计划]
Action: [工具名]
Action Input: [工具参数,JSON 格式]
收到 Observation 后继续思考,直到可以回答为止:
Thought: [分析观察结果]
Final Answer: [给用户的最终回答]
注意:
- 每次只输出一个 Action 或 Final Answer,不要一次输出多个
- 工具名必须和上面列表完全一致
""";
}
文本解析部分------从模型的自由文本输出中抽取 Action 和参数:
private String extractFinalAnswer(String output) {
int idx = output.indexOf("Final Answer:");
if (idx == -1) return output;
return output.substring(idx + "Final Answer:".length()).strip();
}
private String extractAction(String output) {
for (String line : output.split("\n")) {
if (line.startsWith("Action:")) {
return line.substring("Action:".length()).strip();
}
}
return null;
}
private String extractActionInput(String output) {
for (String line : output.split("\n")) {
if (line.startsWith("Action Input:")) {
return line.substring("Action Input:".length()).strip();
}
}
return "";
}
几个关键设计点
为什么用 Map<String, AgentTool> 存工具?
这是"工具注册表"的思路。模型输出 Action: getWeather,我们直接通过 key 在 Map 中找到对应的工具实现。注意 AgentTool 是一个函数式接口,工具通过方法引用(this::getWeather)注册进去。这和 Spring AI 的 @Tool 注解底层是同一个思路------本质都是把工具注册到一个映射表中,通过名称反射调用。
为什么每轮都要传完整的消息历史?
因为大模型本身是无状态的,每次调用都是一个全新请求,它不知道之前发生了什么。Agent Loop 的"记忆"就体现在我们把所有历史消息塞进去------这样模型才能接着推理。
当然,消息越多 Token 越多,这就是 Agent 比普通聊天贵的原因。
System Prompt 为什么每次都要传?
System Prompt 是 Agent 的"规则书",丢了就可能违背各种原则。所以每轮都要带上。
2.5 循环退出控制的几个关键点
| 控制手段 | 说明 |
|---|---|
| 最大循环次数 | 简单任务设 3-5 次,复杂任务设 10-15 次,防止死循环 |
| Final Answer 检测 | 检测到终止标志立即退出 |
| 工具不存在处理 | 返回提示信息,让模型换一个工具 |
| 格式异常处理 | 模型输出格式不符合预期时的兜底逻辑 |
| 多 Action 处理 | 模型一次输出多个 Action 时,只取第一个 |
来看 SimpleReActAgent 中的退出控制是怎么实现的:
// 1. 最大循环次数------for 循环的上限
for (int i = 0; i < maxIterations; i++) { ... }
// 2. Final Answer 检测------检测到关键词立即 return
if (modelOutput.contains("Final Answer:")) {
return extractFinalAnswer(modelOutput);
}
// 3. 格式异常处理------解析不到 Action 时直接终止
if (toolName == null) {
return "模型输出格式异常,无法继续执行";
}
// 4. 工具不存在处理------返回提示让模型换工具
if (tool == null) {
observation = "工具 " + toolName + " 不存在,请换一个";
}
// 5. 超时兜底------循环结束仍未完成
return "超过最大迭代次数(" + maxIterations + "),任务未完成";
Controller 层的调用也很简洁,默认传入最大循环 10 次:
// com.jichi.agent.controller.ReActController
@RestController
@RequestMapping("/api/agent/react")
public class ReActController {
private final SimpleReActAgent agent;
@PostMapping
public String run(@RequestBody TaskRequest request) {
return agent.run(request.task(), 10);
}
record TaskRequest(String task) {}
}
血泪教训:如果没有这些控制,模型可能陷入"上次结果不对->再查一次->还是不对->继续查"的死循环。而且这些调用都是付费的,一跑起来可能直接把预算跑没。
三、工具设计最佳实践:Description 决定成败
3.1 第一原则:Description 是给模型看的,不是给人看的
写代码注释、写 JavaDoc 时,我们习惯给同事看,用的是人类的表达方式。但工具的 description 是直接喂给模型的,需要机器能理解的完整性。
反面教材:
@Tool(description = "获取天气")
模型看到这个,只知道和天气有关,但不知道:是天气预报还是历史天气?返回中文还是英文?参数是城市名还是经纬度?
正确写法 ------来看项目中 AssistantTools 的真实实现:
// com.jichi.agent.tools.AssistantTools
@Tool(description = """
查询指定城市的实时天气。
适用于:用户询问某城市当前天气、今天天气、是否需要带伞等问题。
返回:城市、天气状况、温度范围、湿度、风力。
注意:只查当前天气,不查天气预报。
""")
public String getWeather(
@ToolParam(description = "城市名,中文,例如:北京、上海、深圳") String city) {
return String.format("""
城市:%s
天气:晴转多云
温度:12~20°C
湿度:55%%
风力:东南风 2 级
查询时间:%s
""", city, LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")));
}
这个 description 覆盖了四个维度:功能说明、适用场景、返回内容、边界约束。模型看完就清楚了:查的是什么天气、返回什么内容、什么时候该用、什么情况不该用。
自检方法:写完 description 之后,把自己当成一台机器来读------不要脑补,纯粹看文字,能不能知道这个工具干什么。如果你都搞不清楚,模型更搞不清楚。
3.2 多工具场景下的区分度
单个工具时问题不大,但 Agent 场景通常有 5-10 个工具,这时候区分度就至关重要。
反面教材:
@Tool(description = "查询订单") // 工具 A
@Tool(description = "获取订单信息") // 工具 B
模型看到这两个几乎一样的描述,只能随机选一个------这就解释了为什么有时候 Agent 表现好、有时候表现差。
正确写法 ------来看项目中 CustomerServiceTools 的两个工具是怎么做区分的:
// com.jichi.agent.tools.CustomerServiceTools
@Tool(description = """
根据订单号查询订单状态和物流信息。
适用于:用户询问订单发货情况、快递位置、预计到达时间等物流相关问题。
返回:订单状态、物流单号、当前物流位置、预计到达时间。
不适用于:查询订单商品详情或价格(用 getOrderDetail 工具)。
""")
public String getOrderTracking(
@ToolParam(description = "订单号,格式 ORD + 数字,例如 ORD20240115001")
String orderId) { ... }
@Tool(description = """
根据关键词搜索商品,查看商品信息。
适用于:用户咨询某类商品是否有货、价格区间、推荐哪款商品等。
返回:匹配商品的名称、价格、库存数量、商品评分。
""")
public String searchProducts(
@ToolParam(description = "搜索关键词,例如:耳机、无线、降噪") String keyword,
@ToolParam(description = "最多返回几个结果,默认 3,最多 10") int limit) { ... }
每个工具的职责边界清清楚楚:一个管物流、一个管商品。注意 getOrderTracking 还特意写了"不适用于"------这是在告诉模型什么时候不该选这个工具,比直接告诉"该用什么"更有效。
3.3 工具粒度:不能太细,也不能太粗
太细的问题:
@Tool(description = "获取用户名")
@Tool(description = "获取用户邮箱")
@Tool(description = "获取用户手机号")
获取一个用户的基本信息,要调三次工具、跑三轮循环,Token 飙升,体验极差。
太粗的问题:
@Tool(description = "获取所有信息")
模型看了也懵------"所有信息"到底是什么?这个工具到底能干什么?而且这种工具的内部实现必然很复杂,调用了大量接口,出问题也很难排查。
粒度原则:一个工具完成一件完整的事,这件事有明确的输入和输出。
来看 AssistantTools 中的工具设计,每个工具都遵循了这个原则:
// com.jichi.agent.tools.AssistantTools
// 工具1:查天气------一件完整的事
@Tool(description = """
查询指定城市的实时天气。
适用于:用户询问某城市当前天气、今天天气、是否需要带伞等问题。
返回:城市、天气状况、温度范围、湿度、风力。
注意:只查当前天气,不查天气预报。
""")
public String getWeather(@ToolParam(description = "城市名,中文") String city) { ... }
// 工具2:查时间------另一件完整的事
@Tool(description = "获取当前日期和时间。适用于用户询问今天几号、现在几点、这周星期几等时间相关问题。")
public String getCurrentDateTime() { ... }
// 工具3:查汇率------又一件完整的事
@Tool(description = """
查询两种货币之间的汇率。
适用于:用户询问人民币兑换外币的比率、外汇换算、出国换多少钱等问题。
返回:汇率数值和换算说明。
""")
public String getExchangeRate(
@ToolParam(description = "源货币,3位代码,例如:CNY、USD、EUR、JPY") String from,
@ToolParam(description = "目标货币,3位代码") String to) { ... }
天气是一件事,时间是一件事,汇率是一件事------分开但不过分细分。
3.4 工具返回值:给模型看的,不是给程序员看的
反面教材:直接把对象序列化成 JSON 扔给模型
{"orderId":"ORD123","status":"SHIPPED","items":[{"sku":"SKU001","name":"iPhone","qty":1,"price":5999.00,"discount":0.0,"taxRate":0.13,...}],"createTime":"2024-01-15T10:30:00Z","updateTime":"2024-01-16T08:00:00Z",...}
程序员看得懂,但模型解析起来很吃力,而且大量无关字段会干扰判断、浪费 Token。
正确做法 ------看 CustomerServiceTools.getOrderTracking() 的返回值设计:
// com.jichi.agent.tools.CustomerServiceTools
public String getOrderTracking(String orderId) {
String[] order = MOCK_ORDERS.get(orderId);
if (order == null) return "未找到订单:" + orderId + ",请确认订单号是否正确";
return String.format("""
订单号:%s
状态:%s
物流单号:%s
当前位置:%s
预计到达:%s
""",
orderId, order[0],
order[1] != null ? order[1] : "暂无(订单未发货)",
order[2] != null ? order[2] : "暂无信息",
order[3] != null ? order[3] : "待定");
}
返回的是结构化自然语言 ,而不是原始 JSON。模型处理自然语言的能力远强于解析复杂 JSON,回答质量会高很多。同时注意空值处理------null 不是直接返回给模型,而是翻译成"暂无(订单未发货)"这样的可读文本。
3.5 写操作必须明确返回执行结果
模型需要知道操作到底成功还是失败,才能决定下一步怎么做。看 AssistantTools 中创建提醒的返回值:
// com.jichi.agent.tools.AssistantTools
@Tool(description = """
创建一个定时提醒事项。
适用于:用户说"提醒我..."、"帮我记一下..."、"x点提醒我"等需要设置提醒的场景。
返回:提醒创建结果(成功或失败)。
""")
public String createReminder(
@ToolParam(description = "提醒内容描述") String content,
@ToolParam(description = "提醒时间,格式:yyyy-MM-dd HH:mm,例如:2024-03-20 09:00") String remindAt) {
return String.format("提醒已创建:「%s」,将在 %s 提醒你", content, remindAt);
}
返回明确告诉模型"已创建"以及具体内容和时间。如果返回一个模糊的结果,模型就不知道是该继续还是该重试,整个 Agent 流程可能会卡住。
3.6 工具的异常处理:超时与错误
Agent 场景中,工具调用外部 API 可能超时或失败。关键原则是:不要抛异常,而是返回字符串告知模型------这样模型可以基于错误信息做后续决策。
来看 TimeoutAwareTools 的实现:
// com.jichi.agent.tools.TimeoutAwareTools
@Component
public class TimeoutAwareTools {
private final ExecutorService executor = Executors.newCachedThreadPool();
@Tool(description = "查询外部天气 API,获取实时天气数据")
public String getWeatherFromApi(
@ToolParam(description = "城市名") String city) {
Future<String> future = executor.submit(() -> callExternalWeatherApi(city));
try {
return future.get(5, TimeUnit.SECONDS); // 5 秒超时
} catch (TimeoutException e) {
future.cancel(true);
return "天气服务响应超时(5秒),无法获取实时数据,请告知用户稍后重试";
} catch (Exception e) {
return "天气服务异常:" + e.getMessage() + ",请告知用户稍后重试";
}
}
}
超时后返回的字符串是给模型看的指令------"请告知用户稍后重试"。模型读到这个,就知道该怎么回复用户了。如果直接抛异常,Agent 循环可能直接崩掉。
3.7 安全边界:Agent 场景下的三条铁律
与普通 Function Call 不同,Agent 中的工具调用是模型自动触发的,没有人在中间点"确认执行"。一旦出现越权操作,后果可能很严重。
三条安全原则:
-
写操作必须做权限校验:验证用户身份、检查操作权限,可以直接对接公司内部的权限系统。
-
错误信息必须明确:让模型知道失败原因,才能做出合理的下一步决策。
-
高风险操作加人工确认:比如取消订单、删除数据等操作,即使是 MVP 阶段,也建议加一个用户确认的逻辑。
@Tool(description = "取消指定订单,需要验证用户身份和订单状态")
public String cancelOrder(String orderId, String userId) {
// 1. 权限校验
if (!authService.hasPermission(userId, "ORDER_CANCEL")) {
return "取消失败:当前用户无权取消此订单。";
}
// 2. 状态校验
Order order = orderService.getById(orderId);
if (order.getStatus() == OrderStatus.SHIPPED) {
return "取消失败:订单已发货,请走售后流程。";
}
// 3. 执行操作
orderService.cancel(orderId);
return "订单 " + orderId + " 已成功取消。";
}
四、总结
本文从三个维度拆解了 Agent 的核心原理:
-
Agent 的本质:不是 Function Call 的简单包装,而是从"被动工具调用"到"自主决策"的跨越。核心在于 Agent Loop------一个由模型自己驱动的"观察-思考-执行"循环。三大要素是工具、记忆和规划。
-
ReAct 框架:目前最主流的 Agent 推理框架,通过 Thought-Action-Observation 三阶段循环,让模型在行动前先说出推理过程,从而提升准确率、方便调试、支持自我修正。循环退出控制(最大次数、终止标志、异常处理)是生产环境必须关注的重点。
-
工具设计最佳实践:Description 写给模型看而非给人看;多工具场景要有清晰的区分度;粒度遵循"一个工具一件事"原则;返回值用结构化自然语言而非原始 JSON;写操作必须做权限校验和结果反馈;异常情况返回字符串而非抛异常。
理解了这些底层原理之后,无论使用 LangChain、Spring AI 还是其他框架,你都能知道框架背后到底在干什么------所有框架的底层,跑的都是这些套路。
如果这篇文章对你有帮助,欢迎点赞收藏,后续会继续更新 Agent 实战系列,包括多 Agent 协作、Plan & Execute 等进阶内容。