跳转至

性能监控

📊 除了 LangSmith,你还会用哪些工具监控 LangChain 应用的性能?

LangSmith 是官方首选,但它的成本不低,且依赖外网。在实际生产环境中,我通常会搭配自建监控栈,以达到更灵活、低延迟、低成本的监控。

主要工具组合:

  • OpenTelemetry + Jaeger / Grafana Tempo:用于全链路分布式追踪。LangChain 官方支持 OpenTelemetry,只需配置环境变量即可自动埋点。它能记录每个 LLM 调用、链执行、Retriever 的 Trace,并在 Jaeger 中以瀑布图展示每一步的耗时,直观定位瓶颈。相比 LangSmith,它的优势在于数据完全可控,可对接自建存储。

  • Prometheus + Grafana:用于指标监控。通过自定义回调处理器,在关键事件(on_llm_end, on_chain_end等)中暴露 Counter(调用次数)、Histogram(延迟分布)、Gauge(当前并发数)等指标,然后用 Grafana 可视化。这是生产级应用必备的,LangSmith 虽然也有统计,但 Prometheus 可以集成 AlertManager 做告警。

  • 日志系统 ELK(Elasticsearch, Logstash, Kibana):在回调中输出结构化 JSON 日志,包含 run_id、tags、prompt 摘要、响应预览、token 数、错误信息等。通过 Filebeat 采集到 Elasticsearch,可以在 Kibana 中灵活搜索和分析。尤其在调试 Agent 时,能快速过滤出错误步骤。

  • 专用回调处理器集成 Weights & Biases:LangChain 有内置的 WandbCallbackHandler,可以自动记录每一次 LLM 调用、链执行到 W&B。这对于实验追踪很有用,适合研究阶段。

  • 自定义性能采样器:对于高 QPS 服务,全量监控成本高,我会实现一个采样回调(如只采集 1% 的请求或延迟超过 500ms 的慢请求),把数据发送到 InfluxDB 或 ClickHouse,方便长期趋势分析。

实践经验:在一次线上故障中,我们发现 LangSmith 因网络延迟丢失了部分关键 Trace,而本地的 OpenTelemetry + Jaeger 完整记录了故障时刻的调用链,帮助我们快速定位到是某个工具调用超时。此后我们一直保留两套系统。


🔔 如何在回调中集成 Prometheus 或类似指标收集器?

集成 Prometheus 的核心是在回调处理器中,针对每个事件,调用 Prometheus 客户端库(prometheus_client)暴露的指标对象,递增计数器、观察延迟等。为了避免影响主链性能,必须保证指标的更新操作是线程安全且极快的(Prometheus 客户端内部已做优化)。

步骤:

  1. 安装 prometheus-client,创建指标:
  2. Counter("langchain_llm_calls_total", "Total LLM calls", ["model", "status"]) 统计成功/失败次数。
  3. Histogram("langchain_llm_latency_seconds", "LLM call latency", ["model"]) 统计延迟分布(P50, P99)。
  4. Gauge("langchain_llm_active", "Active LLM calls") 统计当前并发数。

  5. 实现自定义 BaseCallbackHandler(异步环境用 AsyncCallbackHandler):

from prometheus_client import Counter, Histogram, Gauge
import time

class PrometheusCallback(BaseCallbackHandler):
    def __init__(self):
        self._start_times = {}
        self.llm_calls_total = Counter("langchain_llm_calls_total", "Total LLM calls", ["model", "status"])
        self.llm_latency = Histogram("langchain_llm_latency_seconds", "LLM latency", ["model"])
        self.llm_active = Gauge("langchain_llm_active", "Active LLM calls")

    def on_llm_start(self, serialized, prompts, **kwargs):
        model = serialized.get("name", "unknown")
        self.llm_active.inc()
        self._start_times[kwargs["run_id"]] = (time.time(), model)

    def on_llm_end(self, response, **kwargs):
        run_id = kwargs["run_id"]
        start, model = self._start_times.pop(run_id)
        latency = time.time() - start
        self.llm_latency.labels(model=model).observe(latency)
        self.llm_calls_total.labels(model=model, status="success").inc()
        self.llm_active.dec()

    def on_llm_error(self, error, **kwargs):
        run_id = kwargs["run_id"]
        _, model = self._start_times.pop(run_id)
        self.llm_calls_total.labels(model=model, status="error").inc()
        self.llm_active.dec()
  1. 将回调绑定到链上,并通过 HTTP 暴露 /metrics 端点(FastAPI 中可以用 prometheus_fastapi_instrumentator 或手动挂载)。Prometheus 服务器定期抓取即可。

踩坑提醒:

  • 流式 LLM 的 on_llm_new_token 触发频率极高,不要在其中做锁操作或阻塞 I/O。

  • 分布式部署时,每个实例独立暴露 metrics,由 Prometheus 服务发现汇总;Histogram 的分位数计算在 PromQL 中完成。

  • 如果使用 Gunicorn 多进程,prometheus_client 需要 multiprocess 模式,否则各进程指标会互相覆盖。


📈 你如何设计一个仪表盘,实时显示 LLM 调用次数、延迟、错误率?

我会基于 Grafana + Prometheus 搭建仪表盘,因为数据已经在 Prometheus 中,Grafana 有丰富的可视化能力。

面板布局(4 行):

  • 第一行:概览数字
  • 当前 QPS(rate(langchain_llm_calls_total[1m])
  • 今日总调用次数(increase(...[24h])
  • 平均延迟(rate(langchain_llm_latency_seconds_sum[1m]) / rate(..._count[1m])
  • 错误率(sum(rate(...{status="error"}[1m])) / sum(rate(...[1m]))) 这些用 Stat 面板展示,配上颜色阈值。

  • 第二行:趋势图

  • 调用次数和错误次数的时间序列图(Stacked Graph)。
  • 延迟的 P50、P95、P99 折线图(使用 histogram_quantile)。

  • 第三行:模型分布与 Agent 步骤

  • 饼图或柱状图展示不同模型调用占比。
  • 如果有 Agent,用 Gauge 展示 Agent 步数分布。

  • 第四行:实例监控

  • 各实例的 CPU、内存、线程池使用率(来自 Node Exporter 或自定义指标)。
  • 活跃 LLM 并发数(langchain_llm_active)。

实现技巧:

  • 使用 Grafana 的 $datasource 和变量,快速切换环境。

  • 设置告警规则:错误率 > 5% 持续 5 分钟、P99 延迟 > 10s 等,通过 AlertManager 发送到 Slack 或 PagerDuty。

  • 对于流式应用,额外展示首 token 延迟(TTFT),需在 on_llm_new_token 首次触发时记录。

一次实际设计:我在一个客服项目中,把仪表盘投到办公室大屏,团队能实时看到负载。当 QPS 突然飙升时,我们立即扩容了 LLM API 的并发限制,避免了雪崩。


🔍 在高负载下,LangChain 应用的瓶颈通常在哪?你如何做性能剖析?

高负载下的瓶颈按常见程度排序:

  1. LLM API 调用延迟与限流:这是最大瓶颈。外部 API 的响应时间不可控,且通常有 RPM/TPM 限制。表现:大量请求排队,TTFT 升高。

  2. 同步阻塞工具:如果 Agent 的工具使用了同步 I/O(如同步数据库查询、HTTP 请求),它们在线程池中执行,高并发时线程池耗尽,导致请求阻塞。

  3. 回调与日志 I/O:不合理的回调(如每个 token 写磁盘、同步网络上报)会严重拖慢生成速度。

  4. 检索/向量数据库:高并发下,向量数据库的查询延迟和连接池限制成为瓶颈,尤其是 CPU 密集型索引。

  5. Python 全局解释器锁(GIL):对于 CPU 密集操作(如嵌入计算),GIL 会限制多线程并发。

  6. 内存/GC:大量对象创建导致频繁 GC,尤其是 Agent 的 scratchpad 和长对话历史。

性能剖析方法:

  • 使用 py-spy 采样:py-spy top --pid <PID> 实时查看 CPU 占用最高的函数。可以发现是否卡在同步 I/O 或正则解析上。

  • asyncio 慢回调检测:开启 PYTHONASYNCIODEBUG=1 或使用 loop.slow_callback_duration 检测阻塞协程。

  • 集成 OpenTelemetry:在调用链中各环节添加 Span,Jaeger 中直接看到各步骤耗时瀑布图,一目了然。

  • 自定义 @timeit 装饰器:在关键函数(_acall, _atool_execute)上打印耗时和参数。

  • 压力测试工具:用 locust 模拟并发用户,观察系统瓶颈。逐渐增加并发数,当延迟陡增时,查看各监控指标(API 响应时间、线程池使用率、CPU/内存),确定饱和点。

案例:一次高负载下,我们 Agent 的端到端延迟高达 20s。通过 Jaeger 发现,大部分时间耗在 sql_query 工具上——它使用了同步的 psycopg2。改为 asyncpg 异步驱动后,延迟降至 3s,吞吐量提升 5 倍。


🚀 如果让你优化一个 LangChain 应用以降低 50% 的延迟,你会从哪些方面入手?

目标:在不降低生成质量的前提下,将端到端延迟降低一半。需要从模型、网络、数据、架构四个层面系统优化。

  1. 模型与推理优化

  2. 换用更快的模型:例如从 GPT-4 换到 GPT-4 Turbo,或更小的 gpt-3.5-turbo,延迟可降 30-50%。

  3. 使用本地部署模型:如果隐私允许,使用 vLLM 部署量化模型(如 Llama-3 70B),通过 Tensor Parallel 加速,首 token 延迟远低于云 API。

  4. 开启流式输出:从 invoke 改为 stream,首 token 延迟(TTFT)可降至 200ms 内,用户感知大幅提升。

  5. 提示词与上下文优化

  6. 精简 Prompt:删除不必要的示例、指令,使用简短的句子。Prompt 越短,LLM 处理越快。

  7. 利用 Prompt Caching:对于重复的系统指令,使用 Anthropic 或 OpenAI 的 Prompt Caching,减少重复计算。

  8. 优化 RAG 上下文:使用 ContextualCompressionRetrieverLLMLingua 对检索到的文档进行压缩,减少送入 LLM 的 token 数,既能降延迟又省钱。

  9. 异步并发与资源调度

  10. 全链路异步化:确保所有 I/O(LLM 调用、数据库、HTTP)都用异步客户端,避免线程池阻塞。

  11. 合理设置并发:使用 Semaphore 限制并发数,防止 API 限流和资源过载;同时增加 httpx 的连接池大小。

  12. 预加载资源:在应用启动时,加载模型、建立数据库连接池、初始化向量索引,避免冷启动延迟。

  13. 使用连接复用:对 LLM API 使用 HTTP Keep-Alive,减少 TCP 握手开销。

  14. 缓存与复用

  15. LLM 响应缓存:对于重复的 Prompt,使用 Redis 缓存答案。LangChain 有 InMemoryCacheRedisCache,可即插即用。

  16. 检索结果缓存:对热门查询的向量检索结果进行缓存,避免每次实时计算。

  17. 工具结果缓存:如果工具是幂等的(如天气查询),基于参数缓存结果,设置合理的 TTL。

  18. 架构与算法改进

  19. 使用投机采样(Speculative Decoding):用一个小模型快速生成草稿,大模型验证,加速生成。

  20. Agent 优化:减少 Agent 的最大迭代步数,优化工具描述,使其尽快完成;用 Plan-and-Execute 代替 ReAct,一次性规划,并行执行。

  21. 批处理:如果业务允许,将多个请求合并为一次批量推理(对本地模型更有效)。

实际案例:在优化一个合同审查 Agent 时,我们通过:① 将 GPT-4 换成 GPT-4 Turbo(延迟-30%);② Prompt 精简去掉 3 个示例(-10%);③ 检索器增加 Redis 缓存(命中率 40%,总延迟-15%);④ 所有工具改为异步(消除阻塞,-20%)。最终端到端延迟从 12s 降至 4.5s,降幅超过 60%。