(六)构建多智能体旅行客服-如何切换智能体角色
认知有限,难免误差,持续完善
在上一篇中我们把多智能体旅行客服代码运行起来了,这里我们聚焦多智能体切换这个话题,说一说思路,看一看代码,目前我们智能旅行客服中涉及到的智能体如下:
包 | 文件 | 职责 |
---|---|---|
agents | assistant_graph | 整个多智能体Graph构建 |
agents | primary_assistant | 负责调度(supervisor) |
agents | car_rental_assistant | 租车智能助手 |
agents | excursion_assistant | 旅行智能助手 |
agents | flight_booking_assistant | 航班智能助手 |
agents | hotel_booking_assistant | 酒店智能助手 |
多智能体切换思路
首先我们是对话式的智能体,目前在上下文管理这块是多个智能体共用的一个messages(LangGraph State中定义的共享变量),会话中的所有对话信息都会记录到这里面,当然这会面临上下文膨胀、污染的问题,在智能旅行客服中暂不解决此问题,未来我们在上下文工程相关实践中再来看看如何优化。另外,目前我们的切换思路是,当一个助手不能处理当前任务时,是将工作转交给主助手(primary_assistant)进行二次调度,不是将工作直接转交给对应助手。
基于此,我们多智能体切换方案主要就是要解决如下几个问题:
怎么标记:在整体系统中如何记录当前正在服务的助手。
怎么切换:一个助手不能处理当前任务时,怎么切换到主助手,进行二次高度。
怎么管理:在多智能体的对话上下文中如何管理各个助手的对话。
怎么标记:对话角色状态
这很简单,就是在State中定义一个对话角色变量dialog_state,每次转交任务都要更新这个变量
class State(TypedDict):messages: Annotated[list[AnyMessage], add_messages]user_info: strdialog_state: Annotated[list[Literal["assistant","update_flight","book_car_rental","book_hotel","book_excursion",]],update_dialog_stack,]
怎么切换:提示词 + 工具
(1)通过提示词,让助手在遇到自己无法处理的任务时,调用工具将任务转换给主助手,并给出一些转交工作的场景。当然这个提示词LLM判断可能有误,根据场景调整一下即可。
You are a specialized assistant for handling car rental bookings. The primary assistant delegates work to you whenever the user needs help booking a car rental. Search for available car rentals based on the user's preferences and confirm the booking details with the customer. When searching, be persistent. Expand your query bounds if the first search returns no results. If you need more information or the customer changes their mind, escalate the task back to the main assistant.Remember that a booking isn't completed until after the relevant tool has successfully been used.
If the user needs help, and none of your tools are appropriate for it, then "CompleteOrEscalate" the dialog to the host assistant. Do not waste the user's time. Do not make up invalid tools or functions.
Some examples for which you should CompleteOrEscalate:"
- what's the weather like this time of year?
- What flight are available?
- nevermind i think I'll book separately
- Oh wait i haven't booked my flight yet i'll do that first
- Car rental booking confirmed
您是一位专门处理租车预订的助手。当用户需要帮助预订租车时,主助手会将工作委托给您。请根据用户的偏好搜索可用的租车选项,并与客户确认预订详情。搜索时请保持持续性。如果首次搜索未返回结果,请扩大搜索条件范围。如果您需要更多信息或客户改变主意,请将任务转交回主助手。请记住,只有在相关工具成功使用后,预订才算完成。
如果用户需要帮助,但您没有合适的工具来处理,请通过‘CompleteOrEscalate’将对话转交给主助手。请勿浪费用户时间,也不要编造无效的工具或功能。
以下是一些应当使用CompleteOrEscalate转交的场景示例:
- 这个季节的天气怎么样?
- 有哪些航班可选?
- 算了,我想我还是单独预订吧
- 等等,我还没订机票呢,我先去订机票
- 租车预订已确认
(2)工具实现,可以参数里面说明转交工作的理由及示例
from pydantic import BaseModel, Field
class CompleteOrEscalate(BaseModel):"""A tool to mark the current task as completed and/or to escalate control of the dialog to the main assistant,who can re-route the dialog based on the user's needs."""
cancel: bool = Truereason: str
class Config:json_schema_extra = {"example": {"cancel": True,"reason": "User changed their mind about the current task.",},"example 2": {"cancel": True,"reason": "I have fully completed the task.",},"example 3": {"cancel": False,"reason": "I need to search the user's emails or calendar for more information.",},}
(3)判断LLM返回是否发起了CompleteOrEscalate调用,如果发起CompleteOrEscalate调用,就改变dialog_state
def route_update_flight(state: State,
):route = tools_condition(state)if route == END:return ENDtool_calls = state["messages"][-1].tool_callsdid_cancel = any(tc["name"] == CompleteOrEscalate.__name__ for tc in tool_calls)if did_cancel:return "leave_skill"
...
(4)leave_skill,离开当前对话,代码中对应pop_dialog_state
def pop_dialog_state(state: State) -> dict:...messages = []if state["messages"][-1].tool_calls:messages.append(ToolMessage(content="Resuming dialog with the host assistant. Please reflect on the past conversation and assist the user as needed.",tool_call_id=state["messages"][-1].tool_calls[0]["id"],))return {"dialog_state": "pop","messages": messages,}
怎么管理:切换时增加角色转换消息分隔
在state的messages加入相关toolMessage,即在进入某个助手前,先在messages中插件一条工作转交消息,以达分隔对话内容的目的。create_entry_node方法就是用于创建对应的进入节点,代码如下:
def create_entry_node(assistant_name: str, new_dialog_state: str) -> Callable:def entry_node(state: State) -> dict:tool_call_id = state["messages"][-1].tool_calls[0]["id"]return {"messages": [ToolMessage(content="...见下方文本",tool_call_id=tool_call_id,)],"dialog_state": new_dialog_state,}
return entry_node
The assistant is now the {assistant_name}. Reflect on the above conversation between the host assistant and the user.he user's intent is unsatisfied. Use the provided tools to assist the user. Remember, you are {assistant_name}, and the booking, update, other other action is not complete until after you have successfully invoked the appropriate tool.If the user changes their mind or needs help for other tasks, call the CompleteOrEscalate function to let the primary host assistant take control.Do not mention who you are - just act as the proxy for the assistant.
现在助手角色是{assistant_name}。请回顾上述主持助手与用户的对话,当前用户需求尚未得到满足。请使用提供的工具协助用户。请记住,您现在是{assistant_name},且预订、更新或其他操作必须成功调用相应工具后才算完成。若用户改变主意或需要其他帮助,请调用CompleteOrEscalate功能将控制权交还给主主持助手。无需说明您的身份——只需作为助手的代理执行操作。
以上这种方法,一定程度能完成助手来回软交工作的情况,但多轮对话后,这个对话上下文变更越来越复杂,这里LLM也会出现错误的决定。这个需要上下文工程等技术手段再来解决。