从零构建智能对话系统:LangGraph + DeepSeek-R1 + Agentic RAG

884 阅读8分钟

这篇教程将讲解如何结合多种先进技术,构建一个功能强大的智能对话系统。即使你对AI领域知识不甚了解,也能轻松跟上这个教程的步伐。

技术栈简介与背景知识

首先,让我介绍本教程使用的几项核心技术:

  • LangGraph:这是基于LangChain的一个工具,可以帮助我们构建复杂的工作流。想象它是一个状态机,允许我们定义各种状态和它们之间的转换规则。
  • DeepSeek-R1:这是一个大语言模型,类似于ChatGPT,但由DeepSeek公司开发。
  • 函数调用(Function Call) :这项技术允许AI模型根据对话内容自动识别并调用特定函数。类似于你写的程序能够自动决定调用哪个函数来完成任务。
  • Agentic RAG:这是检索增强生成(RAG)的高级版本。传统的RAG像是一本书的查询系统,而Agentic RAG则像是一个会思考的图书管理员,不仅能查找信息,还能判断信息是否有用,以及如何处理和组织这些信息。

为什么需要Agentic RAG?

传统RAG的局限性在于它的"流水线"式处理方式:先检索,再生成,步骤固定且线性。如果检索失败,后续步骤便失去意义。

Agentic RAG通过引入"智能体"(Agent)解决了这个问题。这些智能体可以:

  1. 自主决策:判断是否需要检索信息
  2. 自主规划:决定使用哪种检索方法
  3. 自我评估:评估检索结果是否满足需求
  4. 工具选择:判断是否需要使用外部工具

开发实现:从环境搭建到功能实现

第一步:环境准备与依赖安装

像所有项目一样,我们先导入必要的库:

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:")和解析模型输出,实现了类似函数调用的功能。

第七步:开发辅助功能函数

除了主要的路由功能,我们还需要一系列辅助函数:

  1. 质量评估函数:判断检索结果是否满足需求
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"
  1. 答案生成函数:根据检索到的文档生成最终答案
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调用代码略...
  1. 问题重写函数:当检索失败时,重新优化问题
def rewrite(state: AgentState):
    print("---重写问题---")
    messages = state["messages"]
    original_question = messages[0].content if len(messages)>0 else "N/A"

    # API调用代码略...
  1. 决策函数:判断是否需要使用工具
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()

这个工作流类似于一个状态机或流程图:

  1. 开始于智能体节点
  2. 根据智能体的输出,决定是否需要检索信息
  3. 检索后评估结果质量
  4. 如果结果良好,生成答案;如果结果不佳,重写问题并重新开始

第九步:创建用户界面

使用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 研究的最新进展是什么?")

    # 界面布局代码略...

本项目的额外功能点如下:

  1. 巧妙解决函数调用限制:尽管DeepSeek-R1不直接支持函数调用,我们通过定义特殊文本格式和解析响应,实现了类似功能。这就像是在不支持HTTP的环境中,通过约定特定文本格式来模拟HTTP请求和响应。
  2. 灵活的智能体架构:系统能够自主决定是否需要检索信息,以及如何评估和使用检索结果,而不是机械地遵循预设流程。
  3. 自动问题优化:当初始检索失败时,系统会自动重写问题,类似于人类在搜索引擎中修改关键词以获得更好结果的过程。

总结

Agentic RAG结合函数调用和工作流管理,使AI系统不再像传统的"输入-处理-输出"管道,而是更像一个能够思考、规划和自我纠正的助手。这种方法解决了传统RAG系统的僵化问题,让AI能更灵活地处理各种复杂查询。

这种架构设计思路不仅适用于构建聊天机器人,也可以应用于许多其他AI系统,如代码辅助工具、内容管理系统和知识库查询工具。通过本教程介绍的方法,即使使用功能相对有限的模型,也能构建出功能强大的智能对话系统。