LangGraph状态机框架

LangGraph是基于LangChain之上的一个Agent状态机框架,用于定义和执行LLM和Agent之间的交互逻辑。LangGraph可以简单理解为企业级应用开发中经常用到的“工作流”,只不过工作流节点中执行的是大语言模型(LLM)和工具函数,一些条件判断也是由LLM完成的。基于LangGraph我们能构建功能非常复杂的“智能体”应用。

基本概念

LangGraph最基础的使用方式就是StateGraph,它是一个状态图。

状态 State:状态是状态图的一个全局数据,其中的内容通常是自定义的。

节点 Node:LangGraph的节点可以是一个函数,它的参数和返回值都是状态的实例。在节点运行时可以修改这些状态,节点运行完成后,可以根据状态值判断下一跳节点是哪个。除了我们自定义节点,还有STARTEND这两个特殊节点,代表状态图的开始和结束。

边 Edge:决定节点的下一跳是哪个节点,边可以是固定的也可以是带条件的,带条件的边根据状态条件判断下一跳。

构建状态图

我们这里基于一个非常简单的例子介绍LangGraph的使用,我们的状态图如下图所示。

运行流程如下。

  1. 首先,chatbot_node会接收到用户的问题并设置tool_call调用搜索引擎工具
  2. tavily_search_tool_node封装了Tavily Search工具,它具体执行搜索操作
  3. 搜索完成后,我们的状态再次转移回chatbot_node,LLM将对搜索结果做出总结并结束流程。
  4. 这里chatbot_node其实有两个方向向外的边,判断走哪条路径还需要一个函数,这个函数的逻辑也很简单,只要chatbot_node返回的AIMessage中有tool_call就跳至tavily_search_tool_node,否则就跳至结束节点。

我们这里直接给出完整的代码。

from langchain.globals import set_debug, set_verbose
from langchain_community.tools import TavilySearchResults
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_ollama import ChatOllama
from langgraph.constants import START, END
from langgraph.graph import StateGraph
from pydantic import BaseModel

set_debug(True)
set_verbose(True)

# 大语言模型
llm = ChatOllama(model="llama3.1:8b", temperature=1)

# 工具
tavily_search_tool = TavilySearchResults(
    max_results=3,
    search_depth="advanced",
    include_answer=True,
    include_raw_content=True
)
llm_with_tools = llm.bind_tools([tavily_search_tool])


# 全局状态
class State(BaseModel):
    messages: list


def chatbot_node(state: State):
    messages = state.messages
    if len(state.messages) == 0:
        messages = [
            ("system", """
            You are a helpful AI assistant.
            Answer the user's questions clearly and accurately.
            Use the tavily_search_results_json tool to search for the nouns which you don't know.
            Do not search the web if you already have enough knowledge to answer the question.
            """),
            ("human", "Why the Rust programming is known as the 'Genshin Impact' of the programming world ?")
        ]
    chain = ChatPromptTemplate.from_messages(messages) | llm_with_tools
    ai_message = chain.invoke({})
    messages.append(ai_message)
    return State(messages=messages)


def tavily_search_tool_node(state: State):
    messages = state.messages
    last_ai_message = messages[-1]
    for tool_call in last_ai_message.tool_calls:
        tool_message = tavily_search_tool.invoke(tool_call)
        messages.append(tool_message)
    return State(messages=messages)


def if_complete_search(state: State):
    messages = state.messages
    last_ai_message = messages[-1]
    if hasattr(last_ai_message, "tool_calls") and len(last_ai_message.tool_calls) > 0:
        return "tavily_search_tool_node"
    else:
        return END


graph_builder = StateGraph(State)
graph_builder.add_node("chatbot_node", chatbot_node)
graph_builder.add_node("tavily_search_tool_node", tavily_search_tool_node)
graph_builder.add_edge(START, "chatbot_node")
graph_builder.add_conditional_edges("chatbot_node", if_complete_search)
graph_builder.add_edge("tavily_search_tool_node", "chatbot_node")
graph = graph_builder.compile()

chain = graph | (lambda state: state["messages"][-1]) | StrOutputParser()
result = chain.invoke({"messages": []})
print(result)

LangGraph中,全局状态State可以是一个Pydantic的BaseModel,我们在其中封装了messages数据,这个数据是所有节点产生的消息。chatbot_nodetavily_search_tool_node其实就是具体的节点定义函数,它们的参数和返回值都是State对象。if_complete_search用于判断chatbot_node的下一跳。

LangGraph构建时,我们分别调用了add_nodeadd_edge等函数添加节点和边,最后调用compile()方法,这个方法会对状态图进行一些预检查等操作。最终调用graph时,它invoke()的最终返回值还是会包含State中的字段,我们将最后一条AIMessage取出,并打印其中的文本。

这样一个非常“简单”的LangGraph状态图就构建完成了。学会上面的内容,我们其实已经可以构建更加复杂的状态图了。当然,LangGraph还是比较抽象和难用的,实际开发中我们可以借助LangSmith收集的遥测数据对状态图进行调试,达成我们想要的效果。

打印状态图

LangGraph状态图是用代码构建的,调用以下方法可以打印状态图的Mermaid文本,这样我们就可以把图片渲染出来。

print(graph.get_graph().draw_mermaid())
作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap