ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

LLM落地购物车场景:Agent编排、RAG检索与MCP工具集成实践

2026/8/28 3:08:18 拓冰建站 浏览量
LLM落地购物车场景:Agent编排、RAG检索与MCP工具集成实践 如果只看最近刷屏的 LLM 演示很容易产生一种错觉模型已经什么都能干了。它能写代码、能总结文档、能扮演客服下一件事自然是让它直接替用户把东西买好。但真正做过业务系统的人心里都清楚从“模型说可以下单”到“商品真的进入购物车、价格正确、库存够、状态可查”中间隔着一条很宽的工程河流。From LLM to shopping cart (Portugal)这个标题我理解就是把这条河画成了一条路径左端是 LLM右端是一个真实购物车中间是 Agent、RAG、MCP、状态同步和人工确认。为什么拿购物车当例子因为购物车是最典型的“LLM 必须兑现结果”的场景。模型不能只回答问题它必须对真实业务状态产生写操作。加购、改数量、删商品、清单汇总每个动作背后都牵动商品服务、库存服务、价格服务和会话状态。一次加购失败用户不会觉得是模型的问题而是觉得这个系统坏了。换句话说购物车是对 LLM 应用工程化的最好测试比单纯的问答助手更能暴露问题。这篇文章不打算停在概念层面。我会用购物车这个场景从 LLM 应用开发的角度拆一条可落地的最小链路整体架构、环境准备、工具定义、Agent 主循环、RAG 商品检索、MCP 工具标准化以及上线前真正要小心的坑。读完之后你可以把同样的模式移植到订单、预约、工单、审批等任何“要动真实数据”的 LLM 场景。项目标题里的 Portugal 只是场景背景但它提醒我们只要涉及多语言、本地化商品库和真实交易LLM 落地就不再是“调一个 API”那么简单。1. 为什么“LLM 到购物车”值得拆解先回答一个问题直接让模型把商品加入购物车难在哪里如果只是做演示答案很简单让模型输出一段 JSON代码解析后调用购物车接口就够了。但放到真实业务里问题会立刻变多用户说“我要一个防水背包”系统怎么确认商品库里真的有这个商品模型把商品名写错了怎么办用户说“加两件”模型只加了一件怎么办用户在多轮对话里反复修改数量购物车状态存哪里价格会不会被模型记错这些问题说明LLM 从“能聊天”到“能办事”关键不在模型本身而在于模型外围的工程闭环。购物车场景把这个闭环压缩得非常具体模型必须理解用户意图必须访问真实商品数据必须调用真实写接口必须把结果回传给用户整个过程还要满足电商对准确性的基本要求。购物车同时包含了读操作和写操作这使它成为 LLM Agent 的最佳练习场。把 Portugal 放回标题也能看出这个项目的另一层意图购物车不是英文世界的专利。一个葡萄牙用户可能用葡萄牙语说“Quero uma mochila para caminhadas”系统要能理解不同语言的商品描述要在本地商品库里检索要处理欧元计价和本地化的税费规则。这意味着 LLM 应用必须要支持多语言意图识别、本地化商品召回和真实交易状态同步而不是只做英文 demo。所以这篇文章真正想解决的问题是怎么把一个只会生成的 LLM变成一个能对购物车产生准确写操作的 Agent它适合三类读者准备在电商业务里接入 LLM 的开发者正在做 LLM Agent 应用但不确定工具调用怎么设计的人以及想理解 RAG、MCP 在真实项目里到底有什么用的人。如果你只是想要一个能聊天的机器人本文会超出你的需求如果你要的是让模型真正帮助你完成业务操作这篇文章正好是起点。2. 核心概念LLM、Agent、RAG、MCP 在购物车场景中的角色很多 LLM 应用的文章会把概念名词堆在一起读者看完还是不知道它们各自负责什么。这里直接放到购物车场景里解释。概念通俗理解在购物车场景中的角色LLM 大语言模型根据上下文生成文本的模型理解用户“想买防水背包”的意图生成自然语言回复Agent 智能体控制模型进行多步推理和决策的框架决定“先搜索商品再调用加购工具”并循环执行直到完成Tool 工具调用让模型输出结构化调用参数由代码执行真实操作模型输出add_to_cart(item_idPT-BP-001, quantity1)代码真正写入购物车RAG 检索增强生成先从外部知识库检索相关内容再让模型基于检索结果回答从商品目录中找出真实存在的背包避免模型编造商品名MCP 模型上下文协议把外部工具能力标准化让 Agent 统一连接把购物车服务暴露成标准工具Agent 端无需定制开发会话状态保存多轮对话和业务状态的存储购物车商品列表、数量、总价应存在服务端而非模型上下文在这组概念里LLM 只负责“理解和表达”真正干活的是工具调用。Agent 是控制循环RAG 是为模型提供事实依据MCP 是工具接入的标准化方式。很多人一开始把重点放在“选哪个模型”实际上模型之外的工具链和状态设计才是决定项目成败的部分。顺便说一个容易混淆的点工具调用和 Agent 不是一回事。工具调用是模型的一个能力它让模型输出一段结构化参数Agent 则是在多轮循环里反复使用模型输出、执行工具、把结果回传直到任务完成。购物车场景里用户说“加一件防水背包”模型先调用搜索工具拿到商品 ID再调用加购工具这个过程就是一次典型的 Agent 循环。还有一个值得记住的设计原则不要让模型直接输出“购物车最终状态”。模型应该输出的是操作指令真实状态永远由购物车服务计算并返回。这样才能保证价格、库存、商品信息都来自真实系统而不是模型记忆。这个原则后面会反复用到。3. 整体架构从用户提问到购物车更新的完整链路用购物车场景串起来看一个 LLM 驱动的加购流程从用户输入到最后状态更新通常包含七个环节用户输入用户用自然语言描述需求可能是葡萄牙语也可能是中文、英文。意图识别Agent 让模型判断用户想做什么是搜索商品、加购、改数量还是删除商品。商品检索通过 RAG 或数据库全文检索从商品目录中召回候选商品。工具调用模型输出add_to_cart等工具参数代码执行真实写操作。状态持久化购物车商品和数量写入服务端存储而不是放在模型上下文里。关键动作确认加购可以自动执行但涉及金额敏感动作时必须加入人工确认。结果回写模型基于工具返回结果生成一句用户能看懂的自然语言总结。这个链路的核心是“状态分离”。购物车的真实状态始终在业务系统里模型只是状态的修改者和解释者。这样做的好处非常直接即使模型出错用户看到的购物车状态仍然是准确可查的即使换一个模型购物车数据也不会受影响。架构上通常把系统分成三层。最外层是对话接入层负责接收用户消息、校验格式、管理会话中间是 Agent 编排层负责维护多轮消息、调用模型、执行工具、控制循环次数最底层是业务服务层包括商品服务、购物车服务、库存服务和确认服务。LLM 只出现在编排层业务服务层不该被模型直接穿透。这里要特别提醒一个新手容易踩的坑不要为了“让模型更聪明”就把购物车状态塞进系统提示词。比如把整个购物车 JSON 放到 Prompt 里让模型自己维护这在短期内能跑通但一旦商品多了、并发来了、模型输出了错误 JSON状态就会彻底丢失。更稳妥的做法是让模型每次通过工具读取真实状态Prompt 里只保留指令和约束。4. 环境准备与依赖本文的示例代码以 Python 为主因为 Python 在处理工具调用、向量检索和快速原型时最直接。如果团队技术栈是 Java也可以把同样的设计迁移到 Spring AI配置方式以官方文档为准本文不针对某个框架写死配置。建议环境如下操作系统Linux、macOS、Windows 均可示例不依赖特定系统。Python 版本3.10 或更高主要使用类型标注和 dataclass。Python 依赖openai、python-dotenv、numpy。模型接口兼容 OpenAI Chat Completions 和 Embeddings 接口的服务具体模型名称以你的实际可用模型为准。代码结构建议按模块拆分便于后续扩展。安装依赖的命令如下pip install openai python-dotenv numpy版本请以实际项目为准本文重点演示通用思路。如果你使用pip时需要指定版本可以安装后使用pip freeze | grep openai查看当前版本。在项目根目录创建.env文件写入模型服务地址和密钥。这里只做环境配置示例不要把真实密钥提交到代码仓库# .env OPENAI_API_KEYsk-your-key-here OPENAI_BASE_URLhttps://api.openai.com/v1代码中使用python-dotenv加载环境变量并创建 OpenAI 客户端# cart_demo/config.py import os from dotenv import load_dotenv from openai import OpenAI load_dotenv() client OpenAI() # 请替换为你的环境可用的对话模型和向量模型 MODEL_NAME os.getenv(MODEL_NAME, gpt-4o-mini) EMBEDDING_MODEL os.getenv(EMBEDDING_MODEL, text-embedding-3-small)这一段看起来简单但有一个容易被忽略的点OpenAI()默认会读取环境变量OPENAI_API_KEY和OPENAI_BASE_URL所以配置文件和代码要分离。同时模型名称不要写死在代码里通过环境变量控制这样切换到其他模型时不用改代码。5. 最小闭环实现让 LLM 真正往购物车里加商品现在进入核心部分。我们先用最少的代码跑通“用户说一句话模型调用工具购物车状态被真实更新”的闭环。示例文件按下面的结构组织cart_demo/ ├── config.py ├── cart_service.py ├── tools.py ├── agent.py └── main.py先创建商品目录和购物车服务。为了演示商品列表放在内存中真实项目应替换为数据库或微服务接口。# cart_demo/cart_service.py 购物车服务简化实现。 正式项目中这里通常是对购物车微服务的调用而不是进程内的 dict。 from dataclasses import dataclass, field # 商品目录实际项目来自商品服务或数据库 PRODUCTS [ { item_id: PT-BP-001, name: Porto Trail Backpack 30L, price: 49.90, tags: [mochila, backpack, waterproof, hiking, trekking, 30l], }, { item_id: PT-BP-002, name: Lisbon Urban Sling 8L, price: 29.90, tags: [mochila, bag, urban, lightweight, daily, sling], }, { item_id: PT-SH-001, name: Algarve Beach Towel, price: 19.90, tags: [towela, beach, summer, banho], }, ] PRICE_MAP {p[item_id]: p[price] for p in PRODUCTS} dataclass class CartService: session_id: str items: dict field(default_factorydict) # item_id - quantity def add_item(self, item_id: str, quantity: int 1) - dict: if quantity 0 or quantity 99: raise ValueError(quantity must be between 1 and 99) if item_id not in PRICE_MAP: return {ok: False, error: 商品不存在请先调用 search_products} self.items[item_id] self.items.get(item_id, 0) quantity return { ok: True, session_id: self.session_id, item_id: item_id, quantity: self.items[item_id], cart: dict(self.items), total_amount: self.total_amount(), } def remove_item(self, item_id: str) - dict: self.items.pop(item_id, None) return {ok: True, session_id: self.session_id, cart: dict(self.items)} def get_cart(self) - dict: return {ok: True, session_id: self.session_id, cart: dict(self.items)} def total_amount(self) - float: return round(sum(PRICE_MAP.get(i, 0) * q for i, q in self.items.items()), 2) cart CartService(session_iddemo-session)这里最关键的设计是购物车服务自己维护商品数量和总价模型不参与价格计算。total_amount完全基于PRICE_MAP避免模型记忆价格导致结算错误。接着定义工具。工具必须遵循模型能理解的结构描述要写清楚“什么时候用”和“注意什么”。# cart_demo/tools.py TOOLS [ { type: function, function: { name: search_products, description: 按用户描述搜索可购买商品。调用 add_to_cart 之前必须先调用本工具确认商品存在。, parameters: { type: object, properties: { query: {type: string, description: 商品关键词或用户描述例如 mochila para caminhadas} }, required: [query], }, }, }, { type: function, function: { name: add_to_cart, description: 将指定商品加入购物车成功返回最新购物车内容和总价。, parameters: { type: object, properties: { item_id: {type: string, description: 商品 ID}, quantity: {type: integer, description: 数量默认 1}, }, required: [item_id], }, }, }, ]工具定义的描述直接决定模型会不会正确使用工具。如果描述里没有“先搜索再加购”的约束模型很可能从自己的记忆里编一个商品 ID然后调用add_to_cart。这也是幻觉问题在工具链里最常见的表现。接下来是 Agent 主循环。它做的事情只有三件把用户消息发给模型、查看模型是否要求调用工具、如果要求就执行工具并把结果回传然后继续下一轮直到模型给出最终回答。# cart_demo/agent.py import json from cart_demo.cart_service import PRICE_MAP, PRODUCTS, cart from cart_demo.config import client, MODEL_NAME from cart_demo.tools import TOOLS def search_products(query: str, top_k: int 3) - dict: q query.lower() results [] for p in PRODUCTS: text f{p[name]} { .join(p[tags])}.lower() if q in text or any(len(token) 2 and token in text for token in q.split()): results.append(p) return {results: results[:top_k]} def add_to_cart(item_id: str, quantity: int 1) - dict: return cart.add_item(item_id, quantity) TOOL_IMPL { search_products: search_products, add_to_cart: add_to_cart, } def execute_tool(name: str, args: dict): if name not in TOOL_IMPL: return {ok: False, error: funknown tool: {name}} return TOOL_IMPL[name](**args) def run_agent(user_message: str, max_steps: int 5): messages [{role: user, content: user_message}] for _ in range(max_steps): response client.chat.completions.create( modelMODEL_NAME, messagesmessages, toolsTOOLS, ) message response.choices[0].message if not message.tool_calls: return message.content messages.append(message) for tool_call in message.tool_calls: name tool_call.function.name args json.loads(tool_call.function.arguments) result execute_tool(name, args) messages.append({ role: tool, tool_call_id: tool_call.id, content: json.dumps(result, ensure_asciiFalse), }) raise RuntimeError(工具调用超过最大轮数终止 Agent)这段代码有几个细节值得注意。max_steps是为了防止模型陷入死循环比如模型反复调用同一个工具而不再给出最终回答。每条工具调用结果都通过tool_call_id与对应的调用关联这是当前模型接口的标准要求。json.loads解析模型返回的参数如果模型输出了非法 JSON这里会直接抛异常真实项目应该加上异常捕获和重试。最后是入口文件# cart_demo/main.py from cart_demo.agent import run_agent if __name__ __main__: user_msg Quero uma mochila para caminhadas. Adiciona 1 ao carrinho. print(run_agent(user_msg))这句葡萄牙语的意思是“我想要一个徒步背包加 1 个到购物车”。运行后看到的预期结果会在第 7 章详细