ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

用 agents/context 组装 Agent 系统提示词:Blocks、Providers 与冻结提示词实战指南

用 agents/context 组装 Agent 系统提示词:Blocks、Providers 与冻结提示词实战指南 用 agents/context 组装 Agent 系统提示词Blocks、Providers 与冻结提示词实战指南【免费下载链接】agentsBuild and deploy AI Agents on Cloudflare项目地址: https://gitcode.com/GitHub_Trending/agents1/agentsagents/context是 Cloudflare Agents 框架本仓库agents1/agents中负责系统提示词组装的模块它把一段段带标签label的提示词文本组织成 Block每个 Block 背后挂一个存储 Provider由 Provider 的能力决定该 Block 是只读、可写还是可全文检索并自动为模型装配对应的set_context/search_context工具。本文以官方文档 docs/agents/context.md 为主体结合 源码实现 与 测试用例 深入讲解其运行原理与落地姿势读完你可以在自己的 Agent 中组合出带持久化记忆、可检索知识库且能保持前缀缓存命中的系统提示词。实验性 API 提示与agents/context同级的agents/sessions一样整个agents/context导出面在 API 稳定前可能随版本变化生产接入时请锁定版本。Context 是什么提示词组装不是会话存储agents/context的核心定位是一句话Context is prompt assembly. It is not conversation storage.它只负责把各 Block 渲染成系统提示词不负责消息历史的持久化——消息树、流式读取、压缩与附件卸载由agents/sessions负责。两者是组合而非嵌套的关系Context 与 Session 句柄组合使用而不是寄生在 Session 内部。因此一个 Agent 可以有提示词而没有对话记录也可以有对话记录而没有提示词。这种正交设计在源码注释中被反复强调见 packages/agents/src/context/index.ts。从工程视角看Context 要解决三个现实问题提示词的分层管理把人格设定soul用户记忆memory知识库knowledge等不同性质的提示片段分开声明、分开存储、分开计 token持久化writable 的 Block 能落盘到 Durable Object 的 SQLite跨会话存活前缀缓存保持把渲染好的系统提示词冻结为稳定字符串让模型 Provider 的 prefix cache 跨轮次保持命中避免每次重新渲染出细微差异导致缓存失效。Blocks提示词的最小组织单元基础用法Block 的声明方式非常直接。一个 Block 由label标签、可选的description描述、可选的maxTokenstoken 上限以及provider存储后端组成import { ContextBlocks } from agents/context; const context new ContextBlocks([ { label: soul, provider: { get: async () You are a helpful assistant. } }, { label: memory, description: Facts learned about the user, maxTokens: 1_100, provider: memoryProvider } ]); const system await context.freezeSystemPrompt(); const tools await context.tools();每个 Block 渲染为系统提示词中的一个带标签小节section。渲染逻辑见 blocks.ts 的 renderPrompt()标题头header由以下部分组成标签label的toUpperCase()大写形式描述若有description以(描述)形式拼接在标签后token 占用百分比设置了maxTokens时显示[45% — 495/1100 tokens]这样的占比能力标记[readonly]、[writable]、[loadable]或[searchable]四选一。分隔线由 46 个═字符组成渲染结果形如══════════════════════════════════════════════════ MEMORY (Facts learned about the user) [45% — 495/1100 tokens] [writable] ══════════════════════════════════════════════════ 用户偏好 TypeScript正在使用 Cloudflare Agents空 Block 的渲染规则重要渲染时有一个容易被忽视但很关键的规则空的只读 Block 会被跳过而 writable、searchable 的 Block 即使为空也一定渲染。理由很实际——空的只读 Block 不携带任何信息渲染出来纯属浪费 token而可写/可检索的 Block 必须让模型知道这里有工具可以操作它否则模型不会主动去调用set_context或search_context。该逻辑体现在 renderPrompt() 的跳过条件。maxTokens 的强制语义maxTokens不只是渲染时的展示信息它会在写入时被强制校验。在 setBlock() 中写入内容经estimateStringTokens估算 token 数后若超过maxTokens会直接抛出Block memory exceeds maxTokens: 1200 1100这样的错误写入被拒绝。测试 context.test.ts 同时验证了只读块写入被拒readonly错误与 token 超限被拒两个场景。Providers能力驱动行为的存储后端Provider 是 Block 的行为决定者。agents/context对 Provider 的识别是结构化检查structuralduck-typing不是名义类型检查——即不看类型继承关系只检查对象上有没有对应的方法。官方文档给出的能力矩阵如下Provider shapeBlock behaviorget()Read-only text in the promptget()set()Writable through theset_contexttoolget()search(key)Summary in the prompt,search_contexttoolget()返回 Block 当前内容没有内容时返回null可选方法init(label)会在首次使用前被调用并传入 Block 的 label因此一个 Provider 类可以同时服务于多个 label这正是AgentContextProvider复用同一存储类为soul、memory等多个 Block 提供存储的原理。源码中的接口定义在 blocks.ts 中两个核心接口如下export interface ContextProvider { get(): Promisestring | null; /** Called by the context system to provide the block label before first use. */ init?(label: string): void; } export interface WritableContextProvider extends ContextProvider { set(content: string): Promisevoid; }对应的类型守卫isWritableProvider检查set in provider且set是函数blocks.tsisSearchProvider检查search in providersearch.ts。所以只要你的对象结构上带这些方法框架就按对应能力对待它——这为自定义 Provider 提供了极大的自由度。ContextConfig 的完整字段从 ContextConfig 接口 可以看到一个 Block 配置的全部可选字段label必填Block 的键同时用于工具描述与持久化主键description?展示给 AI 的人类可读描述maxTokens?token 上限写入时强制校验provider?存储后端。省略时只要构造ContextBlocks时传入了defaultProvider工厂就会按 label 自动接线。defaultProvider按 label 提供持久化ContextBlocks构造函数签名是constructor( configs: ContextConfig[], promptStore?: WritableContextProvider, defaultProvider?: (label: string) ContextProvider )第二个参数promptStore用于持久化冻结提示词见下文第三个参数defaultProvider是一个(label) provider的工厂。声明时未带provider的 Block会在加载时通过 withDefaultProvider() 被自动接线到该工厂返回的实例上——这正是 host如Think仅凭 label 就能提供持久化可写 Block的机制来源。Durable SQLite Blocks落盘到 Durable Object 的持久化记忆AgentContextProvider把每个 Block 作为一行存储在 Durable Object 自己的 SQLite 数据库中表名为cf_agents_context_blocksimport { AgentContextProvider } from agents/context; const context new ContextBlocks([ { label: memory, provider: new AgentContextProvider(this, memory) } ]);底层表结构与写入语义构造函数接受任何带 tagged-templatesql方法的对象——Agent本身就具备所以可以直接传this。label参数可选省略时由init()从 Block 声明中补齐。从 sqlite-provider.ts 可以看到表结构与三个实现细节建表是惰性的ensureTable()在首次get()/set()时才执行CREATE TABLE IF NOT EXISTS而不是在构造时。测试 providers.test.ts 专门验证了首次 get 前表不存在、get 后表才出现的惰性建表行为一 label 一行label TEXT PRIMARY KEY同一个 label 的多次写入走ON CONFLICT(label) DO UPDATE的 upsert覆盖更新而非累积记录更新时间updated_at DATETIME DEFAULT CURRENT_TIMESTAMP在每次 upsert 时被刷新。CREATE TABLE IF NOT EXISTS cf_agents_context_blocks ( label TEXT PRIMARY KEY, content TEXT NOT NULL, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP )测试同时验证了不同 label 落在不同行、互不干扰providers.test.ts。setBlock 的写入路径ContextBlocks.setBlock(label, content)是框架侧统一入口blocks.ts其行为链条是检查 Block 存在且writable否则抛Block xxx is readonly检查是否 searchable——keyed provider 必须走setSearchEntry()直接setBlock会抛错估算 token 并强制maxTokens上限更新内存中的 Block并立即调用config.provider.set(content)落盘durable。另外还有appendToBlock(label, content)blocks.ts在已有内容后追加而非覆盖会自动在两个片段间补一个换行分隔。Searchable Blocks基于 FTS5 的知识库AgentSearchProvider用 Durable Object 的 FTS5 全文索引表支撑一个 Blockimport { AgentSearchProvider } from agents/context; const context new ContextBlocks([ { label: knowledge, provider: new AgentSearchProvider(this) } ]);三种方法的行为差异get()不返回条目本身而是返回已索引条目的计数如2 entries indexed.渲染进系统提示词只是让模型知道这个知识库有多大、要不要去搜search(query)通过search_context工具触发返回最多10 条按相关性排序ORDER BY rank的命中每条以[key]开头set(key, content)按 key 替换单条条目同 key 再次写入会先 DELETE 再 INSERT不会产生重复条目。FTS5 表结构从 search.ts 可以看到虚拟表定义CREATE VIRTUAL TABLE IF NOT EXISTS cf_agents_search_fts USING fts5( label UNINDEXED, key UNINDEXED, content, tokenizeporter unicode61 )几个值得注意的设计决策label、key标记为UNINDEXED它们只是过滤字段不参与全文索引content才是被索引的主体所有 label 共享同一张 FTS5 表靠label列做命名空间隔离。测试 providers.test.ts 验证了docs与notes两个 label 之间互不可见分词器porter unicode61英文词干化 Unicode 分词FTS5 表是唯一存储源码注释明确解释了为什么不建镜像行表mirror row table——镜像表会让每一条索引条目的写入量翻倍而索引本身已经能回答计数与查询两类问题。这一点与 Sessions 的消息索引是分离的条目只存在于cf_agents_search_fts不混入 Sessions 的消息表查询注入防护search()会把查询按空白切词逐词加双引号转义后再拼接既保留词间隐式 AND 语义又防止 FTS5 语法注入search.ts。恶意或畸形查询会被 try/catch 吞掉并返回null而不是让工具调用崩溃。测试 providers.test.ts 完整验证了索引、搜索、计数汇总与重写去重四条路径。Frozen Prompts让前缀缓存保持命中这是agents/context在成本优化上最有价值的设计。freezeSystemPrompt()只渲染一次之后每次调用都返回同一个字符串因此 Provider 的前缀缓存prefix cache能跨轮次保持温热。setBlock()会立即写入 Provider但刻意不改变已冻结的提示词需要让新内容生效时显式调用refreshSystemPrompt()从当前 Block 状态重新渲染。// 首次调用加载 providers → 渲染 → 缓存快照 const system await context.freezeSystemPrompt(); // 修改 Block写入持久化但不触碰已冻结的提示词 await context.setBlock(memory, 用户喜欢 Workers); // 仍然返回旧快照前缀缓存继续命中 const same await context.freezeSystemPrompt(); // 显式刷新重新加载所有 provider、重渲染、覆盖快照 const fresh await context.refreshSystemPrompt();测试 context.test.ts 精确验证了这一语义setBlock后freezeSystemPrompt()返回值与冻结前toBe相同refreshSystemPrompt()后才包含新内容。promptStore把冻结提示词也持久化在构造时传入第二个参数promptStore任何 writable provider冻结的提示词本身也会被持久化const context new ContextBlocks( configs, new AgentContextProvider(this, _system_prompt), (label) new AgentContextProvider(this, label) ); const system await context.freezeSystemPrompt();持久化带来的关键收益是冷唤醒一致性freezeSystemPrompt()优先返回promptStore中已存的提示词只有不存在时才加载 providers、渲染并持久化。这样一次冷启动cold wake复用到的是模型已经缓存过的那份逐字节相同的提示词字符串而不是重新渲染出略有不同的版本导致缓存 miss。对应实现见 freezeSystemPrompt()。refreshSystemPrompt()则会重新加载每一个provider、重新渲染并覆盖存储的提示词blocks.ts。另一个细节空提示词也会被持久化测试 context.test.ts 验证了空缓存也算存在——避免空快照被误判为无缓存而反复重渲染。Tools能力驱动的 AI SDK 工具集tools()返回一个 AI SDK 的ToolSet工具完全由 Block 的能力自动装配set_context当存在任意 writable Block 时出现search_context当存在任意 searchable Block 时出现。只有只读 Block 的 Agent 一个工具都拿不到。set_context 的 Schema从 tools() 实现 可以看到该工具的输入结构labelstringenum 限定为所有 writable Block 的 label——写入目标contentstring必填——写入的正文actionstringenum[replace, append]默认replace——覆盖还是追加metadataobject可选仅当存在 searchable Block 时提供包含title与description两个字段。title是稳定标识符——相同 title 的条目原地更新不同 title 创建新条目description是展示在系统提示词中的一行摘要帮助模型决定是否加载该条目。工具描述中会列出所有可写 Block 及其类型writable 或 searchable, keyed entries写入完成后返回新内容的 token 占用情况例如Written to memory. Usage: 45% (495/1100 tokens)。所有错误都会以字符串形式返回给模型而非抛出保证模型可以自我修正。search_context 的 Schemasearch_context只接受labelenum 限定为 searchable Block与querystring两个必填参数返回命中条目或No results found.。它会在描述里明确列出哪些 Block 可被搜索防止模型对只读 Block 发起无效搜索。keyed entry 的 key 生成逻辑值得单独说明contextEntryKey()blocks.ts优先使用metadata.titleslug 化最长 60 字符没有 title 时对内容做 slug FNV-1a 哈希组合保证同内容幂等、不同内容不冲突。Think 集成在 Agent 类中声明 ContextThink在启动阶段通过configureContext()构建自己的ContextBlocksimport type { ContextConfig } from agents/context; class MyAgent extends ThinkEnv { configureContext(): ContextConfig[] { return [ { label: soul, provider: { get: async () You are helpful. } }, { label: memory, description: Learned facts, maxTokens: 2_000 } ]; } }两个对使用者透明的默认行为未声明 provider 的 Block 自动接线到持久化的 per-agent SQLite即前文defaultProvider机制在Think中的应用实现见 think.ts 中configureContext()的调用处冻结系统提示词始终被持久化在_system_prompt这个 label 下无需任何显式 opt-in——冷唤醒自动复用已缓存的提示词。装配好的 Blocks 在onStart()之后通过this.context暴露给 Agent。此外configureContext()返回空数组时getSystemPrompt()仍作为回退路径存在think.ts 中明确标注了这层关系而一旦配置了 context blocksgetSystemPrompt()就会被忽略。结合 Session提示词与对话历史的组合实践与 Context 最常配套的是agents/sessions。两者明确分工Context系统提示词的组装、持久化与冻结本文主题Sessions对话消息树的持久化、流式与字节预算读取、压缩覆盖层、可选全文检索与附件卸载。sessions.md同样标注了这条依赖关系Prompt assembly lives in agents/context and composes with a session handle rather than living inside it。在你自己的 Agent 中标准组合方式是ContextBlocks负责模型永远该知道的人格、记忆、知识库Sessions负责对话历史每条 user/assistant 消息两者的存储同在一个 Durable Object 的 SQLite 中但各用各的表互不干扰。源码与测试索引想深入研读本文涉及的实现可以直接在仓库中定位以下文件packages/agents/src/context/blocks.tsContextBlocks主类、ContextProvider/WritableContextProvider接口、渲染与工具装配packages/agents/src/context/sqlite-provider.tsAgentContextProvider与cf_agents_context_blocks表结构packages/agents/src/context/search.tsAgentSearchProvider、SearchProvider接口与 FTS5 实现packages/agents/src/context/index.ts模块导出面packages/agents/src/tests/context/context.test.ts冻结/刷新语义、空提示词持久化、只读与 token 上限校验packages/agents/src/tests/context/providers.test.tsSQLite 惰性建表、label 隔离、FTS5 索引与搜索验证docs/agents/context.md官方文档原文docs/agents/sessions.md与 Context 组合使用的会话存储。小结agents/context用极简的抽象解决了 Agent 系统提示词的三个核心痛点分层组装Blocks label、持久化与能力分层Provider 的 duck-typing 能力矩阵与成本控制冻结提示词 前缀缓存保持。从只读的soul人格设定到可写的memory持久记忆再到 FTS5 支撑的knowledge可检索知识库全部可以由同一套 Block 机制承载并由框架自动装配对应的工具与提示词渲染——这就是它在Think中被选为默认提示词基础设施的原因。【免费下载链接】agentsBuild and deploy AI Agents on Cloudflare项目地址: https://gitcode.com/GitHub_Trending/agents1/agents创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表