从 RAG 到 Agent:跑通两个 Demo 后,我终于分清了这两个词
英文标题:从 RAG 到 Agent:跑通两个 Demo 后,我终于分清了这两个词
最近接连跑通了两个 LLM 应用的小 Demo:先是一个 RAG 私有文档问答,然后是一个 ReAct 模式的旅行助手 Agent。最大的收获不是代码本身,而是终于搞明白了一件天天被提但一直模模糊糊的
200.1 KB
6.8 KB
中文
最近接连跑通了两个 LLM 应用的小 Demo:先是一个 RAG 私有文档问答,然后是一个 ReAct 模式的旅行助手 Agent。最大的收获不是代码本身,而是终于搞明白了一件天天被提但一直模模糊糊的事——RAG 和 Agent 到底差在哪 。
第一站:RAG——让模型"知道"我的文档
第一个 Demo 很经典:让 LLM 回答它训练数据里不可能有的问题——我自己的笔记。
流程两条管线:
chunks = chunk_text(text, size=500, overlap=50)
embeddings = embedder.encode(chunks)
collection.add(ids=..., documents=chunks, embeddings=embeddings)
def answer(question: str) -> str:
chunks = retrieve(question)
prompt = PROMPT_TEMPLATE.format(context=..., question=question)
response = CLIENT.chat.completions.create(...)
return response.choices[0].message.content
跑通之后回头看,RAG 的特点非常明显:
- 流程是代码写死的 :检索 → 拼 prompt → 生成,永远这三步,一步不会多
- LLM 只被调用一次 ,它自始至终不知道"检索"这件事的存在
- LLM 的角色是拿着参考资料的答题者 ——你喂它三段资料,它负责好好回答,答不出就说"文档里没说"
一句话:RAG 解决的是"知道 "的问题——把模型不知道的私有知识,现场塞给它。
第二站:Agent——让模型"做事"
第二个 Demo 是一个旅行助手,任务一句话:查北京今天的天气,然后根据天气推荐景点 。
注意这是个两步任务,而且第二步依赖第一步的结果。如果是普通程序,我们得自己写"先查天气、再搜景点"的调度逻辑。但 Agent 的做法是:只给模型工具,让模型自己决定怎么串 。
工具就是两个普通 Python 函数,没有任何特殊之处:
def get_weather(city: str) -> str:
data = requests.get(f"https://wttr.in/{city}?format=j1", timeout=10).json()
cur = data["current_condition"][0]
return f"{city}当前天气:{cur['weatherDesc'][0]['value']},气温{cur['temp_C']}摄氏度"
def get_attraction(city: str, weather: str) -> str:
result = tavily.search(query=f"{city} 适合{weather}天气的旅游景点推荐", max_results=3)
return "根据搜索,为您找到以下信息:" + ";".join(r["title"] for r in result["results"])
然后在 System Prompt 里告诉模型有哪些工具、按什么格式说话(Thought / Action / Observation 协议),核心循环长这样:
for i in range(1, MAX_LOOPS + 1):
# ① 问模型下一步干嘛
reply = llm.chat.completions.create(model=MODEL, messages=messages).choices[0].message.content
# ② 模型说"信息够了" → 结束
if "Final Answer" in reply:
return reply.split("Final Answer:")[-1]
# ③ 抠出工具名和参数,真正执行
tool_name, args = parse_action(reply)
observation = TOOLS[tool_name](**args)
# ④ 关键:把工具结果拼成 Observation 喂回对话,进入下一轮
messages.append({"role": "assistant", "content": reply})
messages.append({"role": "user", "content": f"Observation: {observation}"})
实际跑起来的输出(真实运行结果节选):
--- 循环 1 ---
Thought: 用户需要先查询北京今天的天气……第一步先调用天气查询工具。
Action: get_weather(city="北京")
Observation: 北京当前天气:Sunny,气温27摄氏度
--- 循环 2 ---
Thought: 北京今天是晴天,气温27摄氏度,适合户外活动。接下来根据这个天气条件调用景点推荐工具。
Action: get_attraction(city="北京", weather="Sunny")
Observation: 根据搜索,为您找到以下信息:北京在sunny天气下最值得去的旅游景点推荐及理由;……
--- 循环 3 ---
Thought: 工具返回了景点推荐结果……
Final Answer: 北京今天天气晴朗,气温约27°C,非常适合户外游玩……
执行结果:
看循环 2 那行 Action:weather="Sunny" 这个参数是模型自己填的 ——它把循环 1 拿到的天气结果,当作循环 2 的输入传了进去。代码里没有任何一行写"先查天气再搜景点",整个流程的编排完全发生在模型的"脑子"里。
这就是 Agent 的本质:LLM 不只是答题者,而是指挥官 ,工具是它的手脚,那个"问模型 → 干活 → 把结果给模型看"的循环就是它的心脏。
区别,一张表说清
| RAG| Agent
---|---|---
LLM 被调几次| 1 次| N 次(每轮循环一次)
流程谁定的| 代码写死| LLM 运行时自己决定
LLM 的角色| 拿着参考资料的答题者| 调度工具的指挥官
解决什么问题| 知道 :掌握私有知识| 做事 :完成多步任务
能否根据中间结果调整| 不能,管线是直的| 能,下一步取决于上一步
我自己总结的最快判断法:看有没有"循环 + 决策" 。没有循环、模型只出场一次的,是 RAG;有循环、每一步"接下来干嘛"由模型现场判断的,是 Agent。
顺手踩的一个坑
用正则解析模型的 Action: get_weather(city="北京") 后,我最初想用 ast.literal_eval 把参数转成字典——结果直接报 SyntaxError。原因:literal_eval 只认 Python 字典写法 {"city": "北京"},而模型输出的是函数调用写法 city="北京"。最后老老实实用正则逐对提取:
args = dict(re.findall(r'(\w+)="([^"]*)"', args_str))
教训:模型的输出是"协议",解析器必须和 prompt 里约定的格式严丝合缝 ,差一点都跑不通。
最后
两件事并不冲突:完全可以把"检索知识库"包装成一个工具,放进 Agent 的工具箱——那时 RAG 就成了 Agent 的其中一只手,这也是现在很多落地系统的真实架构(Agent 负责决策,RAG 负责供料)。
下一步计划:给这个 Agent 加第三个工具,试试让它在更多工具里自己选择。
环境:Python 3.12 + DeepSeek(大脑)+ wttr.in(天气)+ Tavily(搜索),全文代码不足 100 行。
English / 原文
最近接连跑通了两个 LLM 应用的小 Demo:先是一个 RAG 私有文档问答,然后是一个 ReAct 模式的旅行助手 Agent。最大的收获不是代码本身,而是终于搞明白了一件天天被提但一直模模糊糊的事——RAG 和 Agent 到底差在哪 。
第一站:RAG——让模型"知道"我的文档
第一个 Demo 很经典:让 LLM 回答它训练数据里不可能有的问题——我自己的笔记。
流程两条管线:
chunks = chunk_text(text, size=500, overlap=50)
embeddings = embedder.encode(chunks)
collection.add(ids=..., documents=chunks, embeddings=embeddings)
def answer(question: str) -> str:
chunks = retrieve(question)
prompt = PROMPT_TEMPLATE.format(context=..., question=question)
response = CLIENT.chat.completions.create(...)
return response.choices[0].message.content
跑通之后回头看,RAG 的特点非常明显:
- 流程是代码写死的 :检索 → 拼 prompt → 生成,永远这三步,一步不会多
- LLM 只被调用一次 ,它自始至终不知道"检索"这件事的存在
- LLM 的角色是拿着参考资料的答题者 ——你喂它三段资料,它负责好好回答,答不出就说"文档里没说"
一句话:RAG 解决的是"知道 "的问题——把模型不知道的私有知识,现场塞给它。
第二站:Agent——让模型"做事"
第二个 Demo 是一个旅行助手,任务一句话:查北京今天的天气,然后根据天气推荐景点 。
注意这是个两步任务,而且第二步依赖第一步的结果。如果是普通程序,我们得自己写"先查天气、再搜景点"的调度逻辑。但 Agent 的做法是:只给模型工具,让模型自己决定怎么串 。
工具就是两个普通 Python 函数,没有任何特殊之处:
def get_weather(city: str) -> str:
data = requests.get(f"https://wttr.in/{city}?format=j1", timeout=10).json()
cur = data["current_condition"][0]
return f"{city}当前天气:{cur['weatherDesc'][0]['value']},气温{cur['temp_C']}摄氏度"
def get_attraction(city: str, weather: str) -> str:
result = tavily.search(query=f"{city} 适合{weather}天气的旅游景点推荐", max_results=3)
return "根据搜索,为您找到以下信息:" + ";".join(r["title"] for r in result["results"])
然后在 System Prompt 里告诉模型有哪些工具、按什么格式说话(Thought / Action / Observation 协议),核心循环长这样:
for i in range(1, MAX_LOOPS + 1):
# ① 问模型下一步干嘛
reply = llm.chat.completions.create(model=MODEL, messages=messages).choices[0].message.content
# ② 模型说"信息够了" → 结束
if "Final Answer" in reply:
return reply.split("Final Answer:")[-1]
# ③ 抠出工具名和参数,真正执行
tool_name, args = parse_action(reply)
observation = TOOLS[tool_name](**args)
# ④ 关键:把工具结果拼成 Observation 喂回对话,进入下一轮
messages.append({"role": "assistant", "content": reply})
messages.append({"role": "user", "content": f"Observation: {observation}"})
实际跑起来的输出(真实运行结果节选):
--- 循环 1 ---
Thought: 用户需要先查询北京今天的天气……第一步先调用天气查询工具。
Action: get_weather(city="北京")
Observation: 北京当前天气:Sunny,气温27摄氏度
--- 循环 2 ---
Thought: 北京今天是晴天,气温27摄氏度,适合户外活动。接下来根据这个天气条件调用景点推荐工具。
Action: get_attraction(city="北京", weather="Sunny")
Observation: 根据搜索,为您找到以下信息:北京在sunny天气下最值得去的旅游景点推荐及理由;……
--- 循环 3 ---
Thought: 工具返回了景点推荐结果……
Final Answer: 北京今天天气晴朗,气温约27°C,非常适合户外游玩……
执行结果:
看循环 2 那行 Action:weather="Sunny" 这个参数是模型自己填的 ——它把循环 1 拿到的天气结果,当作循环 2 的输入传了进去。代码里没有任何一行写"先查天气再搜景点",整个流程的编排完全发生在模型的"脑子"里。
这就是 Agent 的本质:LLM 不只是答题者,而是指挥官 ,工具是它的手脚,那个"问模型 → 干活 → 把结果给模型看"的循环就是它的心脏。
区别,一张表说清
| RAG| Agent
---|---|---
LLM 被调几次| 1 次| N 次(每轮循环一次)
流程谁定的| 代码写死| LLM 运行时自己决定
LLM 的角色| 拿着参考资料的答题者| 调度工具的指挥官
解决什么问题| 知道 :掌握私有知识| 做事 :完成多步任务
能否根据中间结果调整| 不能,管线是直的| 能,下一步取决于上一步
我自己总结的最快判断法:看有没有"循环 + 决策" 。没有循环、模型只出场一次的,是 RAG;有循环、每一步"接下来干嘛"由模型现场判断的,是 Agent。
顺手踩的一个坑
用正则解析模型的 Action: get_weather(city="北京") 后,我最初想用 ast.literal_eval 把参数转成字典——结果直接报 SyntaxError。原因:literal_eval 只认 Python 字典写法 {"city": "北京"},而模型输出的是函数调用写法 city="北京"。最后老老实实用正则逐对提取:
args = dict(re.findall(r'(\w+)="([^"]*)"', args_str))
教训:模型的输出是"协议",解析器必须和 prompt 里约定的格式严丝合缝 ,差一点都跑不通。
最后
两件事并不冲突:完全可以把"检索知识库"包装成一个工具,放进 Agent 的工具箱——那时 RAG 就成了 Agent 的其中一只手,这也是现在很多落地系统的真实架构(Agent 负责决策,RAG 负责供料)。
下一步计划:给这个 Agent 加第三个工具,试试让它在更多工具里自己选择。
环境:Python 3.12 + DeepSeek(大脑)+ wttr.in(天气)+ Tavily(搜索),全文代码不足 100 行。