LangGraph中如何接入大模型做问答流程

一、LangGraph底层原理与基础应用

(一)核心概念

  1. 节点:在LangGraph里,节点作为核心组件,以Python函数形式存在,可同步或异步执行。其第一个参数固定为state,承担着接收当前状态、执行特定计算任务,并返回更新后状态的重要职责。通过这种方式,节点实现了数据处理和状态的传递,是构建复杂逻辑的基础单元。
    • 入口点指定了用户输入进入图时首先调用的节点;
    • 条件入口点通过函数判断来确定起始调用的节点,为图的执行提供了多样化的起始条件选择。
  2. :边在LangGraph中起着关键的连接和控制作用,负责定义逻辑的走向、图的起始与结束。它包含多种类型:
    • 普通边用于直接连接两个节点,使数据按顺序从一个节点流向另一个;
    • 条件边则根据函数的调用结果动态决定下一个跳转的节点,实现更灵活的逻辑控制;
  3. 特殊节点
    • START节点专门用于接收用户输入,它是图执行的起点,其主要功能是确定后续首先调用的节点,引导整个图的执行流程。
    • END节点作为终端节点,当图中相关边完成任务且不再需要进行其他操作时,它指示图的执行结束,标志着一次完整的图计算过程完成。

(二)代码实现与知识点详解

from typing_extensions import TypedDict
# 定义输入状态,question字段用于接收用户问题
class InputState(TypedDict):
    question: str
# 定义输出状态,answer字段用于存储回答内容
class OutputState(TypedDict):
    answer: str
# OverallState整合输入输出状态,方便数据传递与处理
class OverallState(InputState, OutputState):
    pass
  • 知识点TypedDict是Python中用于定义类型化字典的工具,通过它可以明确字典中键的类型和值的类型。在LangGraph中,利用TypedDict定义输入输出状态,能够清晰地规定数据的格式,这不仅有助于代码的可读性和可维护性,还方便在图的构建和执行过程中进行数据校验,确保数据的一致性和正确性。
# agent_node函数,处理输入状态并传递数据
def agent_node(state: InputState):
    print("我是一个AI Agent。")
    return {"question": state["question"]}
# action_node函数,处理问题并生成回答
def action_node(state: InputState):
    print("我现在是一个执行者。")
    # 增加state存储,也可以选择不存储
    step = state["question"]
    return {"answer": f"我接收到的问题是:{step},读取成功了!"}
  • 知识点:这两个函数展示了节点的定义方式。节点函数的第一个参数为state,其类型与之前定义的输入状态类型一致。在函数内部,可以根据业务逻辑对state进行处理。这种设计使得每个节点专注于特定的任务,通过对state的操作实现数据的处理和传递,多个节点协同工作就能完成复杂的业务流程。
from langgraph.graph import StateGraph, START, END
# 创建StateGraph对象,指定状态模式、输入输出类型
builder = StateGraph(OverallState, input=InputState, output=OutputState)
# 添加agent_node节点到图中
builder.add_node("agent_node", agent_node)
# 添加action_node节点到图中
builder.add_node("action_node", action_node)
# 添加边,确定节点执行顺序:从START到agent_node
builder.add_edge(START, "agent_node")
# 添加边,从agent_node到action_node
builder.add_edge("agent_node", "action_node")
# 添加边,从action_node到END
builder.add_edge("action_node", END)
# 编译图,检查结构完整性,为调用做准备
graph = builder.compile()
  • 知识点StateGraph类是LangGraph中用于构建图结构的核心类。创建StateGraph对象时,指定OverallState作为状态模式,明确了图中数据的整体结构,同时指定InputStateOutputState作为输入输出类型,规范了数据的输入和输出格式。
    • add_node方法用于向图中添加节点,第一个参数为节点的名称,方便在图中进行标识和引用,第二个参数为节点函数,定义了节点的具体逻辑。
    • add_edge方法用于添加边,确定节点之间的连接关系和执行顺序,从START节点开始,按照添加边的顺序依次执行各个节点,直到END节点。
    • compile方法对图进行编译,在编译过程中会检查图的结构是否完整,例如是否存在孤立节点等问题,只有编译通过的图才能被正确调用。
# 调用图,传入问题,获取并打印回答结果
result = graph.invoke({"question": "今天的天气怎么样?"})
print(result)
  • 知识点invoke方法是用于触发图执行的关键方法。在调用invoke方法时,需要传入符合InputState定义格式的数据,这里传入了包含question字段的字典。图会按照之前定义的节点和边的顺序依次执行,将输入数据在各个节点间传递,最终返回END节点对应的结果,即整个图执行后的输出。通过这种方式,实现了从用户输入到最终输出的完整计算过程。
    在这里插入图片描述

二、接入大模型构建问答流程

(一)模型支持与选择

LangGraph具备强大的模型兼容性,对主流的在线和开源模型均提供接入支持。借助LangChain框架,开发者能够便捷地集成各类模型。本次以OpenAI的GPT模型为例,使用ChatOpenAI方法进行演示。若需接入其他模型,可查阅LangChain官方文档获取详细接入方式。

(二)代码实现与知识点解析

from langgraph.graph import StateGraph
from typing_extensions import TypedDict
from langgraph.graph import START, END

# 定义输入的模式
class InputState(TypedDict):
    question: str

# 定义输出的模式
class OutputState(TypedDict):
    answer: str

# 将InputState和OutputState这两个TypedDict类型合并成一个更全面的字典类型。
class OverallState(InputState, OutputState):
    pass
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
import getpass
import os
# 检查并获取OpenAI API Key
if not os.environ.get("OPENAI_API_KEY"):
    os.environ["OPENAI_API_KEY"] = getpass.getpass("Enter your OpenAI API key: ")
  • 知识点langchain_openai中的ChatOpenAI是LangChain框架中用于与OpenAI的Chat模型进行交互的类。ChatPromptTemplate用于构建提示模板,虽然在当前代码中未实际使用,但在更复杂的场景下,可通过它灵活构建向模型发送的提示信息,以引导模型生成符合需求的回答。getpassos模块用于获取用户输入的OpenAI API Key并设置到环境变量中。在实际应用中,API Key是访问OpenAI模型的凭证,通过这种方式可以安全地获取和使用API Key,避免在代码中硬编码导致的安全问题。
# llm_node函数,调用GPT模型获取回答
def llm_node(state: InputState):
    messages = [
        ("system", "你是一位乐于助人的智能小助理"),
        ("human", state["question"])
    ]
    llm = ChatOpenAI(model="gpt-40", temperature=0)
    response = llm.invoke(messages)
    return {"answer": response.content}
  • 知识点:在llm_node函数中,构建了一个包含系统提示和用户问题的消息列表messages。系统提示用于设定模型的角色和行为,这里将模型设定为乐于助人的智能小助理。然后使用ChatOpenAI创建语言模型对象llm,指定模型为gpt-40temperature参数设置为0,temperature控制模型输出的随机性,值越低输出越确定性。通过调用llm.invoke(messages)方法,将消息列表发送给模型并获取回答,最后返回包含回答内容的字典。这个过程展示了如何在LangGraph的节点函数中调用外部大模型,利用模型的能力处理用户输入并生成回答。
# 创建StateGraph对象,配置状态模式和输入输出类型
builder = StateGraph(OverallState, input=InputState, output=OutputState)
# 添加llm_node节点到图中
builder.add_node("llm_node", llm_node)
# 添加边,确定执行顺序:从START到llm_node再到END
builder.add_edge(START, "llm_node")
builder.add_edge("llm_node", END)
# 编译图,确保结构正确
graph = builder.compile()
  • 知识点:这部分代码与之前构建图的过程类似,创建StateGraph对象,添加llm_node节点,并通过add_edge方法确定图的执行顺序,从START节点开始,调用llm_node节点,最后到END节点。编译图的目的同样是检查图的结构完整性,确保图在执行前没有错误,为后续的调用做好准备。

(三)复杂功能扩展

from typing_extensions import TypedDict, Optional
# 扩展输入状态,新增llm_answer字段接收前一节点回答
class InputState(TypedDict):
    question: str
    llm_answer: Optional[str]
# 定义输出状态
class OutputState(TypedDict):
    answer: str
# 整合输入输出状态
class OverallState(InputState, OutputState):
    pass
  • 知识点:通过TypedDict重新定义InputState,新增llm_answer字段,用于接收前一个节点(这里是llm_node)的输出结果。Optional[str]表示该字段的值可以是字符串类型,也可以是None,增加了数据处理的灵活性。重新定义OverallState以整合新的输入输出状态,确保图中数据的一致性和连贯性。
# llm_node函数,将模型回答存入共享状态
def llm_node(state: InputState):
    messages = [
        ("system", "你是一位乐于助人的智能小助理"),
        ("human", state["question"])
    ]
    llm = ChatOpenAI(model="gpt-40", temperature=0)
    response = llm.invoke(messages)
    return {"llm_answer": response.content}
  • 知识点:与之前的llm_node函数相比,这里的返回值发生了变化,将模型的回答存入llm_answer字段并返回,这样后续节点就可以通过访问state中的llm_answer字段获取该回答,实现了节点之间的数据共享和传递,为构建更复杂的功能提供了基础。
# action_node函数,提取回答并翻译
def action_node(state: InputState):
    messages = [
        ("system", "无论你接收到什么语言的文本,请翻译成法语"),
        ("human", state["llm_answer"])
    ]
    llm = ChatOpenAI(model="gpt-40", temperature=0)
    response = llm.invoke(messages)
    return {"answer": response.content}
  • 知识点action_node函数接收包含llm_answer字段的state,从state中提取llm_answer作为翻译的输入文本。构建新的消息列表,设置系统提示为将文本翻译成法语,然后调用ChatOpenAI模型进行翻译,最后返回翻译后的结果。这个过程展示了如何基于前一个节点的输出进行进一步的处理,通过在不同节点中定义不同的逻辑,实现复杂的业务功能。
# 创建StateGraph对象,配置相关参数
builder = StateGraph(OverallState, input=InputState, output=OutputState)
# 添加llm_node和action_node节点到图中
builder.add_node("llm_node", llm_node)
builder.add_node("action_node", action_node)
# 添加边,构建执行链路:从START到llm_node,再到action_node,最后到END
builder.add_edge(START, "llm_node")
builder.add_edge("llm_node", "action_node")
builder.add_edge("action_node", END)
# 编译图,准备调用
graph = builder.compile()
  • 知识点:重新构建图结构,添加llm_nodeaction_node两个节点,并通过add_edge方法定义它们的执行顺序。从START节点开始,首先调用llm_node处理用户问题并生成回答,然后将回答传递给action_node进行翻译,最后到达END节点结束图的执行。编译图的操作同样是为了检查图结构的正确性,确保图在执行时不会出现错误。
    LangGraph中如何接入大模型做问答流程_第1张图片

三、框架优势与学习建议

(一)优势突显

LangGraph框架展现出卓越的自主性和扩展性。节点函数支持灵活定义,开发者可根据实际需求定制功能。在模型接入方面,既可以借助LangChain集成模型,也能够运用原生API或本地开源模型权重,即便脱离LangChain框架,依然能够独立构建AI Agent程序。

  • 使用 OpenAI 原生 API 调用 GPT 模型:在这个示例中,没有使用 LangChain,而是直接利用openai库的原生 API 来调用 GPT 模型。在llm_node节点函数中,通过openai.ChatCompletion.create方法发送请求到 OpenAI 服务器获取回答,展示了可以绕开 LangChain 实现模型调用,体现了 LangGraph 在模型接入上的自主性。
import os
import openai
from langgraph.graph import StateGraph, START, END
from typing_extensions import TypedDict

# 定义输入的模式
class InputState(TypedDict):
    question: str

# 定义输出的模式
class OutputState(TypedDict):
    answer: str

# 将InputState和OutputState这两个TypedDict类型合并成一个更全面的字典类型。
class OverallState(InputState, OutputState):
    pass

openai.api_key = os.environ.get("OPENAI_API_KEY")

# 定义节点函数,使用OpenAI原生API调用GPT模型
def llm_node(state: InputState):
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": "你是一位乐于助人的智能小助理"},
            {"role": "user", "content": state["question"]}
        ]
    )
    return {"answer": response['choices'][0]['message']['content']}

builder = StateGraph(OverallState, input=InputState, output=OutputState)
builder.add_node("llm_node", llm_node)
builder.add_edge(START, "llm_node")
builder.add_edge("llm_node", END)
graph = builder.compile()

result = graph.invoke({"question": "请介绍一下Python语言"})
print(result)
  • 加载本地开源模型权重(以 Llama 2 为例 ):此示例中,通过transformers库加载本地的 Llama 2 模型权重。在llm_node节点函数里,利用模型和分词器对输入问题进行处理并生成回答。这展示了即使不依赖 LangChain 这类第三方框架,也能将本地开源模型接入到 LangGraph 的节点中,充分体现了 LangGraph 在模型选择和使用方式上的扩展性和自主性。
from langgraph.graph import StateGraph, START, END
from typing_extensions import TypedDict
from transformers import LlamaForCausalLM, LlamaTokenizer

# 定义输入的模式
class InputState(TypedDict):
    question: str

# 定义输出的模式
class OutputState(TypedDict):
    answer: str

# 将InputState和OutputState这两个TypedDict类型合并成一个更全面的字典类型。
class OverallState(InputState, OutputState):
    pass

# 加载本地Llama 2模型和分词器
model_path = "/path/to/your/llama-2-model"  
tokenizer = LlamaTokenizer.from_pretrained(model_path)
model = LlamaForCausalLM.from_pretrained(model_path)

# 定义节点函数,使用本地Llama 2模型
def llm_node(state: InputState):
    inputs = tokenizer.encode(state["question"], return_tensors="pt")
    output = model.generate(inputs)
    result = tokenizer.decode(output[0], skip_special_tokens=True)
    return {"answer": result}

builder = StateGraph(OverallState, input=InputState, output=OutputState)
builder.add_node("llm_node", llm_node)
builder.add_edge(START, "llm_node")
builder.add_edge("llm_node", END)
graph = builder.compile()

result = graph.invoke({"question": "请介绍一下Python语言"})
print(result)

你可能感兴趣的:(大模型Agent开发,LangGraph,人工智能,Agent,大模型)