在LangGraph中有三个重要元素
- StateGraph
- Node
- Edge
StateGraph
首先stategraph是用来描述整个图的,图中的状态会随着多个agent的工作不断的更新,节点node就是用来更新状态的
如何来定义一张图中的状态
每个应用的状态可能不同,所以我们需要根据具体应用场景来决定状态
from langgraph.graph import StateGraph
from typing import TypedDict, List, Annotated
import operator
class State(TypedDict):#该示例中状态定义了两个字段,输入input,allaction
input: str
all_actions: Annotated[List[str], operator.add]
graph = StateGraph(State)
Node
当我们创建好stategraph后,就可以向其中添加node(graph.add_node(name,value))
graph.add_node("model", model)
graph.add_node("tools",tool_executor)
同时node中还有个特殊的节点END,表示状态终止
Edge
先定义从哪个节点开始
graph.set_entry_point("节点名字")
分为normal edge 和conditional edge
normal edge定义了两个节点之间必然的先后关系
conditional edge通过条件来决定下一步的节点是哪里,需要三个东西:上游节点(upstream node)、判定函数、映射关系。
分别表示来自哪里、如何判断去哪、条件结果返回
graph.add_conditional_edge(
"model", #model是上游节点的名字
should_continue, #这是
{
"end":END,
"continue":"tools"
}
)
上述代码中,上游节点是model,判定条件是should_continue,如果should_continue返回end,那就是END节点,如果返回continue那就去tools节点
示例
我们来用一个具体的示例来展示langgraph
首先先定义stategraph
from langgraph.graph import StateGraph
from typing import TypedDict, List, Annotated,Union
import operator
from langchain_core.agents import AgentAction,AgentFinish
from langchain_core.messages import BaseMessage
####################################初始化一个stategraph
class AgentState(TypedDict):
#定义一些初始化的功能
#用户输入字符串
input:str
#对话中之前的消息列表
chat_history:list[BaseMessage]
#agent执行过后的结果,是要继续执行action还是完成finish,还是没有任何的状态None
agent_outcom: Union[AgentAction,AgentFinish,None]
#动作列表和相应的观察结果
#operator.add表明对这个状态的操作是添加到现有值上而不是覆盖掉
再定义两个工具
##################外部的工具,应包含工具描述,工具输入内容的JSON模式,调用的函数
from langchain.tools import BaseTool, StructuredTool, Tool, tool
import random
@tool("lower_case", return_direct=True)
def to_lower_case(input:str) -> str:
"""返回全部小写的输入"""
return input.lower()
@tool("random_number",return_direct=True)
def random_number_maker(input:str) -> str:
"""返回0-100之间的随机数"""
return random.randint(0,100)
tools = [to_lower_case,random_number_maker]
to_lower_case.run('ROONIE')
最后就会输出roonie
现在我们来写一个完整的demo
from langgraph.graph import END, StateGraph
from langgraph.graph import StateGraph
from typing import TypedDict, List, Annotated,Union
import operator
from langchain_core.agents import AgentAction,AgentFinish
from langchain_core.messages import BaseMessage
from langchain_core.agents import AgentFinish
from langgraph.prebuilt.tool_executor import ToolExecutor
from langchain.tools.render import format_tool_to_openai_function
####################################初始化一个stategraph##############################
class AgentState(TypedDict):
#定义一些初始化的功能
#用户输入字符串
input:str
#对话中之前的消息列表
chat_history:list[BaseMessage]
#agent执行过后的结果,是要继续执行action还是完成finish,还是没有任何的状态None
agent_outcome: Union[AgentAction,AgentFinish,None]
#动作列表和相应的观察结果
#operator.add表明对这个状态的操作是添加到现有值上而不是覆盖掉
intermediate_steps: Annotated[list[tuple[AgentAction, str]], operator.add]
#############定义一些自定义的tool##############################
@tool("lower_case", return_direct=True)
def to_lower_case(input:str) -> str:
"""返回全部小写的输入"""
return input.lower()
@tool("random_number",return_direct=True)
def random_number_maker(input:str) -> str:
"""返回0-100之间的随机数"""
return random.randint(0,100)
tools = [to_lower_case,random_number_maker]
tool_executor = ToolExecutor(tools)
####################################定义另一个stategraph################################
from typing import TypedDict, Annotated, Sequence
import operator
from langchain_core.messages import BaseMessage
#不需要中间步骤,全部都在message里面
class AgentState_chatmodel(TypedDict):
message: Annotated[Sequence[BaseMessage], operator.add]
###################################初始化节点############################################
#定义agent
def run_agent(data):
#调用agent可执行对象,并传入数据
agent_outcome = agent_runnable.invoke(data)
#返回代理的结果
return {"agent_outcome":agent_outcome}
#定义执行工具的函数
def execute_tools(data):
#获取最近的代理结果 - 这是在上面的agent中添加的关键字
agent_action = data['agent_outcome']
#执行工具
output = tool_executor.invoke(agent_action)
#打印代理操作
print(f"The agent action is {agent_action}")
#打印工具结果
print(f"The tool result is {output}")
#返回输出
return {"intermediate_steps": [(agent_action, str(output))]}
#定义用于确定哪条conditional edge该走的逻辑
def should_continue(data):
#如果代理结果是AgentFinish,那么返回“end”字符串
#在设置图的流程时将使用这个
if isinstance(data['agent_outcome'], AgentFinish):
return "end"
#否则,返回一个AgentAction
#这里我们返回‘continue’字符串
#在设置图的流程时也将使用这个
else:
return "continue"
#######################################################定义一个新的图(工作流)##################################################
workflow = StateGraph(AgentState) #agentstate是上面初始化过的一个stategraph
#定义两个节点,我们将在他们之间循环
workflow.add_node("agent", run_agent)
workflow.add_node("action", execute_tools) #agent输出不满足我们的要求,那就继续调用它的action
#设置初始节点entrypoint
#将入口节点设为agent,表示这个节点是第一个被调用的
workflow.set_entry_point("agent")
#添加一个conditional egde
workflow.add_conditional_edges(
#首先定义边的起始节点,我们用‘agent’,表示在调用agent节点之后将采取这些边
"agent",
#接下来我们加载将决定下一个调用哪个节点的函数
should_continue,
#最后我们传入一个映射,key是字符串,value是其他节点
#将会发生的是,我们调用“should_continue”,然后其输出将与此映射中的key匹配,根据匹配情况调用相应的节点
{
"continue": "action", #如果是“continue”,则调用工具节点
"end": END #END是一个特殊节点,表示图的结束
}
)
#agent到action是需要conditional egde,但action到agent是不用的,因为action之后肯定要回到agent节点,所以加一个normal edge就行
workflow.add_edge('action', 'agent')
#最后进行编译compile,将其编译成一个LangChain可运行对象
app = workflow.compile()
我们来总结一下一个简单的LangGraph怎么写
- 定义一个stateGraph的类,以及它的一些初始化的功能,比如用户输入、中间步骤、对话历史、agent执行后的结果等
- 自定义一些tool,不定义的话相当于直接让调用的LLM来完成。把tool放入executor里
- 初始化节点:包含定义agent、定义工具函数、定义条件边的逻辑(比如agent返回结果是continue就继续,返回finish就end
- 开始实例化一个workflow:定义agent和action节点、设置初始节点、添加agent起始的conditional edge,添加action到agent的normal edge
- compile