Agent不是无状态的问答机器。一个有用的Agent需要记住用户偏好、跟踪任务进度、维护工作上下文、在会话中断后恢复状态。这些能力统称为上下文管理与状态持久化。掌握它们,才能把Agent从玩具变成工具。
上下文窗口的本质限制
当前主流模型的上下文窗口虽然越来越大,但”能装”不等于”能处理”。模型对长上下文的注意力会稀释,关键信息可能被淹没在大量无关内容中。
上下文窗口的问题不是容量,而是质量。塞满上下文的Agent表现往往不如只给关键信息的Agent。因为模型需要在大量信息中找到相关部分,这个过程本身消耗认知资源。
影响上下文质量的三个因素:信息密度、组织结构和时效性。
信息密度是指上下文中有效信息的比例。一段完整的对话历史可能包含大量寒暄、重复确认和无关讨论,信息密度很低。需要定期清理和压缩。
组织结构是指信息是否有清晰的层次和关联。散乱的上下文让模型难以建立正确的关联,结构化的上下文则帮助模型快速定位。
时效性是指信息是否仍然相关。过时的信息不仅没用,还会干扰判断。比如三天前的临时决定,今天可能已经变了。
上下文窗口优化策略
优化上下文窗口有四个策略:窗口滑动、语义分块、层级组织和主动遗忘。
窗口滑动是只保留最近N轮对话。这是最简单的策略,适合短期任务。缺点是可能丢失关键背景信息,特别是背景信息出现在较早的对话中。
class SlidingWindowContext {
private history: Message[] = [];
private maxMessages: number;
add(message: Message): void {
this.history.push(message);
if (this.history.length > this.maxMessages) {
this.history.shift();
}
}
getContext(): Message[] {
return this.history;
}
}
语义分块是按主题或任务把上下文分成独立块。每个块有明确的主题标识,Agent根据当前任务选择相关块。这比滑动窗口更精确,因为保留了历史信息,只是按需加载。
interface ContextChunk {
id: string;
topic: string;
messages: Message[];
relevance: number;
lastAccessed: number;
}
class SemanticContext {
private chunks: Map<string, ContextChunk> = new Map();
add(message: Message, topic: string): void {
const chunk = this.chunks.get(topic) || {
id: topic,
topic,
messages: [],
relevance: 1.0,
lastAccessed: Date.now(),
};
chunk.messages.push(message);
chunk.lastAccessed = Date.now();
this.chunks.set(topic, chunk);
}
getRelevant(query: string, topK: number = 3): ContextChunk[] {
return Array.from(this.chunks.values())
.sort((a, b) => this.calculateRelevance(b, query) - this.calculateRelevance(a, query))
.slice(0, topK);
}
}
层级组织是把上下文分为不同层级:系统级(全局规则)、会话级(当前对话历史)、任务级(当前任务的特定信息)。不同层级有不同的保留策略和更新频率。
interface HierarchicalContext {
system: {
rules: string[];
preferences: Record<string, unknown>;
};
session: {
history: Message[];
summary: string;
};
task: {
currentGoal: string;
progress: TaskProgress;
artifacts: Record<string, unknown>;
};
}
主动遗忘是定期清理不再需要的信息。不是被动等窗口满了再删,而是主动判断哪些信息已经过时或不再相关。
class ActiveForgetting {
private context: Context;
private retentionRules: RetentionRule[];
async cleanup(): Promise<void> {
for (const item of this.context.items) {
const shouldKeep = await this.evaluateRetention(item);
if (!shouldKeep) {
this.context.remove(item.id);
}
}
}
private async evaluateRetention(item: ContextItem): Promise<boolean> {
for (const rule of this.retentionRules) {
const result = await rule.evaluate(item);
if (result === 'keep') return true;
if (result === 'discard') return false;
}
return true; // 默认保留
}
}
状态机设计:Agent的状态管理
复杂任务不是线性的。Agent需要在不同状态间转换:理解需求、收集信息、制定方案、执行操作、验证结果。状态机是管理这种复杂性的有效工具。
Agent的状态机包含三个要素:状态定义、转换条件和执行动作。
状态定义要明确每个状态的含义和进入退出条件。常见状态包括:idle(空闲)、understanding(理解需求)、gathering(收集信息)、planning(制定计划)、executing(执行)、verifying(验证)、completed(完成)、blocked(阻塞)。
转换条件定义什么事件触发状态变化。比如从understanding到gathering的转换条件是”需求理解完成,但信息不足”。
执行动作定义进入状态时要做什么。比如进入executing状态时,Agent开始调用工具执行计划。
interface State {
id: string;
onEnter?: (ctx: AgentContext) => Promise<void>;
onExit?: (ctx: AgentContext) => Promise<void>;
transitions: Transition[];
}
interface Transition {
target: string;
condition: (ctx: AgentContext) => boolean;
action?: (ctx: AgentContext) => Promise<void>;
}
class AgentStateMachine {
private states = new Map<string, State>();
private currentState: string = 'idle';
defineState(state: State): void {
this.states.set(state.id, state);
}
async handleEvent(event: AgentEvent): Promise<void> {
const state = this.states.get(this.currentState);
if (!state) return;
for (const transition of state.transitions) {
if (transition.condition(this.context)) {
if (state.onExit) await state.onExit(this.context);
if (transition.action) await transition.action(this.context);
this.currentState = transition.target;
const newState = this.states.get(transition.target);
if (newState?.onEnter) await newState.onEnter(this.context);
return;
}
}
}
}
// 定义一个代码生成Agent的状态机
const codeGenStates: State[] = [
{
id: 'idle',
transitions: [
{ target: 'understanding', condition: (ctx) => ctx.hasNewTask() },
],
},
{
id: 'understanding',
onEnter: async (ctx) => await ctx.analyzeRequirements(),
transitions: [
{ target: 'gathering', condition: (ctx) => ctx.needsMoreInfo() },
{ target: 'planning', condition: (ctx) => ctx.requirementsClear() },
],
},
{
id: 'gathering',
onEnter: async (ctx) => await ctx.askQuestions(),
transitions: [
{ target: 'planning', condition: (ctx) => ctx.infoSufficient() },
],
},
{
id: 'planning',
onEnter: async (ctx) => await ctx.createPlan(),
transitions: [
{ target: 'executing', condition: (ctx) => ctx.planApproved() },
{ target: 'understanding', condition: (ctx) => ctx.planRejected() },
],
},
{
id: 'executing',
onEnter: async (ctx) => await ctx.executePlan(),
transitions: [
{ target: 'verifying', condition: (ctx) => ctx.executionComplete() },
{ target: 'blocked', condition: (ctx) => ctx.executionFailed() },
],
},
{
id: 'verifying',
onEnter: async (ctx) => await ctx.verifyResult(),
transitions: [
{ target: 'completed', condition: (ctx) => ctx.verificationPassed() },
{ target: 'executing', condition: (ctx) => ctx.needsFix() },
],
},
{
id: 'blocked',
onEnter: async (ctx) => await ctx.reportBlockage(),
transitions: [
{ target: 'executing', condition: (ctx) => ctx.blockageResolved() },
{ target: 'completed', condition: (ctx) => ctx.userAborted() },
],
},
{
id: 'completed',
onEnter: async (ctx) => await ctx.deliverResult(),
transitions: [
{ target: 'idle', condition: () => true },
],
},
];
状态机的价值在于:Agent行为可预测、可调试、可恢复。每个状态有明确的职责,状态转换有明确的条件,不会在执行过程中迷失方向。
记忆机制:短期记忆与长期记忆
人类有两种记忆:短期记忆(工作记忆)和长期记忆。Agent也需要类似机制。
短期记忆是当前会话中的上下文,包括对话历史、任务状态、中间结果。短期记忆的特点是容量有限、访问快速、会话结束即消失。
长期记忆是跨会话保持的信息,包括用户偏好、项目知识、历史决策。长期记忆的特点是容量大、持久保存、需要主动检索。
两种记忆的实现方式不同。短期记忆通常保存在内存中,直接作为prompt的一部分传给模型。长期记忆需要外部存储,并在需要时检索相关片段。
interface Memory {
shortTerm: ShortTermMemory;
longTerm: LongTermMemory;
}
class ShortTermMemory {
private items: MemoryItem[] = [];
private maxItems = 20;
add(item: MemoryItem): void {
this.items.push(item);
if (this.items.length > this.maxItems) {
this.items.shift();
}
}
getAll(): MemoryItem[] {
return this.items;
}
}
class LongTermMemory {
private store: VectorStore;
async remember(item: MemoryItem): Promise<void> {
const embedding = await this.embed(item.content);
await this.store.add({
id: item.id,
content: item.content,
embedding,
metadata: item.metadata,
});
}
async recall(query: string, limit: number = 5): Promise<MemoryItem[]> {
const queryEmbedding = await this.embed(query);
const results = await this.store.search(queryEmbedding, limit);
return results.map((r) => ({
id: r.id,
content: r.content,
metadata: r.metadata,
}));
}
}
长期记忆的检索是关键。不是所有长期记忆都需要加载,只加载与当前任务相关的。相关性的判断通常基于向量相似度:把查询和记忆都转成向量,计算相似度,取最相关的几个。
async function retrieveRelevantMemories(
query: string,
longTermMemory: LongTermMemory,
options: RetrievalOptions
): Promise<MemoryItem[]> {
// 1. 向量检索
const vectorResults = await longTermMemory.recall(query, options.vectorLimit);
// 2. 时间过滤
const timeFiltered = vectorResults.filter(
(item) => Date.now() - item.metadata.timestamp < options.maxAge
);
// 3. 重要性过滤
const important = timeFiltered.filter(
(item) => item.metadata.importance >= options.minImportance
);
// 4. 去重和排序
return deduplicateAndSort(important);
}
长期存储:状态怎么持久化
Agent状态需要在会话之间持久化。用户可能今天提了一个需求,明天继续讨论;Agent可能中途崩溃,需要恢复。没有持久化,每次会话都是新的开始。
需要持久化的状态包括:用户偏好、任务历史、项目知识、执行日志、中间产物。
持久化策略取决于数据特性和访问模式。用户偏好变化少,可以全量保存;任务历史变化多,可以增量追加;项目知识体积大,需要向量化存储。
interface PersistenceLayer {
// 用户偏好:少量、结构化、经常读取
preferences: {
get(userId: string): Promise<UserPreferences>;
set(userId: string, prefs: UserPreferences): Promise<void>;
};
// 任务历史:大量、时序、按需读取
taskHistory: {
append(taskId: string, event: TaskEvent): Promise<void>;
getRecent(userId: string, limit: number): Promise<TaskSummary[]>;
};
// 项目知识:大量、非结构化、语义检索
projectKnowledge: {
index(projectId: string, documents: Document[]): Promise<void>;
search(projectId: string, query: string): Promise<SearchResult[]>;
};
}
class FilePersistence implements PersistenceLayer {
private basePath: string;
async saveState(agentId: string, state: AgentState): Promise<void> {
const path = `${this.basePath}/${agentId}/state.json`;
await fs.writeFile(path, JSON.stringify(state, null, 2));
}
async loadState(agentId: string): Promise<AgentState | null> {
const path = `${this.basePath}/${agentId}/state.json`;
try {
const data = await fs.readFile(path, 'utf-8');
return JSON.parse(data);
} catch {
return null;
}
}
}
持久化还要考虑版本兼容性。Agent实现可能升级,状态结构可能变化。老状态需要能迁移到新结构,否则升级后状态就丢了。
interface StateMigration {
fromVersion: number;
toVersion: number;
migrate(oldState: unknown): AgentState;
}
class StateManager {
private migrations: StateMigration[] = [];
registerMigration(migration: StateMigration): void {
this.migrations.push(migration);
}
async loadAndMigrate(agentId: string): Promise<AgentState> {
const raw = await this.persistence.loadState(agentId);
if (!raw) return this.createInitialState();
let state = raw;
const currentVersion = this.getCurrentVersion();
while (state.version < currentVersion) {
const migration = this.migrations.find(
(m) => m.fromVersion === state.version
);
if (!migration) {
throw new Error(`No migration found for version ${state.version}`);
}
state = migration.migrate(state);
}
return state;
}
}
会话恢复:断点续传
会话中断是常态。网络波动、系统重启、用户离开,都会导致Agent会话中断。好的Agent应该能在恢复后从中断点继续,而不是重新开始。
会话恢复需要三个要素:检查点、状态快照和恢复逻辑。
检查点是执行过程中的保存点。在关键步骤完成后保存状态,如果后续失败可以从这里恢复,而不是从头再来。
interface Checkpoint {
id: string;
timestamp: number;
state: AgentState;
completedSteps: string[];
pendingSteps: string[];
}
class CheckpointManager {
private checkpoints: Checkpoint[] = [];
async save(state: AgentState, completedSteps: string[]): Promise<void> {
const checkpoint: Checkpoint = {
id: `cp-${Date.now()}`,
timestamp: Date.now(),
state: structuredClone(state),
completedSteps: [...completedSteps],
pendingSteps: this.calculatePending(state),
};
this.checkpoints.push(checkpoint);
await this.persistence.saveCheckpoint(checkpoint);
}
async restore(latest: boolean = true): Promise<AgentState> {
const checkpoint = latest
? this.checkpoints[this.checkpoints.length - 1]
: await this.findLastValidCheckpoint();
if (!checkpoint) {
throw new Error('No checkpoint available');
}
return checkpoint.state;
}
}
状态快照是Agent完整状态的序列化表示。包括当前状态机状态、上下文内容、记忆数据、执行计划。快照要足够完整,让恢复后的Agent行为与中断前一致。
恢复逻辑决定恢复后做什么。简单恢复是回到检查点继续执行。智能恢复是评估中断期间是否有变化(比如用户改了文件),如果有变化则调整计划再执行。
class SessionRecovery {
async recover(sessionId: string): Promise<RecoveryResult> {
// 1. 加载最后状态
const state = await this.stateManager.loadState(sessionId);
// 2. 检测环境变化
const changes = await this.detectChanges(state);
// 3. 决定恢复策略
if (changes.length === 0) {
// 无变化,直接继续
return { action: 'resume', state };
} else if (changes.every((c) => c.type === 'additive')) {
// 只有新增,可以安全继续
return { action: 'resume', state };
} else {
// 有破坏性变化,需要重新规划
const replanned = await this.replan(state, changes);
return { action: 'replan', state: replanned };
}
}
private async detectChanges(state: AgentState): Promise<Change[]> {
const changes = [];
for (const file of state.trackedFiles) {
const currentHash = await this.computeHash(file.path);
if (currentHash !== file.hash) {
changes.push({ type: 'modified', path: file.path });
}
}
return changes;
}
}
上下文压缩:长上下文的生存策略
当上下文不可避免地变长时,压缩是必要的。压缩的目标是在保留关键信息的前提下,减少token数量。
压缩策略分为三个层次:摘要、提取和丢弃。
摘要是对长内容进行概括。对话历史可以总结成一段要点,代码文件可以总结成接口和关键逻辑,文档可以总结成章节大纲。
class ContextCompressor {
async compress(messages: Message[], targetTokens: number): Promise<Message[]> {
let current = messages;
while (this.estimateTokens(current) > targetTokens) {
// 1. 尝试摘要最旧的对话
if (current.length > 10) {
const summary = await this.summarize(current.slice(0, -5));
current = [summary, ...current.slice(-5)];
continue;
}
// 2. 尝试提取关键信息
const extracted = await this.extractKeyInfo(current);
if (extracted.length < current.length) {
current = extracted;
continue;
}
// 3. 丢弃最低优先级内容
current = this.dropLowestPriority(current);
}
return current;
}
private async summarize(messages: Message[]): Promise<Message> {
const summary = await this.llm.complete(
`Summarize the following conversation in 3-5 bullet points:\n\n${messages.map((m) => `${m.role}: ${m.content}`).join('\n')}`
);
return { role: 'system', content: `Previous conversation summary:\n${summary}` };
}
}
提取是只保留关键片段。比如代码审查时,不需要保留整个文件内容,只保留被修改的函数和相关的上下文行。
丢弃是按优先级移除内容。每个上下文片段应该有优先级评分,低优先级的先丢弃。优先级可以根据:信息新鲜度、与当前任务的相关性、用户显式标记的重要性。
function calculatePriority(item: ContextItem, currentTask: string): number {
const age = Date.now() - item.timestamp;
const ageScore = Math.max(0, 1 - age / (24 * 3600 * 1000)); // 24小时内满分
const relevanceScore = calculateRelevance(item.content, currentTask);
const importanceScore = item.importance || 0.5;
return ageScore * 0.3 + relevanceScore * 0.5 + importanceScore * 0.2;
}
多轮对话管理
Agent与用户的交互通常是多轮对话。管理好多轮对话的状态和上下文,是Agent好用的关键。
多轮对话的挑战在于:用户可能在任何一轮改变意图、补充信息、纠正错误;Agent需要在长对话中保持连贯性,不重复提问,不遗忘已确认的信息。
管理多轮对话有三个机制:意图追踪、信息槽位和对话策略。
意图追踪是识别用户每轮输入的真实意图。用户说”改成红色”,意图是修改颜色属性;用户说”不对,我要蓝色”,意图是纠正上一轮的选择。
interface Intent {
type: 'create' | 'modify' | 'delete' | 'query' | 'confirm' | 'correct' | 'abort';
target?: string;
parameters: Record<string, unknown>;
confidence: number;
}
class IntentTracker {
private history: Intent[] = [];
async parse(input: string, context: AgentContext): Promise<Intent> {
const prompt = `
Based on the conversation history and current input, determine the user's intent.
History: ${JSON.stringify(this.history.slice(-3))}
Current input: ${input}
Respond with JSON: { "type": "...", "target": "...", "parameters": {...}, "confidence": 0.0-1.0 }
`;
const result = await this.llm.complete(prompt);
return JSON.parse(result);
}
}
信息槽位是收集完成任务所需的信息片段。比如预订餐厅需要:日期、时间、人数、偏好。每轮对话收集或更新一个或多个槽位。
interface Slot {
name: string;
value: unknown;
status: 'empty' | 'filled' | 'confirmed' | 'ambiguous';
source?: string; // 哪一轮对话填充的
}
class SlotManager {
private slots: Map<string, Slot> = new Map();
defineSlot(name: string): void {
this.slots.set(name, { name, value: null, status: 'empty' });
}
fill(name: string, value: unknown, source: string): void {
const slot = this.slots.get(name);
if (slot) {
slot.value = value;
slot.status = 'filled';
slot.source = source;
}
}
confirm(name: string): void {
const slot = this.slots.get(name);
if (slot) slot.status = 'confirmed';
}
getMissing(): string[] {
return Array.from(this.slots.values())
.filter((s) => s.status !== 'confirmed')
.map((s) => s.name);
}
}
对话策略是决定Agent下一轮说什么。策略可以是:如果信息不全,继续提问;如果信息完整,执行任务;如果有歧义,请求澄清;如果用户纠正,更新槽位。
interface DialoguePolicy {
decide(context: AgentContext): DialogueAction;
}
class InformationGatheringPolicy implements DialoguePolicy {
decide(context: AgentContext): DialogueAction {
const missing = context.slots.getMissing();
if (missing.length > 0) {
return {
type: 'ask',
slot: missing[0],
message: this.generateQuestion(missing[0]),
};
}
if (context.slots.hasAmbiguous()) {
return {
type: 'clarify',
message: this.generateClarification(),
};
}
return {
type: 'execute',
message: '信息已收集完整,开始执行...',
};
}
}
总结与最佳实践
上下文管理与状态持久化是Agent从玩具到工具的必经之路。没有它们,Agent只能处理单次、独立、简短的任务;有了它们,Agent才能处理复杂、持续、长周期的任务。
关键的最佳实践包括:
上下文优化是持续过程。不要指望一次设计好就永远适用。随着任务复杂度增加,需要不断调整上下文策略:从滑动窗口到语义分块,从简单摘要到智能压缩。
状态机让行为可预测。即使不使用完整的状态机框架,也应该为Agent定义清晰的状态和转换条件。这帮助调试,也帮助恢复。
记忆分层提高效率。短期记忆处理当前会话,长期记忆保留跨会话知识。不要什么信息都塞进prompt,该存外部的存外部,该检索时再检索。
持久化考虑版本。状态结构会变化,持久化格式要支持迁移。否则升级一次Agent就丢失所有历史状态。
恢复要智能。不是简单回到中断点,而是评估环境变化后决定是继续、重试还是重新规划。
压缩要有策略。摘要、提取、丢弃三层策略结合使用,根据信息类型选择合适的方法。
对话管理用槽位。把多轮对话看作信息收集过程,用槽位跟踪进度,用策略决定下一步行动。
最终,好的上下文管理让用户感觉不到Agent有”上下文”这个概念。Agent自然记得用户偏好,自然能从上次中断的地方继续,自然不会在长对话中迷失。当这些成为默认行为时,Agent才真正可用。