这篇教程将讲解如何结合多种先进技术,构建一个功能强大的智能对话系统。即使你对AI领域知识不甚了解,也能轻松跟上这个教程的步伐。
技术栈简介与背景知识
首先,让我介绍本教程使用的几项核心技术:
- LangGraph:这是基于LangChain的一个工具,可以帮助我们构建复杂的工作流。想象它是一个状态机,允许我们定义各种状态和它们之间的转换规则。
- DeepSeek-R1:这是一个大语言模型,类似于ChatGPT,但由DeepSeek公司开发。
- 函数调用(Function Call) :这项技术允许AI模型根据对话内容自动识别并调用特定函数。类似于你写的程序能够自动决定调用哪个函数来完成任务。
- Agentic RAG:这是检索增强生成(RAG)的高级版本。传统的RAG像是一本书的查询系统,而Agentic RAG则像是一个会思考的图书管理员,不仅能查找信息,还能判断信息是否有用,以及如何处理和组织这些信息。
为什么需要Agentic RAG?
传统RAG的局限性在于它的"流水线"式处理方式:先检索,再生成,步骤固定且线性。如果检索失败,后续步骤便失去意义。
Agentic RAG通过引入"智能体"(Agent)解决了这个问题。这些智能体可以:
- 自主决策:判断是否需要检索信息
- 自主规划:决定使用哪种检索方法
- 自我评估:评估检索结果是否满足需求
- 工具选择:判断是否需要使用外部工具
开发实现:从环境搭建到功能实现
第一步:环境准备与依赖安装
像所有项目一样,我们先导入必要的库:
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langgraph.graph import END, StateGraph, START
from langgraph.prebuilt import ToolNode
from langgraph.graph.message import add_messages
from typing_extensions import TypedDict, Annotated
from typing import Sequence
from langchain_openai import OpenAIEmbeddings
import re
import os
import streamlit as st
import requests
from langchain.tools.retriever import create_retriever_tool
第二步:创建示例数据库
我们需要两类数据作为知识源:
# 创建示例数据
research_texts = [
"Research Report: Results of a New AI Model Improving Image Recognition Accuracy to 98%",
"Academic Paper Summary: Why Transformers Became the Mainstream Architecture in Natural Language Processing",
"Latest Trends in Machine Learning Methods Using Quantum Computing"
]
development_texts = [
"Project A: UI Design Completed, API Integration in Progress",
"Project B: Testing New Feature X, Bug Fixes Needed",
"Product Y: In the Performance Optimization Stage Before Release"
]
第三步:数据处理与向量化
这一步骤是将文本转换为AI能理解的数值形式(向量):
# 文本分割设置
splitter = RecursiveCharacterTextSplitter(chunk_size=100, chunk_overlap=10)
# 从文本生成文档对象
research_docs = splitter.create_documents(research_texts)
development_docs = splitter.create_documents(development_texts)
# 创建向量存储
embeddings = OpenAIEmbeddings(
model="text-embedding-3-large",
)
这里的RecursiveCharacterTextSplitter就像一把智能剪刀,将长文本切成小片段。每个片段不超过100个字符,相邻片段重叠10个字符以保持上下文连贯性。embeddings则是将文本转换为数字向量的工具,类似于将文字翻译成机器语言。
第四步:创建向量存储和检索器
将文档转换为向量并存储起来,以便后续检索:
research_vectorstore = Chroma.from_documents(
documents=research_docs,
embedding=embeddings,
collection_name="research_collection"
)
development_vectorstore = Chroma.from_documents(
documents=development_docs,
embedding=embeddings,
collection_name="development_collection"
)
research_retriever = research_vectorstore.as_retriever()
development_retriever = development_vectorstore.as_retriever()
这一步相当于创建了两个专门的数据库,一个存放研究内容,一个存放开发内容。每个数据库都有特定的检索工具(retriever),就像是专门的搜索引擎。
第五步:创建智能检索工具
利用检索器创建可在对话系统中使用的工具:
research_tool = create_retriever_tool(
research_retriever,
"research_db_tool",
"Search information from the research database."
)
development_tool = create_retriever_tool(
development_retriever,
"development_db_tool",
"Search information from the development database."
)
# 将工具组合成一个列表
tools = [research_tool, development_tool]
这些工具就像是专门的搜索助手,能根据需要检索特定领域的信息。
第六步:开发智能路由函数
这是系统的核心之一,负责决定用户问题应该由哪个工具处理:
class AgentState(TypedDict):
messages: Annotated[Sequence[AIMessage|HumanMessage|ToolMessage], add_messages]
def agent(state: AgentState):
print("---调用智能体---")
messages = state["messages"]
if isinstance(messages[0], tuple):
user_message = messages[0][1]
else:
user_message = messages[0].content
# 构建提示以获得一致的文本输出
prompt = f"""Given this user question: "{user_message}"
If it's about research or academic topics, respond EXACTLY in this format:
SEARCH_RESEARCH: <search terms>
If it's about development status, respond EXACTLY in this format:
SEARCH_DEV: <search terms>
Otherwise, just answer directly.
"""
# API调用代码略...
if response.status_code == 200:
response_text = response.json()['choices'][0]['message']['content']
print("原始响应:", response_text)
# 将响应格式化为预期的工具格式
if "SEARCH_RESEARCH:" in response_text:
query = response_text.split("SEARCH_RESEARCH:")[1].strip()
# 直接调用研究检索器
results = research_retriever.invoke(query)
return {"messages": [AIMessage(content=f'Action: research_db_tool\n{{"query": "{query}"}}\n\nResults: {str(results)}')]}
elif "SEARCH_DEV:" in response_text:
query = response_text.split("SEARCH_DEV:")[1].strip()
# 直接调用开发检索器
results = development_retriever.invoke(query)
return {"messages": [AIMessage(content=f'Action: development_db_tool\n{{"query": "{query}"}}\n\nResults: {str(results)}')]}
else:
return {"messages": [AIMessage(content=response_text)]}
else:
raise Exception(f"API 调用失败: {response.text}")
这个函数像一个聪明的接线员,根据问题类型将用户引导到研究数据库或开发数据库。最特别的是,尽管DeepSeek-R1本身不支持函数调用,但我们通过约定特定的文本格式(如"SEARCH_RESEARCH:")和解析模型输出,实现了类似函数调用的功能。
第七步:开发辅助功能函数
除了主要的路由功能,我们还需要一系列辅助函数:
- 质量评估函数:判断检索结果是否满足需求
def simple_grade_documents(state: AgentState):
messages = state["messages"]
last_message = messages[-1]
print("评估消息:", last_message.content)
# 检查内容是否包含检索到的文档
if "Results: [Document" in last_message.content:
print("---找到文档,继续生成---")
return "generate"
else:
print("---未找到文档,尝试重写---")
return "rewrite"
- 答案生成函数:根据检索到的文档生成最终答案
def generate(state: AgentState):
print("---生成最终答案---")
messages = state["messages"]
question = messages[0].content if isinstance(messages[0], tuple) else messages[0].content
last_message = messages[-1]
# 从结果中提取文档内容
docs = ""
if "Results: [" in last_message.content:
results_start = last_message.content.find("Results: [")
docs = last_message.content[results_start:]
print("找到的文档:", docs)
# API调用代码略...
- 问题重写函数:当检索失败时,重新优化问题
def rewrite(state: AgentState):
print("---重写问题---")
messages = state["messages"]
original_question = messages[0].content if len(messages)>0 else "N/A"
# API调用代码略...
- 决策函数:判断是否需要使用工具
tools_pattern = re.compile(r"Action: .*")
def custom_tools_condition(state: AgentState):
messages = state["messages"]
last_message = messages[-1]
content = last_message.content
print("检查工具条件:", content)
if tools_pattern.match(content):
print("转到检索...")
return "tools"
print("转到结束...")
return END
第八步:创建工作流系统
将所有功能组合成完整的工作流:
workflow = StateGraph(AgentState)
# 添加节点
workflow.add_node("agent", agent)
retrieve_node = ToolNode(tools)
workflow.add_node("retrieve", retrieve_node)
workflow.add_node("rewrite", rewrite)
workflow.add_node("generate", generate)
# 定义流程
workflow.add_edge(START, "agent")
# 条件判断
workflow.add_conditional_edges(
"agent",
custom_tools_condition,
{
"tools": "retrieve",
END: END
}
)
# 检索后的流程判断
workflow.add_conditional_edges("retrieve", simple_grade_documents)
workflow.add_edge("generate", END)
workflow.add_edge("rewrite", "agent")
# 编译工作流
app = workflow.compile()
这个工作流类似于一个状态机或流程图:
- 开始于智能体节点
- 根据智能体的输出,决定是否需要检索信息
- 检索后评估结果质量
- 如果结果良好,生成答案;如果结果不佳,重写问题并重新开始
第九步:创建用户界面
使用Streamlit创建一个简洁的用户界面:
def main():
st.set_page_config(
page_title="AI 研发助手",
layout="wide",
initial_sidebar_state="expanded"
)
# CSS和界面设置代码略...
# 主要内容
st.title("🤖 AI 研发助手")
st.markdown("---")
# 查询输入
query = st.text_area("输入您的问题:", height=100, placeholder="例如:AI 研究的最新进展是什么?")
# 界面布局代码略...
本项目的额外功能点如下:
- 巧妙解决函数调用限制:尽管DeepSeek-R1不直接支持函数调用,我们通过定义特殊文本格式和解析响应,实现了类似功能。这就像是在不支持HTTP的环境中,通过约定特定文本格式来模拟HTTP请求和响应。
- 灵活的智能体架构:系统能够自主决定是否需要检索信息,以及如何评估和使用检索结果,而不是机械地遵循预设流程。
- 自动问题优化:当初始检索失败时,系统会自动重写问题,类似于人类在搜索引擎中修改关键词以获得更好结果的过程。
总结
Agentic RAG结合函数调用和工作流管理,使AI系统不再像传统的"输入-处理-输出"管道,而是更像一个能够思考、规划和自我纠正的助手。这种方法解决了传统RAG系统的僵化问题,让AI能更灵活地处理各种复杂查询。
这种架构设计思路不仅适用于构建聊天机器人,也可以应用于许多其他AI系统,如代码辅助工具、内容管理系统和知识库查询工具。通过本教程介绍的方法,即使使用功能相对有限的模型,也能构建出功能强大的智能对话系统。