
从 LLM 到购物车这条链路看起来跨度很大实际上是一个很适合做技术验证的 Agent 工程案例用户说一句自然语言模型理解意图调用工具最后把商品真正加进购物车并返回结构化结果。这类 demo 在葡萄牙本地电商、社区商店、餐厅点单等场景里都很有用。这次我们就围绕 From LLM to shopping cart (Portugal) 这个方向完整走一遍从模型对话到购物车落库的搭建和测试流程。先给结论这不是一个需要 100B 大模型的场景常见做法是接入 OpenAI 兼容接口或本地 Ollama 部署的 Qwen / Llama 系列模型配合 Function Calling / Tool Calling 能力由 Agent 层的代码完成商品检索、加购、改数量、删商品等操作。真正的工程难点不在模型而在工具函数怎么定义、购物车状态怎么管理、多轮对话怎么保持上下文以及批量订单怎么处理。接下来我会按能跑起来的链路来组织内容先看这个方案能做什么再讲环境怎么准备、核心代码怎么写、接口怎么测、批量任务怎么接、踩坑怎么排。想要在本地快速复现的同学可以一路照着做下来。1. 核心能力速览先说整个方案的能力边界。这类 LLM 购物车应用的通用能力如下能力项说明项目方向基于 LLM Agent 的自然语言购物车操作核心功能自然语言加购、删商品、改数量、查购物车、结算辅助模型接入OpenAI 兼容接口 / 本地 Ollama / 各类云模型 API关键机制Function Calling / Tool Calling、多轮上下文管理服务形态FastAPI 中间层外部系统可调用 HTTP API是否需要 GPU看模型选择纯 API 方案不需要 GPU本地小模型需 6G 以上显存数据存储开发阶段可用内存 dict 或 SQLite生产建议接正式数据库批量任务支持按订单文件批量解析和加购适用场景葡语/多语言商店 Demo、Agent 应用教学、电商客服预处理硬件门槛低普通开发机即可完成服务端开发测试从这个表可以判断这是一个偏应用工程的项目不是模型训练项目。重点验证的是 LLM 的意图理解、工具调用和业务状态的一致性。2. 这条链路解决什么问题From LLM to shopping cart 的核心不是让模型聊天而是让模型能真正操作业务系统。在葡萄牙本地电商或社区零售场景中常见需求是用户用自然语言描述需求我要 2 瓶波特酒和一包盐渍鳕鱼。系统需要理解商品名称、数量、可能的规格偏好。系统需要把商品信息映射到真实的商品 ID。系统需要执行加购操作并返回购物车总价。如果只靠 Prompt 让模型输出文本会出现两个问题第一模型可能把商品名幻觉成不存在的商品第二即使模型输出了正确的 JSON也需要额外代码去解析、校验、调用后端接口。Function Calling 机制解决的就是这件事模型只负责生成调用哪个函数、传什么参数真正执行动作的是你的代码。从架构上看这条链路是用户输入 - LLM 意图识别与参数抽取 - 工具函数调度 - 购物车服务 - 结构化响应这里有一个容易被忽略的点购物车是一个有状态的系统。用户可能在一个会话里连续操作加一瓶波特酒顺便把刚才的鳕鱼删掉。 这意味着 Agent 层必须维护会话状态或者每次请求都带上当前购物车 ID让工具函数基于同一个购物车上下文去执行。3. 适用场景与使用边界适合用这个方案的人正在学 LLM Agent、Function Calling 的开发者需要一个小而完整的练习项目。想给当地小商店做自然语言点单 Demo 的人。需要验证模型输出 - 业务动作闭环的架构师。做多语言电商客服预处理的团队可以先在购物车场景跑通链路。不适合的场景高并发、强一致的电商核心交易系统。生产环境不会让 LLM 直接写数据库而是由 LLM 生成行为业务层做完整校验和事务控制。需要处理大量长尾商品的场景。如果商品库有几十万 SKU必须配合 RAG / 商品搜索接口不能靠模型记忆。对结果可解释性要求极高的财务、医疗、法务场景。使用边界和合规提醒也很重要项目里如果出现真实用户地址、支付信息、历史订单必须做脱敏涉及真人声音、人脸、肖像的交互功能必须获得明确授权商品数据来自真实商店时要确认是否有数据合规和版权要求。演示环境建议全部使用虚拟数据不接入真实支付。4. 整体架构设计我这里给一个可落地的参考架构本地开发完全够用。核心组件有三个4.1 LLM 接入层负责接收用户输入拼装 system prompt调用模型的 Function Calling 能力获取结构化的工具调用请求。接入层要尽量做成可替换的这样你可以先调云端 API 跑通再切到本地 Ollama。# llm_client.py 示例骨架 from openai import OpenAI client OpenAI( base_urlhttp://localhost:11434/v1, # Ollama 的 OpenAI 兼容端点 api_keyollama # 本地服务可填任意值 ) def chat_with_tools(messages, tools): response client.chat.completions.create( modelqwen2.5:7b, messagesmessages, toolstools, tool_choiceauto ) return response.choices[0].message4.2 Agent 调度层这是一个循环模型返回工具调用请求 - 执行工具 - 把结果回传给模型 - 模型生成最终回复。停止条件是模型不再请求工具调用。def run_agent(user_input, cart_id, session_messages): session_messages.append({role: user, content: user_input}) max_steps 5 for _ in range(max_steps): message chat_with_tools(session_messages, TOOLS) if message.tool_calls: session_messages.append(message) for tool_call in message.tool_calls: result execute_tool(tool_call.function.name, tool_call.function.arguments) session_messages.append({ role: tool, tool_call_id: tool_call.id, content: result }) else: session_messages.append(message) return message.content, session_messages return 处理步骤过多请简化请求, session_messages4.3 购物车状态层开发阶段可以直接用一个字典保存购物车数据。每个购物车有一个唯一 cart_id内部保存商品明细。carts {} def get_cart(cart_id): if cart_id not in carts: carts[cart_id] {items: {}, total: 0.0} return carts[cart_id]生产环境把 carts 替换为数据库表即可接口设计保持一致。5. 环境准备与前置条件5.1 硬件和系统操作系统Windows / macOS / Linux 都可以。纯 API 方案不需要 GPU普通开发机即可。本地模型方案建议 16G 内存 6G 以上显存跑 7B 量化模型CPU 推理也能跑但响应会慢很多。磁盘代码项目很小本地模型文件约 4~8G。5.2 软件依赖Python 3.10 以上。pip 安装 openai、fastapi、uvicorn。选装 ollama用于本地模型推理。pip install openai fastapi uvicorn requests5.3 模型准备两种路线云端 API准备一个 OpenAI 兼容的 API Key模型可用 gpt-4o-mini、gpt-4o、claude 等。本地模型安装 Ollama 后拉取支持工具调用的模型例如 qwen2.5:7b、llama3.1:8b。ollama pull qwen2.5:7b这里要提醒本地小模型的工具调用稳定性不如云端大模型。先跑通链路再决定是否换更强模型。6. 核心链路实现下面开始写代码。先把工具函数定义好再完成 Agent 调度和 FastAPI 服务。6.1 定义工具函数用 JSON Schema 格式描述工具这一步最关键。字段名、类型、描述越清晰模型抽取参数越准。TOOLS [ { type: function, function: { name: add_to_cart, description: 向购物车中添加商品如果商品已存在则累加数量, parameters: { type: object, properties: { cart_id: { type: string, description: 购物车唯一标识 }, product_name: { type: string, description: 商品名称必须是商品库中存在的名称 }, quantity: { type: integer, description: 商品数量默认 1 } }, required: [cart_id, product_name] } } }, { type: function, function: { name: remove_from_cart, description: 从购物车中移除指定商品, parameters: { type: object, properties: { cart_id: { type: string }, product_name: { type: string } }, required: [cart_id, product_name] } } }, { type: function, function: { name: get_cart_info, description: 查看购物车当前所有商品和总价, parameters: { type: object, properties: { cart_id: { type: string } }, required: [cart_id] } } } ]6.2 工具执行函数这里用一个简单的葡萄牙商店商品库做演示数据# products.py PRODUCTS { vinho do porto: {name: Vinho do Porto, price: 18.5}, bacalhau: {name: Bacalhau salgado, price: 12.9}, pastéis de nata: {name: Pastéis de Nata, price: 1.5}, azeite: {name: Azeite Virgem Extra, price: 8.9}, } # tools_impl.py import json from products import PRODUCTS from cart_store import get_cart def execute_tool(name, arguments_json): args json.loads(arguments_json) cart get_cart(args[cart_id]) if name add_to_cart: product_name args[product_name].lower() if product_name not in PRODUCTS: return json.dumps({error: f商品 {args[product_name]} 不存在}, ensure_asciiFalse) qty int(args.get(quantity, 1)) item cart[items].get(product_name) if item: item[quantity] qty else: cart[items][product_name] { name: PRODUCTS[product_name][name], price: PRODUCTS[product_name][price], quantity: qty } cart[total] sum(i[price] * i[quantity] for i in cart[items].values()) return json.dumps(cart, ensure_asciiFalse) if name remove_from_cart: product_name args[product_name].lower() cart[items].pop(product_name, None) cart[total] sum(i[price] * i[quantity] for i in cart[items].values()) return json.dumps(cart, ensure_asciiFalse) if name get_cart_info: return json.dumps(cart, ensure_asciiFalse) return json.dumps({error: f未知工具 {name}})这一段逻辑就是整个应用的核心模型不直接改购物车它只决定调用哪个工具、传什么参数你的代码校验参数、更新状态、返回结果。6.3 启动 FastAPI 服务用 FastAPI 暴露 HTTP 接口方便后面用 curl 调试或接入其他系统。# app.py from fastapi import FastAPI, HTTPException from pydantic import BaseModel from agent import run_agent import uuid app FastAPI(titleLLM Shopping Cart Demo) class ChatRequest(BaseModel): message: str cart_id: str None class ChatResponse(BaseModel): cart_id: str reply: str cart: dict app.post(/api/chat, response_modelChatResponse) def chat(req: ChatRequest): cart_id req.cart_id or str(uuid.uuid4()) reply, messages run_agent(req.message, cart_id, []) # 返回购物车最新状态 from cart_store import get_cart return ChatResponse(cart_idcart_id, replyreply, cartget_cart(cart_id))启动命令uvicorn app:app --host 127.0.0.1 --port 8000启动后访问http://127.0.0.1:8000/docs可以看到 Swagger 文档。7. 功能测试与效果验证服务启动后按下面的用例逐项验证。7.1 基础加购测试curl -X POST http://127.0.0.1:8000/api/chat \ -H Content-Type: application/json \ -d {message: 我要买 2 瓶波特酒和一包盐渍鳕鱼}预期结果模型调用 add_to_cart 两次返回购物车包含 vinho do porto 数量 2、bacalhau 数量 1总价为18.5 * 2 12.9 49.9。判断标准cart字段中的商品名称、数量、总价完全正确。7.2 多轮连续操作测试curl -X POST http://127.0.0.1:8000/api/chat \ -H Content-Type: application/json \ -d {message: 加 3 个葡式蛋挞, cart_id: 上一步返回的cart_id}然后继续发送curl -X POST http://127.0.0.1:8000/api/chat \ -H Content-Type: application/json \ -d {message: 把波特酒删掉, cart_id: 同一cart_id}预期结果第二次请求在原有购物车基础上追加第三次请求移除波特酒。最终购物车只剩 bacalhau 和 pastéis de nata。常见失败原因模型没携带 cart_id或者没有正确理解删掉对应 remove_from_cart。如果出现这类问题优先检查工具描述是否有歧义。7.3 商品不存在测试curl -X POST http://127.0.0.1:8000/api/chat \ -H Content-Type: application/json \ -d {message: 我要买一碗 francesinha}预期结果工具函数返回商品不存在LLM 收到工具错误后应该用自然语言告知用户该商品不在商品库中。判断标准不会向购物车中写入任何不存在的商品并且回复内容明确。7.4 批量任务测试这里再演示一个批量导入场景用户提交一个订单文本Agent 逐行解析并加购。curl -X POST http://127.0.0.1:8000/api/chat \ -H Content-Type: application/json \ -d {message: 请根据以下清单加购波特酒 1 瓶蛋挞 6 个橄榄油 2 瓶}预期结果模型一次对话内连续调用多次 add_to_cart全部执行成功后返回完整购物车状态。如果清单很长注意设置 max_steps 上限避免模型陷入死循环。8. 接口 API 与批量任务设计当这个 demo 要接入真实前端或收银系统时API 层要考虑下面几个问题。8.1 API 路径建议方法路径功能POST/api/chat对话并操作购物车GET/api/cart/{cart_id}查询购物车状态POST/api/cart/{cart_id}/items直接加购不走 LLMDELETE/api/cart/{cart_id}/items/{product_name}删除商品POST/api/batch/orders批量订单导入直接操作购物车的接口要保留因为生产环境不可能所有加购都经过 LLM要留一条确定性路径。8.2 批量订单导入实现批量任务的核心是按行解析 - 逐条加购 - 记录失败项。可以先用 LLM 做信息抽取再用确定性代码执行。app.post(/api/batch/orders) def batch_orders(orders: list[dict]): results [] for order in orders: try: cart_id order.get(cart_id) for item in order[items]: execute_tool(add_to_cart, json.dumps(item)) results.append({cart_id: cart_id, status: ok}) except Exception as e: results.append({cart_id: cart_id, status: failed, error: str(e)}) return {results: results}批量任务建议使用异步队列并把失败项写入日志方便重试。8.3 Python 客户端调用示例import requests url http://127.0.0.1:8000/api/chat payload { message: 加购 4 个蛋挞, cart_id: demo-cart-001 } resp requests.post(url, jsonpayload, timeout60) print(resp.json())9. 资源占用与性能观察这个项目本身对资源要求不高主要观察两个点一是 LLM 服务的响应时延二是本地大模型的资源占用。9.1 时延观察云端 API 方案单轮工具调用通常在 1~3 秒。本地 Ollama 7B 模型 CPU 推理单轮可能 5~15 秒多轮工具调用会成倍增加。本地 GPU 推理7B 量化模型约 1~3 秒。如果连续多次工具调用用户等待时间会叠加。解决方案是给模型明确的指令让它尽量一次性把参数都抽出来减少来回次数。9.2 模型服务占用本地部署 Ollama 时观察方法ollama ps这会列出当前常驻模型和显存占用。Qwen2.5 7B 的量化版本通常占用 5~7G 显存具体以本机实际为准。如果显存不足可以改用 3B 模型或直接切到云端 API。9.3 应用服务资源FastAPI 中间层本身占用内存不到 100M主要开销在 LLM 请求的等待时间和日志记录。生产环境建议给服务设置请求超时避免 LLM 卡住导致 HTTP 请求长时间挂起。10. 常见问题与排查方法问题现象可能原因排查方式解决方案模型返回空内容工具调用参数不合法查看服务端日志中的 tool_calls简化工具描述重试商品加错数量商品名映射失败或数量抽取错误打印模型输出的 arguments商品库增加别名比如 波特酒 - vinho do porto多轮对话丢失上下文没有维护 session_messages检查 Agent 循环中的 messages 是否持续推进保留 session_messages不要每次重新创建LLM 调用超时本地模型推理慢或 API 网络问题单独测试模型接口调大 timeout本地模型换 GPU 或小模型工具调用循环不退出模型反复请求同一个工具检查 max_steps 是否生效增加步骤上限工具执行失败时明确返回错误API 端口被占用8000 被其他服务占用netstat -anofindstr 8000购物车数据丢失服务重启后内存数据清空确认存储方式开发阶段可接受生产接 SQLite / Redis11. 最佳实践与使用建议基于这个 demo 的工程化经验整理几条建议。第一商品库字段设计要兼顾模型容易理解和代码容易校验。商品 ID 用稳定编码商品名称提供中文/英文/葡语多语言别名模型输入提示词里给出商品清单 别名映射能明显降低幻觉。第二所有工具函数都要做参数校验不能信任模型的输出。模型可能抽出小数数量、负数量、不存在的商品名。函数开头必须校验类型、范围、存在性。第三Agent 循环要设置最大步数和超时。一个用户请求最多允许调用 3~5 次工具超过就终止并返回错误。这样可以避免模型陷入重复调用。第四日志要能还原整个链路。建议记录用户输入、模型返回的工具调用、工具执行结果、最终回复。这是排查问题时最有效的依据。第五如果是面向真实用户的购物应用不要直接用 LLM 写订单数据库。正确的做法是 LLM 生成候选动作人类或规则层确认后再落库。尤其是涉及支付、折扣、优惠码的场景必须走确定性代码。第六涉及用户购物车、地址、历史订单等信息时做好最小化存储和脱敏。演示环境用假数据生产环境遵守当地数据保护法规。12. 总结与下一步From LLM to shopping cart 这类项目最值得尝试的点是用最少的代码把 LLM 从聊天机器人变成业务操作员。整个链路跑通后你会发现真正难的并不是模型选择而是工具定义和状态管理。第一步建议先验证基础加购和查购物车这是最核心的两个函数。跑通之后再扩展多轮修改、失败兜底和批量导入。最容易踩的坑是模型不按你的工具 Schema 输出参数这时候不要急着换模型先检查工具描述是否写清楚了什么时候调用、参数怎么填。后续可以继续扩展的方向包括接入 RAG 做更大规模商品检索接入 MCP 连接真实电商后端基于同一个 Agent 骨架做订单查询、物流跟踪等更多工具以及把对话链路从单轮改为多轮记忆。这套模式的表达能力很强购物车只是一个起点。