3.3 上下文工程:怎么管理一个会话里的一切
前两节讲了记忆的演进和管理。这一节讲更大的问题:一个会话里,所有信息怎么组织、怎么注入到 LLM 的上下文中?
一个会话里有多少信息?
一个会话里,LLM 需要知道的东西有很多:
- 系统提示词(你是谁、能做什么)
- 记忆事实(用户叫什么、项目用什么技术栈)
- 项目上下文(AGENTS.md、RepoWiki)
- Skill 文档(怎么用某个工具)
- 监控数据(这次调用花了多少 Token)
- 对话历史(前面聊了什么)
如果手动拼接,每加一种来源就要改代码:
csharp
// 旧做法:手动拼接,改一处动全身
var messages = new List<ChatMessage>();
messages.Add(new ChatMessage(ChatRole.System, systemPrompt));
messages.Add(new ChatMessage(ChatRole.System, memoryFacts));
messages.Add(new ChatMessage(ChatRole.System, projectContext));
messages.Add(new ChatMessage(ChatRole.System, skillDocs));
// ... 还有更多
messages.AddRange(conversationHistory);
messages.Add(new ChatMessage(ChatRole.User, userInput));这种做法的问题是:耦合太紧,扩展太难。
加记忆要改,加监控要改,加 Skill 也要改。每加一种上下文来源,就要动这段代码。
上下文提供者链

更好的做法:上下文提供者链(AIContextProvider Chain)。
每种上下文来源都是一个独立的 Provider,框架自动收集、依次执行:
csharp
// 注册上下文提供者
services.AddSingleton<AIContextProvider>(sp =>
sp.GetRequiredService<MemoryFactsContextProvider>()); // 记忆事实
services.AddSingleton<AIContextProvider>(sp =>
sp.GetRequiredService<MonitoringContextProvider>()); // 监控数据
services.AddSingleton<AIContextProvider>(sp =>
sp.GetRequiredService<TitleContextProvider>()); // 会话标题
services.AddSingleton<AIContextProvider>(sp =>
{
var manager = sp.GetRequiredService<AgentSkillsManager>();
return manager.BuildProvider(); // Skill 文档
});每个 Provider 只做一件事:
csharp
internal class MemoryFactsContextProvider : AIContextProvider
{
protected override async ValueTask<AIContext> ProvideAIContextAsync(
AIContextProvider.InvokingContext context,
CancellationToken cancellationToken)
{
var sessionId = GetSessionId(context.Session);
var facts = await _memoryStore.LoadFactsAsync(sessionId);
if (facts.Count == 0) return new AIContext();
var formatted = MemoryFormatter.FormatForPrompt(facts, _options.MaxMemoryTokens);
return new AIContext
{
Messages = [new ChatMessage(ChatRole.System, formatted)]
};
}
}执行时机
上下文提供者的执行时机是这样的:
用户发消息
↓
Agent 准备调用 LLM
↓
框架自动执行所有 AIContextProvider:
├── MemoryFactsContextProvider → 加载记忆事实 → 注入 SystemMessage
├── MonitoringContextProvider → 记录调用开始 → 发布事件
├── TitleContextProvider → 更新会话标题
└── AgentSkillsProvider → 加载 Skill 文档 → 注入 SystemMessage
↓
所有 Provider 的输出合并到 LLM 上下文
↓
调用 LLM
↓
MonitoringContextProvider 记录调用结束(成功/失败)这个设计的好处是开闭原则:
- 对扩展开放:新增上下文来源,只需新增一个 Provider
- 对修改关闭:不需要改 Agent 调用逻辑,不需要改其他 Provider
比如以后要加一个"用户权限上下文",只需要新增一个 UserPermissionContextProvider,注册到 DI 容器,完事。现有的代码一行不改。
检索策略:用结构换检索
除了记忆系统内部的检索,还有一个更大的问题:Agent 怎么找到项目级别的知识?
这里有三种主流策略:

| 策略 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| RAG + 向量数据库 | 文档切片 → Embedding → 相似度搜索 | 自动化程度高 | 需要向量数据库;切片可能破坏结构 |
| 结构化知识摘要 | 预生成代码库的结构化摘要 | 精准、省 Token、可调试 | 需要生成和维护 |
| 手工策展上下文 | 人工编写项目上下文文件 | 最精准、零基础设施 | 需要人工维护 |
我选择了第二种和第三种的组合。
为什么不用 RAG?
RAG 的核心假设是:信息分散在大量文档中,需要"搜索"才能找到。
但对于编码场景,这个假设不太成立:
- 代码有天然的结构——目录、模块、接口,不需要 Embedding 来"发现"关系
- 开发者知道自己在做什么——不需要"搜索",需要"直接告诉"
- 向量数据库增加基础设施复杂度,对于本地部署场景是个负担
我选择的方案是 RepoWiki 思路:用结构换检索。
项目根目录/
├── .qoder/
│ └── repowiki/
│ └── zh/
│ ├── overview.md ← 项目概览
│ ├── modules/
│ │ ├── workflow.md ← 工作流模块
│ │ ├── aiagent.md ← Agent 模块
│ │ └── ...
│ └── patterns/
│ ├── architecture.md ← 架构模式
│ └── conventions.md ← 编码规范这份摘要是人类可读的 Markdown,不是黑箱向量。它告诉 Agent:项目有哪些模块、每个模块的职责是什么、关键接口长什么样、编码规范是什么。
Agent 拿到这份摘要,就知道该去哪个模块找代码,不需要"搜索"。
再加上手工策展的 AGENTS.md:
AGENTS.md 是更精炼的一层——它不是"项目全貌",而是"Agent 需要知道的最重要的事"。比如:
markdown
## 后端架构(五层)
API 层 → Application 层 → Domain 层 → Repository 层 → Model 层
## 命名规范
| 层级 | 类型 | 示例 |
|------|------|------|
| API | `{模块名}Controller` | `PluginController` |RepoWiki 是"地图",AGENTS.md 是"指南针"。一个告诉你全貌,一个告诉你重点。
业界怎么做的
同样的"上下文管理"问题,其他框架的思路各不相同:
Hermes 走的是"记忆提供者"路线。核心抽象是 MemoryProvider,有完整的生命周期:initialize → prefetch → sync_turn → shutdown。支持插件式扩展——内置提供者 + 最多一个外部提供者(Honcho、Mem0 等)。关键设计:每轮对话前主动召回相关记忆,每轮对话后异步写入新记忆。
LangGraph 的做法是"状态图"——每个节点有自己的状态,状态在节点之间传递。上下文管理变成了"状态管理"。好处是显式、可控;坏处是复杂度高,每个节点都要手动管理状态。
微软 Agent Framework (MAF) 的做法是"上下文压缩系统"(CompactionSystem)——当上下文超过窗口限制时,自动压缩历史消息。这个思路跟我的 CompactAsync 类似,但 MAF 的实现更精细——它会用 LLM 来总结旧消息,而不是简单删除。
AgentScope Java 走的是 RAG 路线——它有 5 个 RAG 扩展(百炼、Dify、RAGFlow、HayStack、pgvector),覆盖了从文档切片到向量搜索的完整链路。这是"用计算换检索"的典型做法。
关键认知
上下文工程的核心设计原则是:可组合、可扩展、可调试。
- 上下文提供者链让不同来源可以独立扩展
- 三层结构(L0/L1/L2)让信息按需加载
- 降级策略让系统在缺少组件时依然能运行
Agent 的智能程度,不取决于模型有多强,取决于你能把多少正确的信息放进它的上下文窗口里。
上下文工程就是解决这个问题的:在有限的窗口里,放进最精准的信息。