您的位置:首页 > 手游攻略 > Context Engineering:让 Agent 在当前步骤看到正确的事实

Context Engineering:让 Agent 在当前步骤看到正确的事实

作者:互联网  时间: 2026-09-01 18:44:55  

Context Engineering:让 Agent 在当前步骤看到正确的事实并不只看表面做法,关键还要理解相关条件、限制和后续影响。

写在前面:不是上下文越长,Agent 就越聪明

做 Chat 时,最省事的做法是把历史消息带上;做 Agent Work 后,这招很快就不够用了。附件、知识库、工具结果、长期记忆、工作区状态都想进来,模型上下文像一个不断往里塞东西的背包。

背包大不代表好用:无关资料会稀释重点,长文本会吞掉 token,跨用户或跨工作区的数据更不能“顺手带上”。

Context Engineering:让 Agent 在当前步骤看到正确的事实

因此,团队在研究 Cherry Studio 时,重点不是“它能塞多少上下文”,而是“它如何让模型在这一步只看到该看的事实”。这正是 Context Engineering 要解决的问题。

1. Context Engineering 解决什么问题

模型输出的质量上限由当前可见上下文决定。

模型输出 = f(System Prompt, 消息历史, 附件, 工具结果, 检索资料, 当前运行时状态)

因此 Context Engineering 的问题不是“如何塞进更多信息”,而是:

  1. 哪些信息是当前任务真正需要的?
  2. 信息以什么形式进入模型:原文件、提取文本、摘要、检索结果还是工具句柄?
  3. 如何控制 token、延迟、隐私和噪声?
  4. 如何确保模型能回到原始证据,而不是根据片段猜测?
  5. 用户继续对话、切换分支、插话或审批后,上下文如何保持一致?

一个实用的目标函数是:

最大化:任务所需证据的覆盖率与可信度最小化:无关 token、过期事实、敏感数据暴露和上下文冲突

Prompt Engineering 定义“模型应怎样做”;Context Engineering 决定“模型此刻根据什么做”。

2. 回到 Cherry Studio:先把上下文拆开

Cherry Studio 不将所有数据混成一个长字符串,而是由不同层分别负责:

Context 层内容主要来源进入模型的方式
System产品规则、Assistant 指令、工具策略Prompt 构造器system instructions
Conversation当前对话分支中的用户、助手与工具消息SQLite / 临时会话messages
Attachments文件、图片、PDF、音频、视频FileManager / FileProcessing原生文件或提取文本
Knowledge私有知识库资料KnowledgeServicekb_* 按需工具结果
Memory身份、偏好、长期事实、事件Agent 数据目录Prompt 回灌或 memory tool
Runtime工作区、语言、会话状态、resume tokenAgent Session Runtimesettings / SDK context

这意味着“上下文”不是单一模块,而是一组数据选择与表示算法。先分层,再决定每层何时进场,能避免所有信息在一开始就挤进同一个请求。

3. 关键算法一:别把整棵消息树都塞进去

3.1 为什么历史不是简单 append

持久化 Chat 支持 regenerate 和多分支回复。用户可以从某条历史消息继续,产生新分支;因此一个 topic 内的全部消息不能都发送给模型。

正确的历史应是:

根节点 → 当前 anchor 的唯一路径

而不是:

该 topic 的全部消息

Cherry Studio 的 PersistentChatContextProvider.buildHistory 按 anchor 回溯路径,再应用“清除上下文”边界。

伪代码:

functionbuildHistory(anchorMessageId) {// 返回从根到 anchor 的单一路径,排除兄弟分支path = messageService.getPathToNode(anchorMessageId)// clear-context 是显式上下文边界,而不是删除历史数据lastClearIndex = findLastIndex(path, message =>message.parts.contains("clear-context"))visiblePath = path.slice(lastClearIndex + 1)return visiblePath.map(message => ({id: message.id,role: toContentRole(message.role),parts: message.parts}))}

该算法复杂度与当前分支路径长度成正比:

时间复杂度:O(depth)空间复杂度:O(depth)

其中 depth 是根节点到 anchor 的消息数量,通常远小于 topic 中全部消息数量。

3.2 clear-context 为什么是逻辑边界

清除上下文并不删除数据库记录。原消息仍可展示、搜索、审计和作为分支历史存在;只是后续模型调用不再看到它之前的内容。

M1 → A1 → M2[clear-context] → A2 → M3模型看到:A2 → M3数据库仍保存:M1 → A1 → M2 → A2 → M3

这个设计避免了两类问题:

  1. 用户希望“重新开始讨论”,却丢失可见聊天记录。
  2. 通过物理删除实现压缩,导致回溯、审计与分支关系损坏。

3.3 当前边界:显式清除,不是自动压缩

普通持久化 Chat 目前以明确的 clear-context part 决定历史边界,并不会依据模型上下文窗口自动摘要或裁剪整段历史。

这是一个刻意需要产品决策的边界。自动压缩虽然能避免超窗,却可能丢失工具状态、审批结果、用户约束和引用来源。若未来引入自动压缩,应把它建模为一条可持久化、可检查、可重放的摘要消息,而不是请求前临时修改历史。

4. 关键算法二:根据模型能力路由附件

同一附件不应以固定方式进入每个模型。例如,视觉模型应接收原图;文本模型则需要 OCR 文本。Cherry Studio 的原则是:

4.1 附件路由决策

functionprepareAttachment(part, modelCapabilities, requestContext) {if (!part.hasFirstPartyFileEntry) {// 外部/gateway 文件保持兼容的原始处理路径returnmaterializeOrReadableFailureNote(part)}file = fileManager.getById(part.fileEntryId)type = classify(file.extension)if (isNativeSupported(type, modelCapabilities)) {native = materializeNativeFilePart(part)return native ?? unreadableFileNote(part.filename)}text = extractByType(type, file)returninlineWithCap(part.modelFacingHandle, text, requestContext)}

isNativeSupported 的概念逻辑:

functionisNativeSupported(fileType, capabilities) {switch (fileType) {case"image": return capabilities.visioncase"audio": return capabilities.audiocase"video": return capabilities.videocase"pdf": return capabilities.nativePdfdefault: returnfalse}}

非原生文件的降级路径:

文件类型非原生模型看到的内容
图片OCR 文本
PDF / Office / 文本 / 代码提取文本
音频 / 视频明确的不支持提示
二进制文件明确的不支持提示
读取或解析失败could not read this file 提示

这里的关键不是“尽量解析一切”,而是永远不要静默丢掉附件。模型必须知道某个附件存在但当前不可读,否则它会基于缺失信息给出过度自信的结论。

4.2 为什么原生文件优先

将原生图片或 PDF 总是先 OCR 抽取为文本,会损失布局、表格、图形和多模态信息;同时会让模型能力向低能力 Provider 退化。

反过来,完全只提供 read_file 工具也不正确:非工具模型或未主动调用工具的模型会完全看不到附件内容。

正确策略是:

模型原生支持 → 发送原文件模型不原生支持 → 主动提供可读文本文本过长 → 内联前段 + 提供受限分页工具无法读取 → 明确说明失败

5. 关键算法三:截断、分页与安全文件句柄

附件全文可能很大。如果不加控制,单个 PDF 就足以挤掉整个对话历史;如果简单截断,又会让模型无法获得后半部分内容。

Cherry Studio 使用“内联首段 + 按需分页”的混合策略。

functioncapInlineText(handle, text, isToolCapable, cap) {if (text.length <= cap) {return text}end = surrogateSafeEnd(text, cap)head = text.slice(0, end)if (!isToolCapable) {return head + `[truncated ${end}/${text.length} chars]`}return head +`[truncated ${end}/${text.length} chars; ` +`call read_file("${handle}", offset=${end}) for more]`}

5.1 为什么需要 surrogateSafeEnd

JavaScript 字符串以 UTF-16 code unit 计数。若直接在 cap 位置 slice,可能切断一个袋里对,例如 emoji 或部分非 BMP 字符,产生非法或显示异常文本。

概念算法:

functionsurrogateSafeEnd(text, requestedEnd) {end = min(requestedEnd, text.length)if (isHighSurrogate(text[end - 1]) && isLowSurrogate(text[end])) {return end - 1}return end}

这是一类很小但重要的 Context Engineering 细节:错误截断不会只影响显示,还可能改变模型对结构化数据、路径或代码片段的理解。

5.2 模型可见的不是内部文件 ID

read_file 使用模型可见文件名作为 handle,而不是暴露 fileEntryId。同名附件需要生成稳定且唯一的别名:

functionuniqueHandle(displayName, used) {base = trim(displayName) || "file"candidate = basesuffix = 2while (used.has(candidate)) {candidate = `${base} (${suffix})`suffix += 1}used.add(candidate)return candidate}

随后只允许模型在本轮附件 allow-list 内解析 handle:

functionreadFileForModel(handle, offset, attachmentAllowList) {ref = attachmentAllowList.find(item => item.handle === handle)if (!ref) {throwsanitizedError("Attached file not found")}returnpageExtractedText(ref.fileEntryId, offset)}

这个设计同时实现:

  1. 防止模型猜测或枚举内部文件 ID。
  2. 防止本轮模型读取未附加的用户文件。
  3. 让错误信息保持在用户可理解的文件名层,而不泄露内部路径和存储结构。

6. 关键算法四:知识库 Scope 的安全收窄

知识库 Context 不应通过“把所有库都搜索一遍”实现。Agent 需要先有可验证的可见范围。

Cherry Studio 将静态绑定视为 ceiling,而不是每轮选择的默认值:

functionresolveKnowledgeBaseScope(configuredIds, selectedIds) {if (configuredIds is empty) {returncanonicalize(selectedIds)}if (selectedIds is empty) {returncanonicalize(configuredIds)}configured = newSet(configuredIds)narrowed = selectedIds.filter(id => configured.has(id))if (narrowed is empty) {// 全部选择越界时,保留原静态绑定,避免无意禁用 Agent 知识returncanonicalize(configuredIds)}returncanonicalize(narrowed)}functioncanonicalize(ids) {returnsort(unique(ids))}

这段逻辑有三个语义:

  1. 静态绑定是权限上限:Renderer 传来的 ID 不能扩大 Agent 可见库。
  2. 本轮选择是收窄器:用户可以临时只让 Agent 使用某几个已绑定库。
  3. 输出规范化:去重与排序使 scope 可比较、可缓存并可作为运行时重建签名。

需要注意不同运行时的空 scope 语义。共享检索核心中空 allow-list 可以表示“不额外限制”;但 Agent Session 会在没有有效 scope 时隐藏或拒绝知识库工具,采用 fail-closed 策略。产品层必须明确“默认允许搜索全部个人库”还是“必须显式选择知识库”,不能把这一差异留给调用方猜测。

7. 关键算法五:Agentic RAG 的渐进式取证

RAG 不是把检索结果自动附在每次请求后。对于知识库问答,Cherry Studio 提供面向模型的逐步工具:

kb_list → kb_search → kb_read

可将其理解为逐层缩小搜索空间:

asyncfunctionanswerWithKnowledge(question, allowedBaseIds) {bases = awaitkb_list({ allowedBaseIds })candidates = chooseRelevantBases(question, bases)hits = awaitkb_search({baseIds: candidates,query: question})evidence = awaitkb_read({baseId: hits[0].baseId,conceptId: hits[0].conceptId})returngenerateAnswer(question, evidence, citationsFrom(hits))}

这不是要求模型机械地执行固定链路,而是表达三种不同粒度的信息访问:

工具信息粒度适用问题
kb_list知识库与资料目录“哪些资料可能有关?”
kb_search相关 chunk“哪些片段支持当前问题?”
kb_read文档全文或指定页/grep“片段是否被断章取义?需要精确引用什么?”

它比“检索 topK 后直接回答”更可靠,因为模型可以在证据不足时主动扩大或深入查询;同时比“整库加入上下文”更节省 token。

8. Agent Session 与普通 Chat 的 Context 差异

普通 Chat 的 Context 核心是消息树路径;Agent Session 的 Context 还包含工作区、runtime connection 和可恢复会话状态。

维度普通 ChatAgent Session
主上下文当前 message-tree 分支当前 turn + driver 会话状态
工作目录不作为通用 Chat 语义session workspace 是 cwd
用户插话运行中的 turn yield 后启动 continuation可在下一次工具调用前注入 steer,或加入 pending queue
恢复锚点持久化消息历史最新 assistant 的 opaque resume token
PromptAssistant prompt + 当前工具策略PromptBuilder + workspace + runtime + session policy

Agent Session 中收到 live follow-up 时,不应该简单把新文本塞进正在进行的模型输入。正确流程取决于 Driver 是否支持 redirect:

functionhandleLiveFollowUp(session, message) {if (session.hasLiveTurn && session.driver.supportsRedirect) {session.driver.redirect({message,systemReminder: true})return}session.pendingTurns.push(message)scheduleNextTurnAfterCurrentCompletes(session)}

对于 Claude Code Driver,redirect 会暂存 steer,并在下一次 PreToolUse 时作为 additionalContext 注入。这样上下文变更发生在工具调用边界,而不是任意流式 token 中间。

9. Context 预算:应该控制什么

Context 预算不应只看 token 总数。至少需要分别观察:

预算 = 历史消息 + Prompt + 附件文本 + 工具定义 + 工具结果 + RAG 证据 + 模型预留输出

其中风险最大的通常不是用户一句话,而是:

  1. 多轮历史中重复出现的附件文本。
  2. 长文件提取内容。
  3. 大型 MCP 的工具定义。
  4. 未裁剪的工具返回。
  5. 大量检索 chunk 或完整网页正文。

建议为每类 Context 设置不同策略:

类型优先策略
系统策略静态化、缓存、版本化
历史消息分支选择、显式 clear、未来可持久化压缩
附件原生优先、提取文本 cap、按需分页
知识库list/search/read 渐进检索、引用保留
工具结果结构化、限长、保留可继续读取的句柄
记忆长期事实内联,事件日志按需检索

任何压缩算法都应保留以下信息:

  1. 用户的硬约束和未完成任务。
  2. 工具执行状态与审批状态。
  3. 关键实体、文件名、路径、引用和错误。
  4. 能回到原始材料的定位符。

10. 这些 Context 坑,最好提前绕开

反模式:将整个会话、所有附件和所有资料直接发送

后果是 token 爆炸、注意力稀释和敏感信息暴露。应改为分支选择、cap、分页和渐进检索。

反模式:把附件可见性完全交给工具调用

弱模型或未调用工具的模型会看不到附件。应至少内联受控的可读内容或不可读提示。

反模式:仅在 UI 层限制知识库选择

Renderer 输入不可信。必须在主进程根据静态绑定重新计算 scope。

反模式:截断后不告诉模型还有内容

模型会把前半段误认为全文。对于工具模型,应提供包含 offset 的 read_file 提示。

反模式:让自动摘要悄悄改写会话事实

摘要一旦取代原历史却不可追溯,用户无法验证信息是否丢失。应持久化摘要、记录其覆盖范围,并保留回读原消息的能力。

11. 小结

Context Engineering 的成熟标志不是支持更多输入类型,而是能够稳定回答:

  1. 模型这一轮实际看到了什么?
  2. 为什么是这些内容,而不是更多或更少?
  3. 附件、资料和历史能否回到原始证据?
  4. 当上下文超出预算、用户插话或文件不可读时,系统如何降级?
  5. 这些规则是否在主进程和运行时边界被强制执行?

一句话总结:

继续阅读

  1. Chat Attachments
  2. Agent Session Runtime
  3. Agent Loop
  4. Knowledge Service
  5. src/main/ai/streamManager/context/PersistentChatContextProvider.ts
  6. src/main/ai/messages/attachmentRouting.ts
  7. src/main/ai/utils/knowledgeScope.ts

最新游戏

更多

Copyright©2010-2019. All rights reserved | 波波三国游戏官网|[email protected]

备案编号:湘ICP备2022015115号-4