1 概述
DeepSeek R1出现后,得益于其出色的推理能力,使得它在任务规划、反思等方面的可以发挥更大的作用。
1.1 原理
今天介绍的工作来自于Medium[1],核心原理如下图。
对于输入,使用Tavily进行联网检索,然后在推理层(Agentic Reasoning Layer)使用DeepSeek R1对检索到的结果进行检查,检查结果会得到如下3部分信息:
- 无用信息:直接丢弃
- 有用信息:保留
- 有信息遗失:需要使用Tavily再次进行检索的信息
循环整个“检索信息-检查检索结果”的过程,直到收集到了所有的信息。
注意:实际运行中,情况要比这个复杂,比如可能循环无法终止、检查结果无法按Prompt要求返回要求的格式导致无法提取到需要的3部分信息等,这些情况在本文提供的示例代码中也增加了适当的容错机制。
1.2 示例
文中给到的示例Query是:加拿大皇家银行的总部在山姆-奥特曼兄弟公司总部的北面吗?(Is the headquarters of RBC north of Sam Altman’s brother’s company’s headquarters?)
这个问题之所以难,在于正常的网页/知识库不太可能把两个关联性不大的内容放在一起描述,也就是使用Embedding、BM25,甚至是结合Rerank,都不太可能直接检索到跟整个问题直接相关的上下文,所以常规的RAG也就无法很好地回答此类问题。假如一次召回无法同时获得加拿大皇家银行的总部位置和山姆-奥特曼兄弟公司总部位置,通过反思可以补充这缺失信息,然后LLM就可以利用预训练时获得的常识——多伦多在洛杉矶的北面——来回答问题了。当然实际情况可能会更复杂,不一定缺失的是位置信息。
原文给出的示例代码使用LangGraph构造了一个Workflow形式的Agent,构造的LangGraph图如下所示:
执行整个Workflow,可以得到类似如下的执行日志,这Medium的示例代码使用满血版R1运行的结果:
2 效果对比
这种方法还是有一定的适用场景的,在我们构造的测试集中也没有表现出比较好的效果来,如果你的场景符合这两个条件,这种方法应该是会取得比较好的效果的:
- 如果出现上下文缺失,由大模型是可以发现信息缺失的,例如类似“A公司比B公司去年的营业额多多少”这种问题,但对于枚举型的问题,例如“符合条件的有哪些”、“办理xxx的步骤是什么”,判断信息缺失是比较困难的
- 由于反思模型可以决定下一步行动,所以模型还是要相对来说比较强,使得已经检索到的正确的信息不至于被判定为与问题无关,这一点从下图的结果也可以看出
效果对比如下图:
3 准备工作
我们依然使用Ollama提供的模型来完成实验,首先需要先拉取deepseek-r1的模型,Ollama默认提供的整个模型是一个7B规模int4量化的模型:
ollama pull deepseek-r1
4 核心代码
本文对应代码已开源,地址在:https://github.com/Steven-Luo/MasteringRAG/blob/main/new_arch/03_r1_reasoning_rag.ipynb
4.1 Prompt
from langchain_core.prompts import PromptTemplate
# 验证Prompt
VALIDATE_PROMPT = PromptTemplate(
input_variables=["retrieved_context", "question"],
template="""
# 任务描述
您是一名检索验证员。
系统会向您提供一个问题和一些背景信息,这些背景信息可能包含问题的答案,也可能不包含。
您的任务是仔细查看这些文本块,并提供包含三个字段的 JSON 响应:
1. status:检索到的背景信息是否包含问题的答案。
- 如果检索到的背景信息包含问题答案,则为 "COMPLETE",否则为 "INCOMPLETE"。只有这两个取值,其他取值都是非法的。
2. useful_information:从检索到的背景信息中获取的有用信息。要简洁明了。
- 大部分情况下,这部分信息不能为空,如果确实没有有用信息,则将其设置为空字符串。
- 如果status是COMPLETE,则这部分一定要有。
3. missing_information:如果要完整回答这个问题,还需要的信息,这个信息将用来使用向量模型进一步检索知识库,以便补充信息。
- 如果status是INCOMPLETE,则这部分信息一定要有。
# 背景信息
---
{retrieved_context}
---
# 返回值要求
请按以下格式以字典形式提供您的答复。请严格按照此格式返回结果,不要包含描述性内容和任何其他无关内容。
```json
{{"status": "<status>",
"useful_information": "<useful_information>",
"missing_information": "<missing_information>"}}```
这里是响应的样例:
```json
{{"status": "COMPLETE",
"useful_information": "The capital city of Canada is Ottawa.",
"missing_information": "The capital city of Mexico"}}```
# 问题
{question}
"""
)
# 答案升成
ANSWER_QUESTION = PromptTemplate(
input_variables=["retrieved_context", "question"],
template="""
你是一个金融分析师,擅长根据所获取的信息片段,对问题进行分析和推理。
你的任务是根据所获取的信息片段(<<<<context>>><<<</context>>>之间的内容)回答问题。
回答保持简洁,不必重复问题,不要添加描述性解释和与答案无关的任何内容。
已知信息:
<<<<context>>>
{retrieved_context}
<<<</context>>>
问题:{question}
请回答:
"""
)
4.2 Workflow构建
import json
import re
n_chunks = 3
def extract_json(text):
match = re.search(r'\{.*\}', text, re.DOTALL)
if match:
try:
return json.loads(match.group())
except json.JSONDecodeError:
print("提取的内容不是有效的JSON")
return None
# 校验检索结果
def validate_retrieval(state: RAGState):
print(f"\n=== STEP 2: VALIDATION (round: {state.get('n_round', 0)})===")
question = state['question']
retrieved_context = state['retrieved_context']
validation_chain = VALIDATE_PROMPT | r1
retry_count = 5
# 兜底
router_decision = 'INCOMPLETE'
missing_information = question
useful_information = ''
reasoning = ''
while retry_count >= 0:
try:
llm_output = validation_chain.invoke({'retrieved_context': retrieved_context, 'question': question}).content
reasoning = llm_output.split('<think>')[1].split('</think>')[0].strip()
response = llm_output.split('</think>')[1].strip()
structured_response = extract_json(response)
router_decision = structured_response['status'].strip().upper()
missing_information = structured_response['missing_information']
useful_information = structured_response['useful_information']
print('structured response:', structured_response)
# print('reasoning:', reasoning)
print("router decision:", router_decision)
break
except Exception as e:
retry_count -= 1
print(f"error, e: {e}, llm_output: {llm_output}, {retry_count} retries left")
return {
'router_decision': router_decision,
'missing_information': missing_information,
'useful_information': useful_information,
'reasoning': reasoning
}
# 回答
def answer(state: RAGState):
print(f"\n=== STEP 3: ANSWERING (round: {state.get('n_round', 0)})===")
question = state['question']
context = state['retrieved_context']
answer_chain = ANSWER_QUESTION | llm
llm_output = answer_chain.invoke({
'retrieved_context': context,
'question': question
}).content
# answer = llm_output.split('</think>')[1].strip()
answer = llm_output.strip()
print('answer:', answer)
return {'answer_to_question': answer}
# 基于校验结果二次检索
def find_missing_information(state: RAGState):
print(f"\n=== STEP 2b: FINDING MISSING INFORMATION (round: {state.get('n_round', 0)})===")
question = state["missing_information"]
print("Searching for:", question)
chunks = vector_db.similarity_search(question, k=n_chunks)
newly_retrieved_context = '\n\n'.join([doc.page_content for doc in chunks])
previously_retrieved_useful_information = state["useful_information"]
combined_context = f"{previously_retrieved_useful_information}\n{newly_retrieved_context}"
# print("newly retrieved context:", newly_retrieved_context)
return {"retrieved_context": combined_context, 'n_round': state.get('n_round', 0) + 1}
def decide_route(state: RAGState):
return state["router_decision"]
workflow = StateGraph(RAGState)
# 定义节点
workflow.add_node('retrieve context', retrieve)
workflow.add_node('is retrieved context complete?', validate_retrieval)
workflow.add_node('answer', answer)
workflow.add_node('find missing information', find_missing_information)
# 定义入口
workflow.set_entry_point('retrieve context')
# 定义边
workflow.add_edge('retrieve context', 'is retrieved context complete?')
workflow.add_conditional_edges(
'is retrieved context complete?',
decide_route,
{
'COMPLETE': 'answer',
'INCOMPLETE': 'find missing information'
}
)
workflow.add_edge('find missing information', 'is retrieved context complete?')
workflow.add_edge('answer', END)
compiled_graph = workflow.compile()
# 使用
result = compiled_graph.invoke({'question': '全球贸易增长情况如何?'})
print(result['answer_to_question'])
如何学习大模型 AI ?
由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。
但是具体到个人,只能说是:
“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。
这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。
我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。
第一阶段(10天):初阶应用
该阶段让大家对大模型 AI有一个最前沿的认识,对大模型 AI 的理解超过 95% 的人,可以在相关讨论时发表高级、不跟风、又接地气的见解,别人只会和 AI 聊天,而你能调教 AI,并能用代码将大模型和业务衔接。
- 大模型 AI 能干什么?
- 大模型是怎样获得「智能」的?
- 用好 AI 的核心心法
- 大模型应用业务架构
- 大模型应用技术架构
- 代码示例:向 GPT-3.5 灌入新知识
- 提示工程的意义和核心思想
- Prompt 典型构成
- 指令调优方法论
- 思维链和思维树
- Prompt 攻击和防范
- …
第二阶段(30天):高阶应用
该阶段我们正式进入大模型 AI 进阶实战学习,学会构造私有知识库,扩展 AI 的能力。快速开发一个完整的基于 agent 对话机器人。掌握功能最强的大模型开发框架,抓住最新的技术进展,适合 Python 和 JavaScript 程序员。
- 为什么要做 RAG
- 搭建一个简单的 ChatPDF
- 检索的基础概念
- 什么是向量表示(Embeddings)
- 向量数据库与向量检索
- 基于向量检索的 RAG
- 搭建 RAG 系统的扩展知识
- 混合检索与 RAG-Fusion 简介
- 向量模型本地部署
- …
第三阶段(30天):模型训练
恭喜你,如果学到这里,你基本可以找到一份大模型 AI相关的工作,自己也能训练 GPT 了!通过微调,训练自己的垂直大模型,能独立训练开源多模态大模型,掌握更多技术方案。
到此为止,大概2个月的时间。你已经成为了一名“AI小子”。那么你还想往下探索吗?
- 为什么要做 RAG
- 什么是模型
- 什么是模型训练
- 求解器 & 损失函数简介
- 小实验2:手写一个简单的神经网络并训练它
- 什么是训练/预训练/微调/轻量化微调
- Transformer结构简介
- 轻量化微调
- 实验数据集的构建
- …
第四阶段(20天):商业闭环
对全球大模型从性能、吞吐量、成本等方面有一定的认知,可以在云端和本地等多种环境下部署大模型,找到适合自己的项目/创业方向,做一名被 AI 武装的产品经理。
- 硬件选型
- 带你了解全球大模型
- 使用国产大模型服务
- 搭建 OpenAI 代理
- 热身:基于阿里云 PAI 部署 Stable Diffusion
- 在本地计算机运行大模型
- 大模型的私有化部署
- 基于 vLLM 部署大模型
- 案例:如何优雅地在阿里云私有部署开源大模型
- 部署一套开源 LLM 项目
- 内容安全
- 互联网信息服务算法备案
- …
学习是一个过程,只要学习就会有挑战。天道酬勤,你越努力,就会成为越优秀的自己。
如果你能在15天内完成所有的任务,那你堪称天才。然而,如果你能完成 60-70% 的内容,你就已经开始具备成为一名大模型 AI 的正确特征了。