ARTICLE DETAIL

建站实战干货

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

Agent 小知识:用 TaoToken 统一 Key 把动态 Prompt 做成系统组件

2026/9/27 22:30:21 拓冰建站 浏览量
Agent 小知识:用 TaoToken 统一 Key 把动态 Prompt 做成系统组件 1. 为什么 Agent 里的 Prompt 不能是一段写死的字符串很多人第一次接触 Prompt都是从一段固定文本开始的你是一名资深工程师请分析下面这段代码找出问题并修复要求保持功能不变、补充测试、给出说明。这种写法在单轮问答里非常好用因为任务边界清晰模型看完就能给答案。但一旦把模型放进 Agent 的执行循环这套思路立刻失效。一个 Coding Agent 要修一个 Bug得先理解项目结构、读相关代码、调搜索工具定位问题、改多个文件、跑测试再根据失败结果调整方向。每一轮模型面对的问题都不一样最初那段固定 Prompt 根本覆盖不了整段任务。Agent 真正依赖的是一份随着任务推进不断重新组装出来的上下文。这就是动态 Prompt 拼装要解决的问题。而拼装出来的东西本质上已经不是一段文字而是一个有明确输入、可单独维护、可被测试的系统组件。这篇就聚焦一件事怎么用 TaoToken 的统一 Key 和 API 通道做接入层把 Prompt 模板、上下文注入、运行时变量拼装封装成一个可复用的系统组件。我会给出 config.toml 和 settings.json 的骨架配置、组件目录结构并完整演示一次从上下文注入到请求发出的验证动作。适合正在写 Agent、被上下文膨胀和 Prompt 维护折磨的开发者。2. 动态 Prompt 的四类信息与拼装顺序在动手写组件之前先把要拼的东西分清楚。一次 Agent 调用的输入通常不是用户某条单独消息而是按结构拼出来的上下文System Prompt 任务目标 当前状态 相关上下文 工具信息 历史结果 当前行动要求。这些信息的生命周期差别很大可以归成四类稳定信息贯穿整个任务包括 Agent 身份、行为规则、安全限制、输出格式一旦确定基本不变。任务信息跟一次任务绑定任务结束就消失比如“修复登录接口的 Token 过期问题”。状态信息随执行不断刷新定位阶段可能是“auth.py 里存在 Token 判断逻辑”改完代码后变成“已修改 auth.py测试用例失败”。外部信息是按需加载的临时内容某个文件内容、一次搜索结果、一段文档、一次工具返回用完就可以移出上下文腾空间。拼装的核心动作就是在每次模型调用前判断这四类信息里哪些该进当前输入哪些不该进。这里有个和成本直接相关的细节拼装顺序。现在主流推理服务大多支持 Prefix Cache相同开头前缀可以复用计算结果。稳定信息每步都不变把它们固定放在最前面让变化的状态和工具返回排在后面前缀就能稳定命中缓存省下延迟和费用。反过来如果每轮都改动开头缓存整段失效成本立刻上去。所以组件的拼装顺序建议固定为system → rules → tools → goal → state → context → history → action。前四段尽量稳定后四段随运行时变化。3. TaoToken 接入层统一 Key 与通道准备动态 Prompt 组件要真正发出请求需要一个稳定的接入层。我用 TaoToken 做这一层原因是它把 Key 和 API 通道统一了组件里不用关心底层换模型、换通道的细节只认一个 base_url 和一把 Key。先拿到访问凭证。打开控制台创建 API Key控制台地址https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite创建完 Key 之后接入文档在这里里面有各语言的调用示例和参数说明接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteAPI 的基础地址是https://taotoken.net/api注意这个地址不带任何查询参数直接作为 OpenAI 兼容的 base_url 使用即可。如果你用的是 Anthropic 风格的接口走 ClaudeCodeAnthropic 这条通道ClaudeCodeAnthropichttps://taotoken.net/ClaudeCodeAnthropic?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteKey 不要硬编码进代码统一走环境变量或配置文件。下面两节给出骨架。4. 组件目录结构与 config.toml 骨架先规划目录。把 Prompt 拆成可管理的模块运行时按当前任务组合这是落地时最省心的做法agent_prompt/ ├── config.toml # 组件级配置模型、通道、缓存策略 ├── settings.json # 运行时变量与模板映射 ├── templates/ │ ├── system.md # 稳定信息身份、规则、安全限制 │ ├── rules.md # 稳定信息行为约束 │ ├── tools.md # 稳定信息工具描述 │ ├── task_template.md # 任务信息目标模板 │ └── action.md # 当前行动要求 ├── builder.py # 拼装逻辑 └── tests/ └── test_builder.py # 组件单测config.toml负责组件级配置把接入层参数和拼装策略都放这里[provider] base_url https://taotoken.net/api api_key_env TAOTOKEN_API_KEY default_model claude-sonnet-4-5 timeout_seconds 60 [prompt] # 稳定前缀顺序固定保证 Prefix Cache 命中 stable_order [system, rules, tools] dynamic_order [goal, state, context, history, action] template_dir templates [context] max_context_tokens 6000 drop_policy fifo # 外部信息超出预算时按先进先出移除 keep_recent_turns 4 # 历史结果只保留最近 4 轮settings.json负责运行时变量与模板映射把“哪个变量填进哪个模板”这件事显式声明出来{ template_map: { system: system.md, rules: rules.md, tools: tools.md, goal: task_template.md, action: action.md }, runtime_vars: { agent_name: code-maintainer, project_type: python-web, output_format: markdown }, state_slots: [current_stage, found_files, last_error], context_slots: [retrieved_files, tool_results] }这两个文件的分工要清楚config.toml管的是组件怎么连、怎么拼、预算多少settings.json管的是这次运行填什么值、哪些槽位是状态、哪些是外部信息。改行为改 config改数据改 settings互不污染。5. builder.py把模板渲染成一次请求拼装逻辑本质是一次模板渲染。下面这个builder.py是可直接跑的最小实现核心是把稳定段和动态段分开处理稳定段拼成前缀动态段按预算裁剪import json import os import tomllib from pathlib import Path from openai import OpenAI BASE Path(__file__).parent def load_config(): with open(BASE / config.toml, rb) as f: return tomllib.load(f) def load_settings(): with open(BASE / settings.json, r, encodingutf-8) as f: return json.load(f) def render_template(name: str, variables: dict) - str: path BASE / templates / name text path.read_text(encodingutf-8) for key, value in variables.items(): text text.replace({{ key }}, str(value)) return text def build_prompt(state: dict, context: dict, history: list) - str: cfg load_config() settings load_settings() tmap settings[template_map] variables {**settings[runtime_vars], **state} segments [] # 稳定前缀顺序固定保证 Prefix Cache 命中 for key in cfg[prompt][stable_order]: segments.append(render_template(tmap[key], variables)) # 任务目标 segments.append(render_template(tmap[goal], variables)) # 状态信息 state_block \n.join( f- {slot}: {state.get(slot, N/A)} for slot in settings[state_slots] ) segments.append(## 当前状态\n state_block) # 外部信息按预算裁剪 budget cfg[context][max_context_tokens] keep_turns cfg[context][keep_recent_turns] ctx_lines [] for slot in settings[context_slots]: for item in context.get(slot, []): ctx_lines.append(f[{slot}] {item}) trimmed ctx_lines[-budget // 50:] if len(ctx_lines) budget // 50 else ctx_lines segments.append(## 相关上下文\n \n.join(trimmed)) # 历史结果只保留最近 N 轮 recent history[-keep_turns:] hist_block \n.join(f{h[role]}: {h[content]} for h in recent) segments.append(## 历史结果\n hist_block) # 当前行动要求 segments.append(render_template(tmap[action], variables)) return \n\n.join(segments) def call_model(prompt: str) - str: cfg load_config() client OpenAI( base_urlcfg[provider][base_url], api_keyos.environ[cfg[provider][api_key_env]], timeoutcfg[provider][timeout_seconds], ) resp client.chat.completions.create( modelcfg[provider][default_model], messages[{role: user, content: prompt}], ) return resp.choices[0].message.content几个关键点值得说明。稳定段用stable_order循环渲染顺序写死在 config 里任何人改模板都不会打乱前缀。外部信息用budget // 50做粗略的条数上限真实项目里可以换成 tokenizer 精确计数。历史结果只取最近keep_recent_turns轮这就是“做减法”的落点。templates/system.md里放稳定信息比如你是 {{agent_name}}负责维护 {{project_type}} 项目。 修改代码前必须先理解现有结构。 完成修改后必须运行测试。 输出格式统一为 {{output_format}}。templates/action.md放当前行动要求## 当前行动 基于以上状态和上下文给出下一步的具体操作。 如果需要更多信息明确说明需要哪个文件或哪次工具调用。6. 验证一次上下文注入到请求发出配置和代码就位后跑一次完整验证。先设置环境变量export TAOTOKEN_API_KEY你的Key然后写一个验证脚本模拟 Agent 第二轮调用的状态from builder import build_prompt, call_model state { current_stage: locate, found_files: auth/login.py, last_error: expired token treated as valid, } context { retrieved_files: [ auth/login.py: validate_token() 位于第 42 行, auth/login.py: 过期判断使用 而非 , ], tool_results: [ grep validate_token - auth/login.py:42, ], } history [ {role: user, content: 修复登录接口 Token 过期问题}, {role: assistant, content: 已定位到 auth/login.py}, ] prompt build_prompt(state, context, history) print( 拼装后的 Prompt ) print(prompt) print(\n 模型返回 ) print(call_model(prompt))运行后你会看到拼装结果的结构最前面是 system、rules、tools 三段稳定前缀接着是任务目标、当前状态、相关上下文、历史结果、当前行动。模型返回会针对validate_token()的边界判断给出具体修改建议。验证成功的标志有三个一是拼装结果里稳定段顺序和 config 里stable_order完全一致二是外部信息只保留了预算内的条目没有把全部历史塞进去三是请求正常返回说明 TaoToken 的 base_url 和 Key 配置正确。如果你想先确认模型通道本身是通的可以到模型对话页面直接发一条消息试试模型对话https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite7. 本篇常见错排查报 401 或鉴权失败。先确认TAOTOKEN_API_KEY环境变量在当前 shell 里真的存在echo $TAOTOKEN_API_KEY看一下。常见坑是 Key 写进了.env但脚本没加载或者 Key 前后带了空格。另外确认 base_url 是https://taotoken.net/api不要手动拼多余的路径。模板变量没被替换。检查settings.json里runtime_vars的键名和模板里{{ }}里的名字是否完全一致大小写敏感。state里的槽位如果没在state_slots声明也不会进状态块。上下文还是太长。max_context_tokens只是条数上限的粗略换算真实项目建议接入 tokenizer 精确计数。另外检查keep_recent_turns是不是设得太大历史结果是最容易膨胀的部分。Prefix Cache 没命中费用偏高。八成是稳定段顺序被打乱了或者有人在 system 模板里塞了运行时变量。稳定段里只放长期不变的内容任何随任务变化的值都应该挪到动态段。组件单测怎么写。tests/test_builder.py里断言拼装结果的段落顺序和 config 一致即可不需要真的调模型。把call_model和build_prompt分开就是为了让拼装逻辑可以脱离网络单独测试。8. 把接入层和组件层分开长期编码更省心动态 Prompt 做成系统组件之后你会发现维护成本大幅下降改行为改模板改预算改 config改数据改 settings三层互不干扰。而接入层用 TaoToken 统一 Key 和通道组件里只认一个 base_url换模型、调通道都不用动拼装逻辑。如果你在写长期运行的 Coding Agent或者需要多轮工具调用的场景建议把接入凭证和额度规划单独管理Coding Plan 适合这种持续编码的用法Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteKey 的创建和管理都在控制台完成接入细节查文档就够API Keyshttps://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite最后留一个我踩过的坑一开始我把state和context混在一个字典里传结果状态槽位被当成外部信息按 FIFO 裁掉了模型第二轮就丢了当前阶段。后来严格按state_slots和context_slots分开声明裁剪只作用于外部信息状态永远保留问题就没了。组件化的价值就在这——出问题的时候你知道该去哪个文件里找。