一、核心挑战与原则(为什么要做 Context Engineering)
1. 上下文窗口的根本限制
-
LLM 的「工作内存」是有限的(context window),把太多东西塞进去会导致:
-
分心(Context Distraction) :历史信息过多,模型过度依赖历史行为,丧失即时推理质量。
-
混乱(Context Confusion) :无关文档或工具描述干扰工具选择或答案生成。
-
冲突(Context Clash) :不同来源互相矛盾,模型不知道取谁。
-
污染(Context Poisoning) :错误信息被保存并被反复利用,错误放大。
-
原则结论:不是盲目追求更大上下文窗,而是做“上下文卫生”:
-
验证(Quality Validation)
-
总结/压缩(Summarization / Compression)
-
剪枝(Pruning)
-
外部卸载(Offload to DB / vector store)
2. 系统架构(动态流水线)
书中强调:不要把系统设计成固定的 “retrieve → generate”,而要把它做成一个 动态决策循环:
用户输入 → 查询增强 → 检索(RAG)→ 提示(Prompt)→ 智能体协调(Agent)→ 工具行动(Tool call)→ 记忆更新 → 输出
智能体会在各环节做判断:是否需要检索、是否需要重写查询、是否选择工具、何时压缩历史等。
二、关键组件精要(以及在工程中的实操建议)
下面每一部分都给出“为什么重要”“工程实践要点”“示例/伪码或注意事项”。
1. 智能体(Agent)——系统的大脑
为什么重要:负责把静态步骤变成动态策略(是否重试检索、是否切换知识库、是否链式调用工具)。
工程实践要点:
-
把 agent 设计为决策层而非直接执行器:它输出“计划”(plan)与“动作(要调用哪个工具、参数)”,由执行层负责调用和安全检查。
-
区分单体 agent(适用于中等复杂任务)与多体架构(specialized agents):
- 单体:实现简单,内部负责多任务决策。
- 多体:每个 agent 专注一类任务(query agent、memory agent、tool agent),需要 orchestration/arbiter。
-
添加反脆弱措施:当工具返回错误或检索结果不相关时,agent 应能 重写查询 / 切换策略 / 降级回答。
示例:Agent 输出计划的伪 JSON
{
"plan": [
{"step": "rewrite_query", "args": {"strategy": "add_keywords"}},
{"step": "search_vector_db", "args": {"collection": "product_docs", "k": 5}},
{"step": "call_tool", "args": {"name": "price_checker", "params": {"sku": "123"}}}
],
"thoughts": "initial query is vague; will expand and search product_docs first"
}
2. 查询增强(Query Augmentation)——把模糊请求变成可检索的查询
关键技术:
-
重写(Rewriting) :清理噪音、补充领域关键词、恢复省略的上下文。
-
扩展(Expansion) :生成多个查询变体以覆盖不同表达(注意避免过度扩展导致 drift)。
-
分解(Decomposition) :把复杂问题拆成子问题并行检索/处理。
-
Query Agent:高级版本,用 agent 动态决定查询策略、路由多个集合、迭代改进查询。
工程建议:
-
用 LLM 做 rewrite/expansion,但限制每次扩展的数量(例如最多 3 个变体)。
-
对扩展结果进行 rerank(可用 LLM 对检索结果做再排序)以控制噪声。
-
对于复杂任务,优先做 decomposition 并在合成阶段把子答案拼接并做一致性验证。
伪码示例(Node.js 风格)
async function augmentQuery(rawQuery) {
const rewritten = await callLLMRewrite(rawQuery);
const expansions = await callLLMExpand(rewritten); // returns up to N variants
return [rewritten, ...expansions];
}
3. 检索(Retrieval)——把外部知识精确带入模型
核心:Chunking(分块)设计决定检索质量。
分块策略概览:
-
Fixed-size:简单、快速,但可能截断语义。
-
Recursive(递归) :按段落→句子→词级别优先分割,保结构。
-
Document-based:按文档语义/标记(Markdown headings / HTML tags)。
-
Semantic / LLM-based:用 LLM 自动判断语义界限,生成语义完整的 chunk。
-
Late-chunking:先用长上下文模型嵌入整文,再基于 token-level 做 chunk(保语境)。
Pre vs Post chunking:
-
Pre-chunking:离线处理,查询时快速;缺点是策略固定,改变需重建索引。
-
Post-chunking(Chunk on demand) :检索整文后根据查询动态切分,灵活但有延迟。
工程实践:
-
对于常变内容/长文档建议采用 后分块 或 layered (hierarchical) chunk + 预计算摘要。
-
总是在 chunk 中保存足够的来源元数据(doc id、章节、位置信息)用于溯源和来源显示。
-
对检索返回结果做 二次 rerank(用 LLM 打分),再把最相关的 2-4 个 chunk 注入 prompt。
JS 分块伪实现(递归)
function recursiveChunk(text, maxTokens, separators = ['\n\n', '\n', '. ']) {
// 如果 text token 数 <= maxTokens 返回 [text]
// 否则按第一个合适的 separator 切分,然后对每个片段递归调用,保证不超限并保留上下文
}
4. 提示技术(Prompting)——把检索到的信息引导进准确结论
经典技巧:
-
Chain of Thought (CoT) :要求模型按步骤推理(在需要复杂推理时很有效)。
-
Few-shot:提供示例以引导输出格式和风格。
-
ReAct:交替输出“思考(thought)”与“动作(action)”,适合需要工具交互的场景。
工具提示(Tool Prompting)要点:
-
明确 函数名、参数类型与含义、返回值结构、限制(例如时区、地域限制)。
-
给出 few-shot 示例说明何时使用该工具、如何填参数、期望结果格式。
示例工具描述片段(system prompt 用)****
get_weather(city: string, date: string) — returns {high, low, condition}. Use only for user requests that ask current or forecast weather; do not call for historical queries.
5. 记忆(Memory)——让 agent 有“历史感”
记忆类型:
-
短期(Short-term / working memory) :存在 context window,用于当前任务。
-
长期(Long-term) :存在 vector DB 等外部存储,用于跨会话的偏好/事实调用(episodic/semantic/procedural)。
管理原则:
-
选择性存储:不是什么都存。用重要性评分(LLM 自动评估)作为入库门槛。
-
定期修剪:按时间 & 访问频率 & 可信度清理旧记忆(例如超过 90 天且不常用的记忆只保留摘要)。
-
任务定制检索:不同任务路由到不同记忆集合(support tickets → episodic;product specs → semantic)。
实操建议:
- 写一个 shouldSave(memory) 的决策函数,基于关键词、重要性分数、上下文角色等。
- 保存的记忆分为“完整记录(稀有)”与“摘要(常态)”,摘要更便于检索与避免污染。
6. 工具(Tools)——让 agent 能“做事”
从欺骗输出到函数/工具调用:早期靠 prompt 生成命令文本,现行做法是 函数调用(structured function call) 。关键在于工具描述与安全边界。
工程实践:
-
工具 metadata 非常重要:name, inputs (types + formats), outputs, limitations。把它们放入 system prompt 或 agent 的工具目录中。
-
执行层在调用前做 参数验证 & 权限检查(防止 LLM 注入恶意参数)。
-
设计工具时考虑“可回滚/幂等性” —— 如果动作失败,如何补偿或重试。
-
跟踪每次工具调用的观测(observation),并将结果 feed 回 agent 作为思考输入(Thought-Action-Observation 循环)。
未来方向:MCP(Model Context Protocol)等标准化协议会简化工具间集成——用标准接口替代海量定制代码。
三、端到端工程样板(Orchestration & Implementation Tips)
下面给出一个实用的 orchestration loop(伪代码)和各环节的工程注意事项。以 Node.js / TypeScript 风格说明。
async function handleUserRequest(userId, rawQuery) {
// 1. Query augmentation
const queries = await augmentQuery(rawQuery); // rewrite + expansions
// 2. Agent analysis: plan what to do
const plan = await agentPlan(userId, queries); // may output steps
// 3. Execute plan step-by-step
for (const step of plan) {
if (step.type === 'search') {
const docs = await vectorSearch(step.collection, step.query, {k: step.k});
const reranked = await rerankWithLLM(docs, rawQuery);
step.result = reranked;
}
if (step.type === 'call_tool') {
// validate params
validate(step.args, toolSchema[step.toolName]);
const res = await callTool(step.toolName, step.args);
step.result = res;
}
// Agent inspects observations and may alter plan (self-healing)
await agentReflect(step, step.result);
}
// 4. Synthesis: use LLM to compose final answer using retrieved chunks + tool outputs
const finalAnswer = await generateAnswer({
promptTemplate,
retrieved: collectTopChunks(plan),
toolsOutput: collectToolResults(plan),
history: getWorkingMemory(userId)
});
// 5. Memory update: decide whether to store summary
if (shouldSaveMemory(rawQuery, finalAnswer)) {
saveMemory(userId, summarize(finalAnswer));
}
return finalAnswer;
}
/**
* Orchestration Skeleton - TypeScript
*
* Single-file project skeleton that demonstrates an orchestration loop for
* a RAG + Agent system. Includes stubs for:
* - QueryAugmentService
* - VectorSearchAdapter (in-memory stub)
* - AgentPlanner (planner that outputs steps)
* - ToolExecutor (structured tool call + validation + auditing)
* - MemoryStore (short + long term memory stubs)
*
* Usage:
* - import { Orchestrator } from './orchestration-skeleton'
* - call orchestrator.handleUserRequest(userId, rawQuery)
*
* This file is intentionally framework-agnostic and contains synchronous
* and asynchronous placeholders that you'll replace with real implementations
* (embedding model, vector DB client, LLM call, auth, telemetry, etc.).
*
* Design goals:
* - Clear interfaces for each component
* - Small, testable pieces for early integration tests
* - Instrumentation hooks (events/logging)
*/
// ---------------------------
// Types
// ---------------------------
export type UserId = string;
export type Query = string;
export type Chunk = {
id: string;
text: string;
docId?: string;
meta?: Record<string, any>;
score?: number; // optional re-rank score
};
export type ToolCall = {
name: string;
args: Record<string, any>;
};
export type ToolResult = {
success: boolean;
output?: any;
error?: string;
};
export type PlanStep =
| { type: 'rewrite'; payload?: any }
| { type: 'search'; collection: string; query: string; k?: number }
| { type: 'call_tool'; tool: ToolCall }
| { type: 'synthesize' }
| { type: 'save_memory'; payload?: any };
export type Plan = { steps: PlanStep[]; thoughts?: string };
// ---------------------------
// Utilities / Simple Logger
// ---------------------------
export const nowIso = () => new Date().toISOString();
export const log = (...args: any[]) => {
// replace with structured logger in prod
console.log('[orchestration]', nowIso(), ...args);
};
// ---------------------------
// Query Augmentation Service
// ---------------------------
export class QueryAugmentService {
constructor(private opts?: { maxExpansions?: number }) {}
async rewrite(raw: Query): Promise<string> {
// TODO: replace with LLM rewrite call
log('rewrite.raw', raw);
const cleaned = raw.trim();
// simple heuristic: remove multiple spaces
return cleaned.replace(/\s+/g, ' ');
}
async expand(rewritten: string): Promise<string[]> {
// TODO: replace with LLM expansion
log('expand.rewritten', rewritten);
const max = this.opts?.maxExpansions ?? 2;
// naive expansions: synonyms or short variants (stub)
const variants = [rewritten];
if (max >= 1) variants.push(rewritten + ' detailed');
if (max >= 2) variants.push(rewritten + ' summary');
return variants.slice(0, max + 1);
}
async decompose(raw: Query): Promise<string[]> {
// for complex queries return array of sub-queries
// TODO: use LLM to split into semantically coherent pieces
log('decompose', raw);
// trivial: return raw as single sub-query
return [raw];
}
async augment(raw: Query) {
const rewritten = await this.rewrite(raw);
const expansions = await this.expand(rewritten);
const decomposed = await this.decompose(rewritten);
return { rewritten, expansions, decomposed };
}
}
// ---------------------------
// Vector Search Adapter (in-memory stub)
// ---------------------------
export class VectorSearchAdapter {
private store: Map<string, Chunk[]> = new Map();
// index into a collection
index(collection: string, chunks: Chunk[]) {
this.store.set(collection, chunks.slice());
log('vector.index', collection, chunks.length);
}
// simple similarity-based stub: returns top-k by substring match + fallback
async search(collection: string, query: string, k = 4): Promise<Chunk[]> {
const rows = this.store.get(collection) ?? [];
// naive scoring: containment + length
const scored = rows.map((c) => {
let score = 0;
if (c.text.toLowerCase().includes(query.toLowerCase())) score += 10;
// shorter distance roughly better
score += Math.max(0, 5 - Math.abs(c.text.length - query.length) / 100);
return { ...c, score } as Chunk;
});
scored.sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
const res = scored.slice(0, k);
log('vector.search', collection, query, 'returned', res.length);
return res;
}
}
// ---------------------------
// Reranker (LLM-based stub)
// ---------------------------
export async function rerankWithLLM(chunks: Chunk[], query: string): Promise<Chunk[]> {
// TODO: call light LLM to score relevance; here we rely on existing score
log('rerank', 'query', query, 'candidates', chunks.length);
// stable sort by score desc
return chunks.sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
}
// ---------------------------
// Agent Planner (stub)
// ---------------------------
export class AgentPlanner {
constructor(private opts?: { defaultCollection?: string }) {}
async plan(userId: UserId, augmented: { rewritten: string; expansions: string[]; decomposed: string[] }): Promise<Plan> {
log('planner.plan', userId, augmented.rewritten);
// naive planner: search top collection with rewritten and expansions, then synthesize
const steps: PlanStep[] = [];
const collection = this.opts?.defaultCollection ?? 'default_docs';
steps.push({ type: 'rewrite' });
// We'll add a search step for each expansion (capped)
for (const q of augmented.expansions.slice(0, 3)) {
steps.push({ type: 'search', collection, query: q, k: 4 });
}
// final synthesis step
steps.push({ type: 'synthesize' });
// optionally save memory
steps.push({ type: 'save_memory' });
return { steps, thoughts: 'baseline planner: expand->search->synth->save' };
}
}
// ---------------------------
// Tool Executor (stub + validator + audit)
// ---------------------------
export class ToolExecutor {
constructor(private toolset: Record<string, (args: any) => Promise<ToolResult>>) {}
async execute(call: ToolCall): Promise<ToolResult> {
log('tool.execute.request', call.name, call.args);
const fn = this.toolset[call.name];
if (!fn) {
const err = `tool ${call.name} not found`;
log('tool.execute.error', err);
return { success: false, error: err };
}
// naive param validation (can be extended with JSON Schema)
try {
const res = await fn(call.args);
log('tool.execute.success', call.name);
return res;
} catch (e: any) {
log('tool.execute.exception', e?.message ?? String(e));
return { success: false, error: e?.message ?? String(e) };
}
}
}
// Example toolset (mock)
export const exampleToolset = {
price_checker: async (args: any) => {
// simulate a price lookup
if (!args?.sku) return { success: false, error: 'missing sku' };
return { success: true, output: { sku: args.sku, price: 123.45, currency: 'USD' } };
},
get_weather: async (args: any) => {
if (!args?.city) return { success: false, error: 'missing city' };
return { success: true, output: { city: args.city, high: 24, low: 16, condition: 'Sunny' } };
}
};
// ---------------------------
// Memory Store (short + long term stub)
// ---------------------------
export class MemoryStore {
// short-term (working buffer), keyed by user + task id
private shortTerm = new Map<string, any[]>();
// long-term vectorized store (we store summaries as text here)
private longTerm = new Map<string, { id: string; summary: string; meta?: any }[]>();
pushShort(userId: UserId, item: any) {
const arr = this.shortTerm.get(userId) ?? [];
arr.push(item);
this.shortTerm.set(userId, arr.slice(-50)); // cap
log('memory.pushShort', userId);
}
readShort(userId: UserId) {
return this.shortTerm.get(userId) ?? [];
}
async saveLong(userId: UserId, summary: string, meta?: any) {
const arr = this.longTerm.get(userId) ?? [];
arr.push({ id: `${Date.now()}`, summary, meta });
this.longTerm.set(userId, arr);
log('memory.saveLong', userId, 'total', arr.length);
return arr[arr.length - 1];
}
async queryLong(userId: UserId, query: string): Promise<{ id: string; score: number; summary: string }[]> {
// naive text match
const arr = this.longTerm.get(userId) ?? [];
const scored = arr.map((r) => ({ id: r.id, summary: r.summary, score: r.summary.includes(query) ? 10 : 0 }));
scored.sort((a, b) => b.score - a.score);
return scored.slice(0, 5);
}
// pruning example
pruneLong(userId: UserId, keep = 10) {
const arr = this.longTerm.get(userId) ?? [];
if (arr.length <= keep) return;
this.longTerm.set(userId, arr.slice(-keep));
log('memory.pruneLong', userId, 'kept', keep);
}
}
// ---------------------------
// LLM generation stub
// ---------------------------
export async function generateFinalAnswer(opts: { promptTemplate: string; retrieved: Chunk[]; toolsOutput?: any[]; history?: any[]; userId?: UserId; rawQuery?: string }): Promise<string> {
// TODO: call production LLM (e.g., OpenAI chat completion) here
log('generateFinalAnswer', 'retrieved', opts.retrieved.length);
const pieces = opts.retrieved.map((c, i) => `(${i + 1}) ${c.text.slice(0, 200)}`);
const tools = (opts.toolsOutput ?? []).map((t, i) => `tool[${i}]=${JSON.stringify(t)}`);
return `ANSWER DRAFT:\nQuery: ${opts.rawQuery}\n\nRetrieved:\n${pieces.join('\n')}\n\nTools:\n${tools.join('\n')}`;
}
// ---------------------------
// Orchestrator - glue everything
// ---------------------------
export class Orchestrator {
constructor(
private augmentSvc: QueryAugmentService,
private vectorAdapter: VectorSearchAdapter,
private planner: AgentPlanner,
private toolExecutor: ToolExecutor,
private memory: MemoryStore
) {}
async handleUserRequest(userId: UserId, rawQuery: Query) {
log('handleUserRequest.start', userId, rawQuery);
// 1. augment
const augmented = await this.augmentSvc.augment(rawQuery);
// 2. plan
const plan = await this.planner.plan(userId, augmented);
log('plan', JSON.stringify(plan, null, 2));
// containers for results
const searches: Chunk[] = [];
const toolOutputs: any[] = [];
// 3. execute steps
for (const step of plan.steps) {
if (step.type === 'rewrite') {
// already performed; no-op
continue;
}
if (step.type === 'search') {
const rawResults = await this.vectorAdapter.search(step.collection, step.query, step.k ?? 4);
const reranked = await rerankWithLLM(rawResults, step.query);
searches.push(...reranked);
}
if (step.type === 'call_tool') {
const res = await this.toolExecutor.execute(step.tool);
toolOutputs.push(res);
}
if (step.type === 'synthesize') {
// limit chunks to top 4 unique by id
const top = dedupeChunks(searches).slice(0, 4);
const answer = await generateFinalAnswer({ promptTemplate: '', retrieved: top, toolsOutput: toolOutputs, history: this.memory.readShort(userId), userId, rawQuery });
// push to short term memory
this.memory.pushShort(userId, { query: rawQuery, answer, at: nowIso() });
// continue loop; save_memory step will persist summary
}
if (step.type === 'save_memory') {
// save a short summary into long-term memory
const summary = `Q: ${rawQuery} | A: (summary candidate) saved at ${nowIso()}`;
await this.memory.saveLong(userId, summary, { source: 'auto' });
}
}
// final assembled output (for this skeleton we return last short-term answer)
const last = this.memory.readShort(userId).slice(-1)[0];
log('handleUserRequest.end', userId);
return last ?? { query: rawQuery, answer: 'no answer generated' };
}
}
function dedupeChunks(chunks: Chunk[]) {
const map = new Map<string, Chunk>();
for (const c of chunks) {
if (!map.has(c.id)) map.set(c.id, c);
}
return Array.from(map.values()).sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
}
// ---------------------------
// Example wiring for local dev / tests
// ---------------------------
export function createExampleOrchestrator() {
const augment = new QueryAugmentService({ maxExpansions: 2 });
const vector = new VectorSearchAdapter();
const planner = new AgentPlanner({ defaultCollection: 'default_docs' });
const toolExec = new ToolExecutor(exampleToolset);
const memory = new MemoryStore();
// seed vector store with simple chunks
vector.index('default_docs', [
{ id: 'c1', text: 'How to set up RAG with vector DB and chunking', docId: 'doc1' },
{ id: 'c2', text: 'Chunking strategies: recursive, semantic, hierarchical', docId: 'doc1' },
{ id: 'c3', text: 'Agents coordinate tools, memory and retrieval for robust systems', docId: 'doc2' },
{ id: 'c4', text: 'Tool calling uses JSON schema and parameter validation', docId: 'doc3' }
]);
const orchestrator = new Orchestrator(augment, vector, planner, toolExec, memory);
return orchestrator;
}
// ---------------------------
// If run directly (node ts-node) demo
// ---------------------------
if (require.main === module) {
(async () => {
const orch = createExampleOrchestrator();
const res = await orch.handleUserRequest('user_1', 'how to build rag system for docs');
console.log('demo result:', res);
})();
}
工程注意事项:
- 每一步都应有超时/重试策略,避免单点阻塞。
- 保持端到端可观测性:请求 id、每次检索/工具调用的 latency、返回的 top-k 文档 id、LLM token cost、agent 决策日志。
- 在生产中把 LLM 调用结果(尤其是 rerank 分数、工具观测)写进追踪系统,方便后期调优与审计。
四、落地实践清单(Checklist & 经验指南)
- 分块策略先试验再决定:先用 Recursive 或 Document-based 作为 baseline;对长报告或法规文本考虑 hierarchical / late-chunking。
- 查询增强放在检索前:实现 rewrite + 1–3 个 expansion;对复杂 query 做 decomposition。
- 检索后做 rerank:用小模型或 LLM 对 top-N 做二次排序,保留 2–4 个最相关 chunk 注入 prompt。
- Prompt 模板工程化:把 system prompt、tool 描述、few-shot 示例作为可配置文件维护;版本化管理。
- Memory 策略化:实现 scoreMemory() 和 pruneMemory() 两个定时任务;长期保存摘要而非冗长日志。
- Tool 安全层:工具调用前校验参数、权限、并记录调用链。
- Agent 可解释性:把 agent 的“thoughts/plan/decisions”记录出来,方便排错与合规稽查。
- 性能与成本衡量:监控 LLM token 用量、向量搜索次数、工具调用次数并以 SLO/成本目标调整返回粒度与策略(例如缓存、高频意图答复用固定模板)。
- 用户体验:在不确定或信息不足时,优先用“澄清问题”的策略而非盲答;在展示结果时给出来源与置信度(“基于 X 文档”)。
五、常见场景实战示例(简短)
场景 A:企业文档问答(Legal / Policy)
-
Chunking:文档-based + hierarchical(章节摘要 + 段落 chunk)
-
Query flow:rewrite → expand → search → rerank → CoT + cite sources
-
Memory:只保存用户偏好与重要 follow-up 任务
场景 B:客服机器人(多轮会话)
-
Memory:强化 episodic(过往对话摘要)
-
Agent:判断是否需要工具(创建 ticket、查询订单)
-
Tools:严格权限与幂等性(创建 ticket 要返回 id,支持回滚)
场景 C:数据分析助理(需要 DSL 或 SQL)
- Decompose:把用户问题拆成子查询(列名/过滤器/汇总)
- Tools:调用数据库/可视化 API(工具参数必须严格范式化)
- ReAct:在需要时交替“思考(SQL)— 执行(运行 SQL)— 观察(结果)— 决定下一步”
六、常见坑与对策(工程维护角度)
-
坑:上下文污染(hallucinated facts in memory)
对策:在写入长期记忆前用 LLM 做“反思”评分,并要求高置信度+来源,或只保存摘要和来源引用。
-
坑:检索到的 chunk 太长或太短
对策:把 chunk 大小/重叠设为可配置参数,A/B 测试不同策略(比如 300–800 tokens),并监控检索命中率与生成质量。
-
坑:工具误用或越权
对策:工具描述 + 执行层参数校验 + 权限层 + 审计日志。
-
坑:成本失控(频繁 LLM 调用)
对策:缓存常见问答、对低价值请求使用小模型或模板、批量化检索/合成。
七、结论与下一步路线(给工程团队的行动建议)
- 先搭一个最小可运行的 pipeline(MVP) :Pre-chunk + vector DB + basic query-rewrite + rerank + LLM generation + simple memory(保存摘要)。验证端到端效果并测稳定性与成本。
- 把 agent 放在 orchestration 层:开始时做简单 rule-based agent,再逐步替换为 LLM-based planner(带解释日志)。
- 引入观测与回路:收集检索质量、rerank 分数、用户反馈,搭建闭环优化策略(自动调整 chunk 大小、rewrite 模式)。
- 定义 memory policy:明确哪些信息入库、如何打分、何时 prune。
- 工具接口标准化:结构化描述工具并在 system prompt 内声明,执行层做校验与审计。
参考(基于电子书核心观点的工程化实现)
- 把“上下文卫生”作为长期工程目标:Quality validation、Summarization、Pruning、Offloading。
- 采用动态 orchestration(Agent)而非静态 pipeline。
- 深入优化分块(chunking)策略,并用后分块或 hierarchical chunk 在需要时恢复上下文。
- 通过 Query Augmentation(rewrite/expand/decompose)显著提升检索召回与精度。
- 记忆系统要有“写入门槛”和“维护任务”,以避免污染。
- 工具使用以函数调用/JSON schema 为主,并且要可审计、可回滚。
- 原文-- 8738733.fs1.hubspotusercontent-na1.net/hubfs/87387…