🔥🔥【封闭结束10】Agent 回复准确率优化与问题定位:别一出错就改 Prompt

1 阅读22分钟

大模型刚能落地那会儿起,我就一直在做相关落地:RAG、AI Coding、workflow、harness、自动化、工作台,也做 MCP、Skills。有的项目在业务里跑稳了,有的半路停了。常听到一句:「模型决定了 Agent 能力。」我觉得只对一半。模型决定能力上限,能不能用得住、错了能否查清原因,更取决于会话、工具、记忆、权限、Trace、回归测试这一整套 Agent 工程(业内也常叫 harness,指模型外面的运行与评测壳子)。
「封闭结束」系列共十篇文章,本篇是第 10 篇,也是本系列最后一篇。用户觉得「答错了」,往往不是模型突然变笨,也不一定是 Prompt 没写好,而是从听懂用户、记住上下文、调工具到生成答案,中间某一步可能已经对不上了。我们要做的,是先用运行链路(Trace)找到第一个对不上的地方,再决定改会话、工具还是 Prompt,并把案例写进回归测试。
希望十篇读下来,你对「生产 Agent 怎么跑起来」有一条完整线索。

继续用系列中的案例 OpsPilot(企业运维助手,虚构)。线上有人反馈:

用户:"明明说的是生产环境,怎么查成测试环境了?"

团队最常见的反应,是打开主 Prompt,再加几条约束。当天可能好一点,过两天换个说法,问题又出现了。最终导致:Prompt 越写越长,却还是说不清究竟错在哪里。

根本问题在于:用户看到的只是最后一句回复,前面还有意图理解、会话记忆、工具参数、知识检索等好几步。任何一步错了,都可能表现成“模型答错了”。

下面先用上面用户这条问题反馈,从头查一遍问题出在哪。然后我们再看如何用L1–L5查问题,常见问题修复、幻觉排查,最后把案例写进回归测试。

这条问题反馈怎么查:先打开 Trace

值班同学用 traceId 拉出这一次完整运行记录。要把问题查清楚,必须看见 Agent 实际执行了什么,而不是只看用户截图里的最后一句回复。

Trace(运行日志):单次 Agent 执行的结构化记录,至少包含用户输入、会话上下文、意图/工具调用参数、工具返回摘要,以及答案中的关键声明与证据引用;用 traceId 把用户的一次请求从 UI 到模型、工具串起来。查问题时要看每一步留了什么记录,不要只看最终回复。

下面是一条简化的 OpsPilot Trace(与【封闭结束】系列开篇「生产查成测试」是同一类线上问题,这里补全了答案声明字段):

const trace = {
  traceId: 'tr_ops_001', // 一次请求的唯一 id,线上反馈时用 traceId 拉全链路
  input: '查一下生产环境 order-service 的错误日志', // 用户原话

  context: {
    tenantId: 'tenant-acme',
    currentEnvironment: 'prod', // 会话里记下的当前环境
  },

  intent: {
    type: 'query_logs',
    environment: 'prod', // 意图识别结果仍是生产
  },

  toolCalls: [
    {
      callId: 'call_1',
      name: 'query_logs',
      args: {
        service: 'order-service',
        environment: 'test', // 第一处明显偏差:参数被填成 test
      },
      status: 'success', // 接口返回成功,不等于环境查对了
    },
  ],

  answerClaims: [
    {
      text: '以下是生产环境日志',
      evidenceCallIds: ['call_1'], // 声称依据这次查询,但这次查的其实是测试库
    },
  ],
}

这条 Trace 暴露了什么?

  • 用户明确要求查询生产环境。
  • 会话状态中的环境是 prod。
  • 意图识别结果也是 prod。
  • 真正发起工具调用时,参数却变成了 test。
  • 工具虽然执行成功,但返回的是错误环境的数据。
  • 最终答案又把它描述成生产环境日志。

因此,第一处明显偏差在 L3:工具参数生成或传参环节(意图和会话都对,错在真正去调工具时填进去的参数)。最后一句还在说「生产环境日志」,看起来像在 L5 胡说,其实前面参数已经错了;若没看 Trace,很容易去改 RAG 或换模型。

团队常走的弯路,和更稳的修法对比如下:

容易误修更稳的正修
主 Prompt 再加一句「务必使用生产环境」查参数在哪一步从 prod 变成 test;在 Tool Runtime 发请求前做一致性校验
换更强模型本例意图已是 prod,换模型往往复现同一参数错误
先动向量库 / RAG这次请求根本没走检索,L4 可先跳过
只盯最后一句自然语言用结构化 Trace 对比 context / intent / toolCalls[].args

status: "success" 只能说明工具调用成功完成,不代表查对了环境,更不代表答案正确。

可以用一小段确定性代码,把「第一个参数偏差」从人工逐项对照变成可自动检查的逻辑。下面示例与上文 Trace 用同一套字段,你线上若是别的日志结构,换成自己的字段名即可:

// 字段和上面 Trace 示例一致,方便写自动检查和回归用例
type OpsTrace = {
  context: { currentEnvironment?: string }
  intent: { environment?: string }
  toolCalls: Array<{ callId: string; args: { environment?: string } }>
}

// 排查用:找第一个与「会话/意图」不一致的工具参数
function findFirstEnvironmentMismatch(trace: OpsTrace) {
  const expected = trace.context.currentEnvironment ?? trace.intent.environment
  if (!expected) return null
  for (const call of trace.toolCalls) {
    const actual = call.args.environment
    if (actual != null && actual !== expected) {
      // 找到第一处参数不对就返回,方便标成「错在工具层」
      return { layer: 'L3' as const, callId: call.callId, expected, actual }
    }
  }
  return null
}

// 真正调接口前:环境和会话对不上就直接拦住,别调错了还显示成功
function assertEnvironmentBeforeCall(
  args: { environment?: string },
  ctx: { currentEnvironment?: string },
) {
  if (!args.environment || !ctx.currentEnvironment) return
  if (args.environment !== ctx.currentEnvironment) {
    throw new Error(
      `environment mismatch: args=${args.environment} context=${ctx.currentEnvironment}`,
    )
  }
}

对 tr_ops_001 跑上面的检查,结论很清楚:会话和意图里都是 prod,工具参数却是 test。同一句用户输入应写进回归用例 env-mismatch-001(见下文「把案例写进回归测试」里 regressionCases 第三条):要查生产时,不许再把 test 传给 query_logs。

问题在 L3,可以这样改:

在 Tool Runtime 里调用 assertEnvironmentBeforeCall:环境对不上就不要调日志接口,把原因写回对话,别让接口显示 success 却查了测试库。

会话里环境已是 prod 时,最终参数要在代码里用会话/意图合并出来,别照搬模型返回的 JSON 里的 environment。只有用户这一轮清楚说了换环境,才用他这句覆盖。

// 调工具前:把模型填的参数和会话里确认的环境合并成最终参数
function mergeLogQueryArgs(
  modelArgs: { service?: string; environment?: string },
  ctx: { currentEnvironment?: string },
  intent: { environment?: string },
) {
  // 会话 / 意图已确认的环境优先,避免模型 JSON 里误填 test
  const settled = ctx.currentEnvironment ?? intent.environment
  return {
    ...modelArgs,
    environment: settled ?? modelArgs.environment,
  }
}

就算请求已经发出,也要看工具返回里标的是哪个环境。若实际是测试库的数据,回复里不能再写「生产环境日志」——要么按真实环境描述结果,要么明确告诉用户:这次查到的不是生产。

参数合并和发请求前校验改完后,用开篇那句用户原话再跑一遍;并把这条场景放进发版前要跑的回归用例 env-mismatch-001(定义见下文「把案例写进回归测试」)。

生产 Trace 还应带上 Prompt/模型版本(是不是刚改过 Prompt 才变差)、工具返回摘要和参数校验结果;字段宜结构化、可检索。涉及凭证或敏感数据时脱敏与访问控制不能省。

分五层查问题(L1–L5)

错在第几层(L1–L5)

L1–L5 是本文用的分层记号,不是行业标准;价值是让团队对同一条线上问题说同一种话。和前面几篇文章大致对应:L1 SSE/HITL 与前端协议,L2 会话与鉴权,L3 ReAct 与 Tool Runtime,L4 RAG,L5 生成与 Prompt。

层级排查范围常见问题
L1SSE 与 HITL流式事件丢失、取消无效、人机确认未展示
L2会话与权限历史截断、身份上下文缺失、租户串线
L3编排与工具意图错误、工具选错、参数错误、循环不停止
L4检索与知识漏召回、错召回、文档过期、版本不一致
L5模型与提示证据正确却编造、格式失败、拒答过度

用户说「不准」,常见还可以归成六类表现:意图错、上下文丢、工具/参数错、同名资源选错、检索证据错、表达超出证据。根因不同,修法也不同;上面 Trace 例属于「工具/参数错 + 表达把错数据说对了环境」。

建议的排查顺序:先找到第一个不对的地方

拿到一条线上问题,沿真实轨迹从 L1 往下扫(不涉及的可跳过;工具型任务重点看调用前后数据):

image.png

图意:沿 L1→L5 顺序往下对 Trace;某一层已确认不对时不必过完后面各层,可直接去修并补回归。未走 RAG 时 L4 可跳过。

第一个和预期不一致的地方,先停下来。 拿不准时,用同一条用户输入再跑一遍对照。若工具参数已是 test 且返回测试日志,答案却说「生产日志」,先查 L3;若工具返回的就是生产日志、答案却描述成测试,再查 L5。

三个常见现场,分别怎么修?

Case 1:用户问功能说明,Agent 却调用实时工具

用户:"日志查询是做什么的?"

这句话是在问功能是啥,应该先查知识库或用文字说明,不该去调实时查日志的接口。但 Agent 却调用了 query_logs,还让用户选环境。

第一个排查点:L3 的意图识别与工具路由。

先把用户话分成两类,路由就不会搅在一起:

用户想干什么该怎么做本例
问说明(功能是啥、怎么用)查知识库或直接文字回答,不要调 query_logs「日志查询是做什么的」
要真去查(给我现在的情况)才调 query_logs 等实时工具「查一下生产环境支付服务最近的错误日志」
容易误修更稳的正修
Prompt 里堆「见到『是什么』别调工具」按上表做意图分类;在工具说明里写死边界:query_logs 只查实时日志,不讲功能
用关键词规则碰运气把上表两种句式都写进回归测试;像「帮我查下日志」这种执行口吻但没说清环境、服务时,先追问,别硬调工具

工具说明不要只写「查日志」——模型不知道「是什么」类问题不能调。LangChain 里用 tool() 的 description 就能写清(示意,和动手篇同一套写法):

import { tool } from '@langchain/core/tools'
import { z } from 'zod'

// 工具说明里写清什么时候能调、什么时候不能调,比主 Prompt 堆砌管用
export const queryLogsTool = tool(
  async ({ service, environment }) => {
    // 这里去调你们的日志查询接口,并校验用户有没有权限
    return JSON.stringify({ service, environment, lines: [] })
  },
  {
    name: 'query_logs',
    description: [
      '在用户已明确要查某服务、某环境的实时错误日志时调用。',
      '需要 service、environment。',
      '不要用于「日志查询是做什么的」「怎么用」等说明类问题;说明类直接用文字或知识库回答,不要调用本工具。',
    ].join(''),
    schema: z.object({
      service: z.string().describe('服务名,如 order-service'),
      environment: z
        .enum(['prod', 'staging', 'test'])
        .describe('环境'),
    }),
  },
)

代码里还可以给两类意图起名字(叫啥无所谓,团队统一即可),例如 knowledge_explain(说明类)、realtime_action(执行类)。修完后写进回归用例(下文 intent-001 等)锁住行为。

一句话:用户只是想弄清功能时,你回答就行,别把「去后台拉日志」那一套搬出来。

Case 2:“它的日志呢?”——上下文丢失

用户:"查一下生产环境的 order-service"

Agent:"当前有 3 个实例……"

用户:"它的错误日志呢?"

这里的「它」指的就是上一轮的 order-service。正常应接着查这个服务的错误日志;若 Agent 又问一遍服务名,或去查了别的服务,多半是上一轮对话没带进本轮。

第一个排查点:L2 的会话上下文。

容易误修更稳的正修
埋怨模型「不懂『它』指谁」用 Trace 看这一轮实际发给模型的聊天记录里有没有 order-service
把历史消息无限加长会话里单独记下「当前在查哪个服务、哪个环境」(见下 ConversationState)

有时不是模型笨,而是发过去的消息太短。窗口截断后,模型可能只收到第二句:

// 反面例子:如果实际只发了这一句,上一轮说的服务名已经丢了
const messagesActuallySent = [
  { role: 'user', content: '它的错误日志呢?' },
  // 上一句「查生产 order-service」没进来,「它」就对不上了
]

修法可以二选一,或一起用:

  • 简单做法:组 Prompt 时把跟当前问题相关的几轮原样带上(对话不长时够用)。LangChain / LangGraph 里就是不要把第二轮削减成只剩最后一句,示意:
import { HumanMessage, AIMessage } from '@langchain/core/messages'

// 多轮原话都要进 messages,只传最后一句会导致「它」指代丢失
const messages = [
  new HumanMessage('查一下生产环境的 order-service'),
  new AIMessage('当前有 3 个实例……'),
  new HumanMessage('它的错误日志呢?'),
]

await agent.invoke({ messages }) // 用 LangGraph 跑 Agent 时同样要把多轮都传进去
  • 稳一点的做法:在服务端维护「当前服务、当前环境」,别只靠模型从历史里自己找。例如:
// 在服务端记住「当前在查谁」,少靠模型从长聊天里猜「它」是谁
type ConversationState = {
  currentService?: string // 上一轮已确认的服务
  currentEnvironment?: string // 已确认的环境
  lastSuccessfulTool?: string // 只记工具名,不能代替「当前服务是谁」
}

测这类问题时,回归测试要带上完整多轮对话,别只测最后一句(下文 context-001)。至少覆盖:

  • 服务名只出现在上一轮;
  • 中间岔开聊别的再问「它」;
  • 上一轮工具失败了,不能把失败时的参数当已确认。

会话里记的字段不用贪多:写清哪些来自用户确认、哪些来自工具结果;拿不准时再问用户一句。

Case 3:同名资源,没问用户就选错了

用户:"查 payment 的监控。"

按名字搜到三个:

  • payment
  • payment-worker
  • payment-test

不止一个能对上时,应让用户点一个或确认一下;系统却没人问,直接用了 payment-test(用户说的 payment 常指线上主服务,未必是测试实例)。

第一个排查点:L3 资源匹配(在查资源的工具/代码里定规则,不是靠最后一句 Prompt 补救)。

容易误修更稳的正修
Prompt 里写「多个候选时要问用户」查资源接口返回「多个候选」时,前端列表或 HITL 让人选,别自动往下走
把三个名字丢给模型「猜最像的」只有唯一匹配才自动查;多个或没有都停住

和 Case 1 一样,工具说明 + 工具返回结构一起约束:匹配逻辑写在 func 里,多个候选时返回 ambiguous,并在 description 里写「不得替用户选一个」(示意):

import { tool } from '@langchain/core/tools'
import { z } from 'zod'

// 搜到多个同名时返回「待用户选择」,后面流程必须停下来
export const searchResourceTool = tool(
  async ({ keyword }) => {
    // 这里去调你们自己的「按名字搜服务」接口
    const candidates = [
      { name: 'payment' },
      { name: 'payment-worker' },
      { name: 'payment-test' },
    ].filter((r) => r.name.includes(keyword))
    if (candidates.length > 1) {
      // 好几个都对得上:只把名单返回,弹列表让人点,别偷偷替用户选一个
      return JSON.stringify({ status: 'ambiguous', candidates })
    }
    if (candidates.length === 0) {
      return JSON.stringify({ status: 'not_found' }) // 没找到就直接说,别硬选一个
    }
    return JSON.stringify({ status: 'exact', item: candidates[0] }) // 唯一匹配才自动继续
  },
  {
    name: 'search_resource',
    description: [
      '按关键词搜索服务或实例。',
      '若搜到多个同名服务,只展示列表等人点选;不要自己挑一个继续查监控。',
    ].join(''),
    schema: z.object({
      keyword: z.string().describe('资源关键词,如 payment'),
    }),
  },
)

匹配结果建议在代码里收成三种(与上例 JSON 对齐),不要让模型拍脑袋:

// 和工具返回的三种结果一致,代码里判断后再决定要不要继续查
type MatchResult =
  | { status: 'exact'; item: Resource } // 只搜到一个,可以放心继续
  | { status: 'ambiguous'; candidates: Resource[] } // 搜到好几个,必须让用户选
  | { status: 'not_found' } // 没搜到,直接告知
  • 唯一匹配:继续查监控。
  • 多个匹配:把列表给用户,选定后再调接口。
  • 没有匹配:告诉用户没找到,别随便挑一个凑数。

一句话:搜到不等于搜对;不确定时停下,比悄悄选错强。

幻觉排查:答案里的每个关键事实都有证据吗?

这里需要先区分两个概念:

  • 工具调用错误: 服务名、环境或参数不对,查回来的就不是用户要的那份(生产查成测试、选错 payment 属这类)。
  • 生成幻觉: 工具返回里根本没有那句话的依据,回复却写得很肯定,像已经查实了一样(本节主要看这类)。

两者有时会同时出现,但修法不同:工具调用错误先修 L3;生成幻觉要在成稿前逐句对证据。下面这段假定工具和参数都没查错,专看有没有「说超了返回」。

别整段扫一眼觉得「挺像对的」,拆成短句,逐句问:返回里有没有支持这句话。

工具示意返回(查的是生产 payment,当前探测正常):

{
  env: 'prod',
  service: 'payment',
  instanceCount: 5,
  errorRate: 0.002, // 0.2%
  health: 'ok', // 只表示此刻正常,没有「之前故障、现已恢复」的记录
}

模型整理成用户看到的一句话,可能是:

Agent:"生产环境支付服务已恢复,共 5 个实例,错误率为 0.2%。"

逐句对照返回:

回复里说了啥工具里实际有什么能不能这么说
服务已恢复只有 health: 'ok',没有故障、恢复时间等记录不能,依据不够
共 5 个实例返回里写明 5 个实例能
错误率 0.2%返回里写明该指标能

拿表格第一行来说:工具只告诉你「现在看着正常」,没有告诉你「刚才出过故障、现在又好了」。没这种前后对比,回复里就不要写「已恢复」——「现在没事」和「刚从故障里恢复」不是一回事。

工具若能返回成功或失败,可以在代码里卡住「失败了还嘴硬说成功」:

// 写答案前先看工具是成功还是失败
type ToolResult<T> =
  | { status: 'success'; data: T } // 成功也要对一下数据里的环境、数字和回复是否一致
  | { status: 'failed'; errorCode: string } // 失败了别让回复还写「已成功」

走的是知识库(RAG)时也一样:答案下面挂了引用,不代表引用的那几段话真的支持这句结论,仍要打开原文对一下。

容易误修更稳的正修
Prompt 里反复写「不要编造」回复拆成短句,每条去对工具结果或检索原文
只看有没有引用标记打开引用段落,看是否支持这句话;像「已恢复」这种要能拿出前后对比

工具是失败就别生成成功话术;数字和状态跟返回对得上。 这类检查写在代码里,一般比加长 Prompt 管用。

把案例写进回归测试:每修一个问题,就留下一条用例

排查完一条线上问题,如果只改代码或 Prompt,却没有把原始对话和问题记下来,下次很可能再次踩坑。

回归测试:把线上踩过的坑冻成「同一句话 / 同一段对话 + 你期望它怎么表现」,发版前再验证旧问题有没有回来。按开发习惯,通常就两块:平时人工记清、维护好;发版前脚本自动跑,但别指望只跑脚本——人工再过一遍。

和 L 层的关系: L1–L5 用来线上查问题时标记「错在第几层」;每条回归用例也标一层,并写清期望。例如 L3 看该不该调工具、参数对不对;L2 看多轮有没有带全。没上 RAG 可暂不写 L4 相关用例。用例 id 和检查项尽量稳定,模型和 Prompt 可以换。

一块:人工记用例,并持续维护

每次修完一条线上问题,就往用例库里加一条(文档、表格、工单都行,不必先上代码)。至少记清这些:

记什么示例
用例 idenv-mismatch-001(团队自定,定下来就别老换)
输入用户原话,或多轮对话全文
错在哪层如 L3 参数变成 test
发版前期望如「不许再把 test 传给 query_logs」

维护和写代码一样重要:补相近说法的变体,改 Prompt 或工具时回头看用例要不要改,删已经过时的。用例从真实翻车里一条一条加,别一次抄一整张表。

本文三个例子,人工库里至少应有对应三条(以后若要脚本化,字段可以和下表对齐):

id来自哪里发版前怎么算过
intent-001Case 1用户问「日志查询是做什么的」→ 只回答,不调 query_logs
context-001Case 2多轮「它的错误日志呢」→ 带全对话,应查 order-service、环境 prod
env-mismatch-001开篇 Trace用户要生产 → 工具参数里不许再出现 test

context-001 一定要测完整多轮,别只测最后一句。

另一块:发版前自动跑,并保留人工抽测

用例稳定、字段统一后,再写进 JSON 或 TypeScript,由脚本调评测入口、用 Trace 自动比对。regressionCases 只是示例变量名;assertMatches 按你们的 Trace 字段实现即可。脚本跑完,仍建议用关键几条原话人工点一遍、看 Trace——和跑单元测试还要看一眼页面是一个道理。

// 回归测试用例表:每条 id 对应一个曾出过的线上问题
const regressionCases = [
  {
    id: 'intent-001', // 发版前测:说明类问题不该调工具
    input: '日志查询是做什么的',
    expected: {
      queryKind: 'knowledge_explain', // 用户在问「是什么」,不是要去查
      tool: null, // 不应调用 query_logs
    },
  },
  {
    id: 'context-001', // 发版前测:多轮对话要整段带上,「它」才能指对服务
    messages: [
      { role: 'user', content: '查生产环境的 order-service' },
      { role: 'assistant', content: '找到 3 个实例' },
      { role: 'user', content: '它的错误日志呢' }, // 「它」依赖上一轮服务名
    ],
    expected: {
      tool: 'query_logs',
      service: 'order-service',
      environment: 'prod',
    },
  },
  {
    id: 'env-mismatch-001', // 发版前测:开篇那条生产误查测试不能复现
    input: '查一下生产环境 order-service 的错误日志',
    context: { currentEnvironment: 'prod' },
    forbidToolArgs: { environment: 'test' }, // 用户要生产时,工具参数里不许再出现 test
  },
]

发版前可写成:for (const c of regressionCases) { const trace = runAgent(c); assertMatches(trace, c) },再挑几条(尤其多轮、环境类)人工复跑对照 Trace。还没有完整评测入口时,也可以先对 Trace 快照做断言,再补端到端回放。

例如,修了「生产查成测试」,除了脚本里的 env-mismatch-001,发版前还应人工试几种服务名、环境说法,甚至用户没说清环境时 Agent 会不会先追问。

用例从线上真实翻车里挑,一条一条加就行;发版前别同时改模型、Prompt、工具和检索,不然变好了也说不清是哪刀起效。排障日志只记必要字段,做好脱敏(脱敏很重要!!!)。

最后总结

Agent 回复不准确,不等于模型一定不够聪明。很多时候,问题发生在模型之外。

这篇文章可以归纳为三句话:

  1. 先查清错在哪,再改代码或 Prompt。 沿 L1–L5 对照 Trace 找第一个对不上的地方,确认是不是它导致了后面的错;别被最后一句「听起来像对的」带偏。
  2. 工具成功不代表业务正确。 资源、环境、参数和返回状态都要核对,不能让模型把失败包装成成功。
  3. 修复必须写进回归测试。 每条线上问题都要留下用例并维护好,发版前脚本和人工都要过(标清对应 L1–L5 哪一层),而不是只改 Prompt 不留证据。

很多看似是模型不行,把 Trace 从头对一遍,往往能看到是哪一步开始不对。也欢迎评论区分享你最难查清的一次线上问题。

十篇各讲什么,以及下一篇

从第 1 篇到第 10 篇,拼的是同一条「生产 Agent 怎么跑起来」的线索,分工大致如下:

篇主题你读完应带走的
1端到端交付一次请求从入口到回复整链长什么样,容易在哪儿断
2意图识别与路由结构化意图,少靠关键词碰运气
3ReAct 循环多步调工具、终止条件、别把工具失败当整轮崩溃
4工具工程化schema、Tool Runtime、超时重试、幂等与审计
5SSE 与 HITL流式事件、暂停恢复、人点头的写操作
6会话与鉴权身份、租户、Run 归属,模型不能代填 userId
7记忆与上下文会话状态、预算,别把整段历史无脑塞给模型
8RAG 知识库切片、检索、权限、引用要和答案对得上
9安全注入、越权、沙箱,工具结果是数据不是指令
10准确率与排障(本篇)Trace、L1–L5、回归测试,先找对层再改

前面九篇是工程方法论,本篇收在「答不准怎么查、怎么留用例」。接下来会再写一篇动手篇:

用 LangChain / LangGraph 搭一套能本地跑起来的运维 Agent 实践 Demo

把十篇关键点落到同一项目里(意图、工具、SSE/HITL、会话鉴权、记忆预算、RAG、安全护栏、Trace 与回归等都会有对应代码)。希望这篇文章对你有所帮助、有所借鉴。