一、为什么需要多智能体?
传统的单体聊天机器人把所有逻辑塞进一个 prompt 里,当业务变复杂(商品查询、下单、支付、物流、售后)时,prompt 会变得臃肿不堪,难以维护。多智能体架构将不同职责拆分为独立的智能体,每个智能体拥有专属的工具集和系统提示,由一个"主管"负责路由,就像公司里不同部门各司其职。
本案例实现一个极简电商助手,包含三个智能体:
-
商品智能体:处理商品列表、详情查询。
-
订单智能体:处理下单、订单状态查询(用户ID自动注入,无需用户提供)。
-
通用搜索智能体:处理商品和订单之外的其他问题。
并加入 下单前人工确认(Human-in-the-loop),防止误操作。

-
主管智能体:用 LLM 对用户输入进行意图分类(product / order / other)。
-
中断节点 :当意图为
order时,图会在order_agent节点前暂停,等待用户确认(y/n)。 -
子智能体 :每个智能体通过
create_agent创建,绑定各自的工具和系统提示。 -
用户ID注入 :通过
RunnableConfig传递,工具内部用get_config()获取,用户无需手动输入。
核心代码解析
状态定义
class CustomState(TypedDict):
messages: Annotated[list, add_messages]
intent: str
模拟数据库工具
class InMemoryDB:
def __init__(self):
self.products = {
"p001": {"id": "p001", "name": "智能手机X", "price": 2999.00, "stock": 50},
"p002": {"id": "p002", "name": "无线耳机Pro", "price": 499.00, "stock": 120},
"p003": {"id": "p003", "name": "智能手表", "price": 1299.00, "stock": 30}
}
self.orders = {}
self.order_counter = 0
#获取所有商品
def get_all_products(self):
return list(self.products.values())
#查询商品
def get_product(self, product_id):
return self.products.get(product_id)
#减库存
def reduce_stock(self, product_id, quantity):
product = self.products.get(product_id)
if product and product["stock"] >= quantity:
product["stock"] -= quantity
return True
return False
#创建订单
def create_order(self, user_id, product_id, quantity, total):
self.order_counter += 1
order = {
"order_id": f"ORD{self.order_counter}",
"user_id": user_id,
"product_id": product_id,
"quantity": quantity,
"total_amount": total,
"status": "pending",
"created_at": datetime.now().isoformat()
}
self.orders[order["order_id"]] = order
return order
def get_order(self, order_id):
return self.orders.get(order_id)
db = InMemoryDB()
工具定义
@tool
def list_products_tool() -> str:
"""查询所有商品列表"""
products = db.get_all_products()
if not products:
return "暂无商品"
result = "【商品列表】\n"
for p in products:
result += f"- {p['name']} (ID:{p['id']}) | ¥{p['price']} | 库存:{p['stock']}\n"
return result
@tool
def get_product_tool(product_id: str) -> str:
"""根据商品ID查询单个商品详情。"""
product = db.get_product(product_id)
if not product:
return f"未找到商品Id为{product_id}的商品"
return f"【商品详情】\n名称:{product['name']}\n价格:¥{product['price']}\n库存:{product['stock']}"
@tool
def create_order_tool(product_id: str, quantity: int) -> str:
"""根据商品ID和数量创建订单,并返回订单详情。"""
config = get_config()
user_id = config.get("configurable", {}).get("user_id")
if not user_id:
return "❌ 无法获取用户ID,请重新登录"
product = db.get_product(product_id)
if not product:
return f"订单创建失败,商品{product_id}不存在"
if product["stock"] < quantity:
return f"订单创建失败,{product['name']}库存不足,当前库存:{product['stock']}"
total = product["price"] * quantity
db.reduce_stock(product_id, quantity)
order = db.create_order(user_id, product_id, quantity, total)
return f"✅ 订单创建成功!订单号:{order['order_id']},总金额:¥{total},状态:{order['status']}"
@tool
def get_order_tool(order_id: str) -> str:
"""根据订单id查询订单详情"""
order = db.get_order(order_id)
if not order:
return f"未找到订单号{order_id}的订单"
product = db.get_product(order["product_id"])
product_name = product["name"] if product else order["product_id"]
return f"""【订单详情】
订单号:{order['order_id']}
商品:{product_name}
数量:{order['quantity']}
总金额:¥{order['total_amount']}
状态:{order['status']}
下单时间:{order['created_at']}"""
@tool
def search_tool(query: str):
"""互联网搜索工具"""
try:
response = zhipuai_client.web_search.web_search(search_engine="search_pro", search_query=query)
if response.search_result:
return "\n\n".join([result.content for result in response.search_result])
return "没有搜索到任何内容"
except Exception as e:
print(e)
return "搜索出现错误"
消息摘要(防止消息过长超过大模型限制)
def summarize_messages(messages:list, max_tokens: int = 2000) -> list:
"""如果消息总长度超过阈值,将旧消息压缩为摘要,保留最近几条原始消息"""
total_len = sum(len(m.content) for m in messages if hasattr(m, "content"))
if total_len < max_tokens:
return messages
#保留最近3条完整消息其余做摘要
keep_recent = 3
recent = messages[-keep_recent:]
old = messages[:-keep_recent]
prompt = f"""请将以下对话历史压缩成一段简洁的中文摘要(保留关键实体:商品名称、订单号、用户意图、已完成的操作):
{chr(10).join(f"{'用户' if isinstance(m, HumanMessage) else 'AI'}:{m.content}" for m in old)}
摘要:
"""
summary = llm.invoke(prompt).content
summary_message = SystemMessage(content=f"[对话历史摘要]{summary}")
return [summary_message] + recent
创建智能体
product_agent = create_agent(
llm,
tools=[list_products_tool, get_product_tool],
system_prompt="你是商品智能体,负责回答所有与商品相关的问题,如查询列表、价格或库存。当用户询问商品列表、价格、库存时,你必须调用工具查询最新的数据。",
name="product_expert"
)
order_agent = create_agent(
llm,
tools=[create_order_tool, get_order_tool],
system_prompt="""你是订单智能体,负责处理订单创建和订单状态查询。
重要:用户ID会自动从系统获取,你不需要询问用户ID。
如果用户提供了商品名称(如"智能手表"),你可以调用 get_product_tool 查找对应的ID。
如果用户未提供数量,默认数量为1。""",
name="order_expert",
)
search_agent = create_agent(
llm,
tools=[search_tool],
system_prompt="你是一个智能助手,负责回答用户关于商品和订单以外的所有问题",
name="search_export"
)
图节点
def supervisor_node(state: CustomState) -> CustomState:
last_msg = state["messages"][-1]
intent_prompt = """根据用户输入,判断应该由哪个智能体处理:
- 如果用户询问商品信息(查询商品列表、商品详情、价格、库存),回复 "product"
- 如果用户想下单购买或查询订单(创建订单、查询订单状态),回复 "order"
- 其它都回复 "other"
用户输入: {user_input}
只回复 product、other 或 order,不要有其他内容。"""
response = llm.invoke(intent_prompt.format(user_input=last_msg.content))
intent = response.content.strip().lower()
state["intent"] = intent
return state
def product_agent_node(state: CustomState, config: RunnableConfig) -> CustomState:
compressed = summarize_messages(state["messages"], max_tokens=2000)
result = product_agent.invoke({"messages": compressed}, config=config)
state["messages"] = result["messages"]
return state
def order_agent_node(state: CustomState, config: RunnableConfig) -> CustomState:
compressed = summarize_messages(state["messages"], max_tokens=2000)
result = order_agent.invoke({"messages": compressed}, config=config)
state["messages"] = result["messages"]
return state
def other_agent_node(state: CustomState, config: RunnableConfig) -> CustomState:
compressed = summarize_messages(state["messages"], max_tokens=2000)
result = search_agent.invoke({"messages": compressed}, config=config)
state["messages"] = result["messages"]
return state
定义图
def route_after_supervisor(state: CustomState) -> Literal["product_agent", "order_agent", "other_agent", END]:
intent = state.get("intent", "")
if intent == "product":
return "product_agent"
elif intent == "order":
return "order_agent"
elif intent == "other":
return "other_agent"
else:
return END
# ------------------ 构建图 -----------------------------
workflow = StateGraph(CustomState)
workflow.add_node("supervisor", supervisor_node)
workflow.add_node("product_agent", product_agent_node)
workflow.add_node("order_agent", order_agent_node)
workflow.add_node("other_agent", other_agent_node)
workflow.set_entry_point("supervisor")
workflow.add_conditional_edges(
"supervisor",
route_after_supervisor,
{
"product_agent": "product_agent",
"order_agent": "order_agent",
"other_agent": "other_agent",
END: END
}
)
workflow.add_edge("product_agent", END)
workflow.add_edge("order_agent", END)
workflow.add_edge("other_agent", END)
memory = MemorySaver()
app = workflow.compile(checkpointer=memory, interrupt_before=["order_agent"])
测试
config = {"configurable": {"thread_id": str(uuid.uuid4()), "user_id": "123"}}
while True:
content = input("用户:")
if content in ('q', 'quit'):
print("AI: bye bye")
break
current_state = app.get_state(config)
app.invoke({"messages": [HumanMessage(content=content)]}, config=config)
state_after = app.get_state(config)
if state_after.next == ("order_agent",):
choice = input("确认下单?(y/n): ")
if choice.lower() == 'y':
app.invoke(Command(resume=True), config=config)
final_state = app.get_state(config)
if final_state.values.get("messages"):
print("AI:", final_state.values["messages"][-1].content)
else:
# 1. 添加取消消息到状态中
cancel_msg = AIMessage(content="订单已取消。")
app.update_state(config, {"messages": [cancel_msg]})
# 2. 终止中断,让图结束执行(不进入 order_agent)
app.invoke(Command(resume=False), config=config)
print("AI:", cancel_msg.content)
continue
else:
final_state = app.get_state(config)
if final_state.values.get("messages"):
print("AI:", final_state.values["messages"][-1].content)
测试验证:
