![[LangChain智能体本质论-04]Agent的状态即通道](http://pic.xiahunao.cn/yaotu/[LangChain智能体本质论-04]Agent的状态即通道)
运行时的Agent作为一个Pregel对象由节点和通道组成节点是无状态的意味着通道承载Agent所有的状态信息。create_agent函数返回的Agent的类型声明如下其第一个泛型参数代表承载整个Agent的状态类型具体的类型为AgentState[ResponseT]后者的泛型参数ResponseT代表格式化输出的Schema。CompiledStateGraph[AgentState[ResponseT],ContextT,_InputAgentState,_OutputAgentState[ResponseT]]1. AgentState作为状态的AgentState对象的数据成员会转换成对应的通道。当我们看到AgentState的三个字段messages、jump_to和structured_response定义的时候是不是感到很熟悉前面演示实例输出的通道就有三个与它们同名。CompiledStateGraph[AgentState[ResponseT],ContextT,_InputAgentState,_OutputAgentState[ResponseT]]classAgentState(TypedDict,Generic[ResponseT]):messages:Required[Annotated[list[AnyMessage],add_messages]]jump_to:NotRequired[Annotated[JumpTo|None,EphemeralValue,PrivateStateAttr]]structured_response:NotRequired[Annotated[ResponseT,OmitFromInput]]JumpToLiteral[tools,model,end]定义在AgentState中绑定为通道的字段可以利用标注的Annotated来定义通道类型。如果定义了reducer函数最终创建的将是一个BinaryOperatorAggregate类型的通道reducer函数会作为它的操作符。通过Annotated标注可以看出messages字段的类型是一个AnyMessage列表AnyMessage是对众多预定义的针对BaseMessage/BaseMessageChunk具体实现类型的统称我的文章消息——Agent与模型交互的媒介中具有针对它们的详细介绍。通过前面演示实例可知messages和jump_to这两个通道的类型分别为BinaryOperatorAggregate和EphemeralValue。这样设计很好理解因为前者用于收集生成的消息只有设计成BinaryOperatorAggregate类型并采用基于追加的reducer函数才能实现。后者用于指导下一步跳转到何处具有邻步有效的特性EphemeralValue类型的通道正式针对这种场景设计的。除了类型前面的演示程序还体现了三个通道针对输入/输出的差异messages通道同时作为输入和输出structured_response通道只作为输出而jump_to通道既非输入也非输出。这是因为structured_response和jump_to字段分别被标注了OmitFromInput和PrivateStateAttr前者将数据成员从输入中剔除后者则同时从输入和输出中除名。如果只作为输入则可以是标注OmitFromOutput。没有对此作显式标注的messages意味这输入和输出均可见。dataclassclassOmitFromSchema:input:boolTrueoutput:boolTrueOmitFromInputOmitFromSchema(inputTrue,outputFalse)OmitFromOutputOmitFromSchema(inputFalse,outputTrue)PrivateStateAttrOmitFromSchema(inputTrue,outputTrue)2. 自定义状态成员默认的状态类型AgentState是一个TypedDict它只定义了三个数据成员我们也可以自定义任意的TypedDict作为Agent的状态Schema。但是很多现有的组件都依赖于这三个成员所以最稳妥的方式还是定义它的子类。接下来我们将通过一个实例演示添加自定义状态数据成员将状态注入工具函数调用Agent是初始化自定义状态成员从调用结果中提取自定义状态成员。fromtypingimportAnnotated,Any,Callable,Sequence,overrideimportbuiltinsfromlangchain.agentsimportcreate_agentfromlangchain.agents.middleware.typesimportOmitFromOutput,OmitFromInputfromlangchain_core.language_modelsimportBaseChatModel,LanguageModelInputfromlangchain_core.messagesimportBaseMessage,AIMessage,ToolMessage,ToolCallfromlangchain_core.callbacksimportCallbackManagerForLLMRunfromlangchain_core.outputs.chat_resultimportChatResult,ChatGenerationfromlangchain_core.toolsimportBaseToolfromlangchain_core.runnablesimportRunnablefromlanggraph.prebuiltimportInjectedStatefromlangchain.agents.middlewareimportAgentStatefromlanggraph.typesimportCommandclassModelSimulator(BaseChatModel):def_generate(self,messages:list[BaseMessage],stop:list[str]|NoneNone,run_manager:CallbackManagerForLLMRun|NoneNone,**kwargs:Any,)-ChatResult:if[messageformessageinmessagesifisinstance(message,ToolMessage)]:generationChatGeneration(messageAIMessage())returnChatResult(generations[generation],llm_output{})tool_call:ToolCall{name:fake_tool,args:{},id:tool_call_001,}generationChatGeneration(messageAIMessage(content,tool_calls[tool_call]))returnChatResult(generations[generation],llm_output{})propertydef_llm_type(self)-str:returnmodel-simulatoroverridedefbind_tools(self,tools:Sequence[builtins.dict[str,Any]|type|Callable|BaseTool],*,tool_choice:str|NoneNone,**kwargs:Any,)-Runnable[LanguageModelInput,AIMessage]:returnselfclassExtendedAgentState(AgentState):foo:Annotated[str,OmitFromOutput]bar:Annotated[str,OmitFromOutput]baz:Annotated[str,OmitFromInput]qux:Annotated[str,OmitFromInput]deffake_tool(state:Annotated[dict,InjectedState])-Command:A fake tooltool_call_idstate[messages][-1].tool_calls[0][id]returnCommand(update{messages:[ToolMessage(,tool_call_idtool_call_id)],baz:state.get(foo,N/A),qux:state.get(bar,N/A),},)agentcreate_agent(modelModelSimulator(),tools[fake_tool],state_schemaExtendedAgentState,)resultagent.invoke(input{foo:Hello,bar:World})# type: ignoreassertresult[baz]Helloassertresult[qux]World我们通过继承AgentState创建了自定义状态类型ExtendedAgentState并为它额外添加了四个字符串类型的成员foo、bar、baz和qux我们利用针对OmitFromOutput和OmitFromInput的注解将foo和bar定义成输入将baz和qux定义成输出。模拟的工具函数fake_tool并没有外部输入而是将当前状态视为输入。为此我们在参数state上应用了Annotated[dict, InjectedState]实现了以字典形式针对状态的参数注入。在默认情况下Agent状态的所有成员都会注入到参数绑定的字典中如果需要对注入的字典进行过滤过滤的字段可以在InjectedState对象中指定。比如Annotated[str, InjectedState(foo)]。classInjectedState(InjectedToolArg):def__init__(self,field:str|NoneNone)-None:self.fieldfield由于作为LangChain引擎的Pregel采用基于BSP的执行机制通道不允许被节点直接修改节点只能提交通道更新请求并最终由执行引擎统一完成针对通道的更新。反映在工具函数上就是它不能在函数内部直接修改某个状态成员的值只能将针对状态的更新封装返回的Command对象上。所以fake_tool会返回一个Command对象它的update字段体现了针对状态的更新在messages列表中添加一个ToolMessage将状态成员foo和bar的值赋值给成员baz和qux。在调用create_agent创建Agent的时候我们直接将ExtendedAgentState类型设置为state_schema参数。model参数设置的是我们用来模拟模型的ModelSimulator对象它采用与FakeModel类似的定义。在调用invoke方法时我们在input参数的字典中添加了成员foo和bar的值。并从作为执行结果的字典中提取通过工具函数设置的baz和qux的值。