[ToolNode在LangGraph中的运用-04]从models节点到tools节点的具有怎样的路由规则?
根据状态针对tools节点的路由体现在如下这个tools_condition函数上模块路径为langgraph.prebuilt。路由规则很简单如果最后一条AIMessage携带ToolCall意味着需要执行相应的工具此时返回“tools”并希望路由到ToolNode节点去执行工具否则这条AIMessage的内容就是最终的回答直接返回__end__;在添加指向ToolNode和其他节点的“条件边”时我们一般将tools_condition作为路由路径映射函数。如果我们的ToolNode具有自定义的名字或者在不需要调用工具时还需要路由到另一个节点而不是直接终止流程需要在调用add_conditional_edge方法时利用第三个参数提供一个字典将tools和__end__映射为具体的节点名称。deftools_condition(state:list[AnyMessage]|dict[str,Any]|BaseModel,messages_key:strmessages,)-Literal[tools,__end__]:ifisinstance(state,list):ai_messagestate[-1]elif(isinstance(state,dict)and(messages:state.get(messages_key,[])))or(messages:getattr(state,messages_key,[])):ai_messagemessages[-1]else:msgfNo messages found in input state to tool_edge:{state}raiseValueError(msg)ifhasattr(ai_message,tool_calls)andlen(ai_message.tool_calls)0:returntoolsreturn__end__虽然在大部分场景下我们会利用LLM的能力来确定待执行的工具并由它生成包含对应ToolCall的AIMessage但是如果根据当前状态已经明确了执行的工具和参数我们也可以自行将工具调用封装成ToolCall列表并置于创建的AIMessage中最后只需将其添加到消息列表中就可以了。如下这个程序演示了处理订单退款请求具体流程如下图所示用户提供退款请求process_refund_request节点分析提交的文本并确定是否是一个包含订单ID和退款金额的有效退款请求。对于有效请求会路由到tools节点执行相关的工具如果金额小于1000直接调用refund工具退款否则调用audit工具作进一步审核。最终交给response节点作统一回复。这个例子仅仅利用了LLM分析语义能力将自然语言请求转换成结构化的输出Refund包含是否有效退款请求、订单ID和金额。虽然不再需要AI参与其余流程但是为了实现针对tools节点的路由和辅助工具的执行process_refund_reques依然会创建一个AIMessage并在其中存放待调用的工具refund或者audit。importoperator,iofromtypingimportAnnotated,TypedDictfromlangchain_openaiimportChatOpenAIfromlangchain_core.toolsimporttool,InjectedToolCallIdfromlangchain_core.messagesimportBaseMessage,HumanMessage,ToolMessage,AIMessage,ToolCallfromlanggraph.graphimportStateGraphfromlanggraph.prebuiltimportToolNode,InjectedState,tools_conditionfromlanggraph.typesimportCommandfromPILimportImageasPILImagefromdotenvimportload_dotenvimportuuid load_dotenv()classRefund(TypedDict):is_valid:boolWhether the refund request is valid, which must contain an order ID and amount.order_id:str|NoneOrder ID for the refund.amount:int|NoneRefund amount.classRefundState(TypedDict):messages:Annotated[list[BaseMessage],operator.add]details:Refund done:boolaudit:strerror:strtooldefrefund(state:Annotated[dict,InjectedState],tool_call_id:Annotated[str,InjectedToolCallId])-Command:Process a refund based on the provided details in the state.order_idstate.get(details,{}).get(order_id)amountstate.get(details,{}).get(amount)contentfRefund of{amount}dollars has been processed for order{order_id}.returnCommand(update{done:True,messages:[ToolMessage(contentcontent,tool_call_idtool_call_id)]})tooldefaudit(state:Annotated[dict,InjectedState],tool_call_id:Annotated[str,InjectedToolCallId])-Command:Audit a refund request based on the provided details in the state.order_idstate.get(details,{}).get(order_id)amountstate.get(details,{}).get(amount)contentfLarge refund ({amount}dollars) detected for order{order_id}, flagging for review.returnCommand(update{audit:content,messages:[ToolMessage(contentcontent,tool_call_idtool_call_id)]})llmChatOpenAI(modelgpt-5.2-chat).bind_tools(tools[refund,audit])defprocess_refund_request(state:RefundState)-dict:prompt Please extract refund details from the users message. The details should include: - Whether the refund request is valid (must contain an order ID and amount). - The order ID for the refund. - The refund amount. llm_with_outputllm.with_structured_output(schemaRefund)# type: ignoremessages[*state[messages],HumanMessage(contentprompt)]refundllm_with_output.invoke(inputmessages)# type: ignoreif(notrefund[is_valid]):errorInvalid refund details. Missing order ID or amount.return{error:error,messages:[AIMessage(contenterror)]}tool_call(ToolCall(nameaudit,args{},idstr(uuid.uuid4()))ifrefund[amount]1000elseToolCall(namerefund,args{},idstr(uuid.uuid4())))return{details:refund,messages:[AIMessage(content,tool_calls[tool_call])],}defresponse(state:RefundState):errorstate.get(error)iferror:return{messages:[AIMessage(contenterror)]}auditstate.get(audit)ifaudit:return{messages:[AIMessage(contentaudit)]}ifstate.get(done):return{messages:[AIMessage(contentRefund processed successfully.)]}Agent创建出来后我们提交两个具有不同退款金额1500和100的请求requestHumanMessage(contentI want to refund an order with ID 12345 for the amount of 1500 dollars.)resultagent.invoke({messages:[request]})# type: ignoreformessageinresult[messages]:message.pretty_print()requestHumanMessage(contentI want to refund an order with ID 12345 for the amount of 100 dollars.)resultagent.invoke({messages:[request]})# type: ignoreformessageinresult[messages]:message.pretty_print()按照处理流程第一个请求的处理金额为1500所以不会正常处理需要进一步审核。 Human Message I want to refund an order with ID 12345 for the amount of 1500 dollars. Ai Message Tool Calls: audit (429704ce-877f-4707-9b26-bac8cfd4197c) Call ID: 429704ce-877f-4707-9b26-bac8cfd4197c Args: Tool Message Name: audit Large refund (1500 dollars) detected for order 12345, flagging for review. Ai Message Large refund (1500 dollars) detected for order 12345, flagging for review.第二个请求涉及金额只有100会直接退款。 Human Message I want to refund an order with ID 12345 for the amount of 100 dollars. Ai Message Tool Calls: refund (fe4ce10a-4d74-47dd-b9ba-ed8c1e223c6b) Call ID: fe4ce10a-4d74-47dd-b9ba-ed8c1e223c6b Args: Tool Message Name: refund Refund of 100 dollars has been processed for order 12345. Ai Message Refund processed successfully.