尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

大模型经营柠檬水摊:一套可复制的AI Agent决策评测框架

大模型经营柠檬水摊:一套可复制的AI Agent决策评测框架 把一个只会在对话框里聊天的大模型扔到一间真实经营的柠檬水摊前让它连续经营七天最后看谁赚到的钱多。这件事听起来像是一段娱乐视频但它背后其实是一个非常值得开发者复刻的 AI Agent 评测实验。很多人把 GPT 和 Claude 当成“问答机器人”只在聊天窗口里问它怎么写代码、怎么翻译文本。但真正有价值的问题是当模型被放进一个“观察状态、做出决策、接受反馈、调整下一步行动”的闭环里它还能不能做出理性判断柠檬水摊就是一个绝佳的场景规则简单状态清晰但包含库存采购、定价、天气预测、现金流约束和多天累计收益。小但五脏俱全。这篇文章不打算只停留在“谁家 AI 更聪明”的娱乐结论上。我会把整个实验按照工程方式拆开如何搭建一个柠檬水摊模拟环境如何通过 API 接入 GPT 和 Claude 作为决策代理如何让它们连续跑多天经营决策如何用累计利润、库存浪费和决策合理性来评估结果最后再总结 Agent 开发中真正容易踩坑的地方。读完你会得到一套可复制的 LLM 决策评测 Demo也能更清楚地判断一个大模型“会聊天”和“会做运营决策”之间到底差了多少。1. 这篇文章真正要解决的问题先说结论柠檬水摊实验不是一个游戏而是一个压缩过的决策智能测试床。它把 LLM 评测从“静态问答”推向“动态决策”这是当前 Agent 开发中最值得关注的方向之一。传统的大模型评测通常围绕“正确答案”展开给一个数学题、一段翻译、一个代码题目看模型的输出是否匹配标准答案。这类评测能反映模型的记忆和理解能力但很难反映它在真实业务中的价值。真实业务有一个共同特征今天的决策会影响明天的状态。你采购了太多柠檬明天的现金就变少你把价格定得太高今天的销量就下滑你没有做广告下雨天的客流就更惨。这些约束在一个静态问答测试里根本不会出现。柠檬水摊正好把这类运营决策压缩到很小的空间里决策变量进货柠檬数量、进货糖包数量、进货纸杯数量、每杯售价、是否打广告。状态变量现金、库存、天气预告、前一天售价、累计利润。约束条件现金不足不能采购每杯柠檬水需要消耗一个柠檬、一份糖、一个纸杯库存卖不掉不产生收入。随机因素当天天气可能与预报不一致客流本身也有波动。所以这篇文章真正要回答的问题是GPT 和 Claude 这类通用大模型能不能在连续多天的经营模拟中表现出“会做生意”的能力它们是否懂得控制库存、调整定价、应对天气变化更重要的是如果我们要把大模型接入生产系统做自动决策应该用什么样的工程框架去约束它、验证它、保护它如果你正在做 Agent 开发、做 LLM 工具调用、或者想把大模型接入业务决策流程这篇文章的示例和排错思路可以直接借鉴。2. 核心概念与场景设定在写代码之前先把实验涉及的核心概念说清楚。很多新手在接触 Agent 开发时会被“工具调用”“Function Calling”“多智能体”这些词吓到但柠檬水摊实验不涉及这些复杂机制只需要理解最基础的闭环。2.1 LLM 作为决策器在这个实验里大模型不是一个聊天对象而是一个“决策器”。每天开始时程序会告诉它当前的现金、库存、天气预告等信息它需要输出一个经营决策买多少原料、卖多少钱、是否打广告。程序拿到这个决策后根据模拟规则计算当天的销售结果再把新的状态反馈给模型。第二天模型会基于新的状态重新决策。这是一个典型的闭环状态输入 - LLM 决策 - 环境执行 - 结果反馈 - 新状态这种结构和真实 Agent 任务是一致的。区别只在于真实 Agent 的环境可能是电商系统、运维平台、CRM而这里的“环境”是一个只有几百行代码的模拟器。2.2 环境、动作与奖励如果熟悉强化学习可以把柠檬水摊理解为一个小型环境环境状态现金、库存、天气、天数。动作空间进货柠檬数、进货糖包数、进货纸杯数、售价、广告策略。奖励信号每天的销售收入和最终累计利润。终止条件运行到设定的总天数。不过这里不需要训练模型而是直接让预训练大模型通过提示词来决策。所以问题的关键在于模型能否根据环境状态和一条系统提示词自己推理出合理的经营策略。2.3 为什么要用 API 而不是网页聊天看视频时很多人以为这个实验是在网页聊天窗口里手动问 AI“今天该进多少货”然后把答案手动填进表单。工程化做法完全不同。我们应该通过 API 直接调用模型让程序自动发送状态、接收决策、执行决策并记录结果。使用 API 有几个明显好处可重复每次调用参数一致方便做对比实验。可控制可以设置 temperature、max_tokens、响应格式等参数。可记录所有输入输出都可以落盘方便分析模型哪一步出了问题。可批量化可以连续跑几十轮而不是手动复制粘贴。因此后面所有示例都基于 OpenAI Python SDK 和 Anthropic Python SDK。3. 环境准备与前置条件这个实验对硬件没有要求一台普通开发机即可。需要准备的主要是 Python 环境和 API 密钥。建议使用 Python 3.10 及以上版本项目最好用虚拟环境隔离依赖。实际测试中版本差异可能影响 SDK 行为但本文重点演示通用思路不绑定精确版本。3.1 创建项目目录和虚拟环境在终端里执行mkdir lemonade-lab cd lemonade-lab python3 -m venv .venv source .venv/bin/activateWindows 环境把最后一步换成.venv\Scripts\activate3.2 安装依赖pip install openai anthropic python-dotenv三个依赖的用途openai用于调用 GPT 系列模型。anthropic用于调用 Claude 系列模型。python-dotenv用于从.env文件读取 API 密钥避免把密钥写进代码。3.3 配置 API 密钥在项目根目录创建.env文件OPENAI_API_KEYsk-your-openai-key ANTHROPIC_API_KEYsk-ant-your-anthropic-key注意.env文件绝不能提交到 Git 仓库建议在.gitignore中加入一行*.env。API 密钥是敏感凭证泄露后可能导致盗刷。如果是在公司服务器上运行更推荐使用 KMS 或配置中心管理密钥而不是直接写在文件里。如果某个模型在当前网络环境或账户下不可用请以你账户可访问的模型为准。例如可以把gpt-4o-mini换成你账户可用的 OpenAI 兼容模型也可以把claude-3-5-sonnet-latest换成你账户可见的 Claude 模型。本文代码只依赖模型返回 JSON 文本不绑定具体模型名。4. 核心流程拆解整个实验可以拆成四个模块模拟环境、LLM 代理、比赛主循环、结果分析。下面逐个说明设计思路。4.1 模拟环境模块模拟环境的作用是维护经营状态并按照规则执行决策。它不需要依赖任何大模型 SDK是一个纯 Python 类。核心规则设计如下初始资金 20 美元初始库存为 0。每天开始前程序获得当天天气预告。决策动作包含进货数量、售价、是否广告。执行顺序先采购扣钱再计算客流和销量最后把收入加到现金上。销量不能超过制作能力可制作杯数等于柠檬、糖、纸杯三者中的最小值。当天卖不掉的库存保留到第二天但不会自动变成现金属于沉淀资金。天气会影响客流晴天客流最多阴天一般雨天最少。广告会提高当日客流因子但需要付出固定成本。为什么把“卖不掉的库存”计入浪费指标因为在真实经营里库存占用资金现金才是生存的关键。这个规则会迫使模型学习控制进货量而不是盲目买买买。4.2 LLM 代理模块LLM 代理负责把环境状态转换成决策动作。它的核心是提示词设计和输出解析。提示词必须明确告诉模型三件事你正在经营一个柠檬水摊目标是在多天内最大化累计利润。输入的 JSON 格式和含义。输出必须是 JSON不能有其他文字。输出解析是关键的一步。GPT 和 Claude 偶尔会输出 Markdown 代码块格式的 JSON需要写一个解析函数去掉多余内容。更稳妥的做法是解析失败时返回一个保守决策而不是让程序崩溃。4.3 对比实验设计要公平对比 GPT 和 Claude需要保证两个模型面对相同的天气序列和客流随机数。做法是固定随机种子。因为运行脚本里天气和客流都依赖同一个random模块只要在每次运行前调用random.seed(42)两个模型就会看到相同的随机序列。这样利润差异就只能归因于模型决策质量而不是运气。建议每组实验跑多个种子比如42、2024、7然后取平均利润。单次运行只能说明某一次的运气多轮平均更有说服力。5. 完整示例与代码实现下面给出完整代码。为了便于理解拆成多个文件项目结构如下lemonade-lab/ ├── .env ├── lemonade_env.py ├── llm_agents.py ├── run_experiment.py └── result_log.jsonl5.1 模拟环境lemonade_env.py# 文件路径lemonade-lab/lemonade_env.py import random from dataclasses import dataclass dataclass class LemonadeEnv: cash: float 20.0 lemons: int 0 sugar: int 0 cups: int 0 price: float 1.0 day: int 0 def reset(self): self.cash 20.0 self.lemons 0 self.sugar 0 self.cups 0 self.price 1.0 self.day 0 def state_text(self, forecast: str) - str: return ( fDay {self.day 1}, forecast{forecast}, fcash${self.cash:.2f}, lemons{self.lemons}, fsugar{self.sugar}, cups{self.cups}, flast_price${self.price:.2f} ) def apply_decision(self, d: dict, weather: str) - dict: self.day 1 buy_lemons max(0, int(d.get(buy_lemons, 0))) buy_sugar max(0, int(d.get(buy_sugar, 0))) buy_cups max(0, int(d.get(buy_cups, 0))) price round(max(0.2, min(float(d.get(price, 1.0)), 5.0)), 2) advertise 1 if d.get(advertise, 0) else 0 cost ( buy_lemons * 0.2 buy_sugar * 0.1 buy_cups * 0.15 advertise * 1.0 ) # 现金不足时按比例缩减采购 if cost self.cash: scale self.cash / cost buy_lemons int(buy_lemons * scale) buy_sugar int(buy_sugar * scale) buy_cups int(buy_cups * scale) cost ( buy_lemons * 0.2 buy_sugar * 0.1 buy_cups * 0.15 advertise * 1.0 ) if cost self.cash: advertise 0 cost ( buy_lemons * 0.2 buy_sugar * 0.1 buy_cups * 0.15 ) self.cash - cost self.lemons buy_lemons self.sugar buy_sugar self.cups buy_cups weather_factor { sunny: 1.3, cloudy: 1.0, rainy: 0.6, }.get(weather, 1.0) ad_factor 1.6 if advertise else 1.0 walkers random.randint(15, 50) customers int(walkers * weather_factor * ad_factor) # 价格越高购买转化率越低 buy_prob max(0.05, 1.2 - 0.35 * price) demand int(customers * buy_prob) capacity min(self.lemons, self.sugar, self.cups) sales min(demand, capacity) revenue sales * price self.cash revenue self.lemons - sales self.sugar - sales self.cups - sales self.price price return { day: self.day, weather: weather, walkers: walkers, demand: demand, capacity: capacity, sales: sales, revenue: round(revenue, 2), cash: round(self.cash, 2), waste_lemons: self.lemons, decision: d, }这段代码的关键点在于采购先于销售扣款现金不足时按比例缩减采购避免产生负现金流。制作能力受三个库存维度约束模型必须尽量让柠檬、糖、纸杯数量匹配。天气和广告都只影响客流最终销量还受价格转化率和库存上限影响。5.2 LLM 代理llm_agents.py# 文件路径lemonade-lab/llm_agents.py import json import os import re from anthropic import Anthropic from openai import OpenAI SYSTEM_PROMPT 你是一个柠檬水摊的经营者。你的目标是在多天内最大化累计利润。 每天只能做一次决策输出 JSON 对象不要输出多余文字不要使用 Markdown。 JSON 字段含义 - buy_lemons当天进货柠檬数量整数 - buy_sugar当天进货糖包数量整数 - buy_cups当天进货纸杯数量整数 - price当天每杯售价小数建议在 0.5 到 3.0 之间 - advertise是否花 1 美元做广告1 表示做0 表示不做 约束 - 现金不足时无法采购 - 每杯需要消耗 1 个柠檬、1 份糖、1 个纸杯 - 卖不掉的库存不会自动变成现金要避免浪费 - 天气影响客流晴天客流最多下雨客流最少 - 价格过高会降低购买人数 请根据当前状态和天气预告理性决策。 .strip() def parse_json(text: str) - dict: cleaned re.sub(rjson|, , text).strip() return json.loads(cleaned) class BaseAgent: def decide(self, state_text: str) - dict: raise NotImplementedError class GPTAgent(BaseAgent): def __init__(self, model: str gpt-4o-mini, temperature: float 0.2): self.model model self.temperature temperature self.client OpenAI(api_keyos.getenv(OPENAI_API_KEY)) def decide(self, state_text: str) - dict: user_prompt 当前状态\n state_text try: resp self.client.chat.completions.create( modelself.model, temperatureself.temperature, max_tokens300, response_format{type: json_object}, messages[ {role: system, content: SYSTEM_PROMPT}, {role: user, content: user_prompt}, ], ) return parse_json(resp.choices[0].message.content) except Exception as e: print(GPT call failed:, e) return self._fallback() def _fallback(self) - dict: return { buy_lemons: 0, buy_sugar: 0, buy_cups: 0, price: 1.0, advertise: 0, } class ClaudeAgent(BaseAgent): def __init__(self, model: str claude-3-5-sonnet-latest, temperature: float 0.2): self.model model self.temperature temperature self.client Anthropic(api_keyos.getenv(ANTHROPIC_API_KEY)) def decide(self, state_text: str) - dict: user_prompt 当前状态\n state_text try: resp self.client.messages.create( modelself.model, max_tokens300, temperatureself.temperature, systemSYSTEM_PROMPT, messages[{role: user, content: user_prompt}], ) text resp.content[0].text return parse_json(text) except Exception as e: print(Claude call failed:, e) return self._fallback() def _fallback(self) - dict: return { buy_lemons: 0, buy_sugar: 0, buy_cups: 0, price: 1.0, advertise: 0, }这里有两个值得注意的工程细节。第一解析函数parse_json剥离了模型可能输出的 Markdown 代码块标记。因为 Claude 在复杂任务中偶尔会输出 json包裹的内容直接json.loads 会失败。第二调用失败时返回一个保守决策而不是抛出异常让整个实验终止。在真实 Agent 系统中这对应“兜底动作”可以避免因为模型临时故障导致业务流程中断。5.3 主程序run_experiment.py# 文件路径lemonade-lab/run_experiment.py import argparse import json import random from lemonade_env import LemonadeEnv from llm_agents import ClaudeAgent, GPTAgent WEATHER_CANDIDATES [sunny, sunny, cloudy, cloudy, rainy] def main(): parser argparse.ArgumentParser() parser.add_argument(--provider, choices[gpt, claude], requiredTrue) parser.add_argument(--days, typeint, default7) parser.add_argument(--seed, typeint, default42) parser.add_argument(--model, typestr, defaultNone) args parser.parse_args() random.seed(args.seed) env LemonadeEnv() if args.provider gpt: agent GPTAgent(modelargs.model or gpt-4o-mini) else: agent ClaudeAgent(modelargs.model or claude-3-5-sonnet-latest) records [] for _ in range(args.days): forecast random.choice(WEATHER_CANDIDATES) # 实际天气以 70% 概率与预报一致模拟预报误差 if random.random() 0.7: actual_weather forecast else: actual_weather random.choice(WEATHER_CANDIDATES) state_text env.state_text(forecast) decision agent.decide(state_text) for key in [buy_lemons, buy_sugar, buy_cups, price, advertise]: if key not in decision: decision[key] 0 record env.apply_decision(decision, actual_weather) records.append(record) print(json.dumps(record, ensure_asciiFalse)) final_profit env.cash - 20.0 total_sales sum(r[sales] for r in records) total_revenue round(sum(r[revenue] for r in records), 2) waste_lemons sum(r[waste_lemons] for r in records) print(\n SUMMARY ) print(fprovider{args.provider} days{args.days} seed{args.seed}) print(ffinal_cash{env.cash:.2f} total_profit{final_profit:.2f}) print(ftotal_revenue{total_revenue} total_sales{total_sales} waste_lemons{waste_lemons}) with open(result_log.jsonl, a, encodingutf-8) as f: for r in records: f.write(json.dumps(r, ensure_asciiFalse) \n) if __name__ __main__: main()主程序的流程很清晰固定随机种子、生成预报和实际天气、让模型决策、环境执行、记录日志。所有结果追加写入result_log.jsonl方便后续统计。注意天气预报和实际天气之间存在 30% 的不一致概率。这是刻意设计的目的是测试模型会不会因为盲目相信预报而采购失误。在真实经营中天气预报本身就不是绝对准确的。5.4 运行命令确认.env文件存在后先跑 GPTpython run_experiment.py --provider gpt --days 7 --seed 42再跑 Claudepython run_experiment.py --provider claude --days 7 --seed 42如果某个模型名称在你的账户下不可用可以通过--model参数指定其他模型名python run_experiment.py --provider gpt --model gpt-4o --days 7 --seed 426. 运行结果与效果验证运行成功后控制台会持续输出每天的决策和经营结果。下面是一次典型运行的日志格式不是特定模型的实测数据而是用来展示日志结构{day: 1, weather: sunny, walkers: 48, demand: 36, capacity: 20, sales: 20, revenue: 24.0, cash: 38.0, waste_lemons: 0, decision: {buy_lemons: 20, buy_sugar: 20, buy_cups: 20, price: 1.2, advertise: 1}}日志末尾会输出汇总信息 SUMMARY providergpt days7 seed42 final_cash31.50 total_profit11.50 total_revenue48.00 total_sales34 waste_lemons8如何判断实验是否成功主要看四个方面程序没有因为 JSON 解析错误或 API 异常中断。每天的现金不为负。最终利润大于 0。库存浪费量相对可控。如果模型把采购量定得远超实际需求累计利润可能为负同时 waste_lemons 会非常高。这说明模型没有理解“库存不会自动变现”这一约束。对比两组实验时不要只看一次结果。建议固定天数把种子分别设为 42、2024、7各跑三轮并记录汇总利润然后看平均值和波动范围。单次运行中某天突然出现高温晴好天气可能让某个模型偶然获得高收入多轮平均能消除这种随机误差。7. 常见问题与排查思路在实际运行这个实验时最容易出现问题的地方不是环境代码而是模型调用和输出解析。下面列出我整理的高频问题。问题现象可能原因排查方式解决方案调用 API 报AuthenticationErrorAPI 密钥缺失或错误检查.env文件是否存在确认密钥前缀修复密钥重启终端让环境变量生效返回的 JSON 不能解析模型输出了 Markdown 代码块或额外文字打印原始文本确认parse_json是否覆盖常见格式增强清洗逻辑先剥离代码块再尝试json.loads连续几天采购为 0模型把输出键名写错查看日志中的decision字段在提示词中强调字段名并在主程序里增加默认值兜底API 请求超时或限流并发过高或网络不稳定查看 SDK 返回的异常信息增加超时参数失败后重试必要时限制并发库存浪费很多模型没有准确预测销量对比天气预告、客流和采购量改进提示词加入前两天销售历史引导模型动态调整运行结果不稳定随机种子没有固定检查主程序是否调用了random.seed在random.seed(args.seed)之后不要插入其他随机数调用成本超预期max_tokens设置过高或模型价格较高统计每天的 token 消耗调整max_tokens优先使用gpt-4o-mini这类低成本模型如果你遇到模型输出一直不合规可以补充少量示例。在系统提示词中增加一个 JSON 示例往往比反复修改字段说明更有效。8. 最佳实践与工程建议把柠檬水摊实验从“玩具”升级成“可用的 Agent 评测框架”需要关注下面几个工程点。8.1 提示词设计要显式给出约束大模型在自由对话中擅长发散但在决策闭环里必须收敛。系统提示词要把目标、字段、约束一次说清。尤其是“卖不掉的库存不会自动变现”这类反直觉规则如果提示词不写出来模型很容易把柠檬囤到发霉然后亏损。8.2 输出必须校验不能直接信任模型模型返回的 JSON 就算能被json.loads解析也可能包含非法值。例如price可能被输出成负数buy_lemons可能被输出成字符串。生产级代码必须在环境执行前对每个字段做类型和范围校验。这个实验是在模拟环境里即使出错也不会造成真实损失但换到真实业务例如自动定价或自动采购输出校验就是安全底线。8.3 增加历史记忆当前版本的提示词只包含当天状态模型看不到前一天发生了什么。这会让模型遗忘自己的失误也无法从反馈中学习。在更接近真实系统的版本里应该把最近 3 天的决策和结果摘要放进提示词最近三天 Day 3: 进20个柠檬售出18杯收入21.6库存剩余2 Day 4: 进25个柠檬售出12杯收入9.6库存剩余15 Day 5: 请根据以上销售趋势调整进货量。这一步对利润提升非常明显因为模型能够感知“昨天囤货太多”这个错误信号。8.4 使用固定随机种子做公平对比对比不同模型或不同提示词时必须控制在单一变量原则下。除了模型身份其他条件都应相同天气序列、客流随机数、初始资金、总天数。用固定种子是成本最低、也最有效的公平性保障。8.5 成本控制与失败兜底调用 API 需要花钱虽然单次决策很便宜但跑大量实验时成本不可忽视。建议限制max_tokens300因为一个柠檬水摊决策 JSON 通常不到 100 个 token。同时在代码里加一层 try-except失败时返回保守决策保证整套实验能够跑完而不是中途崩溃。8.6 结果留痕每一次运行都应该把完整日志落盘包括原始状态、模型决策、天气、销量、现金。这不仅是为了复现结果也是排查模型异常的重要依据。建议用 JSONL 格式一行一条记录后续用 Python 或数据库分析都很方便。9. 总结与后续学习方向这个柠檬水摊实验的核心价值是让我们看到大模型的能力边界不在“会不会聊天”而在“能不能在一个有约束、有反馈、有随机性的环境中持续做出合理决策”。GPT 和 Claude 都可能给出不错的单日决策但连续多天经营后能否控制库存、平衡价格和客流、应对天气预报误差才是评价一个模型“是否具备 Agent 潜力”的重要标准。如果你拿到了代码建议先按默认参数跑通全流程然后做三类改进第一修改提示词加入销售历史观察利润变化。第二增加工具调用能力让模型不仅做决策还能查询历史销售数据、读取外部天气 API。第三把实验从“单模型决策”扩展成“多模型竞争”例如让两个 Agent 分别经营两个摊位再引入互相观察和竞争定价。从工程角度看这个 demo 已经包含了一个通用 Agent 系统的核心要素状态管理、决策器、环境执行、日志记录、结果评估。把柠檬水摊替换成库存管理、投放策略、客户运营套路完全一致。真正的难点从来不是调用一次大模型而是如何围绕模型构建一套可信、可控、可评估的决策闭环。
返回列表