如何在多智能体应用中添加多轮对话¶
在本操作指南中,我们将构建一个应用程序,允许最终用户与一个或多个智能体进行多轮对话。我们将创建一个节点,该节点使用 interrupt
来收集用户输入并路由回活动智能体。
智能体将作为图中的节点实现,该图执行智能体步骤并确定下一个动作
- 等待用户输入以继续对话,或
- 路由到另一个智能体(或返回自身,例如在循环中)通过 移交。
def human(state: MessagesState) -> Command[Literal["agent", "another_agent"]]:
"""A node for collecting user input."""
user_input = interrupt(value="Ready for user input.")
# Determine the active agent.
active_agent = ...
...
return Command(
update={
"messages": [{
"role": "human",
"content": user_input,
}]
},
goto=active_agent
)
def agent(state) -> Command[Literal["agent", "another_agent", "human"]]:
# The condition for routing/halting can be anything, e.g. LLM tool call / structured output, etc.
goto = get_next_agent(...) # 'agent' / 'another_agent'
if goto:
return Command(goto=goto, update={"my_state_key": "my_state_value"})
else:
return Command(goto="human") # Go to human node
设置¶
首先,让我们安装所需的软件包
import getpass
import os
def _set_env(var: str):
if not os.environ.get(var):
os.environ[var] = getpass.getpass(f"{var}: ")
_set_env("ANTHROPIC_API_KEY")
设置 LangSmith 以进行 LangGraph 开发
注册 LangSmith 以快速发现问题并提高 LangGraph 项目的性能。LangSmith 允许您使用跟踪数据来调试、测试和监控使用 LangGraph 构建的 LLM 应用程序 — 在 此处 阅读有关如何开始使用的更多信息。
定义智能体¶
在本示例中,我们将构建一个旅行助理智能体团队,他们可以通过移交相互通信。
我们将创建 2 个智能体
travel_advisor
:可以帮助提供旅行目的地建议。可以向hotel_advisor
寻求帮助。hotel_advisor
:可以帮助提供酒店建议。可以向travel_advisor
寻求帮助。
我们将为智能体使用预构建的 create_react_agent
- 每个智能体都将拥有特定于其专业领域的工具,以及用于移交给另一个智能体的特殊工具。
首先,让我们定义我们将要使用的工具
import random
from typing import Annotated, Literal
from langchain_core.tools import tool
from langchain_core.tools.base import InjectedToolCallId
from langgraph.prebuilt import InjectedState
@tool
def get_travel_recommendations():
"""Get recommendation for travel destinations"""
return random.choice(["aruba", "turks and caicos"])
@tool
def get_hotel_recommendations(location: Literal["aruba", "turks and caicos"]):
"""Get hotel recommendations for a given destination."""
return {
"aruba": [
"The Ritz-Carlton, Aruba (Palm Beach)"
"Bucuti & Tara Beach Resort (Eagle Beach)"
],
"turks and caicos": ["Grace Bay Club", "COMO Parrot Cay"],
}[location]
def make_handoff_tool(*, agent_name: str):
"""Create a tool that can return handoff via a Command"""
tool_name = f"transfer_to_{agent_name}"
@tool(tool_name)
def handoff_to_agent(
state: Annotated[dict, InjectedState],
tool_call_id: Annotated[str, InjectedToolCallId],
):
"""Ask another agent for help."""
tool_message = {
"role": "tool",
"content": f"Successfully transferred to {agent_name}",
"name": tool_name,
"tool_call_id": tool_call_id,
}
return Command(
# navigate to another agent node in the PARENT graph
goto=agent_name,
graph=Command.PARENT,
# This is the state update that the agent `agent_name` will see when it is invoked.
# We're passing agent's FULL internal message history AND adding a tool message to make sure
# the resulting chat history is valid.
update={"messages": state["messages"] + [tool_message]},
)
return handoff_to_agent
API 参考:tool | InjectedToolCallId | InjectedState
现在让我们使用预构建的 create_react_agent
创建我们的智能体。我们还将定义一个专用的 human
节点,其中包含 interrupt
—— 我们将在智能体的最终响应后路由到此节点。请注意,为此,我们将每个智能体调用包装在一个单独的节点函数中,该函数返回 Command(goto="human", ...)
。
from langchain_anthropic import ChatAnthropic
from langgraph.graph import MessagesState, StateGraph, START
from langgraph.prebuilt import create_react_agent, InjectedState
from langgraph.types import Command, interrupt
from langgraph.checkpoint.memory import MemorySaver
model = ChatAnthropic(model="claude-3-5-sonnet-latest")
# Define travel advisor tools and ReAct agent
travel_advisor_tools = [
get_travel_recommendations,
make_handoff_tool(agent_name="hotel_advisor"),
]
travel_advisor = create_react_agent(
model,
travel_advisor_tools,
prompt=(
"You are a general travel expert that can recommend travel destinations (e.g. countries, cities, etc). "
"If you need hotel recommendations, ask 'hotel_advisor' for help. "
"You MUST include human-readable response before transferring to another agent."
),
)
def call_travel_advisor(
state: MessagesState,
) -> Command[Literal["hotel_advisor", "human"]]:
# You can also add additional logic like changing the input to the agent / output from the agent, etc.
# NOTE: we're invoking the ReAct agent with the full history of messages in the state
response = travel_advisor.invoke(state)
return Command(update=response, goto="human")
# Define hotel advisor tools and ReAct agent
hotel_advisor_tools = [
get_hotel_recommendations,
make_handoff_tool(agent_name="travel_advisor"),
]
hotel_advisor = create_react_agent(
model,
hotel_advisor_tools,
prompt=(
"You are a hotel expert that can provide hotel recommendations for a given destination. "
"If you need help picking travel destinations, ask 'travel_advisor' for help."
"You MUST include human-readable response before transferring to another agent."
),
)
def call_hotel_advisor(
state: MessagesState,
) -> Command[Literal["travel_advisor", "human"]]:
response = hotel_advisor.invoke(state)
return Command(update=response, goto="human")
def human_node(
state: MessagesState, config
) -> Command[Literal["hotel_advisor", "travel_advisor", "human"]]:
"""A node for collecting user input."""
user_input = interrupt(value="Ready for user input.")
# identify the last active agent
# (the last active node before returning to human)
langgraph_triggers = config["metadata"]["langgraph_triggers"]
if len(langgraph_triggers) != 1:
raise AssertionError("Expected exactly 1 trigger in human node")
active_agent = langgraph_triggers[0].split(":")[1]
return Command(
update={
"messages": [
{
"role": "human",
"content": user_input,
}
]
},
goto=active_agent,
)
builder = StateGraph(MessagesState)
builder.add_node("travel_advisor", call_travel_advisor)
builder.add_node("hotel_advisor", call_hotel_advisor)
# This adds a node to collect human input, which will route
# back to the active agent.
builder.add_node("human", human_node)
# We'll always start with a general travel advisor.
builder.add_edge(START, "travel_advisor")
checkpointer = MemorySaver()
graph = builder.compile(checkpointer=checkpointer)
API 参考:ChatAnthropic | StateGraph | START | create_react_agent | InjectedState | Command | interrupt | MemorySaver
测试多轮对话¶
让我们使用此应用程序测试多轮对话。
import uuid
thread_config = {"configurable": {"thread_id": uuid.uuid4()}}
inputs = [
# 1st round of conversation,
{
"messages": [
{"role": "user", "content": "i wanna go somewhere warm in the caribbean"}
]
},
# Since we're using `interrupt`, we'll need to resume using the Command primitive.
# 2nd round of conversation,
Command(
resume="could you recommend a nice hotel in one of the areas and tell me which area it is."
),
# 3rd round of conversation,
Command(
resume="i like the first one. could you recommend something to do near the hotel?"
),
]
for idx, user_input in enumerate(inputs):
print()
print(f"--- Conversation Turn {idx + 1} ---")
print()
print(f"User: {user_input}")
print()
for update in graph.stream(
user_input,
config=thread_config,
stream_mode="updates",
):
for node_id, value in update.items():
if isinstance(value, dict) and value.get("messages", []):
last_message = value["messages"][-1]
if isinstance(last_message, dict) or last_message.type != "ai":
continue
print(f"{node_id}: {last_message.content}")
--- Conversation Turn 1 ---
User: {'messages': [{'role': 'user', 'content': 'i wanna go somewhere warm in the caribbean'}]}
travel_advisor: Based on the recommendations, I suggest considering Aruba! It's a fantastic Caribbean destination known for its perfect warm weather year-round, with average temperatures around 82°F (28°C). Aruba is famous for its pristine white-sand beaches, crystal-clear waters, and constant cooling trade winds.
Some highlights of Aruba include:
1. Beautiful Eagle Beach and Palm Beach
2. Excellent snorkeling and diving opportunities
3. Vibrant culture and dining scene
4. Consistent sunny weather (it's outside the hurricane belt!)
5. Great shopping and nightlife in Oranjestad
Would you like me to help you explore more specific aspects of visiting Aruba? Or if you're interested in finding a hotel there, I can connect you with our hotel advisor who can provide detailed accommodation recommendations.
--- Conversation Turn 2 ---
User: Command(resume='could you recommend a nice hotel in one of the areas and tell me which area it is.')
hotel_advisor: Based on the recommendations, I can suggest two excellent options in different areas:
1. The Ritz-Carlton, Aruba - Located in Palm Beach
This luxury resort is situated in the bustling Palm Beach area, known for its high-rise hotels and vibrant atmosphere. The Ritz offers world-class amenities, including a luxurious spa, multiple restaurants, and a casino. The location is perfect if you want to be close to shopping, dining, and nightlife.
2. Bucuti & Tara Beach Resort - Located in Eagle Beach
This adults-only boutique resort is situated on the stunning Eagle Beach, which is wider and generally quieter than Palm Beach. It's perfect for those seeking a more peaceful, romantic atmosphere. The resort is known for its exceptional service and sustainability practices.
Would you like more specific information about either of these hotels or their locations?
--- Conversation Turn 3 ---
User: Command(resume='i like the first one. could you recommend something to do near the hotel?')
travel_advisor: Near The Ritz-Carlton in Palm Beach, there are several excellent activities you can enjoy:
1. Palm Beach Strip - Right outside the hotel, you can walk along this vibrant strip featuring:
- High-end shopping at luxury boutiques
- Various restaurants and bars
- The Paseo Herencia Shopping & Entertainment Center
2. Water Activities (within walking distance):
- Snorkeling at the artificial reef
- Parasailing
- Jet ski rentals
- Catamaran sailing trips
- Paddleboarding
3. Nearby Attractions:
- Bubali Bird Sanctuary (5-minute drive)
- Butterfly Farm (10-minute walk)
- California Lighthouse (short drive)
- Visit the famous Stellaris Casino (located within the Ritz-Carlton)
4. Local Culture:
- Visit the nearby fishing pier
- Take a short trip to local craft markets
- Evening sunset watching on the beach
Would you like more specific information about any of these activities? I can also recommend some specific restaurants or shopping venues in the area!