
如果只看热搜词AI 行业每天都会冒出新概念AI Agent、AI 编程、AI 视频、Token、上下文长度……但在真实的开发流程里这些词最终都会落回到同一个问题上一次模型调用要消耗多少 Token这些 Token 够不够放下上下文以及超出之后系统会怎样失败。AI Mania: From Tulips to Tokens这个标题并不只是在讲经济史它是在提醒我们每一轮技术狂热都会经历从概念膨胀到工程收敛的过程。17 世纪的郁金香、2000 年的互联网以及现在的大模型都不缺真实价值也不缺过度预期。对开发者来说真正值得沉淀下来的不是追热词而是把模型调用变成可测量、可排错、可控制成本的工程能力。下面会以 Token 为主线解释大模型的上下文机制再用一个 AI 小镇开源项目为案例走一遍生成式 Agent 的最小工程链路。1. 从郁金香到 TokenAI 狂热的周期与本质1.1 每次技术狂热中都存在真实需求和过剩预期郁金香泡沫发生在 17 世纪的荷兰。郁金香本身是真实存在的商品稀有球茎也确实有观赏价值但当价格被投机交易推到普通住宅的几倍时市场已经脱离了实物本身的价值。技术领域很容易出现类似现象新技术带来真实能力但在早期阶段人们往往高估它的短期影响低估它的实现成本。大模型热潮里真实需求非常明显文本生成、代码补全、知识问答、内容总结、多轮对话这些能力确实能解决实际问题。与此同时大量“AI 原生项目”只是给原有功能套了一层模型调用并没有考虑上下文窗口、Token 成本、延迟、输出质量和错误恢复。结果就是 Demo 很惊艳生产环境跑两周就发现账单失控或者报错频发。对开发者来说理解这个周期不是为了判断泡沫什么时候破而是为了在方向不明确时仍然保留工程判断力。把“AI 很热”翻译成“我应该掌握哪些工程能力”比收藏一堆热词更有用。1.2 Token 是 AI 时代的计价单位Token 是大模型处理文本的基本单位。它不是一个完整的英文单词也不等于一个汉字而是模型的 Tokenizer 把文本拆出来的最小片段。可以是单词、子词、中文词组也可能是代码片段。例如AI Mania: From Tulips to Tokens这串英文在常见英文 Tokenizer 下可能被切成十几个 Token而一段中文文本可能会因为分词方式不同被切成数量不等的 Token。在 AI 工程里Token 有三重身份长度单位决定了单次请求能放入多少文本和输出多少内容。计费单位使用模型按输入 Token 和输出 Token 计费。速率单位每分钟允许消耗的 Token 数也就是 TPM。理解这三重身份才能解释为什么同一个模型在不同场景下表现差异很大。给模型 200 个 Token 让它写一句话没问题让它输出一份 3000 字的报告就会因为max_tokens限制被截断。让用户连续对话 20 轮如果历史不清空很快会碰到context length exceeded。1.3 把热词映射回工程问题热搜词本身不是技术但它背后的事件和需求可以作为问题入口。与其追问“AI Agent 是不是风口”不如问“Agent 循环里如何控制上下文”与其追问“AI 编程会不会替代程序员”不如问“代码补全工具把多少文件内容塞进了上下文中”。大家讨论的热词真正对应的工程问题AI Agent / Agent 开发多轮工具调用、记忆管理、步数控制、失败重试AI 编程 / Cursor AI 编程上下文裁剪、仓库索引、代码片段拼接、输出校验Token / context length exceeded上下文窗口、提示词压缩、历史滑动窗口TPM速率限制、并发控制、重试退避AI 视频 / AI 广告一键成片多模态任务拆分、中间结果持久化、成本预算Spring AIJava 工程如何把模型调用封装成统一抽象这种映射方式让文章讨论落到具体实现上。后面的内容都以 Token 为核心因为无论是做 Agent、做 AI 写作工具还是做 AI 小镇模拟游戏Token 都是最先暴露问题的环节。2. 理解 Token 之前先理解大模型的上下文机制2.1 上下文窗口、提示词和输出长度大模型在收到一次请求时会把系统提示词、历史对话、用户输入、上下文资料一起打包成输入序列模型会根据这个序列生成输出。这个过程受“上下文窗口”限制也就是模型一次能看到的 Token 总数。常见模型有不同的上下文窗口例如 8K、32K、128K甚至更长。数字看起来很大但在实际应用里消耗得很快。系统提示词占 1000 Token用户问题占 500 Token两轮检索回来的资料各占 2000 Token加上历史对话和输出一次请求很容易超过 5000 Token。如果用的是 8K 窗口可用空间已经所剩不多。context length exceeded (36,183 tokens). cannot compress further.这种报错就是在提示当前请求把所有消息加起来已经超过模型上限而且服务端尝试压缩也没有成功。这个数字36,183 Tokens说明不是普通对话而是某个 Agent、长文档分析或代码仓库上下文把窗口撑爆了。影响 Token 消耗的来源典型大小控制方式系统提示词5002000 Token精简固定不变历史对话每轮 3001000 Token滑动窗口只保留最近 N 轮检索资料每段 5003000 Token先总结再取 Top-K工具返回结果几百到几万 Token截断只保留核心字段模型输出由max_tokens控制分步生成不要一次要全文2.2 输入 Token、输出 Token 与 TPM一次模型调用至少包含两部分 Tokenprompt_tokens和completion_tokens。前者是你给模型的全部输入后者是模型生成的输出。服务商通常会分别计费因为输入和输出的算力成本不一样。TPM也就是 tokens per minute是每分钟允许消耗的输入与输出 Token 总和。它的作用是控制并发负载。即使你的账号余额充足如果请求太密集服务端也会返回 429 或限流错误。TPM 限制对 Agent 场景影响尤其大因为一个 Agent 往往在一分钟里连续调用多次模型每次调用都包含系统提示词和记忆上下文。实际开发中不能只看单次调用成本还要看每分钟的聚合消耗。如果设计一个 AI 小镇里面有 10 个 Agent 每 30 秒各调用一次模型每次消耗 1500 Token那么一分钟的消耗就是 30000 Token。如果服务商 TPM 限额是 30000系统会被卡住。2.3 什么任务最容易吃满 Token不同任务对 Token 的消耗差异很大。从工程经验看最容易吃满 Token 的有几类任务。第一类是长文档分析。把一本 PDF、一篇文章或一堆日志直接塞给模型让它总结、翻译或提取内容输入 Token 会随文本长度线性增长。正确做法是先切块、先检索或者先让模型生成摘要再基于摘要继续处理。第二类是多轮 Agent 调用。Agent 每完成一次工具调用通常都会把本次观察追加到历史里如果循环不设置步数上限历史会膨胀得非常快。失败重试更加致命每次重试都会重复消耗之前的 Token。第三类是 AI 编程辅助。Cursor 这类 AI 编程工具会把当前文件、相关文件、项目配置、编译错误信息一起作为上下文代码库越大单次请求的 Token 越高。这也是为什么大型仓库使用 AI 编程工具时经常出现上下文不足或成本上升。第四类是 RAG 检索增强生成。传统实现会把多个候选文档拼接到 prompt 里让模型阅读候选越多 Token 越高。更好的做法是先粗筛、再重排只把最相关的 23 段放进上下文。3. 用一个 AI 小镇项目跑通生成式 Agent 的工程链路3.1 my_ai_town从生成式智能体论文到开源项目生成式智能体Generative Agents来自 2023 年斯坦福和谷歌的一项工作。研究团队在一个模拟小镇里放入多个 AI 角色每个角色拥有记忆、观察、计划和反思能力它们会在小镇里移动、对话、形成关系行为看起来像真人。这类项目后来被统称为“AI 小镇”。my_ai_town是这类主题的一个开源仓库地址是https://github.com/mewamew/my_ai_town从命名看是一个可以运行的 AI 小镇项目并且提供了 macOS 和 Windows 相关的游戏下载包。实际仓库的目录结构和运行方式以 README 为准。下面的简化实现并不代表仓库原有代码而是用来帮你理解“如果一个 AI 小镇项目要接入大模型核心链路应该怎么写”。学习这类项目时建议先跑通最小 Demo再加入自定义 Agent、记忆检索和 Token 统计。不要一开始就追求几十个 Agent 同时活跃否则第一个碰到的就是上下文和速率限制问题。3.2 最小数据模型Agent、记忆、地点AI 小镇的模拟单位是 Agent。每个 Agent 需要知道自己是谁、在哪里、在做什么以及过去经历过什么。一个最小数据模型可以这样设计{ agent_id: agent_001, name: Alice, status: walking, location: cafe, memory: [ { ts: 10, type: observation, content: 在咖啡馆看到了 Bob } ], plan: [ { ts: 20, action: talk_to, target: Bob } ] }location决定 Agent 能感知到哪些环境信息memory是它的长期记忆plan是它未来几步的行动计划。真实项目里还有场景对象比如tree、house、parking lot但核心逻辑都一样每个 tick 读取环境、检索记忆、调用模型决定下一步。这个数据模型的关键点是不要把全部历史都放进上下文。memory可能很长但每次调用模型时只检索与当前场景相关的 510 条记忆其余部分归档到数据库。3.3 Agent 主循环感知、记忆、规划、行动一个 AI 小镇 Agent 的核心循环可以用伪代码表示class Agent: def __init__(self, agent_id, name, location, llm_client): self.agent_id agent_id self.name name self.location location self.memory [] self.llm_client llm_client def tick(self, world): observation self.perceive(world) memories self.retrieve_relevant_memories(observation, top_k5) response self.llm_client.chat.completions.create( modeldeepseek-chat, messagesbuild_agent_messages(observation, memories), temperature0.7, max_tokens200, ) action parse_action(response) self.memory.append({ ts: world.clock, type: action, content: action, prompt_tokens: response.usage.prompt_tokens, completion_tokens: response.usage.completion_tokens, }) return action一个tick就是一个模拟时间步。每个 Agent 在 tick 里执行四件事感知当前场景。从记忆中检索相关内容。让模型基于感知和记忆生成一个动作。把动作和 Token 消耗写回记忆。这里最关键的是parse_action(response)。模型输出的是自然语言而程序需要把它变成结构化的动作比如{action: move_to, target: cafe}。实际项目中不要直接信任模型输出必须先校验 JSON 格式、字段是否完整、目标地点是否存在。解析失败时可以重试一次也可以让 Agent 选择“原地等待”避免程序崩溃。3.4 接入模型 API 时的参数与 Token 控制使用 OpenAI 兼容接口的代码很常见。下面是一个简化调用示例from openai import OpenAI client OpenAI( api_keyyour-api-key, base_urlhttps://api.example.com/v1, ) resp client.chat.completions.create( modeldeepseek-chat, messages[ {role: system, content: 你是一个 AI 小镇居民。}, {role: user, content: 你正在咖啡馆请决定下一步动作。}, ], temperature0.7, max_tokens200, streamFalse, ) print(resp.usage.prompt_tokens) print(resp.usage.completion_tokens) print(resp.usage.total_tokens)几个关键参数temperature控制随机性。Agent 行为可以设高一点比如 0.7提取关键词、翻译、格式化输出可以设低一点比如 0.2。max_tokens限制输出长度。它不会减少输入 Token但可以防止模型一次性输出超长内容。stream是否流式返回。AI 小镇模拟不需要等待用户打字通常关闭流式简化代码。base_url不同模型服务商提供的接入地址不同以官方文档为准。控制 Token 的做法有几种system提示词固定且精简。每次请求只携带最近几轮历史和 Top-K 记忆。长文本先让模型总结再把摘要放进上下文。工具返回结果只保留核心字段不要原样拼接。4. 验证、排错与 Token 账单的可观测性4.1 运行一个最小 AI 小镇实例的检查点如果希望运行my_ai_town先以仓库 README 为准。一个常见的流程是git clone https://github.com/mewamew/my_ai_town.git cd my_ai_town pip install -r requirements.txt export OPENAI_API_KEYyour-api-key export OPENAI_BASE_URLhttps://api.example.com/v1 python run_town.py --turns 10这里OPENAI_API_KEY和OPENAI_BASE_URL是环境变量具体名称视项目而定。运行之后重点检查三点是否能看到 Agent 初始化日志。每执行一个 tick是否打印 Agent 的动作和模型调用信息。是否记录了每次调用的 prompt、completion、total Token。如果仓库没有现成的 Token 统计代码建议自己加一个TokenMeter把每次调用结果写入日志或 CSV。没有观测的 Agent 项目等于不知道成本从哪来。4.2 常见报错与排查路径AI 应用在真实运行中会出现很多问题。最典型的是这三类上下文超长、速率限制、输出格式错误。问题现象常见原因检查方式处理建议context length exceeded (36,183 tokens). cannot compress further.消息历史过长检索内容过多工具返回结果太大打印prompt_tokens检查 messages 中每段来源滑动窗口截断历史长文档先摘要工具结果限长不要直接堆全文429或rate limit exceededTPM 超过服务商限额查看服务端响应头Retry-After控制台速率指标指数退避重试减少并发缩短 prompt增加缓存输出 JSON 解析失败模型返回了额外文字或 JSON 不完整打印模型原始输出使用 JSON mode 或函数调用解析失败重试设计默认动作兜底Agent 行为重复temperature过低记忆检索不到有效内容查看相同 prompt 是否多次出现提高temperature调整记忆检索增加随机动作Token 快速上涨Agent 循环无步数上限记忆无限膨胀统计每个 tick 的平均 token设置最大步数定期归档记忆每次只取 Top-K排查顺序应遵循“先看输入再看配置最后看模型本身”。例如context length exceeded优先检查 messages 里塞了什么是不是把整个对话历史都放回去了是不是工具返回了一个几十 KB 的日志之后再看max_tokens是否设置合理。最后才需要怀疑模型版本是否支持更长上下文。4.3 记录 Token 与成本估算成本控制不是靠感觉而是靠数据。建议每个调用点都经过一个统计类import csv import time class TokenMeter: def __init__(self): self.records [] def log(self, model, prompt_tokens, completion_tokens): self.records.append({ ts: time.time(), model: model, prompt_tokens: prompt_tokens, completion_tokens: completion_tokens, total_tokens: prompt_tokens completion_tokens, }) def estimate_cost(self, prompt_price_per_million, completion_price_per_million): prompt_cost sum(r[prompt_tokens] for r in self.records) * prompt_price_per_million / 1_000_000 completion_cost sum(r[completion_tokens] for r in self.records) * completion_price_per_million / 1_000_000 return prompt_cost completion_cost不同模型服务商的计费规则不同输入输出价格也不同而且价格会调整。不要写死价格建议从配置中心读取或者在控制台查看账单。一个稳妥的成本估算公式是总费用 输入 Token 总数 × 每百万输入价格 / 1,000,000 输出 Token 总数 × 每百万输出价格 / 1,000,000生产环境还需要设置预算熔断。例如单次任务累计 Token 超过 100 万自动停止 Agent单日成本超过某个阈值发送告警。否则一个调度任务异常重试一晚上账单可能很难看。5. 从 AI 狂热到 AI 工程给开发者的实践清单5.1 学习环境与生产环境的配置差异很多 AI 项目在本地跑得很顺利一上生产就出问题。原因不是模型变了而是本地环境没有速率限制、没有成本压力、没有异常恢复。维度本地 Demo生产环境API Key写在环境变量里方便调试使用密钥管理服务最小权限定期轮换模型版本经常切换新模型固定模型版本先灰度再全量异常处理报错就退出超时、重试、熔断、降级Token 观测可有可无每次调用记录 usage汇总到监控内容安全不校验对用户输入和模型输出做合规过滤数据持久化内存保存使用数据库存储会话和记忆回滚方案改代码重跑保留 prompt 快照支持一键回滚学习环境可以追求快速跑通生产环境必须考虑“如果模型返回不可用内容怎么办”“如果调用失败怎么办”“如果账单超支怎么办”。5.2 提示词与输出结构化把模型输出变成程序能处理的 JSON是 AI 工程的关键一步。一个常见的做法是在 system prompt 中明确指定输出格式SYSTEM_PROMPT ( 你是一个 AI 小镇居民。 请根据当前场景和记忆决定下一步动作。 只输出 JSON不要输出多余文字。 JSON 格式{\action\: \move_to|talk_to|wait\, \target\: \地点或角色名\} ) def build_agent_messages(observation, memories): memory_text \n.join(f- {m[content]} for m in memories) user_text ( f当前时间{observation[time]}\n f当前地点{observation[location]}\n f你看到{observation[scene]}\n f相关记忆\n{memory_text}\n 请决定下一步动作。 ) return [ {role: system, content: SYSTEM_PROMPT}, {role: user, content: user_text}, ]输出结构化的好处是减少解析成本。模型输出{action: move_to, target: cafe}后程序可以直接判断动作是否合法而不是在自然语言里找“想去咖啡馆”这个语义。如果使用支持 JSON Mode 或 Function Calling 的模型优先注册结构化函数这样系统会在底层约束输出格式。5.3 可复用的 Token 治理清单无论是写一个小工具还是开发一个多 Agent 系统都可以按这份清单检查请求前估算一下 prompt 大概多少 Token。每次请求后记录prompt_tokens、completion_tokens、total_tokens。单个请求设置合理的max_tokens不要默认给满。Agent 循环设置最大步数避免死循环。历史对话使用滑动窗口只保留最近 N 轮。长文档先摘要再进入上下文。记忆检索使用向量召回只取 Top-K不插入全量。工具返回结果限制字符数比如 2000 字符以内。为每次任务设置 Token 预算超限后熔断。模型版本和提示词版本都要固定方便回归和回滚。定期重新统计所有 Agent 的平均 Token 成本找出异常高消耗的环节。这份清单可以贴在开发文档里每次接入新的模型调用时逐条检查。5.4 下一步AI Agent、AI 编程与工程红线从郁金香到 Token技术狂热会过去工程能力会留下。如果你已经通过一个 AI 小镇项目跑通了 Agent 主循环下一步可以扩展几个方向加入记忆检索用向量数据库保存历史按语义相似度召回相关记忆。加入工具调用让 Agent 可以读取时间、查看地图、与其他 Agent 通信。加入持久化把 Agent 状态保存到数据库重启后继续模拟。接入更稳定的 Java 工程了解 Spring AI 这类框架统一模型调用和提示词管理。AI Agent 开发很容易陷入“只堆 Prompt”的误区。真正稳定的 Agent 平台需要权限控制、沙箱隔离、操作审计和异常恢复。AI 编程工具也是一样它本质上是把代码上下文压缩成 Token 交给模型。所以工程上要控制仓库大小用.gitignore排除 node_modules、构建产物和大文件避免每次补全都发送大量无关代码。最后保留一条工程红线模型输出永远需要校验。无论是生成代码、生成 JSON还是生成动作指令都要假设结果可能是错的。校验通过再执行校验失败就走兜底分支。这样 AI 应用才不是一个“碰运气”的程序而是一个可以长期维护的系统。