ReAct框架作为智能体的全能解决方案,通过每一步执行“思考”、“行动”、“观察”、“响应”的循环,有效解决用户问题。 然而,这一看似完美的流程背后却隐藏着一个致命缺陷,如果在实际生产环境中应用,必将导致失败。
要深入理解ReAct的这一弱点,我们需从LLM的温度参数入手。
温度
模型的运行过程:通过用户的输入,文字的陆续输出。
其中影响LLM输出的一个重要参数是:温度。
温度较小的时候,输出比较固定,温度较大的时候,创造性比较强。
比如input是“我爱”,输出的词可能是“中国”、“吃”、“股票”、“苹果”。 当温度大的时候,输出内容的选择范围就较大,比如设置范围为1,LLM会在“中国”、“吃”、“股票”、“苹果”之间随机选择。 当温度小的时候,输出内容的选择范围就较小,比如极端的情况下:温度设置为0,则输出的范围只有1个词:“中国”。
这就是温度的作用。
概率模型
了解温度后,可以发现,其实大语言模型(LLM)是一个概率模型,它计算在给定输入的情况下输出结果的概率。如果我们希望模型每次输出的结果更加固定,可以将温度设置为0。
为了简单描述,咱们温度假设都设置为0,最模型的输出就都由输入决定了,不同的输入决定了不同的输出,因为温度是0,所以相同的输入就会有相同的输出。
所以先不管LLM究竟学到了什么,它终究还是一个概率模型。
那就引出了我们今天的话题:这对智能体有什么影响呢?
智能体-ReAct
从0实现function call的文章中[1]我提到过ReAct的提示词如下:
system="""
你在运行一个“思考”,“工具调用”,“响应”循环。每次只运行一个阶段
1.“思考”阶段:你要仔细思考用户的问题
2.“工具调用阶段”:选择可以调用的工具,并且输出对应工具需要的参数
3.“响应”阶段:根据工具调用返回的影响,回复用户问题。
已有的工具如下:
get_weather:
e.g. get_weather:天津
返回天津的天气情况
Example:
question:天津的天气怎么样?
thought:我应该调用工具查询天津的天气情况
Action:
{
"function_name":"get_response_time"
"function_params":{
"location":"天津"
}
}
调用Action的结果:“天气晴朗”
Answer:天津的天气晴朗
"""
这个ReAct的流程是: 思考->行动->观察->响应。。 这样一个循环,例如:
用户提问: 我想查询一下天津的天气
LLM:我思考一下用户的问题,这个应该可以调用工具get_response_time
调用工具。。。
LLM:调用这个工具后,结果是:“天津天气晴朗”
LLM:用户你好,天津的天气晴朗。
这个ReAct最大的问题是,每轮对话都需要LLM先做一次路由,这个路由的功能包括了:思考用户的意图、选择工具等。。(路由就是下图中的LLM-ReAct节点)
这里最大的问题就在这个路由上了,它判断和思考的负载太大了,这个流程如下:
大部分的判断和思考逻辑都卡在这一个LLM-ReAct上。
那它带来的问题是什么呢?
问题1-创意性与工具选择的悖论
如果用户来问一个问题,我希望LLM通过给调用工具后,可以给用户一个更有创造性的回答。 但是很可能做不到,如果我们把工具和输出的文字都放到概率模型上,结构图是这样的:
我们希望温度调试的低一些,如果这样,LLM在选择的时候,就可以选择到正确的工具去调用,而不是输出文字。 但是当我们把温度调低了之后,输出的文字就不在那么有创造性了。
问题2-工具箱选择漂移
上边的ReAct我们只给了它一个工具来调用,如果想象一下,我们给ReAct 50个工具甚至100工具。 这里边的input,就会有100个工具的描述以及用户的提问输入。 这里边的挑战是:
- 需要一个训练良好的LLM,可以很好的区分100个工具描述的细微差异。
- 需要用户的提问输入要特别清晰,因为清晰的输入可以让LLM进行对用户的意图以及工具选择做出很好的判断。
尤其第二点,你根本无法要求用户输入的提问时清晰的,不清晰的提问会导致错误的工具选择,甚至,会导致LLM进入不断的错误循环中。
思考ReAct设计
ReAct的设计初衷是希望构建一个高级抽象的结构,旨在实现一个无所不能的智能体框架,包括思考、工具调用和用户交互等功能。 然而,越是抽象的结构,离实际应用落地就越远。 因为它试图用一个框架满足所有智能体的需求。这就像我们看到很多SaaS平台,它们抽象了很多通用功能,确实很好用,但如果要应用于具体场景中,就必须增加一些特定功能的开发。这也是为什么公司会与许多SaaS平台合作,对SaaS功能进行优化后,补充一些公司特定的需求,以实现与具体场景的对接。 因此我们思考后得出结论:如果使用ReAct,做一些Demo或者特别简单的智能体是可以的。但要让它更贴近业务、满足复杂的业务需求,还需要其他方式来实现。
智能体构建的新思考
场景细分
第一个要思考的事情是:构建的业务场景能不能进行细分。拆分成不同的场景,每个场景只关注一个最小的业务。
之前我们搞过一个RAG应用,目标是把所有的数据放到向量库中,然后通过向量检索全库搜索所有数据,给用户最详细的回答,让搜索没有盲区,搜什么都有。 显然这个应用是失败的,因为不可能将TB级别的数据通过向量检索就完全解决了,有太多语义上相似的chunck但是不相关的chunck出现,后来我们增加了很复杂的意图识别通过数据类别进行分类。这些一切都行不通。
原因在于:你需要将你的应用按照场景细分,而不是数据细分。
比如,对于同样都是医学类数据,科研人员和药店柜员要求的准确程与深度是截然不同的。
构建智能体也是一样的,不是把所有可能会用到的工具都扔给智能体(ReAct)就可以搞定的
要思考:你构建的是什么场景?目标人群是谁?
流程拆分
场景细分之后,要考虑:流程是否可以拆分。不要完全让智能体帮你判断流程走向。
我做过一个场景应用:患者过来问诊,智能体先收集患者的信息,然后根据这些信息做药品下单。
这两步流程我通过ReAct来实现的,大概是让它智能的判断是否已经完全收集患者信息了,如果是就总结相关内容调用function call下单。 它的智能判断让我瞠目结舌: 有时候患者没说完话就下单了,有时候患者说了很多明确表达说明了,还不下单。。 这个部署在生产环境上,肯定是不过关的。
如果将流程拆分出来,不让智能体React来判断,而是我通过固定的编码流程处理患者信息的手、让用户确认、然后在下单。改造之后的流程就完全没有问题了。
多智能体合作
思考过这样的问题吗:coze、dify这些智能体工作流框架已经很好了,为什么还要进行代码开发?尤其是随着越来越多的智能体框架出现,再加上OpenAI也开源了自己的多智能体swarm框架。
原因是:当场景细分、流程拆分后。 要将他们拼凑起来才可以完成更复杂的业务场景。只有工作流的话,简单场景没问题,复杂场景则开发的灵活度就较差了。
像乐高积木一样:先把乐高的各个大部分划分好,逐个搭建好,然后将大块的部分组合起来,这个乐高就有更复杂的功能或者构造了。
写在最后
ReAct的缺点是: 让一个LLM做了太多的事情了,尤其是在意图识别时会有产生很多偏差。
解决方案:
- 不要按照数据拆分,按照场景拆分,先搞定最细粒度的场景。
- 对小场景中的流程进行细致的节点拆分,将固定的流程先行标准化,从而降低大模型在选择过程中出现的不稳定性。
- 逐步叠加小场景,通过多智能体的协同合作,最终构建出完整的大场景。
参考文献
[1]# 深入理解Agent:从0实现function call:mp.weixin.qq.com/s?__biz=MzU…
[2]乐高ev3: www.lego.com/zh-tw/theme…