最小 .NET LLM 可观测性:在 15 分钟内复现超时并进行排查
Source: Dev.to
如果你的 LLM 接口出现超时,仅靠仪表盘往往帮不上忙。你需要的是一条从症状到原因的快速通道。
本文展示了一个小型 .NET 实验室,你可以在其中强制触发受控的 504 超时,并通过可重复的 metrics → trace → logs 工作流进行调试。技术栈包括 ASP.NET Core、Blazor、.NET Aspire、Ollama 和 OpenTelemetry,目标非常务实:在发布之前缩短诊断时间。
核心理念是:可观测性 不是 仪表盘,而是 诊断所需的时间。
我之所以构建这个实验,是因为我已经在没有可靠方式关联日志、追踪和指标的情况下,浪费了太多时间盯着日志看。本文中的 “LLM 工作负载” 指的是一个接口,其中尾部延迟和故障通常来源于模型调用以及提示词或工具的变更,而不仅仅是你的 HTTP 处理程序。
本文 以代码仓库为先,直接使用配套的仓库:
仓库:
- 包含一个 Blazor UI,可触发健康、延迟、超时以及真实模型调用等场景。
一分钟了解技术栈
| Component | Description |
|---|---|
| ASP.NET Core API | 一个小的请求入口,我可以对其进行端到端的仪表化,而不会产生噪音。 |
| Blazor Web UI | 一键健康检查、延迟、超时以及真实模型调用场景。 |
| .NET Aspire AppHost | 本地编排加上 Aspire Dashboard,实现快速切换。 |
Ollama (ollama/ollama:0.16.3) | 真实的本地模型调用行为,无需云端令牌费用。 |
| OpenTelemetry | 日志告诉我 什么,追踪告诉我 哪里,指标告诉我 频率。 |
为什么 LLM 超时感觉不同
- Prompt 变化相当于部署:代码可能保持不变,但延迟和失败模式可能会改变。
- 模型和运行时的变化会影响尾部延迟。
- 工具或依赖调用会放大方差——一次慢调用可能导致超时。
最小关联字段
为了保持快速的故障排查,我希望在所有地方都有几个字段:
| 字段 | 目的 |
|---|---|
run_id | 跟踪单个请求的生命周期 |
trace_id | 跨跨度和服务跟踪执行 |
prompt_version | 将行为关联到提示的更改 |
tool_version | 将故障关联到集成的更改 |
关联应如何呈现
POST /ask → trace_id in the trace span → run_id + trace_id in logs → timeout metric increases
我使用的命名约定
- snake_case 在日志和 JSON 中:
run_id、trace_id、prompt_version、tool_version - camelCase 在 C# 变量中:
runId、traceId、promptVersion、toolVersion
示例日志行
timeout during /ask run_id=9f0f2f3a6fdd4f5f9e9a1f4d8f6c6f3e trace_id=4c4f3b2e86d4d6a6b1f69a0d9d0d9f0a prompt_version=v1 tool_version=local-llm-v1
如果该链中的任意一个环节缺失,故障排查会立即变慢。
调试流程是什么样的
在实际操作中,步骤如下:
- 在 Web UI 中点击 Simulated Timeout (504)。
- 打开 Aspire Metrics,确认
llm_timeouts_total已增加。 - 跳转到 Traces,打开失败的
llm.run。 - 复制
trace_id,然后切换到日志并按trace_id或run_id进行过滤。 - 检查失败是否与特定的
prompt_version或tool_version对应。
这正是实验的核心:通过几个有目的的步骤,从超时症状快速定位可能的原因,而不是盲目猜测。
前置条件
- 已安装并运行 Docker Desktop 或 Docker Engine
- 已安装仓库
global.json中指定的 .NET SDK 版本 - Aspire 工作负载(如果你的设置需要)
dotnet workload install aspire
- 本地可用端口(或调整启动设置):
18888、18889、11434 - 如果使用稳定的 API 端口附录,还需要确保
17100端口空闲
第 1 步 — 克隆并运行仓库
git clone https://github.com/ovnecron/minimal-llm-observability.git
cd minimal-llm-observability
dotnet run --project LLMObservabilityLab.AppHost/LLMObservabilityLab.AppHost.csproj
打开终端中打印的 Aspire Dashboard URL。如果出现身份验证提示,使用终端中提供的一次性 URL。
已修复的本地 HTTP 启动设置
- Aspire Dashboard:
http://localhost:18888 - OTLP 端点(Aspire Dashboard):
http://localhost:18889 - Web UI(
LLMObservabilityLab.Web):从 Aspire Dashboard 资源列表中打开
在 AppHost 启动配置文件中已通过 ASPIRE_ALLOW_UNSECURED_TRANSPORT=true 启用不安全的本地传输。
如果你已经在本地的 11434 端口运行了 Ollama,请停止它或在 AppHost 中更改容器端口映射。
如果 Real Ollama Call 返回 “model not found”,请在运行中的容器中拉取默认模型:
docker exec -it "$(docker ps --filter "name=local-llm" --format "{{.Names}}" | head -n 1)" \
ollama pull llama3.2:1b
第2步 — 在 Web UI 中触发场景
-
打开 Aspire Dashboard → Resources → 点击
web-ui端点。 -
LLMObservabilityLab.Web的根页面提供一键操作:- Healthy Run
- Simulate Delay
- Real Ollama Call
- Simulated Timeout (504)
每次运行会显示:
run_idtrace_id- 状态
- 耗时
-
Web UI 还包含
/drill,提供固定的 15 分钟故障排查清单。
第 3 步 — 生成健康基线(可选)
在 Web UI 中点击 Healthy Run 大约 20 次。这会为您提供以下指标的快速基线:
llm_runs_totalllm_success_total
随后您可以将后续的失败运行与此基线进行比较。
第4步 — 强制超时并进行排查
- 在 Web UI 中点击 Simulated Timeout (504)。
- 立即打开 Aspire Dashboard。
按钮会返回受控的 504,以便您按需演练可观测性管道。
我的排查循环(目标:约 15 分钟)
| 阶段 | 操作 |
|---|---|
| Spot | 在 Metrics 中检查 llm_timeouts_total |
| Drill | 打开失败的 llm.run trace |
| Pivot | 按 trace_id 和 run_id 过滤日志 |
| Inspect | 对比 prompt_version 和 tool_version |
| Mitigate | 首先应用最小的安全修复 |
| Verify | 重新运行超时场景并确认恢复 |
简单的流程指南
- Metrics → 检查
llm_latency_ms是否出现峰值 - Traces → 过滤
scenario=simulate_timeout→ 打开失败的llm.run
我用于快速决策的最小信号
直接由此仓库发出
llm_runs_totalllm_success_totalllm_timeouts_totalllm_errors_totalllm_latency_ms
派生指标
task_success_rate = llm_success_total / llm_runs_total * 100
启动警报启发式
(这些是种子——请根据你的基线进行调优。)
task_success_rate在 30 分钟内下降 > 5 pp- 延迟分位数(源自
llm_latency_ms)相较基线上升 > 30 % - 工具版本范围的成功率(标记为
tool_version的运行)下降 < 90 %
故障排除
| 症状 | 解决办法 |
|---|---|
| 端口 11434 已被占用 | 停止本地 Ollama 实例或更改 AppHost 端口映射 |
| 没有追踪或指标 | 确认 Aspire Dashboard 正在运行且 OTLP 端点可访问 |
| 未找到模型 | 在容器内部运行 ollama pull … |
| CLI 或 API 调用失败 | 从 Aspire Dashboard 复制准确的 API 端点(llm‑api → Endpoints) |
已验证 vs. 观点
可观测性建议常常把硬性事实和个人工作流混在一起。
已验证(在此仓库可复现)
- 场景(健康、延迟、超时、真实调用)均通过 Web UI 触发。
- 关联链路完整:指标计数器 →
llm.run跟踪 → 带有run_id和trace_id的日志。
观点(对我有效,需自行调节)
- “15 分钟”目标循环。
- 上述告警阈值(作为起始种子,而非普遍真理)。
- 恰好四个关联字段(如系统需要可自行添加更多)。
最后思考
目标不是完美的仪表盘,而是缩短 time‑to‑diagnosis。
如果你无法从超时快速定位到确切的跟踪和日志行,你仍然在猜测。
我使用这个实验室找到了适合自己的工作流,希望它能帮助你构建适合自己的可观测性流水线。
如果你遇到问题,打开 GitHub issue,我很乐意提供帮助。