Hindsight 监控与可观测性实战:Prometheus 指标、Grafana 仪表盘与 OpenTelemetry 分布式追踪
【免费下载链接】hindsightHindsight: Agent Memory That Learns项目地址: https://gitcode.com/GitHub_Trending/hindsight2/hindsight
Hindsight(Agent Memory That Learns)为记忆服务的三大核心链路——retain(记忆摄入)、recall(记忆检索)、reflect(代理推理)——提供了完整的可观测性体系:Prometheus 指标、OpenTelemetry 分布式追踪和预置 Grafana 仪表盘。本文基于官方文档 developer/monitoring 并结合 hindsight_api/metrics.py 与 hindsight_api/tracing.py 的源码实现展开,读完你可以:在本地一条命令拉起 Grafana LGTM 监控栈、读懂并告警 Hindsight 的全套指标、按 GenAI 语义约定解读 LLM 调用追踪,并理解指标卡基数控制与跨进程追踪传播的底层机制。
一、可观测性体系总览
Hindsight 的可观测性由三根支柱构成:
- Prometheus 指标:API 进程在
/metrics端点暴露 Prometheus 格式指标,覆盖操作延迟、LLM 调用与 Token 用量、HTTP 请求、数据库连接池和进程资源; - OpenTelemetry 分布式追踪:为记忆操作和 LLM 调用生成 Span 层级,遵循 GenAI 语义约定 v1.37+,可导出到任意 OTLP 兼容后端;
- 预置 Grafana 仪表盘:三套开箱即用的 JSON 仪表盘,覆盖运维、LLM 成本与 API 服务健康。
实现集中在两个模块:指标采集由 metrics.py 中的MetricsCollector完成(基于opentelemetry.exporter.prometheus.PrometheusMetricReader导出),追踪由 tracing.py 中的TracerProvider与LLMSpanRecorder完成。二者均为条件启用:指标默认开启,追踪默认关闭且禁用时零开销(使用NoOpMetricsCollector/NoOpTracer空实现,见 metrics.py#L341-L412 与 tracing.py#L55-L94)。
二、本地快速启动:Grafana LGTM 监控栈
2.1 一条命令启动
本地开发场景使用 Grafana LGTM(Loki、Grafana、Tempo、Mimir)一体化栈:
./scripts/dev/start-monitoring.sh该脚本是一个便捷包装器,直接 exec 到 scripts/dev/monitoring/start.sh,实际编排定义在 scripts/dev/monitoring/docker-compose.yaml。它启动单个grafana/otel-lgtm:latest容器,提供:
- Grafana UI:
http://localhost:3000(容器内设置了GF_AUTH_ANONYMOUS_ENABLED=true与GF_AUTH_ANONYMOUS_ORG_ROLE=Admin,即匿名管理员访问,仅限本地开发); - Traces(Tempo):OTLP 端点
http://localhost:4318(HTTP)与http://localhost:4317(gRPC); - Metrics(Prometheus/Mimir):自动抓取
http://localhost:8888/metrics; - Logs(Loki):日志聚合可用;
- 预置仪表盘:Hindsight Operations、Hindsight LLM Metrics、Hindsight API Service。
生产部署提示:本地监控栈仅用于开发。生产环境应独立部署 Grafana LGTM,或使用商业平台(Grafana Cloud、DataDog、New Relic 等)。
2.2 从源码看容器如何接入 Hindsight
docker-compose.yaml 中有两个容易被忽略的细节:
抓取目标走
host.docker.internal。宿主机上的 Hindsight API(8888 端口)通过extra_hosts: host.docker.internal:host-gateway映射进容器。配套的 prometheus.yml 定义了 5 秒间隔的抓取任务,且同时抓取 API 与独立 worker 两个目标:scrape_configs: - job_name: 'hindsight-api' static_configs: - targets: ['host.docker.internal:8888'] metrics_path: '/metrics' scrape_interval: 5s - job_name: 'hindsight-worker' static_configs: - targets: ['host.docker.internal:8889'] metrics_path: '/metrics'这意味着独立 worker(8889 端口)的异步任务指标也纳入监控——即使宿主上没跑 worker,该目标也只是无害的空抓取。
仪表盘自动挂载。三套 Hindsight 仪表盘 JSON 被只读挂载进容器,并通过 grafana-dashboards.yaml 的 provisioning 配置自动装载,无需手动导入。
2.3 在 API 侧启用追踪
export HINDSIGHT_API_OTEL_TRACES_ENABLED=true export HINDSIGHT_API_OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318这两个环境变量在 config.py#L681-L685 定义,解析逻辑见 config.py#L4817-L4821:HINDSIGHT_API_OTEL_TRACES_ENABLED接受true/1/yes,未设置时默认关闭;启用但缺少 endpoint 时,initialize_tracing_from_config会记录警告并保持追踪关闭——追踪初始化失败永远不会阻止进程启动(见 tracing.py#L212-L264)。
三、Grafana 预置仪表盘
预置仪表盘位于monitoring/grafana/dashboards/,将以下 JSON 文件导入你的 Grafana 实例即可(使用上面的本地监控栈脚本时会自动 provisioning):
| 仪表盘 | 文件 | 说明 |
|---|---|---|
| Hindsight Operations | hindsight-operations.json | 操作速率、延迟百分位、按 bank 的指标 |
| Hindsight LLM Metrics | hindsight-llm.json | LLM 调用量、Token 用量、按 scope/provider 的延迟 |
| Hindsight API Service | hindsight-api-service.json | HTTP 请求、错误率、DB 连接池、进程指标 |
四、Metrics 端点与指标体系
Hindsight 在/metrics暴露 Prometheus 指标:
curl http://localhost:8888/metrics4.1 操作指标(Operation Metrics)
| 指标 | 类型 | 标签 | 说明 |
|---|---|---|---|
hindsight.operation.duration | Histogram | operation, bank_id, source, budget, max_tokens, success | 操作耗时(秒) |
hindsight.operation.total | Counter | operation, bank_id, source, budget, max_tokens, success | 已执行操作总数 |
标签说明:
operation:操作类型(retain、recall、reflect,以及consolidation等异步 worker 任务类型);bank_id:记忆库标识;source:操作触发来源(api、reflect、internal、worker);budget:指定时的预算等级(low、mid、high);max_tokens:指定时的 Token 上限;success:操作是否成功(true、false)。
source标签用于区分:api(客户端直接 API 调用)、reflect(reflect 操作期间的内部 recall 调用)、internal(其他内部操作)、worker(异步 worker 完成、即已认领任务到达终态结果时记录)。
对于source="worker",success是一个"完成吞吐量"信号:false表示任务在重试耗尽后抛出到 poller,或发生了未预期错误;在 executor 内部处理掉并正常返回的失败,这里仍记为success="true"。权威的异步操作失败状态请看hindsight_async_operations{status="failed"}。
源码级补充——客户端取消不计入指标:record_operation上下文管理器在异常路径上会检测OperationCancelledError(见 metrics.py#L620-L671)。客户端断开导致的协作式取消既非成功也非失败,被完全排除在hindsight.operation.total之外,避免拉高失败率或成功率。
4.2 Retain 指标
| 指标 | 类型 | 标签 | 说明 |
|---|---|---|---|
hindsight.retain.documents.total | Counter | outcome, bank_id | retain 处理的文档数,按抽取结果分 |
outcome:facts(retain 后文档有记忆单元)或no_facts(没有);bank_id:记忆库标识。
outcome="no_facts"是关键告警信号:文档已存储但没有产生任何记忆,在重新处理之前对recall和reflect完全不可见——而且 retain 本身是成功的,系统其他任何地方都不会报告这种文档。占比上升通常意味着 retain mission 排除了比预期更多的内容(源码注释追溯到 issue #3040,见 metrics.py#L482-L492)。推荐查询:
sum(rate(hindsight_retain_documents_total{outcome="no_facts"}[15m])) / sum(rate(hindsight_retain_documents_total[15m]))4.3 LLM 指标
| 指标 | 类型 | 标签 | 说明 |
|---|---|---|---|
hindsight.llm.duration | Histogram | provider, model, scope, success | LLM API 调用耗时(秒) |
hindsight.llm.calls.total | Counter | provider, model, scope, success | LLM API 调用总数 |
hindsight.llm.tokens.input | Counter | provider, model, scope, success, token_bucket | LLM 调用输入 Token |
hindsight.llm.tokens.output | Counter | provider, model, scope, success, token_bucket | LLM 调用输出 Token |
标签说明:
provider:LLM 提供商(openai、anthropic、gemini、groq、ollama、lmstudio、bedrock、litellm等);model:模型名(如gpt-4、claude-3-sonnet);scope:该 LLM 调用的用途(memory、reflect、consolidation、answer);success:调用是否成功;token_bucket:Token 计数量桶,用于卡基数控制(0-100、100-500、500-1k、1k-5k、5k-10k、10k-50k、50k+)。
token_bucket的分桶逻辑实现在 get_token_bucket():把连续的 Token 数映射为 7 个离散桶标签,使得可以在不产生高基数序列的前提下分析 Token 用量模式。
源码级补充——成本归因的两个额外计数器:record_llm_call还接受cached_input_tokens与thoughts_tokens参数(见 metrics.py#L725-L795),对应两个文档未展开的计数器:
hindsight.llm.tokens.cached_input:按缓存费率计费的输入 Token 子集(如 Gemini 上下文缓存),可独立跟踪 prompt-cache 命中率;hindsight.llm.tokens.thoughts:推理/思考 Token(Gemini 2.5+ 系列),按输出费率计费但不出现在候选输出里。源码注释明确指出:只按输出量看"显得很便宜"的工作负载,若模型在跑长推理链,实际成本可能很高——这两个计数器让成本归因保持诚实。
4.4 HTTP 请求指标
| 指标 | 类型 | 标签 | 说明 |
|---|---|---|---|
hindsight.http.duration | Histogram | method, endpoint, status_code, status_class | HTTP 请求耗时(秒) |
hindsight.http.requests.total | Counter | method, endpoint, status_code, status_class | HTTP 请求总数 |
hindsight.http.requests.in_progress | UpDownCounter | method, endpoint | 正在处理中的请求数 |
method:HTTP 方法(GET、POST、PUT、DELETE);endpoint:请求路径(做了归一化以降低基数——UUID 替换为{id});status_code:HTTP 状态码(200、400、500等);status_class:状态码类别(2xx、4xx、5xx)。
源码级补充——endpoint 归一化:normalize_http_endpoint() 除替换 UUID 与纯数字 ID 为{id}外,还会把/banks/<任意 bank id>段折叠为/banks/{bank_id}(包括user-123这类非数字 bank id),否则每个 bank 都会创建一条永不淘汰的 OTel 时间序列。这与token_bucket是同一设计思路:先模板化高基数维度,再打点。
4.5 数据库连接池指标
| 指标 | 类型 | 标签 | 说明 |
|---|---|---|---|
hindsight.db.pool.size | Gauge | - | 池中当前连接数 |
hindsight.db.pool.idle | Gauge | - | 池中空闲连接数 |
hindsight.db.pool.min | Gauge | - | 池最小连接数 |
hindsight.db.pool.max | Gauge | - | 池最大连接数 |
4.6 进程指标
| 指标 | 类型 | 标签 | 说明 |
|---|---|---|---|
hindsight.process.cpu.seconds | Gauge | type | 进程 CPU 时间(秒) |
hindsight.process.memory.bytes | Gauge | type | 进程内存用量(字节) |
hindsight.process.open_fds | Gauge | - | 打开的文件描述符数 |
hindsight.process.threads | Gauge | - | 活跃线程数 |
标签取值:CPU 的type为user或system;内存的type为rss_max(最大常驻集大小)。
实现上是四个 observable gauge,在 scrape 时回调采集(见 _setup_process_metrics):CPU/内存来自resource.getrusage(Linux 上ru_maxrss以 KB 计,源码内做 ×1024 换算);open_fds直接统计/proc/self/fd条目数;整个进程指标块仅在resource模块可用时注册,即 Windows 上会被跳过。
4.7 直方图桶边界
为提升百分位精度,三个时长直方图均配置了自定义桶边界(定义于 metrics.py#L73-L79,通过ExplicitBucketHistogramAggregationView 绑定到对应 instrument):
操作耗时桶(秒):
0.1, 0.25, 0.5, 0.75, 1.0, 2.0, 3.0, 5.0, 7.5, 10.0, 15.0, 20.0, 30.0, 60.0, 120.0LLM 耗时桶(秒):
0.1, 0.25, 0.5, 1.0, 2.0, 3.0, 5.0, 10.0, 15.0, 30.0, 60.0, 120.0HTTP 耗时桶(秒):
0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.04.8 源码中的额外指标:运行时卡滞与流水线剖面
除文档列出的核心指标外,MetricsCollector 还暴露了一批面向故障定位的运行时指标,值得在生产监控中一并接入:
| 指标 | 说明 |
|---|---|
hindsight.db.pool.acquire_wait(Histogram) | 调用方获取池化 DB 连接的等待时长——连接池耗尽信号 |
hindsight.retain.phase.duration/hindsight.retain.phase.calls(Histogram/Counter) | retain 各阶段耗时与轮次数。retain 横跨分块、embedding、memories store、Postgres 四个子系统,慢 retain 从此可定位到具体阶段(阶段在子批并发时可能重叠,应与hindsight.retain.duration对比阅读) |
hindsight.recall.phase.duration(Histogram) | recall 各阶段耗时。源码注释指出默认桶(首桶 5 秒)会让所有毫秒级阶段挤进一个桶、百分位失真,因此该直方图显式配置了0.001~10.0秒的细桶;diagnostic标签区分"父阶段的子集"与"兄弟阶段",防止把时间重复求和 |
hindsight.event_loop.stalls/hindsight.event_loop.stall_duration(Counter/Histogram) | 事件循环卡滞检测(配合 loop watchdog),线程阻塞的运行时信号 |
hindsight.consolidation.batch_failures(Counter) | consolidation LLM 批调用失败计数,按failure_class(retry可自愈的传输型失败 /fail_fast响应 schema 被拒的失败)区分——它是"卡住的行"计数之外的缺失信号 |
异步操作/整合积压 gauge(metrics_backlog_enabled开启时) | 队列深度类指标,由后台任务每 30 秒刷新缓存,保证 scrape 路径保持同步 |
卡基数控制配置:record_operation_result中bank_id标签是否附加由配置项metrics_include_bank_id控制(见 metrics.py#L673-L707 与 config.py)。bank 数量多的部署可以关闭它以收敛序列数,所有指标都额外携带tenant标签用于多租户区分。
五、Prometheus 抓取配置
独立部署 Prometheus 时,最小抓取配置如下:
scrape_configs: - job_name: 'hindsight' static_configs: - targets: ['localhost:8888']本地监控栈中的 prometheus.yml 则展示了生产化写法:5 秒抓取间隔、host.docker.internal目标、以及同时覆盖 API(8888)与 worker(8889)两个 job。
六、常用 PromQL 查询
按类型统计操作平均延迟:
rate(hindsight_operation_duration_sum[5m]) / rate(hindsight_operation_duration_count[5m])每分钟 LLM 调用量(按 provider):
rate(hindsight_llm_calls_total[1m]) * 60P95 LLM 延迟:
histogram_quantile(0.95, rate(hindsight_llm_duration_bucket[5m]))各模型累计消耗 Token:
sum by (model) (hindsight_llm_tokens_input_total + hindsight_llm_tokens_output_total)内部 vs API 的 recall 操作:
sum by (source) (rate(hindsight_operation_total{operation="recall"}[5m]))每秒 HTTP 请求数(按 endpoint):
sum by (endpoint) (rate(hindsight_http_requests_total[1m]))HTTP 错误率(5xx):
sum(rate(hindsight_http_requests_total{status_class="5xx"}[5m])) / sum(rate(hindsight_http_requests_total[5m]))P95 HTTP 延迟:
histogram_quantile(0.95, sum by (le) (rate(hindsight_http_duration_seconds_bucket[5m])))数据库连接池利用率 / 活跃连接数:
hindsight_db_pool_size / hindsight_db_pool_max hindsight_db_pool_size - hindsight_db_pool_idleCPU 使用率:
rate(hindsight_process_cpu_seconds{type="user"}[1m])七、分布式追踪(OpenTelemetry)
Hindsight 支持对记忆操作与 LLM 调用做 OpenTelemetry 分布式追踪,遵循 GenAI 语义约定 v1.37+。完整环境变量说明见 developer/configuration 的 OpenTelemetry Tracing 一节。
7.1 快速开始
# 启用追踪 export HINDSIGHT_API_OTEL_TRACES_ENABLED=true export HINDSIGHT_API_OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 # 用 Grafana LGTM(本地开发)查看 traces ./scripts/dev/start-monitoring.sh # 打开 http://localhost:3000 → Explore → Tempo支持任意 OTLP 兼容后端(Grafana LGTM、Langfuse、OpenLIT、DataDog、New Relic、Honeycomb、Pydantic Logfire 等)。除核心两个变量外,config.py#L681-L685 还定义了:
HINDSIGHT_API_OTEL_EXPORTER_OTLP_HEADERS:key1=value1,key2=value2格式的导出请求头(用于商业后端的鉴权);HINDSIGHT_API_OTEL_SERVICE_NAME:覆盖service.name资源属性;HINDSIGHT_API_OTEL_DEPLOYMENT_ENVIRONMENT:deployment.environment.name资源属性。
值得注意的实现细节(见 tracing.py#L151-L209):
- 导出协议是 OTLP HTTP(
OTLPSpanExporter),且 endpoint 若不以/v1/traces结尾会自动追加——所以本地 LGTM 填http://localhost:4318(HTTP 端口)即可,不要填 4317(那是 gRPC 端口,本实现不使用); - API 与独立 worker 都会初始化追踪:
initialize_tracing_from_config同时被 FastAPI lifespan 与独立 worker 入口调用,因此设置HINDSIGHT_API_OTEL_*后,所有执行工作的进程都会上报 traces,而不只是服务 HTTP 的进程; - 优雅退出会强制 flush:
shutdown_tracing直接对 SDK provider 调shutdown(),强制排空BatchSpanProcessor队列——worker 上单个 consolidation span 可能长达数分钟,若进程收到 SIGTERM 时不 flush,批处理队列里的一切都会丢失。
7.2 Span 层级
父 Span(操作层):
hindsight.retain—— 记忆摄入hindsight.recall—— 记忆检索hindsight.recall_embedding—— 查询向量化hindsight.recall_retrieval—— 并行检索(语义、BM25、图、时序)hindsight.recall_fusion—— 倒数排名融合hindsight.recall_rerank—— 交叉编码器重排
hindsight.reflect—— 代理式推理hindsight.reflect_tool_call—— 工具执行(recall、lookup 等)
hindsight.consolidation—— 观察合成hindsight.mental_model_refresh—— 心智模型更新
子 Span(LLM 调用层):
- 按 scope 命名(如
hindsight.memory、hindsight.reflect) - 以 event 形式包含完整 prompt/completion
- 属性遵循 GenAI 语义约定
操作父 Span 由 create_operation_span() 创建,统一打上hindsight.operation与hindsight.bank_id属性;LLM 子 Span 由 LLMSpanRecorder 在调用完成后用显式起止时间戳创建(start_time = end - duration),以兼容各 provider 已有的同步打点模式。
源码级补充——跨进程追踪上下文传播:API 入队的异步任务在 worker 进程执行,worker 本身无从知道是哪个请求入队了它;没有额外机制时,API 的 span 与 worker 的hindsight.retainspan 会是两条互不相关的 trace。Hindsight 的解法是把 W3Ctraceparent注入任务 payload(键_traceparent),worker 侧再提取并续接调用方 trace(见 inject_task_trace_context / extract_task_trace_context)。内部定时任务或追踪关闭时入队的 payload 不携带该键,提取返回 None,逻辑安全降级。
7.3 Span 属性
操作 Span:
hindsight.operation—— 操作类型hindsight.bank_id—— 记忆库 IDhindsight.query—— 查询文本(截断至 100 字符)hindsight.fact_types—— recall 的事实类型hindsight.thinking_budget—— 预算分配hindsight.max_tokens—— Token 上限
LLM Span(GenAI 语义约定):
gen_ai.operation.name—— 恒为"chat"gen_ai.provider.name—— 提供商(openai、anthropic、google等)gen_ai.request.model—— 模型名gen_ai.usage.input_tokens/gen_ai.usage.output_tokens—— 输入/输出 Tokenhindsight.scope—— LLM 调用用途(memory、reflect、consolidation等)
Event:
gen_ai.client.inference.operation.details—— 完整 prompt 与 completion(含输入/输出消息 JSON、系统指令、finish reasons,见 tracing.py#L501-L515)
三个实现层面的注意点:
- 提供商名映射:PROVIDER_NAME_MAPPING 将 Hindsight 内部提供商名规范化到 GenAI 约定——
gemini/vertexai统一映射为google、claude-code映射为anthropic、github-copilot映射为github,因此消费端按gen_ai.provider.name聚合时不会因内部别名而分裂; - 内容截断:单条内容超过 10 万字符(
MAX_CONTENT_LENGTH)会被截断并追加[TRUNCATED: ...]标注,防止 span 超出后端大小限制; - 追踪永不拖累业务:
LLMSpanRecorder.record_llm_call整体包裹在 try/except 中,且外层是CompositeSpanRecorder扇出结构——任一 recorder 失败只记录 debug 日志,既不影响 LLM 调用本身,也不影响其他 recorder。
八、小结
Hindsight 的可观测性设计可以归纳为三条主线:指标覆盖从 HTTP 入口到 LLM 调用的全链路(操作、retain 抽取结果、LLM 成本、连接池、进程资源),并通过token_bucket、endpoint 模板化、metrics_include_bank_id等手段系统性控制卡基数;追踪遵循 GenAI 语义约定,Span 层级与记忆管道(retain/recall/reflect/consolidation)一一对应,且通过 payload 级 traceparent 传播打通了 API 与 worker 两个进程的 trace;工具链上,一条./scripts/dev/start-monitoring.sh即可获得带三套预置仪表盘的本地 LGTM 全栈。把 monitoring/grafana/dashboards/ 中的仪表盘导入生产 Grafana、将hindsight.operation.total的失败率与outcome="no_facts"占比纳入告警,即可在记忆库规模增长时第一时间定位是 LLM 侧、数据库侧还是抽取策略侧出了问题。
【免费下载链接】hindsightHindsight: Agent Memory That Learns项目地址: https://gitcode.com/GitHub_Trending/hindsight2/hindsight
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考