如何解决 LLM Function Calling 意图误判和参数缺失?
自由文本的意图识别无法保证始终正确,但应用可以在信息不足时追问,并通过执行前校验,阻止缺少必填参数的请求进入后端函数。建议采用规则与 LLM 结合的路由方式,用显式状态机管理参数收集,并将会话状态保存在 Redis 中。
模型理解用户输入仍有不确定性,但函数的执行条件可以由应用代码确定。
为什么仅靠 Prompt 和对话历史不够
当前实现把意图识别、参数提取、指代消解和执行决策都交给了模型。任何一个环节判断错误,都可能导致错误调用。
对话历史只是模型可读取的文本,不等于应用已经确认的业务状态。此前出现过的 issue_id 不一定是当前操作对象,尤其是对话中涉及多个工单时。
Tool schemas 可以描述参数结构,却不能证明用户确实想执行该操作,也不能保证 issue_id 指向正确对象。不同模型和接口对 schema 的约束能力需要单独确认,服务端仍应校验。
将处理流程拆成明确的阶段
可以按以下顺序组织请求:
- 读取会话状态,判断是否正在等待用户补充参数。
- 用规则处理应用能够明确识别的输入,例如完整命令或结构化界面事件。
- 将剩余自由文本交给 LLM,提取候选意图、参数和指代信息。
- 由应用检查歧义、合并有效参数,并决定是否追问。
- 参数齐全后,检查对象、权限和业务条件,再调用后端。
规则应覆盖边界清楚的情况。例如,如果按钮关联了明确的工单,应用可以直接提供操作对象。匹配自由文本时,要考虑否定、引用和上下文,不能只因出现 approve 就触发审批。
LLM 的输出应作为候选解析结果。允许模型返回“无法确定”或“需要澄清”,不要强迫每条输入都对应一个可执行函数。
对两个示例分别处理
输入 approve
如果当前没有明确的待处理任务,也没有唯一操作对象,应追问:
要审批哪个工单?请提供 issue_id。
如果业务中 approve 还可能对应多种操作,应先确认操作类型。具体追问取决于应用支持的真实业务含义。
如果系统刚刚就某个已确定的操作询问确认信息,可以在该待确认状态下解释 approve。它不应被无条件当成对任意上一轮操作的确认。
输入 make it high priority
这句话可以提取出候选意图 update_priority 和参数 priority=“HIGH”。接下来要确定 it 指向哪个工单:
- 当前存在一个由用户明确选定、仍有效的工单,可以使用该对象。
- 当前有多个候选工单,应让用户选择。
- 当前没有可确认的对象,应追问 issue_id。
不能因为对话里最近出现过一个工单编号,就自动认为它是操作目标。
用 Redis 保存任务状态
建议保存结构化状态,同时保留必要的对话历史。一个简化示例如下:
{
"status": "collecting_arguments",
"pending_intent": "update_priority",
"slots": {
"priority": "HIGH"
},
"missing_fields": ["issue_id"],
"active_issue_id": null,
"revision": 3
}
在这个状态下,系统已知道用户想修改优先级,但还缺少 issue_id。如果用户在下一条消息中提供工单编号,应优先将它作为待补参数。
同时要允许用户取消、更换目标或开始新任务。例如,用户回复“算了,先查一下状态”时,应识别出任务切换,不能把整句话填进 issue_id。
状态设计需要覆盖:
- 待处理意图、已确认参数、缺失参数和当前追问。
- 当前对象的来源,例如用户明确选择或明确提供的编号。
- 任务完成、取消和过期后的清理规则。
- 会话隔离与并发更新,避免不同请求覆盖彼此状态。
Redis 负责保存状态,状态转移规则、参数合并和冲突处理仍需由应用实现。FastAPI 并发处理请求时,可以通过版本检查或其他原子更新机制维护状态一致性。
在函数调用前设置强制校验
下面的简化示例仅使用 Python 标准库,假设这两个函数的 issue_id 都是非空字符串,update_priority 还需要 priority。实际字段和允许的优先级必须与后端契约一致。
REQUIRED_FIELDS = {
"approve_completion": {"issue_id"},
"update_priority": {"issue_id", "priority"},
}
def validate_call(name, arguments, allowed_priorities):
if not isinstance(name, str) or name not in REQUIRED_FIELDS:
raise ValueError("Unsupported function")
if not isinstance(arguments, dict):
raise ValueError("Arguments must be an object")
required = REQUIRED_FIELDS[name]
missing = required - arguments.keys()
if missing:
raise ValueError("Missing fields: " + ", ".join(sorted(missing)))
# 此简化示例只接受上面声明的字段。
if arguments.keys() - required:
raise ValueError("Unexpected fields")
issue_id = arguments["issue_id"]
if not isinstance(issue_id, str) or not issue_id.strip():
raise ValueError("Invalid issue_id")
if name == "update_priority":
priority = arguments["priority"]
if not isinstance(priority, str):
raise ValueError("Invalid priority")
if priority not in allowed_priorities:
raise ValueError("Unsupported priority")
return dict(arguments)
参数应先从已确认状态和本轮有效输入中完成合并,再进行校验。缺少 issue_id 时,编排层应追问并结束本轮执行,不能继续调用后端,也不能让模型编造编号。
这段代码只演示结构和取值校验。生产实现还需要验证工单是否存在、用户是否有权限,以及当前业务状态是否允许操作。即使参数完整,也可能选错函数或操作错对象,因此参数校验不能替代意图和对象确认。
降低重复执行和错误路由的影响
对会修改状态的操作,应根据业务风险决定是否增加确认步骤。重试需要配合后端幂等机制,避免超时后重复审批或重复修改。
可以降低采样随机性、明确工具说明,并在工具描述中写清哪些情况需要澄清,但这些措施不能保证语义判断完全确定。模型自报的置信度也不应直接作为执行依据。
评估时应分别记录函数是否选对、参数是否提取正确、是否引用了正确对象,以及信息不足时是否进行了追问。测试样本应包含短指令、否定表达、多工单上下文、任务切换和重复请求。
可建立的执行边界是:信息不足、参数不合法,或对象及权限未确认时不执行。自由文本理解仍需保留澄清路径;参数完整也不代表后端调用一定成功,超时和业务拒绝等情况需要独立处理。
备注:内容仅供参考。
