12-Factor Agents 附录(Factor 13):预取上下文(Pre-Fetch)——把模型必然用到的数据用确定性代码提前取回
发布时间:2026/9/30 6:45:14 锦皓数字建站
:预取上下文(Pre-Fetch)——把模型必然用到的数据用确定性代码提前取回`)
文档教程人工智能大模型AI Agent【免费下载链接】12-factor-agentsWhat are the principles we can use to build LLM-powered software that is actually good enough to put in the hands of production customers?项目地址https://gitcode.com/GitHub_Trending/12/12-factor-agents点击查看免费下载导读在构建生产级 LLM Agent 时与其让模型在循环里“想起来去调用某个工具、再等待结果返回”不如提前判断哪些数据大概率会被用到直接用确定性代码把结果塞进上下文窗口。本文以 12-Factor Agents 项目的附录 appendix-13-pre-fetch.md 为主体讲解「预取上下文」这条因子的完整思想、正反两个代码改造路径并结合仓库内的模板 Agent 实现agent.ts给出可落地的工程对照。读完你将掌握如何减少模型往返token round trips、如何把「决定取什么」从模型手里转移到确定性代码手里以及如何把预取结果干净地接入你自己的上下文窗口。一句话记住这条因子如果模型调用工具 X 的概率很高就不要浪费往返次数去“告诉模型去取”而是直接、确定性地把 X 的结果预取出来放进上下文如果你已经知道希望模型调用哪些工具就直接用确定性代码调用它们让模型只负责其中最困难的部分——想清楚如何使用这些工具的输出来推进任务。这一条在项目 README.md 的 Honorable Mentions 中被称为 “Factor 13: Pre-fetch all the context you might need”它并非 12 个主因子之一而是对 Factor 3Own Your Context Window 的深化——AI 工程本质上就是上下文工程Context Engineering预取正是把“模型该看什么”这件事前置到你的代码里。反模式让模型在循环里决定“该去取数据了”设想一个部署 Agent用户在 Slack 里发来“把最新 backend 部署到生产”Agent 需要先拿到已发布的 git tags才能选出要部署的 tag。最常见的做法是在提示词里“提醒”模型去取 tags然后在 Agent 循环里为list_git_tags这个 intent 单独写一个分支。伪提示词长这样When looking at deployments, you will likely want to fetch the list of published git tags, so you can use it to deploy to prod. Heres what happened so far: {{ thread.events }} Whats the next step? Answer in JSON format with one of the following intents: { intent: deploy_backend_to_prod, tag: string } OR { intent: list_git_tags } OR { intent: done_for_now, message: string }对应的循环代码thread {events: [initial_message]} next_step await determine_next_step(thread) while True: switch next_step.intent: case list_git_tags: tags await fetch_git_tags() thread[events].append({ type: list_git_tags, data: tags, }) case deploy_backend_to_prod: deploy_result await deploy_backend_to_prod(next_step.data.tag) thread[events].append({ type: deploy_backend_to_prod, data: deploy_result, }) case done_for_now: await notify_human(next_step.message) break # ...这套写法的问题在哪里注意它的代价链条至少多一次模型往返模型先输出list_git_tags这个 intent确定性代码执行fetch_git_tags()结果回填上下文后还要再把整个 thread 重新发给模型模型才能继续输出真正的部署意图提示词要花 token 去“教育”模型取数据上面伪提示词第一行就是在消耗上下文长度仅仅为了让模型记住“应该先取 tags”模型多了一个可能出错的选择分支list_git_tags是一个模型可调用、可忘记、可调错、可反复调用的分支而它对任务推进本身毫无“智力”贡献——tags 反正总要取取回来之后怎么用才是模型要思考的事。这与仓库 README.md 中描述的 Agent 循环模型一致LLM 决定下一步 → 确定性代码执行工具调用 → 结果追加进上下文 → 直到模型判定 done。把“取 tags”也放进这个循环等于把一件 100% 会发生、零决策价值的事交给了模型去决策白白增加一次往返。方案一预取并把结果直接注入提示模板既然部署前“必定”要看 tags那就别让模型去取——在进入循环之前就把它取好作为模板变量注入- When looking at deployments, you will likely want to fetch the list of published git tags, - so you can use it to deploy to prod. The current git tags are: {{ git_tags }} Heres what happened so far: {{ thread.events }} Whats the next step? Answer in JSON format with one of the following intents: { intent: deploy_backend_to_prod, tag: string - } OR { - intent: list_git_tags } OR { intent: done_for_now, message: string }代码上把fetch_git_tags()挪到循环之前并作为参数传给determine_next_stepthread {events: [initial_message]} git_tags await fetch_git_tags() - next_step await determine_next_step(thread) next_step await determine_next_step(thread, git_tags) while True: switch next_step.intent: - case list_git_tags: - tags await fetch_git_tags() - thread[events].append({ - type: list_git_tags, - data: tags, - }) case deploy_backend_to_prod: deploy_result await deploy_backend_to_prod(next_step.data.tag) thread[events].append({ type: deploy_backend_to_prod, data: deploy_result, }) case done_for_now: await notify_human(next_step.message) break # ...这个改造的收益删掉了一个 intent模型输出空间里不再存在list_git_tags少一个分支就少一类错误忘记调、调了失败、反复调、误调提示词更短更聚焦把“你应该去取 tags”的指令替换成“tags 是这些”的事实陈述上下文只承载信息本身省一次往返模型第一次输出就是deploy_backend_to_prod或者它认为信息不足、要求澄清不需要先取一轮 tags 再回头部署。方案二把预取结果放进线程事件流参数都不必单独传更彻底的做法是连“单独传参”都省掉把预取过程本身建模成 thread 里的两条事件——一条“请求”事件、一条“结果”事件——然后继续让determine_next_step(thread)保持单参数签名thread {events: [initial_message]} # add the request thread[events].append({ type: list_git_tags, }) git_tags await fetch_git_tags() # add the result thread[events].append({ type: list_git_tags_result, data: git_tags, }) - next_step await determine_next_step(thread, git_tags) next_step await determine_next_step(thread) while True: switch next_step.intent: case deploy_backend_to_prod: deploy_result await deploy_backend_to_prod(next_step.data.tag) thread[events].append(deploy_result) case done_for_now: await notify_human(next_step.message) break # ...这一版的优雅之处在于请求与结果都作为事件进入 thread模型看到的上下文从“发生了什么list_git_tags事件 其结果”到“下一步是什么”是一条自然的事件史event history。你不再需要在提示模板里为 tags 单独开槽位prompt 模板保持干净凡是“在进入循环前就能确定需要”的数据都可以用同样的request_event result_event模式预先塞进 thread。这正好呼应 Factor 12Make your agent a stateless reducerAgent 的全部状态收敛为一个事件列表thread任何中间数据包括预取结果都以事件形式追加LLM 只是对这个 reducer 状态的“下一步”做决策。也呼应 Factor 8Own Your Control Flow 中fetch_open_issues的同步分支写法——把工具调用结果 append 进 thread 后continue让模型继续决策预取只是把这个动作从循环内提到了循环前。为什么这样做更好从 token 与注意力效率说起Factor 3Own Your Context Window 明确提出要在 token 与注意力都高效的前提下把上下文以最适合模型理解的结构喂给它。“预取”正是这条原则的落地技巧之一减少模型往返一次往返 一次完整请求的延迟与成本。把“取 tags”这种必然动作移出循环Agent 从“两步走完取 tags → 部署”变成“一步到位”让提示词承载事实而非指令“当前 tags 是 v1.2.3 / v1.2.2 / …” 比 “你最好去取一下 tags” 更有信息密度——模型拿到的是可直接决策的数据而不是一个待办提醒把决策留给模型、把执行留给代码预取不增加模型的“自由度”反而削减了无意义自由度。模型只需要在“用 v1.2.3 部署 backend / 询问更多信息 / 结束”之间选择注意力更集中上下文窗口是有限的为模型省掉“教育它取数据”的段落和中间的往返噪声等于把注意力预算留给真正重要的内容失败模式更可控fetch_git_tags()由确定性代码执行它的错误可以用 try/catch、重试、超时等常规工程手段处理而不是依赖模型“想起来重试”。这一点也可以与 Factor 4Tools are just structured outputs 对照工具调用本质是“模型输出结构化 JSON 确定性代码执行”。预取并没有改变这个本质它只是把一部分工具的“执行时机”从“模型在循环里点名”提前到“代码在进入循环前主动完成”。仓库中的工程化落地模板 Agent 里是怎么做的12-Factor Agents 仓库在 packages/create-12-factor-agent/template/src/agent.ts 中给出了一个最小可运行的计算器 Agent 模板其中几段实现恰好就是上述两种模式的具体形态线程即事件列表序列化为模型可见文本agent.ts#L8-L32export class Thread { events: Event[] []; serializeForLLM() { return this.events.map(e this.serializeOneEvent(e)).join(\n); } serializeOneEvent(e: Event) { return this.trimLeadingWhitespace( ${e.data?.intent || e.type} ${ typeof e.data ! object ? e.data : Object.keys(e.data).filter(k k ! intent).map(k ${k}: ${e.data[k]}).join(\n)} /${e.data?.intent || e.type} ) } }注意serializeOneEvent的输出格式intent...data.../intent——这与 Factor 3 中推荐的“自定义 XML 风格上下文格式”完全一致。预取结果例如list_git_tags_result事件只要进了thread.events就会自动以同样格式出现在模型的上下文里提示模板无需任何改动。循环LLM 决定 → 确定性代码执行 → 事件回填agent.ts#L89-L114export async function agentLoop(thread: Thread): PromiseThread { while (true) { const nextStep await b.DetermineNextStep(thread.serializeForLLM()); thread.events.push({ type: tool_call, data: nextStep }); switch (nextStep.intent) { case done_for_now: case request_more_information: case request_approval_from_manager: return thread; case divide: return thread; case add: case subtract: case multiply: thread await handleNextStep(nextStep, thread); } } }确定性执行工具并把结果 push 回 threadagent.ts#L51-L87export async function handleNextStep(nextStep: CalculatorTool, thread: Thread): PromiseThread { let result: number; switch (nextStep.intent) { case add: result nextStep.a nextStep.b; thread.events.push({ type: tool_response, data: result }); return thread; // subtract / multiply / divide 同理 } }对照附录的预取改造如果这个模板 Agent 想要“预取”完全可以在agentLoop之前先执行某次确定性调用例如预取一个tool_response事件或一条list_git_tags_result事件再进入循环serializeForLLM()与handleNextStep的“执行 → 事件回填”结构不需要任何改动。此外state.ts#L15-L48 中的FileSystemThreadStore会把 thread 同时以 JSON 和serializeForLLM()的文本形式持久化预取进来的事件自然也随之落盘保证中断恢复Factor 6 的 launch/pause/resume后预取数据不丢失。什么时候不适合预取“预取”不是银弹附录并没有给出适用范围但结合上下文工程的常识可以划出几条边界以项目已有原则为据见 Factor 3 的“flexibility to try EVERYTHING”精神数据体积大、且只有小概率被用把海量数据无条件塞进上下文会稀释注意力、挤占窗口预算。预取的前提是“高概率用到”数据有副作用或成本高fetch_git_tags这类只读调用适合预取但像“创建支付链接”“发起训练任务”这类有副作用、且未必会执行的操作预取等于提前执行必须交给模型决策或加入人工审批参考 Factor 7Contact Humans with Tools 与 Factor 8 的create_issue审批分支数据时效性要求高预取发生在进入循环之前如果数据在任务进行中可能过期如实时状态、竞态资源预取的旧数据反而会误导模型这时更该用 Factor 8 的同步分支在循环内按需取。判断的尺子始终是那句总原则能不能确定性地算出“模型必然需要什么”能就预取不能就留给模型在循环里按需取。小结Factor 13 的核心主张可以浓缩为一句话这也是 appendix-13-pre-fetch.md 的原文结论如果你已经知道希望模型调用哪些工具就直接用确定性代码调用它们DETERMINISTICALLY让模型去做最困难的部分——想清楚如何使用这些工具的输出来推进任务。实现上有两种逐步递进的做法做法动作效果方案一循环前fetch_git_tags()结果作为模板变量注入提示词删掉list_git_tagsintent省一次往返提示词更短方案二以list_git_tags请求事件 list_git_tags_result结果事件的形式把预取结果写进 thread提示模板完全不动事件史自然包含预取数据与 stateless reducerFactor 12无缝衔接它与项目其余因子的关系清晰它是 Factor 3 上下文工程 的具体技巧它以 Factor 4 工具即结构化输出 为底层模型它与 Factor 8 控制流 的同步分支互为补充它与 Factor 12 无状态 reducer 的事件流天然契合。在 create-12-factor-agent 模板 的Thread.serializeForLLM()与agentLoop()中你能看到这套思想的最小可运行形态——把“必然要用的数据”在进入循环前确定性取回剩下的就交给模型去思考如何使用它们。赞分享文档教程人工智能大模型AI Agent【免费下载链接】12-factor-agentsWhat are the principles we can use to build LLM-powered software that is actually good enough to put in the hands of production customers?项目地址https://gitcode.com/GitHub_Trending/12/12-factor-agents点击查看免费下载相关推荐版本发布检查清单 v2.3.0版本发布检查清单 v2.3.0 前置条件 所有测试通过 性能基准测试完成 向后兼容性验证 文档更新完成 部署准备 环境配置验证 回滚方案测试 监控告警配置 发布文档教程人工智能大模型AI Agent智能合约函数调用追踪神器Surya ftrace命令高级用法智能合约函数调用追踪神器Surya ftrace命令高级用法 Surya是一套用于探索Solidity合约的实用工具集其中ftrace命令是智能合约开发者分12-Factor Agents架构模式可复用的设计模板12 Factor Agents架构模式可复用的设计模板 引言为什么需要新的AI应用构建范式 你是否曾经尝试构建一个生产级的LLM应用却在80%完成度时文档教程人工智能大模型AI Agent上一篇react-slingshot 缓存策略Service Worker 与 HTTP 缓存配置下一篇如何使用AuroraDNS.GUI新手入门的完整配置指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。