拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

深入学Agent Harness工程(10):System Prompt运行时组装——用TaoToken统一Key打通配置链路

深入学Agent Harness工程(10):System Prompt运行时组装——用TaoToken统一Key打通配置链路 1. 为什么固定 System Prompt 在 Agent Harness 里会失真Agent Harness 跑到第 10 篇这个阶段很多人会撞上同一个坑Memory 已经接进调用链了工具注册表也动态增长了可 System Prompt 还是开局写死的那一大段字符串。结果就是模型看到的“自我描述”和 Harness 真实状态对不上——注册表里明明有read_file、edit_filePrompt 里还写着“你只有 Bash”工作目录早就切到子项目了system 消息里还挂着旧路径.memory/MEMORY.md已经写满跨会话笔记Prompt 却对记忆只字不提。这不是 Prompt 写得太短而是它没有由真实运行状态生成。System Prompt 运行时组装Runtime Assembly要解决的核心问题就是把“规则来源”和“最终发给模型的输入”之间架一个组装函数状态归状态文本归文本缓存归缓存三层职责分开。这篇就按这个思路给出可复制的settings.json与config.toml骨架演示用 TaoToken 统一 Key 打通 Cline 与 CC Switch 的配置链路并附上组装结果校验和报错排查动作。适合谁看已经在写 Agent Loop、想让 Prompt 随工具/目录/记忆状态自动更新的开发者以及用 Cline 做长任务、想统一管理多工具 API 通道的人。核心检索词就三个——Agent Harness、System Prompt、运行时组装。先明确一个容易混的点。OpenAI Agents SDK 的 Context Management 把 context 分成两类本地代码可见的运行 context用户 ID、依赖对象、工具状态和模型可见的 LLM context真正进入对话历史、instructions、工具结果的信息。本地字典里有workspace或memories不代表模型自动知道。Harness 必须主动选择字段、转成文本、放进请求。反过来数据库连接、密钥、权限令牌这些只该留在执行侧绝不能因为“顺手序列化”就漏进 system 消息。所以组装流程拆成三个函数最清晰update_context()读真实状态assemble_system_prompt()选择并排列 sectionget_system_prompt()按稳定键复用或重建。前一步只提供结构化事实后一步才决定哪些内容进入模型。三者不能混——update_context()不写自然语言方便单测状态assemble_system_prompt()不碰文件系统保证相同输入相同输出get_system_prompt()只决定是否复用不能偷偷改 section 内容。2. TaoToken 前置统一 Key 与 API 通道在把组装逻辑接进真实调用之前得先有一条稳定的 API 通道。多工具协作场景里最烦的就是每个客户端配一套 Key、一套 Base URL改一次要动好几个文件。TaoToken 在这里的作用就是提供统一的 Key 和 OpenAI-compatible 通道让 Cline、CC Switch 以及你自己的 Harness 脚本共用同一套凭证。先拿 Key。打开控制台创建 API Keyhttps://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentconsole创建后在 API Keys 页面复制注意它只完整显示一次https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentapi-keys接入文档在这里字段和错误码都以它为准https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentdocAPI 基地址统一用https://taotoken.net/api注意这个地址不加 UTM 参数直接作为base_url使用。Key 建议放环境变量别硬编码进仓库export TAOTOKEN_API_KEYsk-你的key验证通道是否通先用一条最小请求探路curl https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d { model: claude-sonnet-4-20250514, messages: [{role: user, content: ping}], max_tokens: 16 }返回里能看到choices[0].message.content就说明通道正常。这一步别跳过——后面组装报错时你得先能区分是 Prompt 组装的问题还是通道的问题。3. 可复制配置settings.json 与 config.toml 骨架3.1 Cline 的 settings.jsonCline 走 OpenAI-compatible 配置把 Base URL 指向 TaoTokenKey 用环境变量引用。下面这份骨架可以直接改{ cline.apiProvider: openai, cline.openAiBaseUrl: https://taotoken.net/api, cline.openAiApiKey: ${env:TAOTOKEN_API_KEY}, cline.openAiModelId: claude-sonnet-4-20250514, cline.customInstructions: You are a coding agent. Follow the runtime system prompt assembled by the harness., cline.enableSystemPromptOverride: false }关键点enableSystemPromptOverride设为false让 Cline 不要用自己那套固定 system 覆盖你 Harness 组装出来的结果。customInstructions只放稳定基线动态部分交给运行时组装。3.2 CC Switch 的 config.tomlCC Switch 用来在多个模型/通道之间切换配置写成 TOMLdefault_profile taotoken [profiles.taotoken] base_url https://taotoken.net/api api_key_env TAOTOKEN_API_KEY model claude-sonnet-4-20250514 max_tokens 8000 [profiles.taotoken.headers] X-Client agent-harness [prompt] # 稳定 section 由 harness 组装这里只声明来源 assembly runtime cache_scope processassembly runtime是个约定标记告诉你的 Harnesssystem 消息不从这里读而是走get_system_prompt()。cache_scope process对应进程内缓存别误当成跨进程共享。3.3 组装函数骨架把 section 拆成稳定名称避免混成一个无法定位的大字符串PROMPT_SECTIONS { identity: You are a coding agent operating inside a sandboxed workspace., tools: , # 由注册表生成 workspace: , # 由授权 workspace 对象生成 } def assemble_system_prompt(context: dict) - str: sections [ PROMPT_SECTIONS[identity], build_tools_section(context[enabled_tools]), build_workspace_section(context[workspace]), ] memories context.get(memories, ) if memories: sections.append(fRelevant memories:\n{memories}) return \n\n.join(sections)注意build_tools_section从真实注册表生成而不是手写三个工具名。手写就是双写注册表一改文字就漂移。3.4 状态读取与缓存键import json from pathlib import Path MEMORY_INDEX Path(.memory/MEMORY.md) _last_context_key None _last_prompt None def update_context(context: dict, messages: list) - dict: memories if MEMORY_INDEX.exists(): content MEMORY_INDEX.read_text().strip() if content: memories content return { enabled_tools: list(TOOL_HANDLERS.keys()), workspace: str(WORKDIR), memories: memories, } def get_system_prompt(context: dict) - str: global _last_context_key, _last_prompt key json.dumps(context, sort_keysTrue, ensure_asciiFalse, defaultstr) if key _last_context_key and _last_prompt: return _last_prompt _last_context_key key _last_prompt assemble_system_prompt(context) return _last_promptsort_keysTrue让字典键顺序稳定避免内容相同却因插入顺序不同产生不同 key。别用hash()它受进程随机化影响还处理不了可变字典。4. 验证请求与成功结果组装完必须验证不能只看字符串 diff。分三步。第一步验证 section 是否被真实状态消费。故意在.memory/MEMORY.md写一行然后跑ctx update_context({}, []) print(ctx[memories]) # 应输出记忆正文 print(get_system_prompt(ctx)) # 应包含 Relevant memories如果memories有值但 Prompt 里没有说明组装函数没消费这个字段——这正是教学代码里常见的“缓存失效但内容没变”问题。第二步验证缓存键变化。删掉记忆文件再跑一次观察 key 是否改变、Prompt 是否回退到无记忆版本ctx2 update_context({}, []) assert ctx2[memories] assert Relevant memories not in get_system_prompt(ctx2)第三步验证最终协议边界。组装结果只有装进rolesystem才成为模型输入def chat_completion(model, system, messages, tools, max_tokens): request_messages list(messages) if system: request_messages [ {role: system, content: system}, *request_messages, ] return client.chat.completions.create( modelmodel, messagesrequest_messages, toolstools, max_tokensmax_tokens, ).choices[0]跑通后你会看到工具执行创建了非空.memory/MEMORY.md→ 下一轮update_context()读到内容 → context key 改变 → 缓存失效并加入 memory section → 下一次请求携带新的 system 消息。这条链路能证明触发位置和数据流但不能证明模型一定遵守新 section——那要靠评估不是靠静态推演。想直接看模型对组装结果的反应可以用模型对话页面手动发一条带 system 的请求对比https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentchat5. 本篇常见错排查报错一KeyError: enabled_tools。组装函数直接读了 context 里不存在的键。检查update_context()是否在每次工具回填后都被调用别只在启动时调一次。报错二Prompt 每轮都变缓存从不命中。大概率是 key 里混进了时间戳、随机 ID 或完整消息历史。缓存键只能包含稳定、可序列化、允许参与判断的字段。把messages整个塞进 key 是最常见的污染源。报错三工具描述和注册表不一致。模型调用不存在的能力或不知道新工具。根因是toolssection 手写而非从TOOL_HANDLERS生成。改成build_tools_section(list(TOOL_HANDLERS.keys()))。报错四401/403。先确认base_url是https://taotoken.net/apiKey 从环境变量正确读取。用第 2 节的 curl 单独验证通道排除是组装问题还是凭证问题。报错五敏感状态泄漏。update_context()把本地对象全序列化了密钥、内部路径进了 system 消息。记住这是“授权投影”不是普通序列化先判断字段是否与任务相关、是否允许发往模型服务、是否需要脱敏再决定表达方式。报错六规则冲突。identity 说“直接执行”Memory 说“修改先确认”当前任务又授权修改。没有优先级和作用域模型只能猜。给 section 建立稳定 ID、版本、来源和优先级冲突时按确定顺序裁决。排查顺序建议先 curl 验通道 → 再单测update_context()→ 再验assemble_system_prompt()输出 → 最后看缓存 key。一层层隔离别一上来就怀疑模型。6. 长期编码与 Agent 场景的接入建议如果你要把这套组装逻辑用在长期编码或 Agent 任务上建议走 Coding Plan把多工具、多轮次的调用统一到一条通道上管理省得每个客户端单独配 Keyhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcoding-planClaude Code / Anthropic 兼容接入的说明在这里https://taotoken.net/doc/claudecode-anthropic?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentclaudecode-anthropic生产化时缓存作用域别只用两个全局变量。把 tenant、user、project、agent、环境和配置版本纳入缓存命名空间同时避免把密钥或高频噪声放进 key。命中率不是唯一目标错误复用比重新组装一次更危险。Prompt、Permission 和工具 Schema 各管一段Prompt 影响模型怎么判断调用意图Schema 限制参数结构Permission 在执行前决定是否放行handler 和沙箱决定副作用实际能做到什么。动态 Prompt 再准也替代不了执行侧的硬校验。
返回列表