大模型落地差异分析:智能问答→RAG→Agent的提示词结构对比

321 阅读9分钟

针对当下大模型比较成熟的几种应用模式,包括智能问答、RAG、Agent、Agent+MCP等等,大家理解时容易陷入两种极端:

当你刚开始入门时,看到这些概念一定很混淆,往往把大模型LLM想的很神奇,感觉它什么都能干,什么业务场景都能用。

当你通过cherrystudio或dify等工具,按照网上一些教程来实现过一些场景时,往往又会感觉很僵硬,只会照着做,并没有理解大模型LLM的本质。

本文以容易理解的信息化视角,将LLM视作一个黑盒的文字系统,通过比较输入提示词+输出成果在几种LLM应用模式下的不同,来分析这些应用模式的差异。

个人觉得哪怕我们对LLM内部的encode、decode、transformer、attention、MoE等概念和架构一知半解、似懂非懂,也不妨碍我们从使用角度去理解不同场景下LLM的应用模式。

最基础的LLM调用提示词(系统指令+用户输入)

一些最基础的问答场景中,你会要求LLM模仿某种角色来回答用户的提问,或者要求LLM根据用户的输入来生成相应的内容,同时要求遵守某种结构进行生成,并做一些限制(比如不能随意发挥)等。

以上这些要求,其实就对应LLM的系统提示词,在Dify中配置界面大致如下:

上图system系统提示词就对应高阶指令,它是用来告诉LLM要如何响应用户诉求,要输出何种格式等等,是由LLM应用开发者预先设置好的,所以它被称之为system系统级指令,而user提示词对应是用户实际输入信息,LLM在微调和强化过程中就被要求,要优先遵循system系统指令。其底层实现逻辑可以这样理解:

类似dify和coze这类工具底层,实际也是通过API方式访问LLM的,以适配OpenAI的接口规范为例,在这种最基础场景下,具体访问LLM的输入参数结构是:

可以看到输入该服务的messages包括两部分组成,一个是开发者提前设置好的role:system指令内容,一个是用户实际输入问题role:user内容。

既然通过信息化视角来理解,那有输入就必然有输出,进一步看看LLM服务的输出格式:

在choices/message结构内部,role:assistant这个节点的作用,是用来标记大模型输出信息(为啥要这么标记下,后面会讲),然后才是具体LLM生成内容的content节点。

多轮对话提示词(增加上下文的输入)

以上是最基础的一次对话过程,如果在LLM的反馈基础上,你还要反复进行追问,也就是要实现多轮对话,其底层实现逻辑是:

如图,核心就是要把上一轮对话结果也作为提示词的一部分输入给LLM,具体输入参数结构是:

这段输入示范中有两个关键点,一是把上一轮对话的提示词再次输入,二是把上一轮对话大模型的反馈信息也再次输入,对应就是上一节LLM输出的role:assistant这个节点的内容。

之所以要把历史对话上下文再次输入给LLM,是因为LLM本身是没有记忆的,它永远是依据输入信息进行内容生成的,所以为了实现多轮对话,是需要智能体Agent负责做好上下文记忆,并在下一轮对话时,将前序对话内容也放到输入参数中。

看到这里有同学就要问了,反正就是把历史对话作为输入嘛,那么LLM是不是可以无限制的多轮对话呢?然而实际各种APP中通常都有对话轮数的限制,有两个原因,一是LLM都有tokens限制,每轮对话都叠加前面对话的上下文,后面会越来越长,超过LLM的tokens限制就会被截断。

二是即使有些LLM可以支持很大tokens,甚至有百万级tokens的,但你上下文对话信息很长时,还是会干扰LLM的注意力,就和人脑一样,这个时候它对你最新问题的回答就会产生偏差。

所以业界对提示词工程有个经验就是,写的越清晰、简明、扼要,LLM理解得越好,很多新手常见的问题,就是以为提示词要写得越详细越好、越长越好,殊不知LLM注意力也会发生偏差。

RAG方式提示词(增加检索知识的输入)

在以上利用LLM进行对话的基础上,当你发现LLM缺少最新知识、或者缺乏某个行业领域内部知识时,通常就需要采用RAG方案了,也就是要通过检索外挂知识库来增强LLM的生成能力,其底层实现逻辑是:

其核心就是要把知识检索到的内容,也作为提示词的一部分输入给LLM,对应输入参数结构是:

本质就是把检索到的知识片段,也放到role:user的content中,让LLM知道这些背景知识,其实和用户直接输入信息是一样的,所以还是用role:user这个类别,对于LLM来说它压根不用知道知识库的存在。

所以并不是所有需要用到知识的地方,都要使用向量化知识库去做分片向量存储和语义相似度检索的,也并不是LLM对向量知识库有什么特殊依赖,这些组合方案都只是一种工程化范式而已,大多数场景可以参照这种方案去落地而已。

比如一些简单场景要用到固定的几条知识,完全可以不用分片,不向量化,不用检索,每次调用LLM时,都自动附加到role:user中,输入给LLM就可以了。

工具调用提示词(早期functions方式)

在以上调用LLM的基础上,如果你还需要在AI Agent中进一步调用工具,比如要调用工具查询数据库,比如要根据用户意图,调用查询天气、订票的工具等,其底层实现逻辑是:

其核心就是要把可以提供的工具描述信息,也作为提示词的一部分输入给LLM,对应输入参数结构是:

对应图中,在之前messages节点同级,增加了一个functions输入节点,对应工具或函数的描述信息,包括函数名称、和对应入参信息,注意这里functions节点是个数组,意味着可以输入多个可用的工具描述信息,供LLM选择和判断。

和前面几个小节略有不同的是,之前system、user、assistant等role的输入,都是在messages这个节点里面,而这里funtions节点则是和messages节点同级并列。

对于这种有工具描述的输入,LLM因为进行相应的训练,所以会根据用户的问题,判断是否需要调用工具,以及要调用哪个合适的工具,最终在LLM的输出信息中反馈要调用的工具,其对应输出结构是:

对这段输出结构于之前的基础对话中LLM的输出结构做个对比,对应的role还是assistant,但content为null,额外增加了funtion_call的节点。

你的AI Agent中通过解析LLM的这个反馈信息,就可以得到要调用的函数名和对应要用到的参数值,再去执行这个函数即可。

工具调用提示词(后来改成tools方式)

以上是LLM早期的function call机制,自动24年底Anthropic推出MCP协议以后,各厂商最新推出的LLM,经过强化训练后,也都支持标准的工具调用模式,其底层实现逻辑和上节funcnion call机制基本类似,但具体输入参数结构有所调整:

输入结构看起来变化不大,就是把functions节点换成了tools节点,同样也是一个数组。但LLM的输出结构就变化比较大了:

输出结构中用tool_calls节点替代了function_call节点,其最大变化是从原来输出单个function,变为现在输出tool数组,意味着LLM认为根据用户意图,有可能需要调用一组工具,所以通过数组方式输出。

当你的AI Agent需要根据工具调用结果,让LLM决策下一步的动作时,也就是笔者之前在有关ReAct模式自主决策AI Agent文章中介绍的底层实现逻辑,就需要将调用结果再次输入给LLM,其对应输入参数结构是:

和前面提到的多轮对话场景的输入有点类似,也是通过在role:assistant节点中的调用工具信息,以及role:tool节点中将工具调用结果信息,反馈给LLM的。

当然实际使用MCP框架时,输入给LLM的tools数组不用手动组织了,都是框架会自动从mcp server中拉取工具描述信息,包括调用工具的环节,也是在mcp client的sdk中封装好了。

图片

延伸阅读:在以上工具调用场景中,有一些比较极端复杂的场景,假如要输入给LLM可用工具数量很多,意味着要输入很多的描述信息列表,你会发现提示词会变得很长,如果再结合ReAct机制多轮次调用,那就很难避免prompt bloat提示词膨胀,这会导致LLM大量消耗tokens,同时降低LLM处理速度,而且会造成LLM注意力不集中,很可能给出错误的tool选择。

这里推荐一篇文章《RAG-MCP:突破大模型工具调用瓶颈,告别Prompt膨胀》,它是通过将工具描述信息放到知识库中,每次按需检索要用的工具信息再输入给LLM,这样可以有效规避这个问题。

文章总结:本文是针对基础对话模式>RAG应用模式>Agent应用模式,基于Prompt视角分析这些模式下对LLM使用的区别,以帮助读者更好理解这些模式的底层实现逻辑。