在LangGraph中有三个重要元素
- StateGraph
- Node
- Edge
StateGraph
首先stategraph是用来描述整个图的,图中的状态会随着多个agent的工作不断的更新,节点node就是用来更新状态的
如何来定义一张图中的状态
每个应用的状态可能不同,所以我们需要根据具体应用场景来决定状态
from langgraph.graph import StateGraph
from typing import TypedDict, List, Annotated
import operatorclass State(TypedDict):#该示例中状态定义了两个字段,输入input,allactioninput: strall_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,还是没有任何的状态Noneagent_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,还是没有任何的状态Noneagent_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