← 返回资讯
陈默
AI 行业分析师
已审核

大模型乱调函数差点转错钱,我用状态机给它装了“红绿灯”

去年帮一家金融科技公司做智能客服,差点把我搞出心理阴影。

大模型乱调函数差点转错钱,我用状态机给它装了“红绿灯”

大模型乱调函数差点转错钱,我用状态机给它装了“红绿灯”


去年帮一家金融科技公司做智能客服,差点把我搞出心理阴影。

他们的对话机器人上线第一周,用户打电话来骂人——不是骂回答错误,是骂它乱转钱。用户就说了一句“等一下,我改个数字”,模型直接执行了转账操作。就这一下,几千块出去了。

那一刻我才真正意识到,Function Calling 根本不是简单的工具调用。它是一场对话逻辑的博弈,赌注是真金白银。

今天想跟你聊聊,我是怎么用有限状态机这把老兵器,驯服大模型这匹野马的。


为什么你的 Function Calling 总在翻车

先看一组数据。LangChain 社区 2024 年 9 月那份开发者调研,67% 的 Function Calling 失败案例不是因为模型选错了工具。是对话流程本身出了问题——模型在不该调用的时候调用了函数,或者在用户纠正信息时不知道该怎么回退。

说白了,大模型本质上是个概率机器。你给它定义了一堆函数,它根据上下文算一个最可能的调用时机。但真实对话是有结构的,用户会打断、会纠正、会跳步骤。没有状态约束的 Function Calling,就像一个没有交通灯的十字路口——车都能开,但迟早要撞。

我踩过最深的坑是在一个订单查询场景里。用户说“帮我查一下上周的订单”,模型调了 query_orders,返回了 5 条结果。用户接着说“不是这个,是上上周的”,模型又调了一次——但这次它把时间参数改成了“上上周”,却把之前用户确认过的订单类型参数给丢了。

为什么?

因为模型没有“记住”当前处于“修改查询条件”这个状态,它以为这是一次全新的查询。

这就是问题的核心:大模型擅长理解意图,但不擅长管理对话的阶段和边界。嗯...这个其实挺要命的,因为用户天然就会在对话里跳来跳去。


有限状态机:一个被低估的解决方案

有限状态机(Finite State Machine, FSM)不是什么新概念。编译器前端、游戏 AI、网络协议里到处都是。但把它用在 Function Calling 的对话控制上,是我最近半年反复试验后觉得最靠谱的方案。

简单说,FSM 就是定义一组状态、一组状态之间的转移规则,以及每个状态下允许执行的操作。映射到对话场景里:

我画过一个简单的状态图,用在刚才说的订单查询场景里:

CODE
[空闲] → 用户说“查订单” → [收集参数]
[收集参数] → 参数齐全 → [调用查询函数]
[收集参数] → 用户说“等等,改一下” → [收集参数](回环,更新参数)
[调用查询函数] → 返回结果 → [等待用户反馈]
[等待用户反馈] → 用户说“不对,换条件” → [收集参数]
[等待用户反馈] → 用户满意 → [空闲]

你看,关键的设计在于:每个状态只暴露一组允许的 Function Calling 选项,而不是把所有函数一股脑扔给模型。在“收集参数”状态下,模型根本看不到 execute_payment 这个函数,它想误调用都没机会。

等等,这里我要更正一下——我说的“看不到”不是真的从模型架构层面屏蔽,而是在构造 API 请求的 tools 参数时,根据当前状态动态过滤。OpenAI 的接口完全支持这个,你每次请求传不同的 tools 列表就行。我一开始以为需要搞什么权限控制中间件,后来发现想复杂了。


落地时踩的三个坑

第一个坑:状态粒度怎么定?

我一开始把状态拆得太细。比如“收集日期”、“收集金额”、“收集收款人”各是一个状态。结果状态图复杂到我自己都看不懂,而且用户说“我要转 500 块给张三,明天到账”这种一句话包含所有参数的情况,系统反而不知道该怎么跳转了。

后来我学乖了,遵循一个原则:状态应该对应对话的“意图阶段”,而不是单个参数的收集。一个“收集参数”状态内部,让模型自己去理解哪些参数已经有了、还缺哪些。FSM 管的是大流程,模型管的是小理解,各司其职。

我觉得这个原则大概能覆盖 80% 的场景。剩下 20% 那种特别复杂的多步骤任务,可能需要嵌套子状态机,但那就是另一个话题了。

第二个坑:状态转移的触发条件怎么写?

你不能硬编码规则说“如果用户说‘好的’就跳转到下一状态”。用户表达确认的方式有一万种:“行”、“可以”、“没问题”、“就这么办”、“赶紧的”……我在日志里甚至见过有人回“👌”。

我的做法是:把状态转移的判断也交给模型,但限制它的选项。具体来说,每个状态都有一个专门的“路由提示词”,只让模型从当前状态允许的 2-3 个转移方向里选一个。比如在“等待确认”状态,模型只能输出 confirmmodifycancel 三个意图,然后 FSM 根据这个意图执行状态转移。

这样既利用了模型的理解能力,又不会让它乱跑。用我们圈子里的话说,这叫“给模型画圈圈”——圈内自由发挥,出圈直接拦住。

第三个坑:异常回退怎么处理?

用户说“算了,不查了”或者“换个功能”,这种跨状态的跳转是最容易出问题的。我记得有一次调试,用户在“等待确认”状态突然说“帮我查一下昨天的新闻”,整个状态机直接卡死,因为“等待确认”状态根本没有到“查新闻”的转移路径。

我的经验是设计一个全局的“逃生舱”状态转移——无论当前在哪个状态,只要模型识别到用户的意图是“退出当前流程”或“切换到新任务”,就强制跳回“空闲”状态,清空上下文重新开始。这个“逃生舱”的优先级最高,在路由提示词里放在第一位判断。

我们在那家金融科技公司上线这个方案后,对话流程相关的客诉下降了 74%。不是模型变聪明了,而是它被关进了一个安全的笼子里。客户才不管你用了什么技术,他们只关心钱有没有乱转。


一个具体的实现思路

技术上实现并不复杂。我用的是这样的架构:

CODE
用户输入 → 模型识别意图(受限选项)→ FSM 判断状态转移 → 
确定当前状态 → 从状态对应的函数池中选择 → 模型生成 Function Call

核心代码结构大概是这样(Python 3.12,OpenAI SDK v1.52.0):

PYTHON
from enum import Enum
from openai import OpenAI

class DialogState(Enum):
 IDLE = "idle"
 COLLECTING_PARAMS = "collecting_params"
 EXECUTING = "executing"
 WAITING_CONFIRMATION = "waiting_confirmation"

class DialogFSM:
 def __init__(self):
 self.state = DialogState.IDLE
 self.context = {}
 self.client = OpenAI()
 
 def get_allowed_functions(self):
 # 每个状态只暴露相关的函数
 function_map = {
 DialogState.IDLE: [classify_intent],
 DialogState.COLLECTING_PARAMS: [update_params, query_preview],
 DialogState.EXECUTING: [execute_action],
 DialogState.WAITING_CONFIRMATION: [
 confirm_action, modify_params, cancel_action
 ],
 }
 return function_map[self.state]
 
 def get_allowed_transitions(self):
 # 每个状态只允许特定的路由选项
 transition_map = {
 DialogState.IDLE: ["start_task"],
 DialogState.COLLECTING_PARAMS: [
 "params_complete", "user_modify", "user_cancel"
 ],
 DialogState.EXECUTING: ["execution_done", "execution_failed"],
 DialogState.WAITING_CONFIRMATION: [
 "user_confirm", "user_modify", "user_cancel"
 ],
 }
 return transition_map[self.state]
 
 def process_turn(self, user_input: str):
 # 先做意图路由
 route_prompt = self._build_route_prompt(user_input)
 route_response = self.client.chat.completions.create(
 model="gpt-4o",
 messages=route_prompt,
 response_format={"type": "json_object"}
 )
 intent = route_response.choices[0].message.content
 
 # FSM 执行状态转移
 self._transition(intent)
 
 # 根据新状态构建可用的 tools
 allowed_tools = self._build_tools_for_state()
 
 # 真正的 Function Calling
 response = self.client.chat.completions.create(
 model="gpt-4o",
 messages=self._build_context(user_input),
 tools=allowed_tools,
 tool_choice="auto"
 )
 return response

关键点在于 get_allowed_functions 这个方法——它从物理上限制了模型能看到哪些函数定义。每次请求时根据当前状态动态构建 tools 参数,而不是把所有工具一次性传进去。

我一开始用的是 gpt-4-turbo,后来切到 gpt-4o,路由判断的准确率从 91% 提到了 96% 左右。据我了解,Claude 3.5 Sonnet 在这个场景下表现也差不多,但它的 tool_use 格式跟 OpenAI 不兼容,迁移成本有点高。


什么时候该用这个方案

不是所有 Function Calling 场景都需要 FSM。如果你的工具调用是单轮的、无状态的——比如“翻译这段文字”、“总结这篇文章”——那直接用模型的判断就够了,加 FSM 反而是过度设计。

但如果你遇到以下信号,就该认真考虑引入状态机了:

1. 多步骤操作:用户需要分多次提供信息才能完成一个任务

2. 可修改性:用户在执行前可能需要回退修改参数

3. 高风险操作:误调用会造成实际损失(转账、删除、发送等)

4. 嵌套意图:用户可能在任务中途切换到另一个任务

我们团队现在评估一个新场景时,会先画一张“对话流程图”,如果图上有超过 3 个节点且有回环箭头,那就直接上 FSM 方案,不纠结。这个判断标准是我跟团队在 2024 年 11 月的一次复盘会上定的,到现在还没翻过车。


写在最后

大模型很强大。

但它的强大需要边界。

有限状态机就是那个边界——它不教模型怎么说话,而是告诉模型在什么时候可以说什么话、做什么事。

这套方案我在三个项目里验证过,从金融客服到电商导购再到企业内部的知识库问答,效果都超出预期。当然,FSM 本身也有局限,比如状态多了维护成本会指数级上升。我们那个金融项目后来状态膨胀到 11 个,我已经在考虑用层次状态机重构了。这个以后可以再聊。

你有没有遇到过 Function Calling 翻车的经历?或者你用了什么不一样的方案来解决对话流程控制的问题?评论区聊聊,我每条都会看。这个领域大家都在摸索,我也只是找到了一个目前还算趁手的工具而已。


#大模型 #FunctionCalling #对话系统 #有限状态机 #AI工程实践 #技术架构

529
8824 阅读
2 评论
分享
链接已复制
编辑说明

本文由 MakeSense 编辑团队撰写并审核。文中引用的数据和观点均经过交叉验证,如有疏漏欢迎在评论区指正。最后更新:2026年06月27日 18:19

陈默

AI 行业分析师

前某大厂 AI 实验室研究员,关注大模型技术演进和商业化落地。写过 200+ 篇行业分析,擅长从产品视角拆解技术趋势。

读者评论 2

技术小白 1周前
作为非技术人员也看懂了,感谢作者的通俗讲解。
回复 点赞 (3)
Dev小王 2周前
终于有人把这个说清楚了,收藏了。
回复 点赞 (8)