![LangChain和MAF-03]完全不同的Agent设计哲学](http://pic.xiahunao.cn/yaotu/LangChain和MAF-03]完全不同的Agent设计哲学)
1. LangChain总的来说LangChain提供了如下三种Agent创建方式:1.1 LangGraph我们根据推理任务为Agent定义一个状态类型,并基于此状态类型创建一个StateGraph通过添加节点和边来构建一个图结构来定义Agent的推理流程最后通过调用StateGraph的compile方法来将这个图结构编译成一个被我们视为Agent的CompiledStateGraph。Pregel是CompiledStateGraph基类一个Pregel对象本质上是一个具有状态的Actor模型。Agent的状态通过一系列的通道来维护作为Actor的节点通过订阅通道的变化来驱动执行而它执行的结果体现为针对通道的更新。StateGraph针对Pregel的编译过程中图的节点编程Pregel的节点图的边体现为节点针对通道更新的订阅关系。当作为Agent的Pregel对象被执行时用户的输入被写入对应的通道订阅此通道的节点被收集并创建对应的任务在下一个推理步骤中并发执行。节点任务执行后又会更新对应的通道新的通道更新又会触发新的节点任务的执行如此循环往复直到没有新的节点被触发了或者达到设定的步骤上限此时整个推理过程就结束了。这个所谓的推理步骤被称为超步Superstep整个推进机制被称为BSPBulk Synchronous Parallel机制。具体的执行流程可以参阅我的文章“驱动Node执行的原力”。1.2 create_agent函数我们将create_agent函数作为工厂通过指定模型组件、工具列表、系统执行、状态和静态上下文Schema类型、结构化输出Schema类型、Checkpointer、存储和缓存以及中间件等参数来创建一个Agent对象。create_agent函数根据指定的模型和工具集创建的Agent依然是采用上面这道机制创建的。默认状态类型为AgentState将messages作为核心成员承载了模型、用户和工具三者之间的对话历史。create_agent函数依然会先创建作为Builder的StateGraph并为其添加两个节点一个用于封装模型节点默认名称为model另一个承载所有的工具(节点默认名称为tools)。模型节点与工具节点之间具有一条动态条件边当LLM返回的AIMessage包含工具调用时被激活的这条边会路由到tools节点完成工具调用反之则意味着AIMessage携带的就是最终的结果整个推理过程就此结束。工具节点与模型节点有一条静态边所以工具执行后会再次回到模型节点后者在新的状态下完成下一步推理。这种AgentState 双节点的结构虽然简单但却能满足常规的推理任务。对于更复杂的推理任务一方面我们可以扩展AgentState提供更多的状态成员来承载更多的上下文信息另一方面我们也可以通过注册AgentMiddleware来添加更多的节点来完善工作流。具体来说注册的AgentMiddleware提供了如下的功能添加状态字段如果AgentMiddleware涉及到针对状态更新对应的状态字段会定义在state_schema字段返回的状态类型中。此状态状态类型通常是AgentState的子类定义其中的字段最终会转换成通道用于注册工具当Middleware被注册到创建的Agent上时存储在其tools字段中的工具会自动注册到Agent上。这相当于提供了一种模块化的工具开发和注册的方式添加节点当Middleware重写了before_agent/abefore_agent、before_model/abefore_model、after_model/aafter_model、after_agent/aafter_agent方法都会在状态图中相应的位置添加一个节点。Middleware相当于利用此方式完善了Agent的工作流包装模型和工具调用Middleware利用重写的wrap_model_call/awrap_model_call、wrap_tool_call/awrap_tool_call方法对模型和工具的调用进行包装将AOP引入到模型和工具的调用中使得在调用前后添加一些额外的操作变得非常简单。比如很多Middleware都具有各自的系统提示词它们基本上都是利用重写的wrap_model_call/awrap_model_call方法的方式实现针对系统提示词的注入1.3 create_deep_agent函数Deep Agents提供create_deep_agent函数创建一个深度Agent能够将复杂的规划、文件管理和多Agent协作能力一键集成到你的应用中。它其实最终也会调用create_agent函数来创建Agent它仅仅在调用此方法时利用注册的Middleware为Agent添加了更多的功能如任务规划、用于上下文管理的文件系统、Sub-Agent生成和长期记忆等功能。从LangGraph和LangChain创建Agent的方式可以看出它们相当于是自助餐和套餐的差别。LangGraph赋予完全的自由度我们可以根据具体的推理任务对状态图进行DIY。虽然灵活自由实则对用户提出了更高的要求。相比之下LangChain利用create_agent函数创建的Agent具有固定结构却能满足大部分的推理任务。它相当于提供了一款符合大众口味的基础套餐。在点餐的时候我们可以选择这个基础套餐也可以在此基础上添加、替换和剔除一些配菜来满足个性化的需求这就是Middleware赋予的定制能力。LangChain和Deep Agents的关系就像是基础套餐和升级套餐的关系。LangChain利用create_agent函数构建的Agent只具有基础的功能任务定制的功能都需要通过注册Middleware来提供。企业级Agent来往往具有一些公共的功能需求如任务规划、用于上下文管理的文件系统、Sub-Agent生成和长期记忆等功能。如果任何一个功能都要求用户通过注册Middleware来实现的话无疑也会增加用户的使用门槛。于是Deep Agents对套餐进行了升级在通过create_deep_agent函数创建Agent的时候根据提供的配置自动注册了对应的Middleware。综上所述LangChain采用万法归一的设计哲学create_agent和create_deep_agent函数的背后依然是通过LangGraph的方式来创建Agent。所以整个LangChain平台所谓的Agent本质上就是一个Pregel对象所以Agent在任何地方的执行方式都是一致的。这是认为LangChain在设计上优于MAF的一个主要的原因。2. MAF于LangChain类似MAF提供Workflow来定义Agent的推理流程Workflow可以视为MAF的LangGraph不仅功能一致背后的设计思路也几乎一样都是基于同一篇文章Pregel: a system for large-scale graph processing来设计的;都是基于基于Superstep的BSPBulk Synchronous Parallel机制来驱动Agent的执行最终都体现为一个具有状态的Actor模型虽然LangChain提供了不同的Agent创建方式由于最终的Agent都是利用LangGraph来创建的所以从执行层面看Agent只有一种唯一的形式。但是对于MAF来说这仅仅是Workflow的设计而且它对应MAF众多Agent类型的一种。在它之上MAF定义了一个抽象的AIAgent然后提供了一系列不同类型的Agent实现。LangChain和MAF都提供都通过消息关联的方式来在用户和Agent之间保持一种连续的对话状态。LangChain将维持对话的上下文称为Thread而MAF则称之为Session并通过抽象类AgentSession来表示。我们通过在每次调用关联一个AgentSession对象的方式当前调用纳入某个Session。作为基类的AIAgent定义了创建AgentSession的方法CreateSessionAsync。AgentSession对象自身就是一个承载可序列化会话状态的容器所以AIAgent还定义了用来对它进行序列化和反序列化的SerializeSessionAsync和DeserializeSessionAsync方法。public abstract class AIAgent { public ValueTaskAgentSession CreateSessionAsync(CancellationToken cancellationToken default) this.CreateSessionCoreAsync(cancellationToken); public ValueTaskJsonElement SerializeSessionAsync( AgentSession session, JsonSerializerOptions? jsonSerializerOptions null, CancellationToken cancellationToken default) this.SerializeSessionCoreAsync(session, jsonSerializerOptions, cancellationToken); public ValueTaskAgentSession DeserializeSessionAsync( JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions null, CancellationToken cancellationToken default) }LangChain中并为将状态封装在一个容器对象中进行传递会话状态总是以Checkpoint的形式基于thread_id进行存储所以调用时只需要指定对应的thread_id就可以了Agent会自动加载对应的Checkpoint来恢复状态。针对AIAgent的调用分两种:阻塞式调用通过如下这些重载的RunCoreAsync方法来完成整个执行流程结束之后返回一个AgentResponse作为响应内容流式响应对应RunStreamingAsync方法在执行过程中不断地响应中间结果。方法返回一个IAsyncEnumerableAgentResponseUpdate对象AgentResponseUpdate代表了Agent在执行过程中的一个更新可以是一个新的响应、一个工具调用的结果或者一个状态更新等。public abstract class AIAgent { public TaskAgentResponse RunAsync( IEnumerableChatMessage messages, AgentSession? session null, AgentRunOptions? options null, CancellationToken cancellationToken default); public async IAsyncEnumerableAgentResponseUpdate RunStreamingAsync( IEnumerableChatMessage messages, AgentSession? session null, AgentRunOptions? options null, CancellationToken cancellationToken default) }LangChain中的Agent类型都是继承自Pregel的CompiledStateGraph类型因为都是通过StateGraph编译而成。但是MAF中的Agent可就多了比如ChatClientAgent这是MAF中最通用的Agent类型。它直接基于推理服务构建可以适配任何实现了IChatClient接口的服务如Azure OpenAI、OpenAI、Anthropic等。支持函数调用Function Calling、多轮对话上下文管理以及结构化输出WorkflowHostAgent用于在Agent体系中Host并运行完整的MAF Workflow。它将一个复杂的、基于图Graph的执行逻辑封装成一个普通的Agent接口。外部看来它只是一个Agent但内部其实运行着一个包含多个Superstep的Pregel计算图。用于实现复杂任务的拆解、循环重试以及需要严格逻辑顺序的业务流程。这就是我们上面说得与LangChain Agent采用相同设计基于MAF Workflowd的Agent类型