大模型刚能落地那会儿起,我就一直在做相关落地: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。
| 层级 | 排查范围 | 常见问题 |
|---|---|---|
| L1 | SSE 与 HITL | 流式事件丢失、取消无效、人机确认未展示 |
| L2 | 会话与权限 | 历史截断、身份上下文缺失、租户串线 |
| L3 | 编排与工具 | 意图错误、工具选错、参数错误、循环不停止 |
| L4 | 检索与知识 | 漏召回、错召回、文档过期、版本不一致 |
| L5 | 模型与提示 | 证据正确却编造、格式失败、拒答过度 |
用户说「不准」,常见还可以归成六类表现:意图错、上下文丢、工具/参数错、同名资源选错、检索证据错、表达超出证据。根因不同,修法也不同;上面 Trace 例属于「工具/参数错 + 表达把错数据说对了环境」。
建议的排查顺序:先找到第一个不对的地方
拿到一条线上问题,沿真实轨迹从 L1 往下扫(不涉及的可跳过;工具型任务重点看调用前后数据):
图意:沿 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 的监控。"
按名字搜到三个:
paymentpayment-workerpayment-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 可以换。
一块:人工记用例,并持续维护
每次修完一条线上问题,就往用例库里加一条(文档、表格、工单都行,不必先上代码)。至少记清这些:
| 记什么 | 示例 |
|---|---|
| 用例 id | env-mismatch-001(团队自定,定下来就别老换) |
| 输入 | 用户原话,或多轮对话全文 |
| 错在哪层 | 如 L3 参数变成 test |
| 发版前期望 | 如「不许再把 test 传给 query_logs」 |
维护和写代码一样重要:补相近说法的变体,改 Prompt 或工具时回头看用例要不要改,删已经过时的。用例从真实翻车里一条一条加,别一次抄一整张表。
本文三个例子,人工库里至少应有对应三条(以后若要脚本化,字段可以和下表对齐):
| id | 来自哪里 | 发版前怎么算过 |
|---|---|---|
intent-001 | Case 1 | 用户问「日志查询是做什么的」→ 只回答,不调 query_logs |
context-001 | Case 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 回复不准确,不等于模型一定不够聪明。很多时候,问题发生在模型之外。
这篇文章可以归纳为三句话:
- 先查清错在哪,再改代码或 Prompt。 沿 L1–L5 对照 Trace 找第一个对不上的地方,确认是不是它导致了后面的错;别被最后一句「听起来像对的」带偏。
- 工具成功不代表业务正确。 资源、环境、参数和返回状态都要核对,不能让模型把失败包装成成功。
- 修复必须写进回归测试。 每条线上问题都要留下用例并维护好,发版前脚本和人工都要过(标清对应 L1–L5 哪一层),而不是只改 Prompt 不留证据。
很多看似是模型不行,把 Trace 从头对一遍,往往能看到是哪一步开始不对。也欢迎评论区分享你最难查清的一次线上问题。
十篇各讲什么,以及下一篇
从第 1 篇到第 10 篇,拼的是同一条「生产 Agent 怎么跑起来」的线索,分工大致如下:
| 篇 | 主题 | 你读完应带走的 |
|---|---|---|
| 1 | 端到端交付 | 一次请求从入口到回复整链长什么样,容易在哪儿断 |
| 2 | 意图识别与路由 | 结构化意图,少靠关键词碰运气 |
| 3 | ReAct 循环 | 多步调工具、终止条件、别把工具失败当整轮崩溃 |
| 4 | 工具工程化 | schema、Tool Runtime、超时重试、幂等与审计 |
| 5 | SSE 与 HITL | 流式事件、暂停恢复、人点头的写操作 |
| 6 | 会话与鉴权 | 身份、租户、Run 归属,模型不能代填 userId |
| 7 | 记忆与上下文 | 会话状态、预算,别把整段历史无脑塞给模型 |
| 8 | RAG 知识库 | 切片、检索、权限、引用要和答案对得上 |
| 9 | 安全 | 注入、越权、沙箱,工具结果是数据不是指令 |
| 10 | 准确率与排障(本篇) | Trace、L1–L5、回归测试,先找对层再改 |
前面九篇是工程方法论,本篇收在「答不准怎么查、怎么留用例」。接下来会再写一篇动手篇:
用 LangChain / LangGraph 搭一套能本地跑起来的运维 Agent 实践 Demo
把十篇关键点落到同一项目里(意图、工具、SSE/HITL、会话鉴权、记忆预算、RAG、安全护栏、Trace 与回归等都会有对应代码)。希望这篇文章对你有所帮助、有所借鉴。