langgraph构建智能RAG

     1.概述

        本文基于文件加载器(PyPDFLoader)、向量库(Chroma)和langgraph工作流构建一个RAG应用。

        工作流思路如下:

        1)进入chatbot节点。chatbot节点根据用户输入确定是否需要调用工具,如果不需要调用工具,则直接生成应答,否则执行function calling,并进入工具节点

        2)工具节点调用工具进行检索,并判断检索结果是否与问题相关,如果不相关则进入问题重写节点对问题进行改写,如果相关则进入生成解答节点。

       3)问题重写节点调用大模型对问题进行改写,改写后再次进入chatbot节点

       4)生成解答节点根据用户请求和检索的上下文生成最后的应答

     2.文档分块

     2.1加载文档

        首先,安装PDF文件加载包pypdf:

#pip  install  pypdf

# pip install langchain-chroma

 

        加载文件,代码如下:

from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters  import RecursiveCharacterTextSplitter

 

loader = PyPDFLoader ("../data/beijing_annual_report_2024.pdf")

docs = loader.load()

        docs为Document类型的数组,每页都是一个Document对象,查看一下文档页数和第一页的内容:

print(f"####len = {len(docs)}####")
docs[0].page_content

        输出内容如下,共13页文档:

####len = 13####

'各位代表:\n现在,……;支持**新区“三校一院”交钥匙项目开\n学开诊,公共服务共建共享不断深化。'

      2.2.文档分块

        langchain提供了多种分块器,分别适用不同的场景。这里选用最常用的递归字符文本分块器。

from langchain.text_splitter import RecursiveCharacterTextSplitter

character_splitter = RecursiveCharacterTextSplitter(

    separators=["\n\n", "\n", "。", ",", "、", ";", ""],#块分隔符

    chunk_size=1000, #块大小

    chunk_overlap=0) #块重叠大小

 

splits = character_splitter.split_docoments(docs)

        查看一下有多少块:

len(splits)

     3.向量化存储

       本文使用chroma文件存储,当然生产系统肯定要使用服务器存储。 

       首先导入相关的库:

from langchain_huggingface import HuggingFaceEmbeddings
from langchain_chroma import Chroma

        使用向量库时必须先初始化嵌入模型,这里使用text2vec-base-chinese嵌入模型:

embedding = HuggingFaceEmbeddings(
    model_name='../models/text2vec-base-chinese'  # 高效的语义模型
)

        把分块数据加载到chroma向量库中:

persist_directory = "../data/chroma" #chroma库文件路径
vectordb = Chroma.from_documents(
    documents=splits,
    embedding=embedding,
    persist_directory=persist_directory  # 允许我们将persist_directory目录保存到磁盘上
)

        进行语义查询:

vectordb.similarity_search("关于就业相关数据", k=2)

        结果如下:

[Document(metadata={'comments': '', 'page': 2, 'sourcemodified': "D:20240208203412+12'34'", 'subject': '', 'author': 'sky', 'producer': '', 'moddate': '2024-02-08T20:34:12+12:34', 'keywords': '', 'creator': 'WPS 文字', 'trapped': '/False', 'creationdate': '2024-02-08T20:34:12+12:34', 'total_pages': 13, 'title': '', 'page_label': '3', 'source': '../data/beijing_annual_report_2024.pdf', 'company': ''}, page_content='四是……),
 Document(metadata={'sourcemodified': "D:20240208203412+12'34'", 'creator': 'WPS 文字', 'page_label': '3', 'creationdate': '2024-02-08T20:34:12+12:34', 'moddate': '2024-02-08T20:34:12+12:34', 'comments': '', 'producer': '', 'keywords': '', 'source': '../data/beijing_annual_report_2024.pdf', 'page': 2, 'company': '', 'subject': '', 'total_pages': 13, 'author': 'sky', 'trapped': '/False', 'title': ''}, page_content='……,深')]

     4.创建chatbot

      4.1检索工具化

        使用langchain库中的create_retriever_tool方法,把向量库检索器工具化,用于绑定到大模型中,具体代码如下:

from langchain_classic.tools.retriever import create_retriever_tool
retriever = vectordb.as_retriever()
retriever_tool = create_retriever_tool(
    retriever,
    "retrieve_relevent_information",
    "retrieve relevent information about beijing annual report.",
)

        测试工具:

retriever_tool.invoke({"query":"关于就业相关数据"})

      4.2绑定工具

        使用deepseek-chat大模型,并绑定检索工具。

"""

最早选用的是deepseek-chat,但后面调用时发现如果绑定了检索工具,那么对于检索主体之外的问题,则不能回答,比如:我让它写一个关于中国足球的段子,它的回答内容如下:

================================== Ai Message 

我主要专注于北京年度报告相关的信息查询,目前无法为您创作关于中国足球的段子。我的功能范围比较有限,主要是帮助您检索北京年度报告中的具体信息。

如果您需要了解北京年度报告中的相关内容,我很乐意为您提供帮助。或者您可以尝试其他更适合创作娱乐内容的工具来编写足球段子。

====================================================================

所以只能改用qwen-plus。

"""

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    model = 'deepseek-chat',
    api_key = "sk-*",
    base_url = "https://api.deepseek.com",
)

model = llm .bind_tools(tools=[retriever_tool,])

      4.3创建chatbot

        创建一个chatbot节点,该节点将用于后面的图中,具体代码如下:

from langgraph.graph import MessagesState

def chatbot(state: MessagesState)->dict:
    #根据当前的状态确定是检索工具,还是直接作答
    response = model.invoke(state["messages"])
    return {"messages": [response]}

        随意问一个问题,比如让它写一个关于中国足球的段子:

input = {"messages": [{"role": "user", "content": "写一个关于中国足球的段子"}]}
chatbot(input)["messages"][-1].pretty_print()       

        chatbot的应答内容如下:

================================== Ai Message ==================================

中国足球就像是一个勤奋的学生,每次考试都充满期待地走进考场,结果出来后却发现,不仅没及格,连试卷都拿反了。教练说:“别灰心,我们明年继续努力!”于是球迷们又开始熬夜看比赛,结果发现——这次连球门都找不到了!守门员和前锋互相问:“咱们是攻还是守?”最后裁判叹了口气:“要不……咱们踢毽子吧,至少咱擅长。”
 

        再问一个关于政府工作报告的问题:

input = {"messages": [{"role": "user", "content": "北京市政府工作报告中关于改善民生有哪些举措?"}]}
chatbot(input)["messages"][-1].pretty_print()

        可以看到,function calling的相关信息:

================================== Ai Message ==================================
Tool Calls:
  retrieve_relevent_information (call_42dca5834a714fd688182f)
 Call ID: call_42dca5834a714fd688182f
  Args:
    query: 北京市政府工作报告 民生 改善举措
 

      5.相关性评价

        以下方法使用大模型对从向量库中检索的内容和用户的问题进行相关性判断,相关返回Y,不相关返回N。根据判断结果,返回对应的节点名。该方法用于作为图中条件。

from langchain_core.prompts import ChatPromptTemplate
from typing import Literal
from langchain_core.output_parsers import StrOutputParser
def discrininate_documents(
    state: MessagesState,
) -> Literal["generate_answer", "rewrite_question"]:
    DISCRIMINATE_PROMPT = (
        "你是一个判别器,针对检索出的文档与用户问题的相关性进行判断. \n "
        "以下是检索出的文档: \n\n {context} \n\n"
        "以下是用户问题: {question} \n"
        "如果文档内容与用户问题语义上相关,则标识为相关,否则为无关. \n"
        "相关时返回Y,否则返回N."
    )

    question = state["messages"][0].content #用户的问题
    context = state["messages"][-1].content #检索结果
    prompt = ChatPromptTemplate.from_messages(
        [
           ("user", DISCRIMINATE_PROMPT)
        ]        
    )
    model = prompt|llm|StrOutputParser() 
    response = model.invoke({"question" : question, "context" : context})
    if response == "Y":
        return "generate_answer"
    else:
        return "rewrite_question"

        首先以相关数据进行测试,问题是“改进民生举措”:

from langchain_core.messages import convert_to_messages

input = {
    "messages": convert_to_messages(
        [
            {
                "role": "user",
                "content": "改善民生举措",
            },
            {
                "role": "assistant",
                "content": "",
                "tool_calls": [
                    {
                        "id": "1",
                        "name": "retrieve_relevent_information",
                        "args": {"query": "改善民生举措"},
                    }
                ],
            },
            {"role": "tool", "content": "……住宅物业服务覆盖率达\n到 97%。", "tool_call_id": "1"},
        ]
    )
}
discrininate_documents(input)

        然后,以不相关内容进行测试:

from langchain_core.messages import convert_to_messages

input = {
    "messages": convert_to_messages(
        [
            {
                "role": "user",
                "content": "改善民生举措",
            },
            {
                "role": "assistant",
                "content": "",
                "tool_calls": [
                    {
                        "id": "1",
                        "name": "retrieve_blog_posts",
                        "args": {"query": "改善民生举措"},
                    }
                ],
            },
            {"role": "tool", "content": "中国足球就像是一个勤奋的学生,每次考试都充满期待地走进考场,结果出来后却发现,不仅没及格,连试卷都拿反了。教练说:“别灰心,我们明年继续努力!”于是球迷们又开始熬夜看比赛,结果发现——这次连球门都找不到了!守门员和前锋互相问:“咱们是攻还是守?”最后裁判叹了口气:“要不……咱们踢毽子吧,至少咱擅长", "tool_call_id": "1"},
        ]
    )
}
discrininate_documents(input)

      6.问题改写

        有些情况下,虽然向量库中有所需要的素材,但因为提问的语句有问题,所以未检索到相关的内容,此时需要对问题进行改写,使问题更加的明确和具体。

REWRITE_PROMPT = (
    "检查用户输入问题并推测用于的真实意图和想法.\n"
    "用户初始问题如下----:"
    "\n ------- \n"
    "用户初始问题如下----{question}"
    "\n ------- \n"
    "根据用户的真实意图对问题进行改写并输出改写后的问题"
     "注意,仅需要输出改写后的问题"
)


def rewrite_question(state: MessagesState):
    """Rewrite the original user question."""
    messages = state["messages"]
    question = messages[0].content
    prompt = REWRITE_PROMPT.format(question=question)
    model = llm|StrOutputParser()
    response = model.invoke([{"role": "user", "content": prompt}])
    return {"messages": [{"role": "user", "content": response}]}

        测试一下:

input = {
    "messages": convert_to_messages(
        [
            {
                "role": "user",
                "content": "改善民生",
            },
            {
                "role": "assistant",
                "content": "",
                "tool_calls": [
                    {
                        "id": "1",
                        "name": "retrieve_relevent_information",
                        "args": {"query": "改善民生"},
                    }
                ],
            },
            {"role": "tool", "content": "meow", "tool_call_id": "1"},
        ]
    )
}

response = rewrite_question(input)
print(response["messages"][-1]["content"])

      7.解答问题

        此处解答问题是基于用户问题和检索出的上下文给出的解答:

GENERATE_PROMPT = (
    "你的任务是回答用户的提问. "
    "回答问题时需要参考{context}中的内容. " 
    "以下是用户问题"
    "Question: {question}. "
    "如果不知道如何作答,请诚实回答 '我不知道'. "
    "保持答案简洁,不要超过3句.\n"
)


def generate_answer(state: MessagesState):
    """Generate an answer."""
    question = state["messages"][0].content
    context = state["messages"][-1].content
    prompt = GENERATE_PROMPT.format(question=question, context=context)
    response = llm.invoke([{"role": "user", "content": prompt}])
    return {"messages": [response]}

        测试一下:

input = {
    "messages": convert_to_messages(
        [
            {
                "role": "user",
                "content": "改善民生有哪些举措?",
            },
            {
                "role": "assistant",
                "content": "",
                "tool_calls": [
                    {
                        "id": "1",
                        "name": "etrieve_relevent_information",
                        "args": {"query": "改善民生有哪些举措"},
                    }
                ],
            },
            {
                "role": "tool",
                "content": "持……。持续打好蓝天保卫战",
                "tool_call_id": "1",
            },
        ]
    )
}

response = generate_answer(input)
response["messages"][-1].pretty_print()

      8.创建图

        基于以上的成果组件创建图,具体代码如下:

from langgraph.graph import StateGraph, START, END
from langgraph.prebuilt import ToolNode
from langgraph.prebuilt import tools_condition


graph = (
    StateGraph(MessagesState)
    .add_node(chatbot)
    .add_node("retrieve", ToolNode([retriever_tool]))
    .add_node(rewrite_question)
    .add_node(generate_answer)
    .add_edge(START, "chatbot")  
    .add_conditional_edges("chatbot", tools_condition, 
        {
            "tools": "retrieve",
            END: END,
        }
    )
    .add_conditional_edges("retrieve", discriminate_documents)
    .add_edge("generate_answer", END)  
    .add_edge("rewrite_question",  "chatbot")
    .compile()
)

        可视化工作流,检查图的正确性:

from IPython.display import Image, display

display(Image(graph.get_graph().draw_mermaid_png()))

        调用图:

for chunk in graph.stream(
    {
        "messages": [
            {
                "role": "user",
                "content": "工作报告中有哪些改进民生的举措?",
            }
        ]
    }
):
    for node, update in chunk.items():
        print("Update from node", node)
        update["messages"][-1].pretty_print()
        print("\n\n")

        输出结果如下:

Update from node chatbot
================================== Ai Message ==================================
Tool Calls:
  retrieve_relevent_information (call_a527c420e54d47a597b63e)
 Call ID: call_a527c420e54d47a597b63e
  Args:
    query: 工作报告 改进民生 举措

Update from node retrieve
================================= Tool Message =================================
Name: retrieve_relevent_information

…………

Update from node generate_answer
================================== Ai Message ==================================

……,助力改善民生。

 

【RIS 辅助的 THz 混合场波束斜视下的信道估计与定位】在混合场波束斜视效应下,利用太赫兹超大可重构智能表面感知用户信道与位置(Matlab代码实现)内容概要:本文围绕“IS 辅助的 THz 混合场波束斜视下的信道估计与定位”展开,重点研究在太赫兹(THz)通信系统中,由于混合近场与远场共存导致的波束斜视效应下,如何利用超大可重构智能表面(RIS)实现对用户信道状态信息和位置的联合感知与精确估计。文中提出了一种基于RIS调控的信道参数估计算法,通过优化RIS相移矩阵提升信道分辨率,并结合信号到达角(AoA)、到达时间(ToA)等信息实现高精度定位。该方法在Matlab平台上进行了仿真验证,复现了SCI一区论文的核心成果,展示了其在下一代高频通信系统中的应用潜力。; 适合人群:具备通信工程、信号处理或电子信息相关背景,熟悉Matlab仿真,从事太赫兹通信、智能反射面或无线定位方向研究的研究生、科研人员及工程师。; 使用场景及目标:① 理解太赫兹通信中混合场域波束斜视问题的成因与影响;② 掌握基于RIS的信道估计与用户定位联合实现的技术路径;③ 学习并复现高水平SCI论文中的算法设计与仿真方法,支撑学术研究或工程原型开发; 阅读建议:此资源以Matlab代码实现为核心,强调理论与实践结合,建议读者在理解波束成形、信道建模和参数估计算法的基础上,动手运行和调试代码,深入掌握RIS在高频通信感知一体化中的关键技术细节。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值