Nodejs也能写Agent - 22.LangGraph篇 - 上下文工程

上一篇我们把可观测性立起来了:streamEvents、LangSmith、结构化日志。出了错,你至少能看见「卡在哪一步」。

但说句扎心的:trace 再漂亮,也救不了窗口里塞的是垃圾 。历史消息、RAG 片段、ToolMessage 一股脑堆进去------要么超限直接报错,要么噪声淹没关键句,模型一本正经地胡话。可观测性回答「发生了什么 」;上下文工程(Context Engineering) 回答「模型看到了什么」------这才直接决定它能不能推对。

这一篇把 Context Engineering 和 Prompt Engineering 掰开,讲清一次调用里上下文怎么组成、Token 怎么预算,以及 RAG 注入时怎么少塞垃圾。

老规矩,本文以官网最新文档核对过(Context engineering in agentsShort-term memoryPrebuilt middleware)。Agent 入口继续用 createAgent------别再抄 createReactAgent。网上不少教程手写一个同名 trimMessages------别这么干 ,官方就有 trimMessages;摘要优先走 summarizationMiddleware

一、Prompt Engineering ≠ Context Engineering

PromptTemplate / ChatPromptTemplate------那是 Prompt Engineering:把单条指令写清楚、格式对、few-shot 到位。

对话一变长、接上 RAG、再套多轮 tool 调用,上下文窗口就成了稀缺资源。这时你优化的不再是「这句话怎么措辞」,而是「这一整窗里放什么、什么顺序、超了怎么砍」------这就是 Context Engineering。

维度 Prompt Engineering Context Engineering
关注点 单条 prompt 的措辞、格式、few-shot 整段上下文的组成、顺序、长度与质量
范围 通常 system + 当前 user system + 历史 + 检索片段 + 工具结果 + 元数据
目标 让模型「理解任务」 让模型「在有限窗口内看到最相关信息」

官网说得更狠一点:Agent 不可靠,往往不是模型不够聪明,而是没把「对的」上下文喂进去。AI Engineer 的头号工作,就是这件事。

二、一次调用里模型到底看到什么

先用落地直觉拆开------一次 Agent 调用的 context,通常长这样:
#mermaid-svg-b3c1beLJncSaaXYy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-b3c1beLJncSaaXYy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-b3c1beLJncSaaXYy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-b3c1beLJncSaaXYy .error-icon{fill:#552222;}#mermaid-svg-b3c1beLJncSaaXYy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-b3c1beLJncSaaXYy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-b3c1beLJncSaaXYy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-b3c1beLJncSaaXYy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-b3c1beLJncSaaXYy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-b3c1beLJncSaaXYy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-b3c1beLJncSaaXYy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-b3c1beLJncSaaXYy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-b3c1beLJncSaaXYy .marker.cross{stroke:#333333;}#mermaid-svg-b3c1beLJncSaaXYy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-b3c1beLJncSaaXYy p{margin:0;}#mermaid-svg-b3c1beLJncSaaXYy .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-b3c1beLJncSaaXYy .cluster-label text{fill:#333;}#mermaid-svg-b3c1beLJncSaaXYy .cluster-label span{color:#333;}#mermaid-svg-b3c1beLJncSaaXYy .cluster-label span p{background-color:transparent;}#mermaid-svg-b3c1beLJncSaaXYy .label text,#mermaid-svg-b3c1beLJncSaaXYy span{fill:#333;color:#333;}#mermaid-svg-b3c1beLJncSaaXYy .node rect,#mermaid-svg-b3c1beLJncSaaXYy .node circle,#mermaid-svg-b3c1beLJncSaaXYy .node ellipse,#mermaid-svg-b3c1beLJncSaaXYy .node polygon,#mermaid-svg-b3c1beLJncSaaXYy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-b3c1beLJncSaaXYy .rough-node .label text,#mermaid-svg-b3c1beLJncSaaXYy .node .label text,#mermaid-svg-b3c1beLJncSaaXYy .image-shape .label,#mermaid-svg-b3c1beLJncSaaXYy .icon-shape .label{text-anchor:middle;}#mermaid-svg-b3c1beLJncSaaXYy .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-b3c1beLJncSaaXYy .rough-node .label,#mermaid-svg-b3c1beLJncSaaXYy .node .label,#mermaid-svg-b3c1beLJncSaaXYy .image-shape .label,#mermaid-svg-b3c1beLJncSaaXYy .icon-shape .label{text-align:center;}#mermaid-svg-b3c1beLJncSaaXYy .node.clickable{cursor:pointer;}#mermaid-svg-b3c1beLJncSaaXYy .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-b3c1beLJncSaaXYy .arrowheadPath{fill:#333333;}#mermaid-svg-b3c1beLJncSaaXYy .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-b3c1beLJncSaaXYy .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-b3c1beLJncSaaXYy .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-b3c1beLJncSaaXYy .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-b3c1beLJncSaaXYy .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-b3c1beLJncSaaXYy .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-b3c1beLJncSaaXYy .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-b3c1beLJncSaaXYy .cluster text{fill:#333;}#mermaid-svg-b3c1beLJncSaaXYy .cluster span{color:#333;}#mermaid-svg-b3c1beLJncSaaXYy div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-b3c1beLJncSaaXYy .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-b3c1beLJncSaaXYy rect.text{fill:none;stroke-width:0;}#mermaid-svg-b3c1beLJncSaaXYy .icon-shape,#mermaid-svg-b3c1beLJncSaaXYy .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-b3c1beLJncSaaXYy .icon-shape p,#mermaid-svg-b3c1beLJncSaaXYy .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-b3c1beLJncSaaXYy .icon-shape .label rect,#mermaid-svg-b3c1beLJncSaaXYy .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-b3c1beLJncSaaXYy .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-b3c1beLJncSaaXYy .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-b3c1beLJncSaaXYy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Context 组成
System Prompt
历史 Messages
RAG 检索片段
Tool 结果 ToolMessage
当前 User Message

部分 来源 说明
System Prompt 固定或动态 角色、规则、工具使用约定
历史 Messages Checkpointer / Memory 多轮对话累积
RAG 片段 Retriever Top-K 注入 prompt 的参考文档
Tool 结果 ToolMessage ReAct 环里每次 tool 返回
当前 User Message 用户输入 本轮问题

官网再给你一层更完整的坐标系------你能控的不只是「消息列表」,而是三类上下文:

Context Type 你在控什么 Transient / Persistent
Model Context 进模型的东西:instructions、message history、tools、用哪颗模型、response format 多为 Transient(只改本轮喂给模型的内容)
Tool Context 工具能读什么、写什么(State / Store / Runtime Context) Persistent
Life-cycle Context 模型调用与工具调用之间发生什么(摘要、guardrails、日志......) Persistent

数据从哪来,也要分清:

数据源 范围 例子
Runtime Context 单次会话配置 userId、权限、环境
State(短期记忆) 当前 thread messages、tool 结果、上传文件
Store(长期记忆) 跨会话 用户偏好、沉淀事实

落地机制是 Middleware。 createAgentmiddleware 让你在 agent loop 的钩子上改上下文------不必把裁剪逻辑糊进业务节点。
#mermaid-svg-UuJdrSzlOJa4SCi6{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-UuJdrSzlOJa4SCi6 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UuJdrSzlOJa4SCi6 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UuJdrSzlOJa4SCi6 .error-icon{fill:#552222;}#mermaid-svg-UuJdrSzlOJa4SCi6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UuJdrSzlOJa4SCi6 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UuJdrSzlOJa4SCi6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UuJdrSzlOJa4SCi6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UuJdrSzlOJa4SCi6 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UuJdrSzlOJa4SCi6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UuJdrSzlOJa4SCi6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UuJdrSzlOJa4SCi6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UuJdrSzlOJa4SCi6 .marker.cross{stroke:#333333;}#mermaid-svg-UuJdrSzlOJa4SCi6 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UuJdrSzlOJa4SCi6 p{margin:0;}#mermaid-svg-UuJdrSzlOJa4SCi6 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-UuJdrSzlOJa4SCi6 .cluster-label text{fill:#333;}#mermaid-svg-UuJdrSzlOJa4SCi6 .cluster-label span{color:#333;}#mermaid-svg-UuJdrSzlOJa4SCi6 .cluster-label span p{background-color:transparent;}#mermaid-svg-UuJdrSzlOJa4SCi6 .label text,#mermaid-svg-UuJdrSzlOJa4SCi6 span{fill:#333;color:#333;}#mermaid-svg-UuJdrSzlOJa4SCi6 .node rect,#mermaid-svg-UuJdrSzlOJa4SCi6 .node circle,#mermaid-svg-UuJdrSzlOJa4SCi6 .node ellipse,#mermaid-svg-UuJdrSzlOJa4SCi6 .node polygon,#mermaid-svg-UuJdrSzlOJa4SCi6 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UuJdrSzlOJa4SCi6 .rough-node .label text,#mermaid-svg-UuJdrSzlOJa4SCi6 .node .label text,#mermaid-svg-UuJdrSzlOJa4SCi6 .image-shape .label,#mermaid-svg-UuJdrSzlOJa4SCi6 .icon-shape .label{text-anchor:middle;}#mermaid-svg-UuJdrSzlOJa4SCi6 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-UuJdrSzlOJa4SCi6 .rough-node .label,#mermaid-svg-UuJdrSzlOJa4SCi6 .node .label,#mermaid-svg-UuJdrSzlOJa4SCi6 .image-shape .label,#mermaid-svg-UuJdrSzlOJa4SCi6 .icon-shape .label{text-align:center;}#mermaid-svg-UuJdrSzlOJa4SCi6 .node.clickable{cursor:pointer;}#mermaid-svg-UuJdrSzlOJa4SCi6 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-UuJdrSzlOJa4SCi6 .arrowheadPath{fill:#333333;}#mermaid-svg-UuJdrSzlOJa4SCi6 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-UuJdrSzlOJa4SCi6 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-UuJdrSzlOJa4SCi6 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UuJdrSzlOJa4SCi6 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-UuJdrSzlOJa4SCi6 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UuJdrSzlOJa4SCi6 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-UuJdrSzlOJa4SCi6 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-UuJdrSzlOJa4SCi6 .cluster text{fill:#333;}#mermaid-svg-UuJdrSzlOJa4SCi6 .cluster span{color:#333;}#mermaid-svg-UuJdrSzlOJa4SCi6 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-UuJdrSzlOJa4SCi6 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-UuJdrSzlOJa4SCi6 rect.text{fill:none;stroke-width:0;}#mermaid-svg-UuJdrSzlOJa4SCi6 .icon-shape,#mermaid-svg-UuJdrSzlOJa4SCi6 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UuJdrSzlOJa4SCi6 .icon-shape p,#mermaid-svg-UuJdrSzlOJa4SCi6 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-UuJdrSzlOJa4SCi6 .icon-shape .label rect,#mermaid-svg-UuJdrSzlOJa4SCi6 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UuJdrSzlOJa4SCi6 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-UuJdrSzlOJa4SCi6 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-UuJdrSzlOJa4SCi6 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} tool_calls
done
start
beforeModel
Model Call
afterModel
Tools
end

  • wrapModelCall :改本轮送给模型的 messages / tools / prompt------瞬时,默认不改 State。
  • beforeModel / afterModel :可以返回 State 更新(例如删消息、换摘要)------持久,Checkpointer 下次还能看见。

搞不清 Transient vs Persistent,你就会踩这个坑:以为「裁过了」,结果 Checkpointer 里旧历史还在,下一轮又全塞回来。

三、Token 预算:三种控窗策略

模型窗口有限(本地小模型尤其狠)。管理原则很简单:给每块预算,超限有明确裁剪 / 压缩规则。

1. 保留最近 N 轮

只留最近几轮 user-assistant,更早的直接丢掉。实现最简单,适合短会话、demo。

别手写一个叫 trimMessages 的函数去抢官方名字------下面用官网 API。

2. 官方 trimMessages:按 token / 边界裁剪

LangChain 提供 trimMessages:按 maxTokensstrategy: "last"startOn / endOn 裁消息列表,尽量保住对话结构(例如从 human 起、在 human/tool 结束,避免 AI↔Tool 成对被拦腰砍断)。

瞬时裁剪 (只改本轮喂给模型的内容,State 原样保留)------用 wrapModelCall

typescript 复制代码
import { createAgent, createMiddleware, trimMessages } from "langchain";
import { ChatOllama } from "@langchain/ollama";
import { tool } from "@langchain/core/tools";
import * as z from "zod";

const getWeather = tool(
  async ({ city }: { city: string }) => `${city}:晴,25°C`,
  {
    name: "get_weather",
    description: "查询城市天气",
    schema: z.object({ city: z.string() }),
  }
);

/** 教学用:按「条数」近似计数;生产请换成真实 tokenCounter(或模型自带计数) */
const roughCounter = (msgs: { length: number } | unknown[]) =>
  Array.isArray(msgs) ? msgs.length : 0;

const transientTrim = createMiddleware({
  name: "TransientTrim",
  wrapModelCall: async (request, handler) => {
    const trimmed = await trimMessages(request.messages, {
      maxTokens: 12, // 演示阈值;生产按模型窗口设
      strategy: "last",
      startOn: "human",
      endOn: ["human", "tool"],
      includeSystem: true, // 保住开头的 system
      tokenCounter: roughCounter,
    });
    // override:只改本轮请求,不写回 State
    return handler(request.override({ messages: trimmed }));
  },
});

const llm = new ChatOllama({ model: "qwen2.5:7b", temperature: 0 });
const agent = createAgent({
  model: llm,
  tools: [getWeather],
  systemPrompt: "需要天气时调用 get_weather。回答简洁。",
  middleware: [transientTrim],
});

持久裁剪 (真把 State 里的旧消息清掉,配合 Checkpointer 才有意义)------beforeModel + RemoveMessage

typescript 复制代码
import { RemoveMessage } from "@langchain/core/messages";
import { createAgent, createMiddleware, trimMessages } from "langchain";
import { MemorySaver, REMOVE_ALL_MESSAGES } from "@langchain/langgraph";
import { ChatOllama } from "@langchain/ollama";

const persistTrim = createMiddleware({
  name: "PersistTrim",
  beforeModel: async (state) => {
    const trimmed = await trimMessages(state.messages, {
      maxTokens: 20,
      strategy: "last",
      startOn: "human",
      endOn: ["human", "tool"],
      includeSystem: true,
      tokenCounter: (msgs) => msgs.length, // 演示用;生产换真实计数
    });
    // 先清空再写入裁剪结果 → State 永久变短
    return {
      messages: [new RemoveMessage({ id: REMOVE_ALL_MESSAGES }), ...trimmed],
    };
  },
});

const agent = createAgent({
  model: new ChatOllama({ model: "qwen2.5:7b", temperature: 0 }),
  tools: [],
  middleware: [persistTrim],
  checkpointer: new MemorySaver(),
});

// 同一 thread_id 多轮 invoke:裁剪结果会跟着存档走
await agent.invoke(
  { messages: [{ role: "user", content: "我叫小明" }] },
  { configurable: { thread_id: "u-1" } }
);
策略 改 State? 适合
wrapModelCall + trimMessages 否(Transient) 调试、按调用临时瘦身、还想保留完整审计历史
beforeModel + RemoveMessage 是(Persistent) Checkpointer 长会话,必须真的减负
summarizationMiddleware 是(Persistent) 长对话要「记得大概」,不能硬砍细节

3. 摘要压缩:summarizationMiddleware

硬 trim 会丢信息。长会话更常见的做法:旧消息用另一颗(可更小更便宜的)模型压成摘要,永久写回 State,只保留最近若干条原文。

typescript 复制代码
import { createAgent, summarizationMiddleware } from "langchain";
import { ChatOllama } from "@langchain/ollama";
import { MemorySaver } from "@langchain/langgraph";

const chatModel = new ChatOllama({ model: "qwen2.5:7b", temperature: 0 });
// 摘要可以用同一模型,生产常换成更小/更便宜的
const summaryModel = new ChatOllama({ model: "qwen2.5:7b", temperature: 0 });

const agent = createAgent({
  model: chatModel,
  tools: [],
  checkpointer: new MemorySaver(),
  middleware: [
    summarizationMiddleware({
      model: summaryModel,
      trigger: { tokens: 4000 }, // 越过阈值才摘要
      keep: { messages: 20 }, // 保留最近 20 条原文
    }),
  ],
});

触发条件还可写成「多条件 AND」或「数组 OR」(见官网 Prebuilt middleware)。注意:摘要是文本向压缩------多模态大图不会被「压小」,只会被摘要文字替代;图多的场景要把媒体放对象存储,消息里只留 URL。

四、RAG 怎么注入才不搅浑

这里只盯一件事:检索到的文档怎么塞进 prompt------格式不对,模型分不清「资料」和「问题」,引用也乱。

分隔符 + 引用格式

text 复制代码
--- 检索到的参考文档 ---
[来源: doc-a.md]
......
--- 参考文档结束 ---

用户问题:......

并明确要求:答不出就说不知道;引用时标 [来源: xxx]

typescript 复制代码
import { Document } from "@langchain/core/documents";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { ChatOllama } from "@langchain/ollama";
import { StringOutputParser } from "@langchain/core/output_parsers";
import { RunnableSequence } from "@langchain/core/runnables";

/** 把检索文档格式化成带来源的 context */
function formatRagContext(docs: Document[]): string {
  return docs
    .map(
      (d, i) =>
        `[来源: ${d.metadata.source ?? `doc-${i}`}]\n${d.pageContent}`
    )
    .join("\n\n---\n\n");
}

const prompt = ChatPromptTemplate.fromTemplate(
  `根据以下参考文档回答问题。若无法从文档得出答案,请说「我不知道」。
回答时请用 [来源: xxx] 标注引用。

--- 检索到的参考文档 ---
{context}
--- 参考文档结束 ---

用户问题:{question}`
);

const llm = new ChatOllama({ model: "qwen2.5:7b", temperature: 0 });

const ragChain = RunnableSequence.from([
  async (input: { question: string; docs: Document[] }) => ({
    context: formatRagContext(input.docs),
    question: input.question,
  }),
  prompt,
  llm,
  new StringOutputParser(),
]);

// ragChain.invoke({ question: "...", docs: retrievedDocs })

Top-K 与 chunk 也是预算

旋钮 太大 建议起步
k 噪声淹没相关句 3~5
chunkSize 单条占满窗口 视文档类型:FAQ 可 200~300,论述可更大

多轮 + RAG + tool 结果三者叠加时,先给历史 / RAG / tools 各自定预算 ,再决定 trim 还是摘要------别等 API 报 context length exceeded 再救火。

工具结果特别脏、特别长时,还可以看官网的 contextEditingMiddleware(如 ClearToolUsesEdit:专门清旧 tool 调用块,避免 ToolMessage 永久占地。本篇不展开,知道有这号预置中间件即可。

五、反模式速查表

反模式 后果 缓解
塞满 context 「迷失」在无关信息里,忽略关键句 预算 + trim / 摘要
检索噪声淹没相关信息 基于错误资料一本正经胡答 k、重排、分隔符、强制「无则不知」
历史从不裁剪 超限报错或被截断 trimMessages / summarizationMiddleware
Tool 结果永不清理 ToolMessage 挤占 user 问题空间 只留近几轮 tool;或 context editing
system prompt 过长 规则/工具说明占满窗口 精简 system;动态工具子集(官网 Tool Context)
把瞬时 trim 当永久清理 Checkpointer 下轮又全量塞回 长会话用 Persistent 策略

常见坑

  1. 只改 Prompt 不管理 context:长对话 + RAG 后必然爆窗。
  2. RAG 无分隔直接拼接:模型分不清文档与问题。
  3. trim 丢掉 system :角色与规则蒸发------设 includeSystem: true,或保证 system 始终在保留集里。
  4. 裁断 AI↔Tool 成对消息 :部分供应商会直接拒收非法历史------用 startOn / endOn 保结构。
  5. k 过大:10+ chunk 塞满窗口,噪声赢相关。
  6. 摘要当真理:细节会丢;关键事实该进 Store / 结构化记忆,别全靠一段 summary。
  7. 自写同名 trimMessages:和官方 API 撞车,后人难维护------用官网的。
相关推荐
Tangyuewei1 小时前
单 Agent 不够用了:AI 编程进入舰队模式
人工智能
艾斯特_1 小时前
工作流与多Agent协作:LangGraph、MCP和A2A的应用分层
人工智能·python·ai
悟天特斯1 小时前
智慧楼宇AI节能:从数据采集到智能决策的全链路实践
大数据·人工智能
腾视科技AIoT1 小时前
私有云时代来临:AI NAS如何重塑你的数字生活?
人工智能·ai·生活·nas·企业存储·ainas·家庭存储
IT小盘1 小时前
04-大模型流式输出原理-SSE与Python实现
开发语言·网络·人工智能·python
阳光是sunny2 小时前
LangGraph实战教程:预定义状态MessagesState与AgentState
前端·人工智能·后端
网络工程小王2 小时前
【HCIE-AI】11.模型 昇腾迁移适配-精度调试-性能调优
人工智能·学习·华为·迁移学习·昇腾
不如语冰2 小时前
AI大模型入门-Python进阶-上下文管理与with语句
开发语言·数据结构·数据库·人工智能·pytorch·redis·python