news 2026/8/30 8:43:30

从LLM到购物车:基于Function Calling的智能购物Agent实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从LLM到购物车:基于Function Calling的智能购物Agent实践

从 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_url="http://localhost:11434/v1", # Ollama 的 OpenAI 兼容端点 api_key="ollama" # 本地服务可填任意值 ) def chat_with_tools(messages, tools): response = client.chat.completions.create( model="qwen2.5:7b", messages=messages, tools=tools, tool_choice="auto" ) return response.choices[0].message

4.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_messages

4.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 requests

5.3 模型准备

两种路线:

  1. 云端 API:准备一个 OpenAI 兼容的 API Key,模型可用 gpt-4o-mini、gpt-4o、claude 等。
  2. 本地模型:安装 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_ascii=False) 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_ascii=False) 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_ascii=False) if name == "get_cart_info": return json.dumps(cart, ensure_ascii=False) 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(title="LLM 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_model=ChatResponse) 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_id=cart_id, reply=reply, cart=get_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直接加购,不走 LLM
DELETE/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, json=payload, timeout=60) 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 / Redis

11. 最佳实践与使用建议

基于这个 demo 的工程化经验,整理几条建议。

第一,商品库字段设计要兼顾模型容易理解和代码容易校验。商品 ID 用稳定编码,商品名称提供中文/英文/葡语多语言别名,模型输入提示词里给出"商品清单 + 别名映射",能明显降低幻觉。

第二,所有工具函数都要做参数校验,不能信任模型的输出。模型可能抽出小数数量、负数量、不存在的商品名。函数开头必须校验类型、范围、存在性。

第三,Agent 循环要设置最大步数和超时。一个用户请求最多允许调用 3~5 次工具,超过就终止并返回错误。这样可以避免模型陷入重复调用。

第四,日志要能还原整个链路。建议记录用户输入、模型返回的工具调用、工具执行结果、最终回复。这是排查问题时最有效的依据。

第五,如果是面向真实用户的购物应用,不要直接用 LLM 写订单数据库。正确的做法是 LLM 生成候选动作,人类或规则层确认后再落库。尤其是涉及支付、折扣、优惠码的场景,必须走确定性代码。

第六,涉及用户购物车、地址、历史订单等信息时,做好最小化存储和脱敏。演示环境用假数据,生产环境遵守当地数据保护法规。

12. 总结与下一步

"From LLM to shopping cart" 这类项目最值得尝试的点,是用最少的代码把 LLM 从"聊天机器人"变成"业务操作员"。整个链路跑通后,你会发现真正难的并不是模型选择,而是工具定义和状态管理。

第一步建议先验证基础加购和查购物车,这是最核心的两个函数。跑通之后再扩展多轮修改、失败兜底和批量导入。最容易踩的坑是模型不按你的工具 Schema 输出参数,这时候不要急着换模型,先检查工具描述是否写清楚了"什么时候调用、参数怎么填"。

后续可以继续扩展的方向包括:接入 RAG 做更大规模商品检索,接入 MCP 连接真实电商后端,基于同一个 Agent 骨架做订单查询、物流跟踪等更多工具,以及把对话链路从单轮改为多轮记忆。这套模式的表达能力很强,购物车只是一个起点。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/30 8:42:35

STM32U5视频播放停止问题定位:LTDC时钟与DMA2D同步修复实战

MCU跑视频显示&#xff0c;说难不难&#xff0c;但真要在量产阶段遇到“画面突然停了”这种问题&#xff0c;血压直接拉满。这次我在STM32U5G9ZJT6Q平台上就撞上了这样一个“Video Stop”的案子——视频播放到一半画面定格&#xff0c;屏幕不刷新&#xff0c;系统任务还活着&am…

作者头像 李华
网站建设 2026/8/30 8:42:33

欢聚时代2017Java校招笔试C卷复盘:从基础考点到避坑指南

前阵子整理旧硬盘&#xff0c;翻出一份自己当年整理的欢聚时代2017校招笔试题目&#xff08;JAVA基础类&#xff09;C卷的复盘笔记。这套题放到现在看&#xff0c;依然很有嚼劲。它没有堆砌冷门偏题&#xff0c;反而把Java开发天天会碰的基础概念老老实实考了一遍&#xff0c;却…

作者头像 李华
网站建设 2026/8/30 8:39:39

C/C++校招笔试考点全解析:从指针内存到字符串算法

爱奇艺2018秋季校招的C/C工程师第二场笔试&#xff0c;到现在还有人翻出来问。说实话&#xff0c;这一场题目在当年算是有代表性的——难度比第一场略高&#xff0c;出题方向更偏向工程细节&#xff0c;不是说你把《C Primer》翻一遍就能稳过的。我当年刷完这套题复盘了很久&am…

作者头像 李华
网站建设 2026/8/30 8:37:55

trackerslist 完整指南:78 个 Tracker 怎么选,下载提速不靠玄学

trackerslist 完整指南&#xff1a;78 个 Tracker 怎么选&#xff0c;下载提速不靠玄学 【免费下载链接】trackerslist Updated list of public BitTorrent trackers 项目地址: https://gitcode.com/GitHub_Trending/tr/trackerslist BT 下载卡在个位数 KB/s、peer 数显…

作者头像 李华
网站建设 2026/8/30 8:37:32

用 Remotion 音乐可视化模板生成 1080×1080 频谱视频

用 Remotion 音乐可视化模板生成 10801080 频谱视频 【免费下载链接】remotion &#x1f3a5; Make videos programmatically with React 项目地址: https://gitcode.com/GitHub_Trending/re/remotion Remotion 是一个用 React 写代码来产出视频的框架&#xff0c;它的 …

作者头像 李华
网站建设 2026/8/30 8:37:18

IAR支持Andes RISC-V内核,嵌入式开发迁移实践指南

前两天逛嵌入式技术社区&#xff0c;看到一条消息被讨论得挺多&#xff1a;IAR Embedded Workbench正式官宣支持Andes晶心科技的RISC-V内核。做MCU开发的老哥应该都知道IAR在老牌嵌入式工具链里的地位&#xff0c;而Andes又是RISC-V处理器IP里出货量排得上号的一家&#xff0c;…

作者头像 李华