LangChain-9-多agent电商助手案例

一、为什么需要多智能体?

传统的单体聊天机器人把所有逻辑塞进一个 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)

测试验证:

相关推荐
Arvid7 小时前
LangGraph 记忆系统设计实战
langchain
不会敲代码18 小时前
MCP 实战第二弹:集成高德地图、文件系统、Chrome DevTools,打造能看能写能操控浏览器的超级 Agent
langchain·llm·mcp
Csvn9 小时前
🌟 LangChain 30 天保姆级教程 · Day 28|RAG 性能优化实战!缓存+异步+批处理,让 AI 响应快如闪电!
langchain
Csvn9 小时前
🌟 LangChain 30 天保姆级教程 · Day 29|RAG 监控告警实战!用 Prometheus + Grafana 打造 AI 运维驾驶舱!
langchain
GHL2842710909 小时前
LangChain学习
学习·ai·langchain
Trouvaille ~10 小时前
零基础入门 LangChain 与 LangGraph(七):真正理解 LangGraph——从工作流、状态图到三个核心案例
python·langchain·agent·workflow·langgraph·ai应用开发·智能体开发
怕浪猫10 小时前
第17章 、LangChain缓存与性能优化
langchain·llm·ai编程
名字不好奇10 小时前
LangGraph 记忆系统设计实战
人工智能·langchain·ai编程·langgraph
Jolyne_1 天前
前端从0开始的LangChain学习(一)
前端·langchain