目录

构建一个主助理代理Primary-Assistant,它可以根据用户需求调用不同的工具如航班查询酒店预订汽车租赁等,并确保对话逻辑合理

构建一个“主助理”代理(Primary Assistant),它可以根据用户需求调用不同的工具(如航班查询、酒店预订、汽车租赁等),并确保对话逻辑合理

build_agent_runnables.py

from tools.tools_lookup_policy import lookup_policy
from tools.tools_flights import search_flights, update_ticket_to_new_flight, cancel_ticket
from tools.tools_hotels import search_hotels, book_hotel, update_hotel, cancel_hotel
from tools.tools_excursions import search_trip_recommendations, book_excursion, update_excursion, cancel_excursion
from tools.tools_car_rental import search_car_rentals, book_car_rental, update_car_rental, cancel_car_rental
from load_config import LoadConfig
from langchain_community.tools.tavily_search import TavilySearchResults
from agentic_system_design.build_agent_prompts import AgentPrompts
from agentic_system_design.complete_or_escalate import CompleteOrEscalate
from agentic_system_design.build_agent_assistants import ToFlightBookingAssistant, ToBookCarRentalAssistant, ToHotelBookingAssistant, ToBookExcursionAssistant

def build_primary_assistant_runnable(self):
    primary_assistant_tools = [
        TavilySearchResults(max_results=CFG.tavily_search_max_results),
        search_flights,
        lookup_policy,
    ]
    primary_assistant_runnable = AGENT_PROMPTS.primary_assistant_prompt | CFG.llm.bind_tools(
        primary_assistant_tools
        + [
            ToFlightBookingAssistant,
            ToBookCarRentalAssistant,
            ToHotelBookingAssistant,
            ToBookExcursionAssistant,
        ]
    )
    return primary_assistant_tools, primary_assistant_runnable

build_agent_assistants.py

from langchain_core.runnables import Runnable, RunnableConfig
from agentic_system_design.build_agent_state import State
# from langchain_core.pydantic_v1 import BaseModel, Field
from pydantic import BaseModel, Field


class Assistant:
    """
    A class to manage interactions with a runnable agent and ensure valid responses.

    Attributes:
        runnable (Runnable): An instance of the Runnable class used to invoke actions and obtain results.

    Methods:
        __call__(state: State, config: RunnableConfig) -> dict:
            Executes the runnable with the provided state and configuration, and handles invalid responses by updating
            the state with appropriate messages until a valid response is obtained.

    Args:
        runnable (Runnable): The runnable instance that performs the actual work and provides the result.
    """

    def __init__(self, runnable: Runnable):
        """
        Initializes the Assistant with a runnable instance.

        Args:
            runnable (Runnable): An instance of the Runnable class to be used for invoking actions.
        """
        self.runnable = runnable

    def __call__(self, state: State, config: RunnableConfig):
        """
        Executes the runnable with the given state and configuration, and ensures the response is valid.

        The method continuously invokes the runnable until a valid response is obtained. If the response is invalid (e.g., 
        no tool calls and empty or invalid content), it updates the state with a message prompting for a real output.

        Args:
            state (State): The current state of the agent, including messages and other relevant information.
            config (RunnableConfig): Configuration settings for the runnable.

        Returns:
            dict: A dictionary containing the updated messages and result from the runnable invocation.

        Example:
            result = self(state, config)
        """
        while True:
            result = self.runnable.invoke(state)

            if not result.tool_calls and (
                not result.content
                or isinstance(result.content, list)
                and not result.content[0].get("text")
            ):
                messages = state["messages"] + \
                    [("user", "Respond with a real output.")]
                state = {**state, "messages": messages}
                messages = state["messages"] + \
                    [("user", "Respond with a real output.")]
                state = {**state, "messages": messages}
            else:
                break
        return {"messages": result}


# Primary Assistant
class ToFlightBookingAssistant(BaseModel):
    """Transfers work to a specialized assistant to handle flight updates and cancellations."""

    request: str = Field(
        description="Any necessary followup questions the update flight assistant should clarify before proceeding."
    )


class ToBookCarRentalAssistant(BaseModel):
    """Transfers work to a specialized assistant to handle car rental bookings."""

    location: str = Field(
        description="The location where the user wants to rent a car."
    )
    start_date: str = Field(description="The start date of the car rental.")
    end_date: str = Field(description="The end date of the car rental.")
    request: str = Field(
        description="Any additional information or requests from the user regarding the car rental."
    )

    class Config:
        schema_extra = {
            "example": {
                "location": "Basel",
                "start_date": "2023-07-01",
                "end_date": "2023-07-05",
                "request": "I need a compact car with automatic transmission.",
            }
        }


class ToHotelBookingAssistant(BaseModel):
    """Transfer work to a specialized assistant to handle hotel bookings."""

    location: str = Field(
        description="The location where the user wants to book a hotel."
    )
    checkin_date: str = Field(description="The check-in date for the hotel.")
    checkout_date: str = Field(description="The check-out date for the hotel.")
    request: str = Field(
        description="Any additional information or requests from the user regarding the hotel booking."
    )

    class Config:
        schema_extra = {
            "example": {
                "location": "Zurich",
                "checkin_date": "2023-08-15",
                "checkout_date": "2023-08-20",
                "request": "I prefer a hotel near the city center with a room that has a view.",
            }
        }


class ToBookExcursionAssistant(BaseModel):
    """Transfers work to a specialized assistant to handle trip recommendation and other excursion bookings."""

    location: str = Field(
        description="The location where the user wants to book a recommended trip."
    )
    request: str = Field(
        description="Any additional information or requests from the user regarding the trip recommendation."
    )

    class Config:
        schema_extra = {
            "example": {
                "location": "Lucerne",
                "request": "The user is interested in outdoor activities and scenic views.",
            }
        }

self.builder.add_node("primary_assistant", Assistant(
            AGENT_RUNNABLES.primary_assistant_runnable))
        self.builder.add_node(
            "primary_assistant_tools", create_tool_node_with_fallback(
                AGENT_RUNNABLES.primary_assistant_tools)
        )

这段代码的作用是构建一个“主助理”代理(Primary Assistant),它可以根据用户需求调用不同的工具(如航班查询、酒店预订、汽车租赁等),并确保对话逻辑合理。代码主要由以下几个部分组成:


1. build_primary_assistant_runnable(self) 方法

功能:

这个方法创建了主助理(Primary Assistant),并为它绑定多个可用的工具,让它能够处理各种任务。

关键步骤:

  • 定义工具 primary_assistant_tools

    primary_assistant_tools = [
        TavilySearchResults(max_results=CFG.tavily_search_max_results),
        search_flights,
        lookup_policy,
    ]
    • TavilySearchResults(max_results=CFG.tavily_search_max_results) :使用 Tavily 搜索 API 进行信息查询,最多返回 CFG.tavily_search_max_results 个搜索结果。例如,如果 CFG.tavily_search_max_results=5 ,那么搜索 “瑞士旅游” 可能返回 5 个相关网页。
    • search_flights :一个航班搜索工具(未提供具体实现),假设它能查询航班价格、时间等信息。
    • lookup_policy :是一个查询政策信息的工具,比如航班改签政策等。
  • 绑定其他工具(代理任务)

    primary_assistant_runnable = AGENT_PROMPTS.primary_assistant_prompt | CFG.llm.bind_tools(
        primary_assistant_tools
        + [
            ToFlightBookingAssistant,
            ToBookCarRentalAssistant,
            ToHotelBookingAssistant,
            ToBookExcursionAssistant,
        ]
    )
    • 这里 AGENT_PROMPTS.primary_assistant_prompt 可能是一个预设的提示词(Prompt),用来指导 LLM(大模型)回答问题。
    • CFG.llm.bind_tools(...) 绑定了所有工具,包括 primary_assistant_tools 和四个具体的助理任务:
      • ToFlightBookingAssistant :航班更新/取消助理
      • ToBookCarRentalAssistant :租车预订助理
      • ToHotelBookingAssistant :酒店预订助理
      • ToBookExcursionAssistant :旅行推荐助理

    示例

    • 用户询问:“我想预订 3 月 15 日从纽约到伦敦的航班。”
    • primary_assistant_runnable 可能会调用 search_flights 来获取航班信息,并返回可选航班列表。

2. Assistant

功能:

  • 这个类用于执行 Runnable (可运行对象),确保大模型的回答是有效的。
  • 如果大模型的回答无效,比如内容为空或没有调用工具,它会不断地让大模型重试,直到得到有效的回答。

关键步骤:

class Assistant:
    def __init__(self, runnable: Runnable):
        self.runnable = runnable
  • self.runnable 绑定了 Runnable 对象,表示这个助手可以运行大模型的推理逻辑。
def __call__(self, state: State, config: RunnableConfig):
    while True:
        result = self.runnable.invoke(state)
  • invoke(state) 运行模型,并传入 state (对话的当前状态,例如用户的历史对话信息)。
if not result.tool_calls and (
    not result.content
    or isinstance(result.content, list)
    and not result.content[0].get("text")
):
  • 这里检查模型的输出是否有效:
    • result.tool_calls 是否为空?(即模型有没有调用工具)
    • result.content 是否为空?(即回答内容是否为空)
    • result.content[0].get("text") 是否为空?(如果内容是一个列表,里面有没有文本)

如果模型回答无效,则:

messages = state["messages"] + [("user", "Respond with a real output.")]
state = {**state, "messages": messages}
  • 强制让模型返回一个“真实的”回答,并更新 state ,让它重试。

示例

  • 用户: “请帮我预订一辆车。”
    • 如果模型的初次回答是空的,代码会让它重试,直到返回“我可以帮您预订,请提供租车的地点和时间。”

3. ToFlightBookingAssistant 等四个工具

这些 BaseModel 代表具体的助理任务。 使用 pydantic 定义数据结构,确保用户提供的参数符合要求

航班更新助理
class ToFlightBookingAssistant(BaseModel):
    request: str = Field(
        description="更新航班助手在继续操作之前需要澄清任何必要的后续问题"
    )
  • 这个助理用于处理航班更新或取消, request 字段表示它可能需要向用户询问更多信息。

示例

  • 用户输入:“帮我取消航班。”
  • request 可能被设为 "请提供航班号,以便处理取消请求。"

租车预订助理
class ToBookCarRentalAssistant(BaseModel):
    location: str = Field(description="The location where the user wants to rent a car.")
    start_date: str = Field(description="The start date of the car rental.")
    end_date: str = Field(description="The end date of the car rental.")
    request: str = Field(description="Any additional information or requests from the user regarding the car rental.")
  • 需要提供 location (地点)、 start_date (开始日期)、 end_date (结束日期)和 request (额外需求)。

示例

{
    "location": "Basel",
    "start_date": "2023-07-01",
    "end_date": "2023-07-05",
    "request": "I need a compact car with automatic transmission."
}

用户可能输入:“我要在巴塞尔租一辆车,从 7 月 1 日到 7 月 5 日。”

助理会调用相应的租车 API 进行查询。


4. self.builder.add_node(...)

self.builder.add_node("primary_assistant", Assistant(
    AGENT_RUNNABLES.primary_assistant_runnable))
  • 这里将 primary_assistant_runnable (主助理)添加到构建器 self.builder 中,表示它是系统的一个节点。
self.builder.add_node(
    "primary_assistant_tools", create_tool_node_with_fallback(
        AGENT_RUNNABLES.primary_assistant_tools)
)
  • create_tool_node_with_fallback(...) 可能是一个函数,用来保证工具可用。如果工具失败,系统可能会尝试其他方法。

总结

  1. 构建了一个“主助理”系统 ,它可以使用多种工具(搜索、航班查询、租车、酒店预订等)。
  2. 使用 Assistant 确保大模型回答有效,避免返回空内容。
  3. 定义了 ToFlightBookingAssistant 等助理任务 ,确保用户输入符合格式,并调用相应的工具。
  4. 使用 self.builder.add_node(...) 构建系统节点 ,让“主助理”成为整个代理系统的一部分。

示例流程

用户: “我要预订 3 月 10 日到 3 月 15 日在伦敦的酒店。”

primary_assistant_runnable 识别到需要 ToHotelBookingAssistant

→ 助理询问:“您是否有酒店偏好,比如靠近地铁或带早餐?”

→ 调用 API 返回可选酒店列表