news 2026/6/14 2:44:19

[对比学习LangChain和MAF-07]如何引入人机交互的审批流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
[对比学习LangChain和MAF-07]如何引入人机交互的审批流程

如何执行的工具涉及一些较为敏感的操作,必需要经过人工审批才能执行,这时候就需要引入人机交互(Human-In-The-Loop)的机制。无论是LangChain还是MAF,都提供了相应的机制来支持人机交互,下面我们来看看它们是如何实现的。

1. LangChain

LangChain的人机交互是利用中断实现的,作为Agent状态图的任何一个节点都可以直接调用interrupt函数在任何一个时间点中断Agent的执行流程,并等待外部的输入来继续执行。除此之外,它还提供了一个HumanInTheLoopMiddleware中间件是整个过程变得更简单。

1.1 手工中断引入审批

如下的演示程序创建的Agent注册了一个用于转账的transfer工具,工具函数中会调用interrupt函数通过触发一个中断,等待用户的确认来决定是否继续执行转账操作。

fromlangchain.agentsimportcreate_agentfromlanggraph.typesimportinterrupt,Commandfromlangchain.toolsimporttoolfromlangchain_openaiimportChatOpenAIfromlanggraph.checkpoint.memoryimportMemorySaverfromlangchain_core.runnablesimportRunnableConfigfromdotenvimportload_dotenvimportasyncio,uuid load_dotenv()@toolasyncdeftransfer(account_from:str,account_to:str,amount:float):""""Execute a bank transfer between two accounts immediately"""ifinterrupt(f"Do you want to transfer{amount}from{account_from}to{account_to}?")=="yes":returnf"Transfer of{amount}from{account_from}to{account_to}has been completed."return"Transfer request declined."agent=create_agent(model=ChatOpenAI(model="gpt-5.2-chat"),tools=[transfer],checkpointer=MemorySaver(),)asyncdefperform_transfer(decision:str):config:RunnableConfig={"configurable":{"thread_id":uuid.uuid4().hex}}result=awaitagent.ainvoke(input={"messages":[{"role":"user","content":"Transfer $100 from ``4242 4242 4242 4242` to `5555 5555 5555 4444`.",},]},config=config,version="v2")interrupt_id=result.interrupts[-1].idinterrupt_value=result.interrupts[-1].valueprint(f"""\ Interrupt received during transfer process: ID:{interrupt_id}, Value:{interrupt_value}""")result=awaitagent.ainvoke(input=Command(resume=decision),config=config)print(result["messages"][-1].content)asyncio.run(perform_transfer("yes"))asyncio.run(perform_transfer("no"))

调用Agent提供转账的功能实现在perform_transfer函数中,参数decision代表用户对于转账操作的确认,批准(approve)或拒绝(reject)为两种可用的选项。由于中断会触发持久化,而持久化是基于Thread进行的,所以调用Agent的时候需要利用RunnableConfig提供thread_id,相同的RunnableConfig被使用后续的恢复调用中。调用Agent的ainvoke方法时,我们显式指定了version参数为"v2",这样我们才可以执行结果中利用interrupts字段来获取中断列表(由于节点的并发执行,所以可能在同一个推理步骤会产生多个中断)。我们提供这两种决定先后调用了perform_transfer函数,最终会得到如下的输出结果:

Interrupt received during transfer process: ID: 519f2744bc91d0af06c90b924814c91f, Value: Do you want to transfer 100.0 from 4242 4242 4242 4242 to 5555 5555 5555 4444? ✅ **Transfer Successful!** I've completed the transfer of **$100** with the following details: - **From:** 4242 4242 4242 4242 - **To:** 5555 5555 5555 4444 - **Amount:** $100.00 If you need a receipt, want to make another transfer, or have any other banking requests, just let me know!
Interrupt received during transfer process: ID: 2d4d3358c895de0bf15c1bb94e076ffa, Value: Do you want to transfer 100.0 from 4242 4242 4242 4242 to 5555 5555 5555 4444? ❌ **Transfer Failed** The transfer of **$100** from account **4242 4242 4242 4242** to **5555 5555 5555 4444** could not be completed because the request was **declined** by the system. ### What you can do next: - ✅ Double‑check the account numbers for accuracy - 💳 Ensure the source account has sufficient funds - 🔒 Confirm there are no restrictions or holds on either account - 🔁 Try the transfer again later If you’d like, I can help you retry the transfer or look into possible reasons for the decline. Just let me know!

1.2 利用HumanInTheLoopMiddleware引入审批流程

在前面演示的实例中,我们在工具函数中调用interrupt函数来触发中断来引入人机交互。下面的引入HumanInTheLoopMiddleware的版本,两个版本基本是等效的。

fromlangchain.agentsimportcreate_agentfromlanggraph.typesimportinterrupt,Commandfromlangchain.toolsimporttoolfromlangchain_openaiimportChatOpenAIfromlanggraph.checkpoint.memoryimportMemorySaverfromlangchain.agents.middlewareimportHumanInTheLoopMiddleware,InterruptOnConfigfromlangchain.agents.middleware.human_in_the_loopimportDecision,ApproveDecision,RejectDecisionfromlangchain_core.runnablesimportRunnableConfigfromdotenvimportload_dotenvimportasyncio,uuid load_dotenv()@toolasyncdeftransfer(account_from:str,account_to:str,amount:float)->str:""""Execute a bank transfer between two accounts immediately"""returnf"Transfer of{amount}from{account_from}to{account_to}has been completed."agent=create_agent(model=ChatOpenAI(model="gpt-5.2-chat"),tools=[transfer],checkpointer=MemorySaver(),middleware=[HumanInTheLoopMiddleware(interrupt_on={"transfer":InterruptOnConfig(allowed_decisions=["approve","reject"],description=f"Do you want to conduct such a transfer?")})],)asyncdefperform_transfer(decision:Decision):config:RunnableConfig={"configurable":{"thread_id":uuid.uuid4().hex}}result=awaitagent.ainvoke(input={"messages":[{"role":"user","content":"Transfer $100 from ``4242 4242 4242 4242` to `5555 5555 5555 4444`.",}]},config=config,version="v2")interrupt_id=result.interrupts[-1].idinterrupt_value=result.interrupts[-1].valueprint(f"""\ Interrupt received during transfer process: ID:{interrupt_id}, Value:{interrupt_value}""")result=awaitagent.ainvoke(input=Command(resume={"decisions":[decision]}),config=config)print(result["messages"][-1].content)asyncio.run(perform_transfer(ApproveDecision(type="approve")))asyncio.run(perform_transfer(RejectDecision(type="reject",message="I don't feel safe about this transfer, so I want to reject it.")))

对于HumanInTheLoopMiddleware的更多细节,在我的文章“通过HumanInTheLoopMiddleware引入人机交互”中有更详细的介绍,感兴趣的读者可以点击链接查看。

2.2. MAF

在MAF中,如果某个工具在执行之前需要用户介入审批,只需要利用ApprovalRequiredAIFunction这个标签来装饰这个工具即可。整个审批流程的借助于FunctionInvokingChatClient这个IChatClient中间件来实现,此中间件通过拦截LLM的响应,并完成对工具的调用,进而将ReAct循环引入ChatClientAgent。如果待执行的工具被装饰了ApprovalRequiredAIFunction中间件,它不会直接调用这个工具,此时它会生成一个请求审批的消息,并利用一个类型为ToolApprovalRequestContentAIContent来描述这个待审批的工具调用。

用户在接收到这个消息后,做出批准或者拒绝的决定,然后利用这个ToolApprovalRequestContent创建一个ToolApprovalResponseContent,最后根据这个ToolApprovalResponseContent来生成一条ChatMessage,将它作为输入采用同一个Session再次调用Agent。请求消息再次被FunctionInvokingChatClient拦截到后,它会根据消息中的ToolApprovalResponseContent来判断用户的决定,如果是批准(approve),它就继续调用被ApprovalRequiredAIFunction装饰的工具来完成工具调用;如果是拒绝(reject),它就会直接返回一个表示工具调用被拒绝的消息给LLM。

不论是批准还是拒绝,都会生成一条角色为ToolChatMessage,作为主体内容的FunctionResultContent用来描述工具调用的结果或者拒绝的原因。LLM在接收到这个消息后,就可以根据工具调用的结果来决定下一步的行动了。

对于上面演示的用于模拟银行转账的实例,在MAF中可以采用如下的方式来是实现。如代码片段所示,我们定义了一个Transfer方法来模拟银行转账的操作,并利用AIFunctionFactory.Create方法将其转换成一个AIFunction对象,然后利用ApprovalRequiredAIFunction来装饰这个工具。在调用AsAIAgent方法创建Agent的时候,我们将这个工具注册到Agent中。

usingAzure.AI.Projects;usingdotenv.net;usingMicrosoft.Extensions.AI;usingOpenAI;usingSystem.ClientModel;usingSystem.ComponentModel;DotEnv.Load();varmodel=Environment.GetEnvironmentVariable("MODEL")!;varapiKey=Environment.GetEnvironmentVariable("API_KEY")!;varopenAIUrl=Environment.GetEnvironmentVariable("OPENAI_URL")!;vartool=newApprovalRequiredAIFunction(AIFunctionFactory.Create(method:Transfer,name:"Transfer"));varagent=newOpenAIClient(credential:newApiKeyCredential(key:apiKey),options:newOpenAIClientOptions{Endpoint=newUri(openAIUrl)}).GetChatClient(model:model).AsIChatClient().AsAIAgent(tools:[tool]);varsession=awaitagent.CreateSessionAsync();varresponse=awaitagent.RunAsync(message:"Transfer $100 from ``4242 4242 4242 4242` to `5555 5555 5555 4444`.",session:session);while(responseisnotnull){varmessage=response.Messages.Last();varapprovalRequestContent=message.Contents.OfType<ToolApprovalRequestContent>().FirstOrDefault();if(approvalRequestContentisnull){Console.WriteLine(message.Text);break;}vartoolCall=(FunctionCallContent)approvalRequestContent.ToolCall;Console.WriteLine($"Tool `{toolCall.Name}` is requesting approval to execute with arguments:");foreach(var(k,v)intoolCall.Arguments!){Console.WriteLine($"-{k}:{v}");}Console.Write("Approve it [Y/N]: ");varinput=Console.ReadLine();boolisApproved=input?.Trim().ToUpper()=="Y";varapprovalResponse=approvalRequestContent.CreateResponse(isApproved);response=awaitagent.RunAsync(newChatMessage(ChatRole.User,[approvalResponse]),session);}[Description("Execute a bank transfer between two accounts immediately.")]staticstringTransfer([Description("Bank account from which money is transfered.")]stringfrom,[Description("Bank account to which money is transfered.")]stringto,[Description("Amount of money to be transfered.")]decimalamount)=>$"Transfer of{amount}from{from}to{to}has been completed.";

我们调用Agent的CreateSessionAsync方法来创建一个Session对象,并将它作为参数传入后续的RunAsync调用中,以保证在同一个Session上下文中进行交互。在第一个RunAsync调用中,我们传入了一个用户消息来请求转账操作,并在一个循环中处理响应。在循环中,我们首先检查响应消息中是否包含ToolApprovalRequestContent,如果没有,说明没有工具调用请求,我们就直接输出消息文本并退出循环;如果有,我们就提取出工具调用的相关信息,并提示用户输入批准(Y)还是拒绝(N)。根据用户的输入,我们创建一个ToolApprovalResponseContent对象,并将它作为用户消息的一部分再次调用RunAsync方法来继续与Agent的交互。如下所示的是两种不同的输入对应的输出结果:

Tool `Transfer` is requesting approval to execute with arguments: - from: 4242 4242 4242 4242 - to: 5555 5555 5555 4444 - amount: 100 Approve it [Y/N]: Y ✅ **Transfer Complete!** I've successfully transferred **$100** from the account **4242 4242 4242 4242** to **5555 5555 5555 4444**. If you need a receipt, want to make another transfer, or have any other requests, just let me know! 💸
Tool `Transfer` is requesting approval to execute with arguments: - from: 4242 4242 4242 4242 - to: 5555 5555 5555 4444 - amount: 100 Approve it [Y/N]: N I wasn’t able to complete that transfer — it was rejected. Before I try again, I need to confirm a couple of things with you: 1. **Do you confirm you want to transfer $100** from account **4242 4242 4242 4242** to account **5555 5555 5555 4444**? 2. Are these **bank account numbers** (not card numbers), and are they authorized for transfers? Please reply with a confirmation like **“Yes, confirm the transfer”**, or let me know if any details need to be changed.

MAF针对人机交互审批流程是通过FunctionInvokingChatClient这个ChatClient中间件实现的,该中间件同时在MAF中实现了最重要的ReAct循环机制,我的文章FunctionInvokingChatClient:ReAct循环和工具审批实现者提供了针对该中间件的详细介绍,感兴趣的读者可以点击链接查看。此外,MAF还提供了一个名为ToolApprovalAgent的Agent中间件实现了Don’t ask again模式的工具调用审批流程,这也是HarnessAgent采用的一种Harness手段,感兴趣的读者可以查看ToolApprovalAgent-摆脱重复审批的烦恼这篇文章来了解更多细节。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/14 2:21:04

点云编码选型指南:八叉树 vs. 直接编码(DCM),在自动驾驶与元宇宙中如何抉择?

点云编码技术深度解析&#xff1a;八叉树与直接编码在自动驾驶与元宇宙中的实战选型当高精度激光雷达每秒生成数百万个空间数据点&#xff0c;当元宇宙场景需要实时渲染数十亿级别的三维物体&#xff0c;点云编码技术便成为决定系统性能的关键瓶颈。面对动态变化的自动驾驶环境…

作者头像 李华
网站建设 2026/6/14 2:15:24

35岁程序员收藏!大模型时代如何突破职业瓶颈,这6条出路请码住

文章分析了中国互联网行业从高速增长到增速放缓的背景&#xff0c;指出AI技术发展对程序员职业带来的冲击&#xff0c;尤其对35岁左右程序员的挑战。文章总结了AI替代程序员的具体方向&#xff0c;并剖析了35岁程序员面临的结构性困境&#xff0c;包括薪资与产出不匹配、体力下…

作者头像 李华
网站建设 2026/6/14 2:12:58

如何快速掌握Blender建筑建模:面向初学者的完整实战指南

如何快速掌握Blender建筑建模&#xff1a;面向初学者的完整实战指南 【免费下载链接】building_tools Building generation addon for blender 项目地址: https://gitcode.com/gh_mirrors/bu/building_tools 还在为Blender中复杂的建筑建模而烦恼吗&#xff1f;building…

作者头像 李华
网站建设 2026/6/14 2:10:41

《一张图看懂:社保断缴后,哪些资格会清零?很多人到用时才后悔》

很多人在搜索“社保断缴攻略”“社保补缴指南”“北京社保断缴怎么办”“上海社保断缴影响”时&#xff0c;往往已经处在一个被动阶段&#xff1a;要买房、要落户、要摇号&#xff0c;才发现社保出现了断档。社保看似只是每个月的一笔缴费&#xff0c;但它背后绑定的&#xff0…

作者头像 李华