各位同仁,各位技术爱好者,大家好!
今天,我将与大家深入探讨一个在大型语言模型(LLM)与外部工具交互领域至关重要的话题——“Tool Response Feedback”(工具响应反馈),以及如何巧妙地设计提示词,引导LLM在工具返回错误时,进行参数的自我修正并重试。在当今的AI应用中,LLM不再仅仅是文本生成器,它们正逐渐成为能够感知、规划并执行复杂任务的智能代理。而与外部工具的集成,正是赋予LLM这种能力的基石。然而,工具执行并非总是一帆风顺,错误在所难免。如何让LLM从错误中学习,进而自我修复,这正是我们今天要解决的核心问题。
一、理解“Tool Response Feedback”:智能代理的感知之眼
在LLM与工具交互的范式中,LLM首先根据用户指令和当前上下文,决定调用哪个工具以及传入哪些参数。这个过程我们称之为“工具调用规划”(Tool Calling Planning)。一旦LLM生成了工具调用指令,例如一个JSON对象或一个函数签名,这个指令会被发送给一个执行器(Executor),由它来实际调用外部API或执行本地代码。
“Tool Response Feedback”指的正是工具执行器将工具的运行结果,无论是成功的数据返回还是失败的错误信息,反馈给LLM的过程。这不仅仅是简单地将API响应抛给LLM,更关键的是,LLM需要能够理解这些响应,并据此调整其后续行为。
为什么Tool Response Feedback如此重要?
- 实现闭环控制: 没有反馈,LLM就像一个盲人摸象,无法得知其行动的实际效果。反馈机制是构建LLM代理的智能闭环控制系统的核心。
- 错误检测与诊断: 当工具执行失败时,反馈的错误信息是LLM诊断问题、理解失败原因的唯一线索。
- 自我修正与适应: 这是我们今天重点讨论的部分。通过理解错误反馈,LLM可以识别出参数问题、环境限制等,并尝试修正其规划,重新尝试。
- 增量学习与优化: 长远来看,LLM可以从大量的成功和失败反馈中“学习”更有效的工具使用策略,优化其参数选择和调用时机。
- 提升用户体验: 自动化错误处理和重试机制,可以减少用户在遇到问题时需要手动干预的次数,提升系统的鲁棒性和用户满意度。
简单来说,Tool Response Feedback是LLM从“工具调用者”升级为“工具使用者”的关键。它让LLM不仅仅是发出指令,更是能够理解指令执行结果,并据此调整策略的智能实体。
二、工具调用中常见的错误类型及其LLM认知挑战
在设计LLM的自我修正机制之前,我们必须先了解工具调用中可能出现的错误类型。这些错误各有特点,对LLM的理解和修正提出了不同的挑战。
| 错误类型 | 描述 | 示例 | LLM认知挑战 |
|---|---|---|---|
| 参数验证错误 | LLM提供的参数不符合工具的预期格式、类型、范围或枚举值。 | – date 格式错误 (如 2023-13-40)– quantity 为负数或非数字– city 不在允许的城市列表中 |
需要理解错误消息中的具体约束(格式、范围、枚举),并找到原始指令中对应参数进行修正。 |
| 业务逻辑错误 | 参数本身合法,但组合或业务场景不满足工具的业务规则。 | – 预订的日期已满 – 购买商品库存不足 – 用户没有权限执行某操作 |
错误信息可能更抽象,需要LLM结合上下文理解业务规则,可能需要尝试其他参数组合或告知用户。 |
| 外部服务错误 | 工具依赖的第三方服务出现故障、超时或返回错误。 | – 外部API 500 错误 – 数据库连接失败 – 网络超时 |
LLM通常无法直接解决这类问题,但可以尝试重试(如果错误是瞬时的),或告知用户/寻求帮助。对LLM而言,这是“黑盒”错误。 |
| 认证/授权错误 | LLM或其代表的用户没有足够的权限调用该工具或访问特定资源。 | – API Key 无效 – 用户未登录 – 访问被拒绝 |
LLM无法自行获取权限,需要告知用户或管理员。 |
| 环境配置错误 | 工具运行环境配置不正确,例如缺少必要的环境变量、文件路径错误等。 | – 配置文件丢失 – 环境变量未设置 |
类似于外部服务错误,LLM通常无法直接处理,需要运维介入。 |
| 语义理解错误 (LLM侧) | LLM虽然调用了正确的工具,但由于对用户意图理解偏差,导致选择的参数与用户真实需求不符,但工具执行成功。 | 用户想查“北京天气”,LLM误以为是“上海天气”并成功查询。 | 这种错误最难检测,因为工具返回成功。需要LLM在收到结果后,通过与用户意图的对比来判断是否需要进一步修正。这超出了本讲座的范围,但值得注意。 |
LLM在处理这些错误时,最大的挑战在于:
- 符号接地问题: LLM理解的是文本,它需要将错误消息中的文本描述,准确地映射到它之前生成的具体参数及其意图上。
- 推理与规划: 仅仅知道错误类型不够,LLM还需要基于错误信息进行推理,找出修正策略,并重新规划工具调用。
- 上下文保持: 在多次重试和修正中,LLM必须始终保持对用户原始意图的理解,避免“跑偏”。
三、设计提示词引导LLM自我修正的原则与策略
现在,我们进入核心部分:如何设计提示词,让LLM能够有效地利用错误反馈进行自我修正。
3.1 核心设计原则
- 明确的错误处理指令: 在初始提示中就告知LLM,当工具返回错误时应该怎么做,而不是让它自己猜测。
- 结构化的错误反馈: 工具执行器返回的错误信息应当尽可能地结构化,便于LLM解析和理解。
- 提供修正指导: 提示词可以包含常见错误的修正建议或参数约束,帮助LLM更快地找到正确方向。
- 鼓励“思考”(Chain-of-Thought): 引导LLM在重试前先分析错误,解释其修正思路。
- 限定重试次数: 避免无限循环,浪费资源。
3.2 提示词设计的关键组成部分
一个用于自我修正的提示词通常包括以下几个部分:
- 系统角色定义: 明确LLM的身份和职责。
- 工具描述: 详细说明可用的工具、其功能、预期参数及其数据类型、约束、示例。
- 用户指令: 用户的原始请求。
- 工具调用历史: 记录LLM之前尝试调用的工具和参数。
- 工具执行结果(错误反馈): 这是最关键的部分,包含详细的错误信息。
- 自我修正指令: 明确要求LLM根据错误信息分析问题、修正参数并重新调用工具。
3.3 具体的提示词设计策略与代码示例
我们将通过一个具体的场景来演示:预订航班。
场景: 用户希望预订从北京到上海的航班,但可能提供了错误的日期格式、不支持的城市代码或错误的乘客数量。
工具定义 (示例 Python 函数):
import datetime
class FlightBookingTool:
def __init__(self):
self.supported_cities = ["北京", "上海", "广州", "深圳", "成都"]
def book_flight(self, origin: str, destination: str, date: str, passengers: int) -> dict:
"""
预订航班的工具。
参数:
- origin (str): 出发城市,必须是中国支持的城市名称。
- destination (str): 目的城市,必须是中国支持的城市名称。
- date (str): 航班日期,格式为 YYYY-MM-DD。日期必须在未来。
- passengers (int): 乘客数量,必须是正整数,且最多不能超过5人。
"""
errors = []
# 1. 城市验证
if origin not in self.supported_cities:
errors.append(f"出发城市 '{origin}' 不支持。支持的城市有:{', '.join(self.supported_cities)}")
if destination not in self.supported_cities:
errors.append(f"目的城市 '{destination}' 不支持。支持的城市有:{', '.join(self.supported_cities)}")
if origin == destination:
errors.append("出发城市和目的城市不能相同。")
# 2. 日期格式验证
try:
flight_date = datetime.datetime.strptime(date, "%Y-%m-%d").date()
if flight_date < datetime.date.today():
errors.append(f"航班日期 '{date}' 不能是过去或今天。请提供未来的日期。")
except ValueError:
errors.append(f"日期格式错误。请使用 YYYY-MM-DD 格式,例如: {datetime.date.today() + datetime.timedelta(days=7)}")
# 3. 乘客数量验证
if not isinstance(passengers, int) or passengers <= 0:
errors.append(f"乘客数量 '{passengers}' 必须是正整数。")
elif passengers > 5:
errors.append(f"乘客数量 '{passengers}' 不能超过5人。")
if errors:
return {"status": "error", "message": "参数校验失败。", "details": errors}
else:
# 模拟成功预订
return {
"status": "success",
"message": "航班预订成功。",
"booking_details": {
"origin": origin,
"destination": destination,
"date": date,
"passengers": passengers,
"booking_id": "FL" + str(hash(f"{origin}-{destination}-{date}-{passengers}"))[:8],
},
}
# 实例化工具
flight_tool = FlightBookingTool()
模拟LLM代理的函数 (简化版):
为了演示,我们模拟一个LLM代理与工具的交互循环。
import json
# 模拟LLM的思考和工具调用生成
def mock_llm_generate_tool_call(prompt: str) -> dict:
"""
模拟LLM根据prompt生成工具调用(JSON格式)。
如果LLM决定不调用工具,它将返回一个文本响应。
"""
print(f"n--- LLM Input Prompt ---n{prompt}n------------------------")
# 在实际应用中,这里会调用真正的LLM API
# 为了演示,我们根据prompt内容模拟LLM的行为
# 第一次尝试:假设LLM会尝试调用book_flight
if "book_flight" in prompt and "Tool Call History" not in prompt:
# 模拟LLM第一次可能犯的错
if "北京" in prompt and "上海" in prompt and "明天" in prompt and "3个人" in prompt:
return {
"tool_name": "book_flight",
"parameters": {
"origin": "北京",
"destination": "上海",
"date": "明天", # 错误格式
"passengers": 3
}
}
elif "洛杉矶" in prompt: # 模拟LLM提供不支持的城市
return {
"tool_name": "book_flight",
"parameters": {
"origin": "洛杉矶",
"destination": "上海",
"date": "2024-12-25",
"passengers": 2
}
}
elif "超过5个人" in prompt: # 模拟LLM提供过多乘客
return {
"tool_name": "book_flight",
"parameters": {
"origin": "北京",
"destination": "上海",
"date": "2024-12-25",
"passengers": 6
}
}
elif "今天" in prompt: # 模拟LLM提供过去日期
return {
"tool_name": "book_flight",
"parameters": {
"origin": "北京",
"destination": "上海",
"date": datetime.date.today().strftime("%Y-%m-%d"),
"passengers": 1
}
}
# 第二次及以后尝试:假设LLM会根据反馈修正
if "Tool Execution Error" in prompt:
# 假设LLM根据错误信息进行修正
if "日期格式错误" in prompt and "明天" in prompt:
print("LLM: 检测到日期格式错误,将 '明天' 修正为 '2024-12-25'。")
return {
"tool_name": "book_flight",
"parameters": {
"origin": "北京",
"destination": "上海",
"date": "2024-12-25", # 修正后的日期
"passengers": 3
}
}
elif "出发城市 '洛杉矶' 不支持" in prompt:
print("LLM: 检测到出发城市不支持,将 '洛杉矶' 修正为 '北京'。")
return {
"tool_name": "book_flight",
"parameters": {
"origin": "北京",
"destination": "上海",
"date": "2024-12-25",
"passengers": 2
}
}
elif "乘客数量 '6' 不能超过5人" in prompt:
print("LLM: 检测到乘客数量超限,将 '6' 修正为 '5'。")
return {
"tool_name": "book_flight",
"parameters": {
"origin": "北京",
"destination": "上海",
"date": "2024-12-25",
"passengers": 5
}
}
elif "航班日期不能是过去或今天" in prompt:
print("LLM: 检测到日期是过去或今天,将日期修正为 '2024-12-25'。")
return {
"tool_name": "book_flight",
"parameters": {
"origin": "北京",
"destination": "上海",
"date": "2024-12-25", # 修正为未来日期
"passengers": 1
}
}
# 如果LLM无法修正,或者没有工具调用,则返回文本
return "抱歉,我无法完成您的请求,请检查信息或稍后再试。"
def execute_tool_call(tool_call: dict) -> dict:
"""
执行模拟的工具调用。
"""
tool_name = tool_call.get("tool_name")
parameters = tool_call.get("parameters")
if tool_name == "book_flight":
return flight_tool.book_flight(**parameters)
else:
return {"status": "error", "message": f"未知工具: {tool_name}"}
def run_llm_agent(user_query: str, max_retries: int = 3) -> str:
"""
模拟LLM代理与工具交互的完整流程,包括错误修正。
"""
chat_history = []
tool_call_history = []
# 初始系统提示和工具描述
system_prompt = f"""
你是一个航班预订助手,能够帮助用户预订航班。
请根据用户的请求,使用下方提供的工具。
可用工具:
```json
{{
"name": "book_flight",
"description": "{flight_tool.book_flight.__doc__.strip()}",
"parameters": {{
"type": "object",
"properties": {{
"origin": {{"type": "string", "description": "出发城市,例如:北京"}},
"destination": {{"type": "string", "description": "目的城市,例如:上海"}},
"date": {{"type": "string", "description": "航班日期,YYYY-MM-DD 格式,例如:2024-12-25"}},
"passengers": {{"type": "integer", "description": "乘客数量,正整数,最多5人"}}
}},
"required": ["origin", "destination", "date", "passengers"]
}}
}}
你必须严格按照上述工具的参数要求来生成调用。
如果工具返回错误,你必须分析错误信息,理解问题所在,然后修正参数并重新调用工具。
在重试之前,请在你的“思考”中说明你为什么修正了参数以及如何修正的。
如果多次尝试后仍无法成功,请告知用户。
当前日期: {datetime.date.today().strftime("%Y-%m-%d")}
"""
current_prompt = f"{system_prompt}nn用户请求: {user_query}"
for attempt in range(max_retries):
print(f"n--- Attempt {attempt + 1}/{max_retries} ---")
# LLM生成工具调用或文本响应
llm_response = mock_llm_generate_tool_call(current_prompt)
if isinstance(llm_response, dict) and "tool_name" in llm_response:
tool_call = llm_response
print(f"LLM决定调用工具: {tool_call['tool_name']},参数: {json.dumps(tool_call['parameters'], ensure_ascii=False)}")
tool_call_history.append({"attempt": attempt + 1, "tool_call": tool_call})
# 执行工具
tool_result = execute_tool_call(tool_call)
print(f"工具执行结果: {json.dumps(tool_result, ensure_ascii=False)}")
if tool_result["status"] == "success":
return f"航班预订成功!预订详情: {json.dumps(tool_result['booking_details'], ensure_ascii=False)}"
else:
# 工具执行失败,需要反馈给LLM进行修正
error_message = json.dumps(tool_result, ensure_ascii=False)
# 构建包含错误反馈的新的提示词
feedback_prompt = f"""
{system_prompt}
用户请求: {user_query}
Tool Call History:
{json.dumps(tool_call_history, indent=2, ensure_ascii=False)}
Tool Execution Error (from last attempt):
{error_message}
请分析上述错误信息,理解为什么工具调用失败了。
在你的“思考”中说明你将如何修正参数,然后生成一个新的工具调用。
"""
current_prompt = feedback_prompt
print("LLM收到错误反馈,准备重试…")
else:
LLM生成了文本响应,或者无法生成工具调用
return llm_response
return "抱歉,经过多次尝试,我仍无法成功预订航班。请检查您的请求或稍后再试。"
— 演示 —
print("n===== 演示1: 日期格式错误 =====")
user_request_1 = "帮我预订一张明天从北京到上海的机票,3个人。"
print(run_llm_agent(user_request_1))
print("n===== 演示2: 不支持的出发城市 =====")
user_request_2 = "我想预订一张从洛杉矶到上海的机票,2个人,日期是2024年12月25日。"
print(run_llm_agent(user_request_2))
print("n===== 演示3: 乘客数量超限 =====")
user_request_3 = "帮我预订一张从北京到上海的机票,6个人,日期是2024年12月25日。"
print(run_llm_agent(user_request_3))
print("n===== 演示4: 过去或今天日期 =====")
user_request_4 = "帮我预订一张从北京到上海的机票,1个人,日期是今天。"
print(run_llm_agent(user_request_4))
#### 3.3.1 提示词核心结构分析
让我们分解`run_llm_agent`函数中构建的提示词:
1. **系统角色与通用指令:**
你是一个航班预订助手,能够帮助用户预订航班。
请根据用户的请求,使用下方提供的工具。
...
你必须严格按照上述工具的参数要求来生成调用。
如果工具返回错误,你必须分析错误信息,理解问题所在,然后修正参数并重新调用工具。
在重试之前,请在你的“思考”中说明你为什么修正了参数以及如何修正的。
如果多次尝试后仍无法成功,请告知用户。
```
这部分定义了LLM的身份,强调了工具调用的准确性,并**明确地指示了错误处理的流程和要求LLM进行“思考”**。
-
详细的工具描述 (JSON Schema):
{ "name": "book_flight", "description": "预订航班的工具。参数: ...", "parameters": { "type": "object", "properties": { "origin": {"type": "string", "description": "出发城市,例如:北京"}, "destination": {"type": "string", "description": "目的城市,例如:上海"}, "date": {"type": "string", "description": "航班日期,YYYY-MM-DD 格式,例如:2024-12-25"}, "passengers": {"type": "integer", "description": "乘客数量,正整数,最多5人"} }, "required": ["origin", "destination", "date", "passengers"] } }提供工具的完整功能签名和参数的JSON Schema。这是LLM理解工具及其约束的关键。详细的
description字段尤其重要,它告诉LLM每个参数的语义和一些基本规则(如日期格式、乘客数量限制)。 -
用户请求:
用户请求: 帮我预订一张明天从北京到上海的机票,3个人。保留原始用户意图,确保LLM在修正过程中不会偏离。
-
工具调用历史 (用于上下文和避免重复错误):
Tool Call History: [ { "attempt": 1, "tool_call": { "tool_name": "book_flight", "parameters": { "origin": "北京", "destination": "上海", "date": "明天", "passengers": 3 } } } ]这一部分至关重要。它提供了一个审计追踪,让LLM知道它之前尝试了什么。在更复杂的场景中,这可以帮助LLM避免重复相同的错误,甚至可以用于实现更高级的“禁止列表”策略(即,如果某个参数组合多次失败,就不要再尝试了)。
-
结构化的工具执行错误反馈:
Tool Execution Error (from last attempt): ```json { "status": "error", "message": "参数校验失败。", "details": [ "日期格式错误。请使用 YYYY-MM-DD 格式,例如: 2024-12-25" ] }这是自我修正的“燃料”。我们确保工具的错误返回是JSON格式,包含
status、message和details。details字段尤其重要,它包含了具体的错误原因和明确的修正建议(如“请使用 YYYY-MM-DD 格式”)。LLM可以更容易地解析和理解这种结构化信息,而不是一段模糊的自由文本。 -
明确的修正指令:
请分析上述错误信息,理解为什么工具调用失败了。 在你的“思考”中说明你将如何修正参数,然后生成一个新的工具调用。这再次强化了LLM的行动指令,并鼓励Chain-of-Thought推理,使其在修正前先“思考”再“行动”。
3.3.2 自我修正的LLM内部机制(模拟)
在mock_llm_generate_tool_call函数中,我们模拟了LLM的“思考”过程:
- 错误识别: LLM接收到包含
Tool Execution Error的提示后,会解析JSON格式的错误信息。 - 定位问题参数: 根据
details中的描述,LLM识别出是date参数出了问题,因为它收到了“日期格式错误”的提示。 - 理解修正建议: 错误信息明确指出了“请使用 YYYY-MM-DD 格式”。
- 执行修正: LLM会回顾原始请求“明天”,并结合当前日期和建议格式,将“明天”转换为一个合法的未来日期,例如
2024-12-25。 - 生成新的工具调用: LLM用修正后的参数生成一个新的
book_flight工具调用。
这种“思考-修正-重试”的循环是LLM实现自我修正的关键。
四、高级考量与优化
4.1 语义错误(Semantic Errors)的处理
前面提到的“语义理解错误”是最难处理的。当LLM成功调用了工具,但结果与用户的真实意图不符时,工具返回的是成功信息,没有显式错误。
处理策略:
- 结果确认: 在工具调用成功后,LLM可以尝试向用户总结结果,并询问用户是否满意。例如:“您预订了从北京到上海的航班,日期是2024年12月25日,乘客3人。请问这是您想要的吗?”
- 多模态或多工具交叉验证: 结合其他工具或信息源对结果进行验证。例如,预订机票后,再查询一下该航班的信息,看是否与用户描述一致。
- 用户反馈循环: 在生产环境中,收集用户对LLM行为的反馈,可以用于微调LLM或改进其规划逻辑。
这部分通常需要更复杂的代理设计,涉及到对用户意图的持续跟踪和主动的用户交互。
4.2 重试策略与资源管理
- 指数退避(Exponential Backoff): 在多次重试之间增加等待时间,尤其适用于外部服务暂时性故障。
- 错误类型特定的重试: 对于参数验证错误,LLM应该立即尝试修正并重试;对于外部服务错误,可以尝试几次后放弃或转人工。
- 成本考量: 每次LLM调用都会产生费用。限制
max_retries至关重要,避免无限重试导致成本激增。 - 缓存与记忆: LLM可以记住哪些参数组合是无效的,避免在后续会话中重复尝试。
4.3 动态工具描述与参数建议
- API文档作为上下文: 将更详细的API文档作为LLM的上下文提供,特别是在参数描述中难以完全表达的复杂逻辑。
- 上下文相关的参数建议: 工具可以根据LLM提供的部分参数,动态地提供下一步的建议。例如,如果LLM只提供了出发城市,工具可以反馈支持的目的城市列表。
- Enum或List参数: 对于有明确枚举值的参数,可以在工具描述中直接列出,如
"enum": ["北京", "上海", "广州"],方便LLM选择。
4.4 可观察性与调试
- 详细日志: 记录LLM的每次思考、工具调用、工具响应和重试过程,方便开发者调试和优化。
- 可视化工具: 开发可视化界面来展示LLM的决策路径和工具交互链,有助于理解复杂行为。
五、展望与思考
我们今天探讨的Tool Response Feedback与LLM的自我修正能力,是构建真正智能、鲁棒的AI代理不可或缺的一环。它将LLM从简单的指令执行者提升为具备学习和适应能力的智能体。通过精心设计的提示词,我们能够赋予LLM在面对工具错误时,分析、诊断并自我修复的能力,这极大地增强了LLM在现实世界复杂任务中的应用潜力。随着LLM能力的不断提升和工具生态的日益完善,我们有理由相信,未来的AI代理将能够更加自主、高效地与各种工具进行交互,为人类带来前所未有的智能体验。