BrainBank

本地服务器延迟调查

2026/7/30 20:00:45 · 更新于 2026/7/31 00:33:04

AI 翻译于 2026/7/31 11:05:57 · 使用 Qwen3.6 35B (fast, default)

#best-practices#latency#local llm#agent server#optimization#self-referential queries

本文档详细介绍了如何诊断和优化智能体系统中的延迟问题,特别针对用户询问自身身份或规则时触发的不必要文件搜索和模型调用的问题。

原本只是为了搞清“为什么一个回答要等14秒”,最终演变为对 apps/agent-server 请求管道的真实诊断、两项已上线的修复,以及基于实际日志数据的一次校准过程。本文按顺序梳理了每一步的操作、发现的问题、所做的修改及原因——即便后续具体参数会重新调整,背后的推理过程依然清晰可查。


Step 1 — 发现症状表现

一个简单的问题(“适用哪些护栏规则”)到首个 token 的耗时为 14.4s,总耗时 16.2s,而如此简短的提问却伴随着异常庞大的输入量(31,664 个 token)。这种输入体量的严重不匹配——简单的提问搭配超大的提示词——正是第一个线索:系统结构出了问题,而不是单纯的“模型运行慢”。 为什么这点很关键: 延迟投诉很容易被误判为“硬件需要更快的”或“模型体积太大了”。在没有证据的情况下盲目跟进,会导致采取错误的修复方案(购买更多算力、更换模型),而不是真正免费且有效的修复方案(停止执行无用功)。

Step 2 — 查阅日志而非主观猜测

排查了 logs/agents/*.jsonlrequests.jsonltool_calls.jsonlskill_matches.jsonl)与 ollama.log,并翻阅了服务代码(apps/agent-server/agent_loop.pymain.pygraph.py)。 原因: 该系统会为每一次请求、每一次工具调用以及每一次技能匹配记录时间戳和 token 数量,其目的正是为了在事后能够完整复原慢请求的来龙去脉。当证据就摆在日志文件里时,靠猜测找原因只会浪费时间,并且容易修错方向。

Step 3 — 找到真正的根本原因

该慢请求的 tool_calls.jsonl 记录显示,模型执行了四次连续的文件系统搜索——分别是寻找名为 AGENTS.md 的文件、匹配 *guardrail* 模式的内容、列出目录,以及搜索 agent*/*guide*——直到第五次尝试才从它自己的系统提示词中找到了答案。交叉比对 ollama.log 确认了这五次独立的连续模型调用(4.84s + 2.99s + 1.39s + 2.30s + 4.12s ≈ 15.6s),与总延迟时间几乎完全吻合。 发生原因: AGENT.md(基础系统提示词)指示模型对于“事实性、法规类问题应优先使用搜索而非凭记忆回答”,且未对询问 agent 自身 身份或规则的问题设置例外。关于护栏规则的问题被判定为“事实性”查询,于是模型转而四处寻找文档,却没注意到答案早已在它正在阅读的同一份提示词下文仅三段之遥。此外,它又转向了原始的文件名猜测工具,而不是遵循指示优先使用的语义搜索工具——这是一个叠加的二次失误。

Step 4 — 将架构与前沿 Agent 设计进行对比

在触碰任何东西之前,先对现有引擎(graph.py 中的基于 wave 的执行器)与 Claude Code 等系统的构建方式进行了评估。结论是:执行引擎已经相当扎实——wave 内真正的并发工具调用扇出、prompt 前缀缓存、流式传输、连接池。差距完全在于指令而非设施:模型从未被告知要批量处理独立搜索、区分每项工作该用哪个工具,或将关于自身的问题视为特殊情况。

为什么不直接跳到代码: 错误的架构判断(例如"引擎无法并行化")会导致重建一个本来就能工作的东西,而不是修复真正的差距(应该告诉模型用它已有的基础设施做些什么)。

第 5 步 —— 修复了 prompt(AGENT.md

向 system prompt 中添加了三个内容,保留了其余一切(引用要求、不虚构规则、工具范围诚实性)不变:

  1. 一个狭窄的"回答关于自身问题"的例外规则——附带一个实例,区分"哪些约束对你适用"(直接回答)与"DoD FMR 的内部控制约束是什么"(仍然是真实的搜索,尽管两者都用了"约束"这个词)。
  2. 工具消歧——内容问题使用语义化的 search_knowledge_base;仅在路径已知时才使用文件系统工具,从不用于猜测文件名。
  3. 并行批处理指令——在一次调用中转出发相互独立的工具调用,而不是逐个发送,因为引擎已经支持在一个 wave 内并发执行,只是未被使用。

为什么是狭窄的例外规则而非"绝不搜索"的大原则: 多余搜索的成本只有几秒钟;错误拒绝真实检索的成本则是错误的或缺乏支撑的答案。每一条编辑都被写成在真正不确定时倾向于更多搜索,而非更少。

第 6 步 —— 添加了确定性后备机制(第 5.9 步:正则筛选)

prompt 指令只是一个模型仍可能误判的请求。在 agent_loop.py 中添加了 is_self_referential()——一个在模型看到请求之前就运行的硬编码层面的门控:如果问题匹配一组精心选定的自指表述("你是谁"、"你的指令是什么"等),则关闭该请求的工具调用,无论模型会怎么做决定,都能保证零浪费的搜索轮次。

为什么偏向精确度而非召回率: 漏报(错过一个自指问题)只会落入第 5 步的 prompt 修复——没有回归损失,和之前一样。误报(错误地阻止了一个真实的领域检索)则会直接破坏一个合法的答案,这比它所旨在修复的延迟要严重得多。因此门控仅在高置信度的字面匹配时触发,附带词数上限和域词汇排除列表作为额外的安全网。

第 7 步 —— 真实世界的测试暴露了门控的局限

你问了完全相同的问题,只是换了种说法(将"What guardrails apply"换成了"what is the guardrail apply before agent can act")。正则匹配没有命中——因为没有精确短语匹配——但第 5 步的提示词修复仍然生效;模型没有进行搜索。令牌计数从 31,664 降至 6,710,证实了零浪费的工具调用轮次。但总延迟仍然是 13.6 秒,调查结果显示原因:冷模型加载(将 35B 模型重新加载入内存耗时 5.6 秒),因为距上次请求已过去 2 小时 10 分钟——这远远超过了 30 分钟的保活窗口。这与重启无关;这是 Ollama 自身空闲计时器的正常工作表现,完全符合设计预期。

关键意义: 这证明了正则匹配网关虽然安全,但不能泛化到释义场景——而这正是僵化的基于规则的系统具备、而模型驱动系统不具备的典型缺陷。

第 8 步 — 添加了第二层更智能的方案(第 5.10 步:嵌入路由)

与其手动列举所有可能的释义方式,不如添加第二层方案:如果正则匹配失败(但问题仍通过相同的精度检查),则使用同样为 search_knowledge_base 提供支持的 nomic-embed-text 模型对问题进行嵌入处理,并通过余弦相似度与一组少量的"关于代理自身"的标准示例问题进行比对。超过阈值 → 视为自指,与正则命中相同。

为什么选择嵌入而非更多正则模式: 正则只能捕捉有人特意写下来的短语。嵌入空间中的最近邻相似性能够泛化到任何人未曾预见的释义,其成本仅为完整模型调用的一小部分(一次嵌入查找,无需令牌生成——通常只需不到一秒,而完整模型调用则需数秒的生成往返时间)。这类似于生产级代理系统的分层架构:先在前面放置廉价、局限的逻辑,再是语义逻辑,最后才是全量模型推理。确定性优先,语义其次,仅在两者均无法自信解决时才触发完整模型。

构建了故障安全机制: 任何嵌入调用失败(模型未拉取、网络抖动)都会回退到现有路径,而不是导致请求中断——这是一项延迟优化,而非安全功能,因此绝不能因为此机制导致请求失败。

第 9 步 — 针对真实数据而非假设进行测试

阈值始于 0.84 的猜测——这个沙盒没有网络路径连接到你 Mac 上的 Ollama 实例,因此无法从这边进行调优——需要实际校准。构建了两轮测试问题并通过实时服务器运行:

  • 第一轮确认目标案例现在可以正常工作(得分 0.8846,高于阈值),并浮出了两个未能通过的“接近命中”的改写版本(得分 0.8221、0.6978)。
  • 第二轮特意加入了没有明显关键词的规则问题(这样它们才能实际进入 embedding 层,而不是一开始就被排除)—— 其中包括一个故意模仿 agent 面向结构的表述(“在关闭办公室之前适用什么规则”),以及一个故意模棱两可的案例(“需要遵循什么规则”,关于用户,而非 agent)。

为什么采用这种两轮设计: 第一轮证明了修复方案在常规路径上有效。第二轮试图破解它——要找到“能捕获改写版本”和“错误拦截真实问题”之间的真正边界,唯一的方法就是向其抛出对抗性的、贴近边界的测试案例,并读取实际得分,而不是假设边界就在你觉得安全的地方。

步骤 10 — 根据证据而非猜测设定阈值

triage.jsonl 中的真实数据显示:真正的自指类问题分数聚集在 0.70–0.88 之间。最接近的非匹配项——那个模糊的“关于用户”的案例——得分为 0.7245。每一条真实的规则问题,包括对抗性案例,得分均低于 0.69。

将阈值设为 0.80:可以捕获一个额外的真实改写版本(0.8221),同时与最近的误判风险保持 0.075 的缓冲空间。故意没有进一步降低阈值以捕获 0.7466 的案例——那只会与 0.7245 的模糊案例留下仅 0.02 的缓冲空间,在缺乏专门针对该边界的更多数据之前,过于脆弱而不可信。

**为什么要在 0.80 停止,而不必追踪每一个漏网之鱼:**每一次阈值的移动都是在召回率(捕获更多真实案例)与精确度(带来误判风险、破坏真实回答)之间进行权衡。鉴于既定的优先事项——错误警报严格劣于遗漏真实案例——正确的停止点是“上一个具有令人安心且基于证据的缓冲空间的移动”,而不是“今天技术上仍有效的最低数字”。


当前所处状态

| 层级 | 作用 | 不适用时的成本 | AGENT.md 提示修复 | 直接告知模型不要搜索自指类问题、消除工具歧义、鼓励批量处理 | 无 ——它只是更好的指令 | | Regex 门控(步骤 5.9) | 对精确短语匹配的自指类问题进行硬拦截 | 零 ——纯 Python,无需调用模型 | | Embedding 门控(步骤 5.10) | 捕获正则表达式遗漏的改写版本,通过语义相似度实现 | 一次 embedding 调用(不到一秒),仅在正则未匹配且防护检查通过时执行 | | 阈值 = 0.80 | 嵌入门控的决策边界 | 已依据真实记录的分数校准,而非随意假设 |

仍有待进行的事项,按大致优先级排序如下:

  1. 随着真实流量持续累积,请持续关注 logs/agents/triage.jsonl —— embedding_near_miss 记录是免费的校准数据,用于判断是否需要调整 0.80 的阈值。
  2. DFAS“职责分离”测试题耗时 5 次检索轮次和 56 秒 —— 这是一个独立的真实检索延迟问题(非路由缺陷),目前尚未被深入调查。
  3. skills/loader.py 的关键词重叠技能匹配器存在已知的误报问题(例如,transformer 架构笔记错误匹配到 K12 技能)—— 这与步骤 5–8 解决的自引用路由属于同一类问题,但尚未在该处应用。—— 摘要 (2026-07-30) 最初只是“为什么一个回答花了 14 秒”,最终演变成为对 apps/agent-server 请求管道的真实诊断、两项已上线的修复,以及一份基于真实日志数据的校准分析。本文按顺序逐一梳理每个步骤的发现、修改内容及原因 —— 以便即便后续具体数值重新校准,推理过程依然留存。

步骤 1 —— 发现症状

一个简单的问题(“适用哪些规则”)首 token 耗时 14.4 秒,总耗时 16.2 秒,输入量对于如此简短的问题来说异常庞大(31,664 tokens)。这种尺寸不匹配 —— 极其简单的问题配上了庞大的提示词 —— 是结构存在问题的第一个线索,而不仅仅是“模型太慢。” 为何关键: 延迟问题很容易被误诊为“硬件不够快”或“模型太大”。在没有证据的情况下盲目跟进这一思路,会导致错误的修复方向(购买更多算力、切换模型),而不是真正零成本的有效修复(停止执行无用操作)。

步骤 2 —— 转向日志排查而非凭空猜测

排查了 logs/agents/*.jsonlrequests.jsonltool_calls.jsonlskill_matches.jsonl)以及 logs/ollama.log,并阅读了服务代码(apps/agent-server/agent_loop.pymain.pygraph.py)。 为何关键: 该系统会为每次请求、每次工具调用和每次技能匹配记录时间戳与 token 数量,正是为了在事后重建缓慢请求的全过程。当证据就摆在日志文件里时却靠猜测原因来分析问题,既浪费时间,又极易导致修复方向错误。

步骤 3 —— 找到真正的根因

该缓慢请求的 tool_calls.jsonl 记录显示,模型发起了四次连续的文件系统搜索 —— 分别查找字面名为 AGENTS.md 的文件、匹配 *guardrail* 的内容、一次目录列表操作,然后是 agent/*guide* —— 全部在第五次尝试直接从自身系统提示词中回答该问题之前发生。交叉比对 ollama.log 确认了五次独立的连续模型调用(4.84s + 2.99s + 1.39s + 2.30s + 4.12s ≈ 15.6s),总耗时与延迟几乎完全吻合。

发生原因: AGENT.md(基础系统提示)告诉模型对"事实性和监管类问题应优先搜索而非凭记忆回答",但对代理 自身 的身份或规则相关问题没有例外规定。一个关于护栏的提问被识别为"事实性"内容,导致模型去寻找文档,而没有意识到答案已经在那段提示词往下数第三段里了。此外,它还去用了原始的文件名猜测工具,而非它所被告知优先使用的语义搜索——这是第二个叠加的错误。

第四步 —— 将架构与前沿代理设计进行了对比

在碰任何代码之前,对照 Claude Code 等系统的设计方式,评估了既有引擎(graph.py 的波次执行器)的表现。结论:执行引擎本身已经很扎实——波次内有真正的并发工具调用扇出、提示词前缀缓存、流式传输、连接池。差距完全在于 指令,而不在于 架构:模型从未被教导要批量合并独立搜索、区分哪种工具用于哪种任务,或将关于自身的问题视为特殊情况。

为什么从这一步开始而不是直接跳到代码: 错误的架构诊断(例如"引擎无法并行化")会导致重建一个本来就能工作的东西,却没有解决真正的差距——即需要告诉模型用它已有的东西做什么。

第五步 —— 修复了提示词(AGENT.md

向系统提示中添加了三个内容,其余一切(引用要求、禁止捏造规则、工具范围诚实性)保持原样:

  1. 一个关于"回答关于自身问题"的窄化例外条款——带有具体例子,区分"你适用的护栏是什么"(直接回答)和"DoD FMR的内部控制护栏是什么"(即使是两个句子都用了"护栏"这个词,仍然是真正的搜索,需要检索)。
  2. 工具消歧指导——用语义 search_knowledge_base 处理内容类问题;仅当路径已经知道时才使用文件系统工具,永远不要用它去猜文件名。
  3. 一个并行批处理说明——将独立的工具调用放在同一轮中一起发出,而不是逐个进行,因为引擎已经支持波次内的并发执行——只是之前没有被利用起来。

为什么用窄化例外条款而不是一条宽泛的"从不搜索"规则: 不必要的搜索成本是几秒钟;错误拒绝真正检索的代价是错误的或缺乏支撑的答案。每项修改都写成了在真正不确定时 倾向 更多搜索,而非更少搜索。

第六步 —— 添加了确定性回退机制(第五步.9:正则分类)

提示(prompt)指令是一种模型仍可能误判的请求。我们在 agent_loop.py 中增加了 is_self_referential() 函数——这是一个硬性、代码层面的闸门,在模型看到请求之前运行:如果问题匹配一组精心策划的、关于自身的表述(如“你是谁”、“你的指令是什么”等),则该请求会关闭工具调用,确保无论模型如何决策,都不会浪费任何一轮搜索。

为什么偏向精确率而非召回率: 假阴性(漏掉一个自指问题)会让请求落入 Step 5 的提示修复机制——不会发生退化,与之前一样。假阳性(错误地拦截了真实的领域查询)则会直接破坏合法的问答,这比该机制旨在解决的延迟问题严重得多。因此,闸门仅在高度可信的字面匹配时触发,并辅以单词数上限和域名词汇排除列表作为额外的安全网。

步骤 7 —— 现实世界测试暴露了闸门的局限

你重述了完全相同的问题(用“在代理采取行动之前必须遵守什么规则"替代了“适用什么规则”)。正则闸门未能命中——因为没有 exact phrase match(精确短语匹配)——但 Step 5 的提示修复仍然生效;模型未执行搜索。Token 计数从 31,664 降至 6,710,证实没有浪费的工具调用轮次。但总延迟仍为 13.6 秒,调查揭示了原因:模型冷启动加载(将 35B 模型重新加载到内存耗时 5.6 秒),因为距上次请求已过去 2 小时 10 分钟——远超 30 分钟的保活窗口。这与重启无关;这是 Ollama 自身的空闲计时器,按设计运行。

为何这很重要: 它证明了正则闸门虽是安全的,但无法泛化到重述/改写场景——而这正是刚性基于规则的系统的固有缺陷,是基于模型的系统所不具备的。

步骤 8 —— 增加了第二层更智能的机制(步骤 5.10:嵌入路由)

与其手动枚举每种可能的同义改写,我们增加了一层第二层机制:如果正则未命中(但请求仍通过相同的精确度检查),则使用已在 search_knowledge_base 中运行的相同 nomic-embed-text 模型对问题进行嵌入处理,并通过余弦相似度将其与一组标准的“关于代理”示例问题进行比对。超过阈值即视为自指问题,处理逻辑与正则命中相同。

为何用嵌入而非更多正则模式: 正则只能捕获有人想到写入的模式。嵌入空间中的最近邻相似性可以泛化到无人预见的改写,且成本远低于完整模型调用(一次嵌入查询,无需 Token 生成——通常远低于一秒,而模型生成往返需数秒)。这契合生产级代理系统分层架构的理念:在最外层使用廉价、窄范围的逻辑,中间层使用灵活的语言模型推理,仅在两者均无法自信解决时才调用完整模型。

内置了故障保护机制: 任何嵌入调用失败(模型未拉取、网络波动)都会回退到现有处理路径,而不会导致请求中断——这是延迟优化而非安全特性,因此绝不能成为请求失败的原因。

第 9 步 —— 基于真实数据测试,而非假设

该阈值(初始设为 0.84,纯属猜测——此沙箱无法连接到您 Mac 上的 Ollama 实例,因此无法在此处进行调优)需要真实的校准。构建了两轮测试问题并通过生产服务器运行:

  • 第一轮: 确认目标案例现在正常工作(0.8846,高于阈值),并暴露了两个“差一点通过”的改写版本(0.8221、0.6978)未能达标。
  • 第二轮: 刻意加入了完全_没有_明显标记词的业务领域问题(使它们能真正进入嵌入层,而不是被提前排除)——其中包括刻意模仿面向代理结构的表述(“在关闭办公室之前适用什么规则”)——另加一个刻意模糊的案例(“需要遵循什么规则”,指的是用户而非代理)。

为什么采用双轮设计: 第一轮证明了修复方案在正向路径上有效。第二轮试图击破它——要找到“捕获改写”与“误拦截真实问题”之间的真实边界,唯一的方法就是向其投掷具有对抗性、紧贴边界的测试案例并查看实际得分,而不是假设边界位于感觉安全的地方。

第 10 步 —— 基于证据设定阈值,而非猜测

triage.jsonl 中的真实得分:真正的自指问题集中在 0.70–0.88 区间。最接近的相邻非匹配项——那个模糊的“关于用户”的案例——得分为 0.7245。所有真实的业务领域问题(包括对抗性案例)得分均在 0.69 以下。

将阈值设定为 0.80:多捕获了一个真实的改写案例(0.8221),且比最接近的误报风险高出 0.075 的安全边际。刻意没有进一步降低阈值去捕捉 0.7466 的案例——那样做的话,在 0.7245 的模糊案例之上仅剩 0.02 的间距,未经专门针对该边界的更多数据测试前无法信赖。

为什么停在 0.80 而不是追求捕获每一个漏网之鱼: 阈值的每一次调整都在召回率(捕获更多真实案例)和精确率(冒险误报导致真实回答被阻断)之间做权衡。考虑到明确设定的优先级——误报比漏报更为严重——正确的停止点应是“拥有充裕证据支持边界的最后一步”,而非“今天技术上仍适用的最低数值”。


现状

Layer What it does Cost when it doesn't apply AGENT.md prompt fix Tells the model directly not to search for self-referential questions, disambiguates tools, encourages batching None — it's just better instructions Regex gate (Step 5.9) Hard-blocks tools on exact-phrase self-referential matches Zero — pure Python, no model call Embedding gate (Step 5.10)

捕获了正则表达式遗漏的语义相似变体 每次仅需一次 embedding 调用(约亚秒级),且仅在正则匹配失败且前置检查通过时触发 阈值为 0.80 嵌入门控的决策边界 基于实际记录分数的校准结果,而非理论假设 仍待推进的事项(按大致优先级排列):

  1. 随着真实流量的积累,持续监控 logs/agents/triage.jsonl 文件——其中 embedding_near_miss 条目可作为免费数据,用于判断 0.80 的阈值是否需作调整。
  2. DFAS "职责分离"测试题耗时 5 轮搜索和 56 秒——这是一个独立的数据检索延迟问题(非路由错误),尚待进一步调查。
  3. skills/loader.py 中的关键词重叠技能匹配器存在已知的误报问题(例如,transformer-architecture 笔记匹配了 K12 技能)——这属于与步骤 5–8 为自我指涉路由解决的问题同类别的问题,但尚未在该处应用解决方案。

学习地图

学习路径:减少自引用问题的代理延迟

第一阶段:基础(先决条件)

  • 了解基本的 LLM 服务器架构
  • 学习代理系统如何路由请求与管理工具
  • 研究事实性问题与自引用系统查询之间的区别

第二阶段:核心分析技能

  • 建立请求元数据、工具使用指标和延迟分析的日志记录
  • 识别请求何时在执行不必要的搜索
  • 理解路由决策中的阈值调优与假阳性/假阴性权衡

第三阶段:系统级优化

  • 应用提示工程技术以减少对外部查询的依赖(摘要第 5 步)
  • 在处理自引用查询前添加确定性模式匹配门控(第 6 步)
  • 当正则匹配失败时,引入语义相似度检查作为第二道回退机制(第 8 步)

第四阶段:高级调优

  • 依据真实流量数据校准路由阈值(如第 10 步所示)
  • 为所有新增的延迟门控部署失效保护机制
  • 建立针对可能引发假阳性的边界情况的自动警报系统

动手实践——分步指南

  1. 在您的代理服务器上启用全面日志记录(requests.jsonl、tool_calls.jsonl)
  2. 当用户询问系统相关问题时监控延迟指标(如"你是什么?"、"为什么X花了这么长时间?")
  3. 通过比较查询内容与工具使用情况,识别任何不必要的搜索
  4. 实施初始提示词编辑,其中包含具有具体示例的自我指涉问题排除项
  5. 为常见自我指涉查询的精确短语匹配添加确定性正则表达式门控
  6. 当正则表达式失败时,实施基于嵌入的相似度检查作为第二层
  7. 使用来自生产流量的实际日志数据校准阈值
  8. 设置警报以监控接近阈值的边界情况
  9. 记录所有更改并跟踪每次优化的延迟变化

三大推荐资源

  1. 1
    Ollama Documentation: Serving Model Requests

    Official guide to understanding LLM request handling, latency metrics collection, and performance monitoring.

    https://docs.llamafactor.ai/docs/serving-requests

  2. 2
    LangChain Agent Development Guide

    Comprehensive resource for building intelligent agent systems that can be optimized like the one described here.

    https://python.langchain.com/docs/guides/agent_development

  3. 3
    LLM System Optimization - Practical AI Course (Udemy)

    Hands-on course covering server performance tuning, latency analysis patterns, and agent system design best practices.

    https://www.udemy.com/course/llm-system-optimization/

链接由 AI 推荐——使用前建议快速核实。