在上一篇文章LLM在现实中的赋能 ,提到了很多LLM的衍生工程,agent、RAG,目的都是为了让LLM能够给出接近用户问题的答案的回答。对于RAG是什么以及怎么做到的我已经写了一篇文章 来介绍的,这里再给大家具体介绍一下agent是如何给LLM加上决策的翅膀。在本文中,我主要会介绍一下agent的运作原理以及一些agent在实施过程中的需要注意的关键问题,最后再基于当前主流的框架 langchain
和 llamaIndex
介绍agent的实现细节。
agent框架运作 接下来,我将介绍一下agent的原理以及我认为的其中几个比较重要的关键点
原理 )众所周知的是,LLM都是训练好的模型。训练的过程是将已有的材料数据输入到编写好的模型中,通过多轮训练形成最后输出模型。例如训练好的 deepseek-chat
、deepseek-reasoner
、gpt-o1
等,但是这意味当用户给出了输入之后,LLM只能通过已有的知识给出输出。所以原有的交互过程入下图所示: ) Agent 的出现部分是为了解决 LLM 静态知识不足的问题,但更核心的价值在于让 LLM 能够通过外部工具和环境交互来扩展能力。Agent 框架使 LLM 在一定程度上表现出自主性、适应性、感知能力和目标导向特征。其本质是将 推理(Reasoning) 与 行动(Acting) 结合,使模型能够根据推理过程决定如何调用工具并利用结果,从而生成更合适的响应。 通过查看当前主流框架 langchain
和 LlamaIndex
的分析,发现两者都采用了 Thought/action/observation
的思想,在框架中都有自己的输出模版
LlamaIndex的输出prompt
Langchain的prompt 所以根据上述两个框架的实现,agent最终又落到了prompt engineer上
agent什么时候会停止当前的loop 每个框架中都会设置一个LLM搜索答案的最大深度,如果超过最大深度或者LLM自身认为它得到了最终的答案,就会将这些结果整理并输出给用户。
为什么agent给出准确的答案 因为在使用LLM的时候,所有的框架都会给他们一段系统级别的prompt,在这些prompt中会告诉他可以使用一些注册好的工具,他可以自主决策是否要使用工具。比如出现了一些,我想要查找当前最新的xxx的时候,LLM就是在thoughts中输出 考虑到用户需要了解最新的xxx,我应该先调用search tool
,其中 调用search tool
就是Action,然后外部工具又会给LLM输入最为observation,这样反复的循环,最终在可以介绍的循环范围内,停止LLM的交互,将结果返回给用户。经过这样多轮的推理,就可以降低AI幻觉的出现,给予用户较为准确的答案。
1 2 3 4 5 6 7 8 9 10 11 12 "The output should be in one of the following formats:\n" "1. To call a tool:\n" "```\n" "Thought: <thought>\n" "Action: <action>\n" "Action Input: <action_input>\n" "```\n" "2. To answer the question:\n" "```\n" "Thought: <thought>\n" "Answer: <answer>\n" "```\n"
影响最终token产成速度 在现实中有很多信息会影响最终数据的生成,这里不会关心如何解决这些问题,但会把可能导致效率低下的问题列举出来
当前工具和LLM接口调用的返回速度较慢
当前LLM的推理速度比较慢
prompt构建的不到位,导致LLM不能较好的理解用户的意图,或者长度过长
react的次数较多
如何实现一个最小化的agent 实现细节 首先我们来拆分一下这个任务,前面已经详细的分析了agent的原理,就是 Thought/Action/Observation
。拆分成具体的任务就是
Thought,将user context+prompt向LLM输入,得到LLM的决策结果
Action,使用工具
Observation,获得工具的结果,决策工具获取的结果是否能够满足用户的需求 👆🏻三个步骤具体化如下
Thought,使用不同的对应的sdk调用LLM,定义system prompt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 REACT_PROMPT_TEMPLATE = f""" 你是一个可以使用工具的智能助手。你可以使用以下工具: {TOOL_DESCS} 请严格按以下格式回答(每次只输出一个 Thought/Action/Final Answer): Thought: 你当前的思考 Action: 要使用的工具名,必须是 [{TOOL_NAMES} ] 之一 Action Input: 工具的输入参数,必须是合法 JSON Observation: 工具返回的结果(由系统填充,你无需输出) ...(这个 Thought/Action/Observation 可以重复 N 次) Thought: 我现在可以回答了 Final Answer: 你的最终答案 开始! Question: {{input}} {{history}} """ def call_llm (prompt: str ) -> str : """调用 OpenAI LLM(你也可以替换为本地模型)""" from openai import OpenAI client = OpenAI(api_key=os.getenv("OPENAI_API_KEY" ), base_url="https://api.deepseek.com" ) response = client.chat.completions.create( model="deepseek-chat" , messages=[{"role" : "user" , "content" : prompt}], temperature=0.0 , max_tokens=512 , stream=False ) return response.choices[0 ].message.content.strip()
Action,解析LLM给出的输出,是否包含一些特殊的关键字,从而做出对应的动作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 def parse_react_output (text: str ) -> Dict [str , Any ]: """解析 LLM 输出,提取 Thought / Action / Action Input / Final Answer""" lines = text.split("\n" ) result = {"type" : None , "content" : None , "action_input" : None } for line in lines: if line.startswith("Thought:" ): result["type" ] = "thought" result["content" ] = line[len ("Thought:" ):].strip() elif line.startswith("Action:" ): result["type" ] = "action" action_name = line[len ("Action:" ):].strip() result["content" ] = action_name elif line.startswith("Action Input:" ): try : result["action_input" ] = action_input except : result["action_input" ] = line[len ("Action Input:" ):].strip() elif line.startswith("Final Answer:" ): result["type" ] = "final_answer" result["content" ] = line[len ("Final Answer:" ):].strip() return result
Observation,将用户的输入以及调用工具获得结果,再次输入给大模型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 def run_react_agent (question: str , max_steps: int = 5 ) -> str : history = "" for step in range (max_steps): prompt = REACT_PROMPT_TEMPLATE.format (input =question, history=history) llm_output = call_llm(prompt) print (f"LLM 输出:\n{llm_output} \n{'-' *50 } " ) parsed = parse_react_output(llm_output) if parsed["type" ] == "final_answer" : return parsed["content" ] elif parsed["type" ] == "action" : action_name = parsed["content" ] action_input = parsed["action_input" ] tool = next ((t for t in TOOLS if t.name == action_name), None ) if not tool: observation = f"Error: 工具 '{action_name} ' 不存在" else : try : if isinstance (action_input, dict ): observation = tool.func(**action_input) else : observation = tool.func(str (action_input)) except Exception as e: observation = f"Tool error: {str (e)} " print (f"[工具调用] {action_name} ({action_input} ) → {observation} " ) history += f"\nThought: {parsed.get('content' , '...' )} " history += f"\nAction: {action_name} " history += f"\nAction Input: {json.dumps(action_input, ensure_ascii=False )} " history += f"\nObservation: {observation} " print (f"[Step {step + 1 } ] 历史:\n{history} \n{'-' *50 } " ) else : history += f"\nThought: {parsed.get('content' , '...' )} " return "达到最大步数,未能得出最终答案。"
测试
这里我给定的问题是 周杰伦最新的专辑是什么?
用上述agent生成的结果如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 python ./minimum_agent.py [Step 1] 调用 LLM... LLM 输出: Thought: 我知道周杰伦是一位著名的歌手,但我不确定他最新的专辑是什么。为了提供准确的信息,我应该搜索最新的相关新闻或官方发布。 Action: Search Action Input: {"query": "周杰伦最新专辑"} -------------------------------------------------- [工具调用] Search({'query': '周杰伦最新专辑'}) → 直接回答: Zhou Jilun's latest album is expected in August or September 2025. He recently released a new song titled "即兴曲" in June 2025. His previous album, "最伟大的作品," was released in July 2022. 搜索结果 (5 条): 1. [时隔3年,继《最伟大的作品》后,周杰伦宣布要发新专辑 - 上观](https://m.jfdaily.com/wx/detail.do?id=857039): # 时隔3年,继《最伟大的作品》后,周杰伦宣布要发新专辑 纵览 2025-02-09 06:55 来源:上观新闻 作者:九派新闻 2月8日,周杰伦在社交媒体宣布他的新专辑预计在今年暑假期间发布,这让粉丝们兴奋不已。 2月2日,周杰伦在社交平台上发文,称自己半夜还在跟方文山弄歌词。他打趣到:“重点是,他真的还写得出来,他应该不是以前拖拖拉拉的方文山了。” 在周杰伦附上的聊天对话里可以看到,... 2. [周杰伦2000-2020所有专辑总集结 - 知乎专栏](https://zhuanlan.zhihu.com/p/483651514): 2016-周杰伦的床边故事 Image 23 本专辑为周杰伦与妻子昆凌结婚生子后发行的首张专辑,为周杰伦个人第14张专辑。新专辑以床边故事命名,专辑设计打造成有声书概念,诉说10个与众不同、充满想像的音乐故事。专辑中与张惠妹首度合唱,是继《依然范特西》专辑中的《千里之外》与费玉清的合作后,再次跨公司与线上歌手合唱,并且收录于专辑中。 01.床边故事 02.说走就走 03.一点点 04.... 3. [周杰伦_百度百科](https://baike.baidu.com/item/%E5%91%A8%E6%9D%B0%E4%BC%A6/129156): 1/2 ### 为他人创作 | | | | | | --- --- | 歌曲名称 | 职能 | 演唱者 | 所属专辑 | 发行时间 | | 星动力 | 作曲 | 刘畊宏 | 单曲 | 2024-1-12 | | AMIGO | 作曲 | 玖壹壹 | 单曲 | 2022-10-25 | | 叱咤风云 | 作曲, 电吉他演奏 | 范逸臣, 柯有伦 | 单曲 | 2021-1-10 ... [Step 1] 历史: Thought: Search Action: Search Action Input: {"query": "周杰伦最新专辑"} Observation: 直接回答: Zhou Jilun's latest album is expected in August or September 2025. He recently released a new song titled "即兴曲" in June 2025. His previous album, "最伟大的作品," was released in July 2022. 搜索结果 (5 条): 1. [时隔3年,继《最伟大的作品》后,周杰伦宣布要发新专辑 - 上观](https://m.jfdaily.com/wx/detail.do?id=857039): # 时隔3年,继《最伟大的作品》后,周杰伦宣布要发新专辑 纵览 2025-02-09 06:55 来源:上观新闻 作者:九派新闻 2月8日,周杰伦在社交媒体宣布他的新专辑预计在今年暑假期间发布,这让粉丝们兴奋不已。 2月2日,周杰伦在社交平台上发文,称自己半夜还在跟方文山弄歌词。他打趣到:“重点是,他真的还写得出来,他应该不是以前拖拖拉拉的方文山了。” 在周杰伦附上的聊天对话里可以看到,... 2. [周杰伦2000-2020所有专辑总集结 - 知乎专栏](https://zhuanlan.zhihu.com/p/483651514): 2016-周杰伦的床边故事 Image 23 本专辑为周杰伦与妻子昆凌结婚生子后发行的首张专辑,为周杰伦个人第14张专辑。新专辑以床边故事命名,专辑设计打造成有声书概念,诉说10个与众不同、充满想像的音乐故事。专辑中与张惠妹首度合唱,是继《依然范特西》专辑中的《千里之外》与费玉清的合作后,再次跨公司与线上歌手合唱,并且收录于专辑中。 01.床边故事 02.说走就走 03.一点点 04.... 3. [周杰伦_百度百科](https://baike.baidu.com/item/%E5%91%A8%E6%9D%B0%E4%BC%A6/129156): 1/2 ### 为他人创作 | | | | | | --- --- | 歌曲名称 | 职能 | 演唱者 | 所属专辑 | 发行时间 | | 星动力 | 作曲 | 刘畊宏 | 单曲 | 2024-1-12 | | AMIGO | 作曲 | 玖壹壹 | 单曲 | 2022-10-25 | | 叱咤风云 | 作曲, 电吉他演奏 | 范逸臣, 柯有伦 | 单曲 | 2021-1-10 ... -------------------------------------------------- [Step 2] 调用 LLM... LLM 输出: Thought: 我现在可以回答了 Final Answer: 周杰伦最新的专辑是2022年7月发行的《最伟大的作品》。根据最新消息,周杰伦已宣布新专辑预计在2025年暑假期间(8月或9月)发布,并在2025年6月先行发布了新歌《即兴曲》。 -------------------------------------------------- 🎯 最终答案: 周杰伦最新的专辑是2022年7月发行的《最伟大的作品》。根据最新消息,周杰伦已宣布新专辑预计在2025年暑假期间(8月或9月)发布,并在2025年6月先行发布了新歌《即兴曲》。
最终在浏览中搜索,来验证结果是否正确 LLM通过自己的决策获得和实际情况相同的结果。
总结 在使用agent之前,对于其定义和本身都感觉十分的模糊。通过这次的整体梳理,突然发现agent好像还是prompt工程的一种延伸,只不过是一类更加特殊的prompt。给LLM定义了一个特殊的角色,可以使用工具的角色。至此以后,agent的神秘感不在,以后更多的可能是关注其性能问题,如何能够在更短时间内获得用户想要的数据以及如何在token大量输入的情况下也可以让LLM能够快速的输出。
参考