1. 从“黑盒”到“白盒”为什么我们需要观测 Hermes如果你最近在折腾本地大模型应用尤其是那些能帮你自动处理文档、联网搜索、甚至接入钉钉/微信的智能体Agent那你大概率听说过 Hermes。它就像一个功能强大的“数字员工”能帮你完成很多重复性工作。但不知道你有没有遇到过这样的场景你给 Hermes 发了个指令让它去查一下最新的行业报告并总结然后它就陷入了漫长的沉默。你盯着它的界面除了一个“正在思考”的提示你完全不知道它卡在了哪一步——是在调用大模型 API 时超时了是联网搜索被某个网站屏蔽了还是它在解析你给的 PDF 文档时内存溢出了这就是典型的“黑盒”状态。我们只知道输入和可能很久以后才有的输出中间过程完全不可见。当问题发生时排查起来就像在黑暗中摸索效率极低。而“可观测性”Observability要做的就是给这个黑盒装上“仪表盘”和“行车记录仪”让我们能实时看到它的“心跳”指标/Metrics、“呼吸”日志/Logs和“行动轨迹”追踪/Traces。具体到 Hermes这意味着我们需要知道性能如何处理一个用户请求平均耗时多久调用大模型的延迟高不高内存和CPU使用率是否正常是否健康服务是否在运行与钉钉机器人、微信客户端的连接是否稳定依赖的本地模型服务是否可用内部发生了什么一个用户查询具体经历了哪些步骤意图识别、工具调用、模型推理、结果整合每个步骤花了多长时间在哪一步失败了如何优化哪个技能Skill最耗时哪种工具调用最容易出错用户最常使用哪些功能要实现这些手动加打印语句print是远远不够的。我们需要一个系统化的方案。而OpenTelemetryOTel正是当前云原生领域事实上的可观测性数据采集标准它提供了统一的 API 和 SDK 来收集指标、日志和追踪。Elastic则是一个强大的可观测性后端平台擅长存储、索引和可视化这些海量数据。将 OTel 与 Elastic 结合就能为 Hermes 构建一个从数据采集、传输到存储、分析、告警的完整可观测性管道。简单来说我们的目标就是用 OTel 作为“传感器”和“数据采集器”深入 Hermes 内部采集其运行时的各项数据然后用 Elastic 作为“数据中心”和“监控大屏”来存储这些数据并通过 Kibana 进行可视化分析最终让我们对 Hermes 的运行状态了如指掌。2. 观测蓝图设计OTel 如何与 Hermes 集成在动手敲代码之前我们必须先理清观测的架构。Hermes 作为一个智能体框架其核心流程通常涉及用户输入、智能体Agent调度、工具Tools执行、大模型LLM调用等环节。我们的观测需要覆盖这个完整链路。2.1 理解 Hermes 的运行时架构与观测切入点虽然 Hermes 的具体实现可能因版本和部署方式而异例如hermes-agent客户端、hermes-desktop桌面应用或自定义部署但其核心组件是类似的。我们需要在这些关键位置埋点HTTP/gRPC 服务入口如果 Hermes 提供了 API 服务例如用于接收钉钉/微信消息的 Webhook这是追踪的起点。我们需要记录每个请求的唯一条目 ID、用户信息、请求内容等。智能体Agent执行层这是 Hermes 的大脑。我们需要观测一个 Agent 从启动到结束的完整生命周期记录它调用了哪些技能Skill、做了多少次决策Reasoning、以及最终输出了什么。工具Tools调用层这是智能体与外界交互的手脚。例如调用web_search工具进行联网搜索调用read_file工具读取文档。这里是最容易出问题的地方网络超时、权限错误、解析失败必须详细记录每次工具调用的输入、输出、耗时和状态成功/失败。大模型LLM调用层无论是调用云端 API如 OpenAI, Minimax还是本地部署的模型如通过 Ollama都需要记录每次调用的 prompt、返回的 completion、token 使用量、耗时和费用如果适用。技能Skill执行层每个 Skill 是一个具体的功能模块。需要记录 Skill 的执行耗时、成功率和可能的错误信息。系统资源层监控运行 Hermes 的主机或容器的 CPU、内存、磁盘 I/O、网络 I/O 等基础指标。2.2 OTel 数据采集策略自动与手动结合OTel 提供了不同层次的集成方式对于 Hermes 这样的 Python 应用我们可以采用组合策略自动仪表化Auto-instrumentation这是最省力的方式。OTel 为许多流行的 Python 库提供了自动埋点支持。例如对于 HTTP 框架如果 Hermes 使用 FastAPI、Flask 或 DjangoOTel 可以自动捕获请求和响应的追踪与指标。对于 HTTP 客户端如果 Hermes 使用requests或aiohttp进行外部调用如调用大模型 APIOTel 可以自动追踪这些外部调用。对于数据库如果使用 SQLite如hermes sqlite可能用于存储会话或知识库OTel 可以自动追踪 SQL 查询。部署时通常只需要设置环境变量或几行初始化代码就能启用这些自动仪表化。手动仪表化Manual instrumentation自动仪表化无法覆盖业务逻辑。对于 Hermes 的核心概念——Agent、Tool、Skill 的执行我们必须手动埋点。这需要我们在关键的业务代码处使用 OTel 的 API 来创建 Span追踪中的单个操作单元和记录指标。例如在一个 Tool 的执行函数开头和结尾手动创建 Span并为其添加属性如tool.nameweb_search,query...记录事件如start,complete或error和状态。提示一个最佳实践是先通过自动仪表化快速获得网络、数据库等底层组件的可观测性再针对核心业务逻辑进行精准的手动埋点。这样既能快速搭建起观测框架又能确保关键业务流路的透明度。2.3 数据流与后端选型为什么是 ElasticOTel Collector 或 SDK 收集到数据称为 Telemetry Data后需要发送到一个后端进行存储和分析。虽然 OTel 支持 Jaeger、Prometheus 等多种后端但选择 Elastic特别是其 Elastic StackElasticsearch Kibana有显著优势三支柱统一平台Elasticsearch 可以同时高效存储和索引追踪Traces、指标Metrics和日志Logs。这意味着我们可以在 Kibana 的一个界面里关联查看一次失败请求的详细追踪、当时的系统指标峰值以及相关的错误日志实现真正的端到端根因分析。强大的关联分析能力Elasticsearch 的倒排索引和 Kibana 的 Lens、APM 等应用可以轻松实现跨数据集的关联查询。例如我们可以快速找出所有调用了web_search工具且耗时超过 5 秒的追踪然后进一步查看这些追踪发生时系统的网络带宽使用情况。成熟的告警与机器学习Elastic Stack 内置了强大的告警功能Alerting和机器学习Machine Learning异常检测。我们可以设置规则例如“当 Hermes 的请求错误率在 5 分钟内超过 5% 时触发告警”或者利用机器学习自动发现工具调用耗时的异常模式。对 OTel 的原生支持Elastic 提供了 OTel 的原生集成。我们可以通过配置 OTel Collector 的otlpexporter将数据直接发送到 Elasticsearch 的专用端点数据会自动被解析并映射到 Elastic 的通用模式ECS上开箱即用。因此我们的数据流设计如下Hermes 应用 (OTel SDK)-OTel Collector (可选用于聚合、处理)-Elasticsearch-Kibana (用于可视化/告警)。3. 实战部署一步步搭建 Hermes 可观测性栈理论清晰后我们进入实战环节。假设我们有一个基于 Python 开发的 Hermes 智能体服务。以下是详细的搭建步骤。3.1 环境准备与 OTel SDK 集成首先在你的 Hermes 项目环境中安装必要的 OTel Python 包。pip install opentelemetry-api opentelemetry-sdk pip install opentelemetry-exporter-otlp-proto-http # 用于将数据通过HTTP发送到Collector或Elastic pip install opentelemetry-instrumentation # 基础仪表化工具 # 根据你使用的库安装对应的自动仪表化包 pip install opentelemetry-instrumentation-fastapi # 如果使用FastAPI pip install opentelemetry-instrumentation-requests # 如果使用requests库 pip install opentelemetry-instrumentation-sqlite3 # 如果使用sqlite3接下来在你的 Hermes 应用启动入口通常是main.py或app.py的顶部初始化 OTel SDK。这里我们配置一个控制台导出器用于调试和一个 OTLP HTTP 导出器用于发送到 Elastic。import os from opentelemetry import trace from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter from opentelemetry.sdk.resources import Resource, SERVICE_NAME, SERVICE_VERSION # 1. 创建资源标识你的服务 resource Resource(attributes{ SERVICE_NAME: hermes-agent-service, SERVICE_VERSION: 1.0.0, deployment.environment: os.getenv(ENV, development), }) # 2. 设置全局的 TracerProvider trace.set_tracer_provider(TracerProvider(resourceresource)) tracer_provider trace.get_tracer_provider() # 3. 创建导出器 # 控制台导出器仅开发环境使用生产环境应关闭 console_exporter ConsoleSpanExporter() # OTLP HTTP 导出器指向你的 OTel Collector 或 Elastic APM 服务器 # 假设 Collector 运行在本地 4318 端口 otlp_exporter OTLPSpanExporter( endpointhttp://localhost:4318/v1/traces, # 如果直接发送到 Elastic APM Server端点是类似 http://elasticsearch:8200 ) # 4. 创建处理器并添加到 TracerProvider span_processor_console BatchSpanProcessor(console_exporter) span_processor_otlp BatchSpanProcessor(otlp_exporter) tracer_provider.add_span_processor(span_processor_console) tracer_provider.add_span_processor(span_processor_otlp) # 5. 启用自动仪表化需在应用代码导入前执行 from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor from opentelemetry.instrumentation.requests import RequestsInstrumentor from opentelemetry.instrumentation.sqlite3 import SQLite3Instrumentor # 假设你的 app 是 FastAPI 实例 # FastAPIInstrumentor.instrument_app(app) RequestsInstrumentor().instrument() SQLite3Instrumentor().instrument()关键配置解析Resource定义了产生遥测数据的服务实体。SERVICE_NAME和deployment.environment是至关重要的属性它们会在 Kibana 中用于筛选和分组数据。BatchSpanProcessor这是生产环境的推荐处理器。它会将多个 Span 批量打包后再发送极大地提高了传输效率减少了对应用性能的影响。OTLPExporter的endpoint这是配置的枢纽。如果你部署了独立的 OTel Collector就指向 Collector 的 OTLP 接收端口默认4318。如果你使用 Elastic Cloud 或自建 Elastic Stack 并开启了 APM 服务器则需要指向 APM 服务器的入口通常是http://elastic-host:8200。具体地址需要根据你的 Elastic 部署方式调整。3.2 核心业务逻辑的手动埋点实践现在让我们深入到 Hermes 的业务代码中对 Agent 和 Tool 的执行进行手动埋点。假设我们有一个简单的工具调用函数。from opentelemetry import trace from opentelemetry.trace import Status, StatusCode tracer trace.get_tracer(__name__) def web_search_tool(query: str, max_results: int 5): 模拟一个联网搜索工具。 # 为这次工具调用创建一个独立的 Span with tracer.start_as_current_span(web_search_tool) as span: # 为 Span 添加自定义属性这些是排查问题的关键上下文 span.set_attribute(tool.name, web_search) span.set_attribute(tool.input.query, query) span.set_attribute(tool.input.max_results, max_results) span.set_attribute(component, hermes-tools) try: # 模拟一些处理逻辑 span.add_event(start_searching) # 这里应该是实际的搜索代码例如使用 requests 库 # 由于我们已经自动仪表化了 requests这个外部调用也会被自动追踪并作为当前 Span 的子 Span。 # response requests.get(fhttps://api.search.com?q{query}) # results parse_response(response) # 模拟耗时和结果 import time time.sleep(0.5) # 模拟网络延迟 results [fResult {i} for {query} for i in range(max_results)] span.add_event(search_completed) # 记录结果信息注意避免记录敏感数据 span.set_attribute(tool.output.result_count, len(results)) # 将 Span 状态标记为 OK span.set_status(Status(StatusCode.OK)) return results except Exception as e: # 如果发生异常记录错误信息并将状态标记为 ERROR span.record_exception(e) span.set_status(Status(StatusCode.ERROR, str(e))) # 添加错误属性 span.set_attribute(error.type, type(e).__name__) span.set_attribute(error.message, str(e)) # 重新抛出异常或返回错误 raise手动埋点要点start_as_current_span创建了一个名为web_search_tool的 Span。这个名称在 Kibana 的追踪视图中会清晰显示。set_attribute这是注入上下文的核心。我们记录了工具名、输入参数、组件类型。当在 Kibana 中搜索所有tool.nameweb_search的追踪时这些属性就是筛选条件。add_event在 Span 的时间线上标记关键事件点如“开始搜索”、“搜索完成”有助于在时间轴上精确定位阶段。异常处理在except块中record_exception和set_status(StatusCode.ERROR)是标准操作。这能确保任何错误都会在追踪中高亮显示并且异常堆栈信息会被记录下来极大方便了排错。上下文传播使用with tracer.start_as_current_span(...)确保了在这个代码块中创建的任何子 Span比如通过requests库发起的 HTTP 请求都会自动成为当前 Span 的一部分形成完整的调用树。对于 Agent 的主循环你也可以用类似的模式创建一个更顶层的 Span 来包裹整个 Agent 的思考-行动周期。3.3 部署与配置 OTel Collector可选但推荐虽然 SDK 可以直接将数据发送到 Elasticsearch通过 Elastic 的 APM 服务器但在生产环境中我强烈建议使用OTel Collector。它是一个独立的进程扮演了“遥测数据网关”的角色好处多多解耦与缓冲应用只负责将数据发送到本地的 Collector由 Collector 负责转发到后端。即使 Elasticsearch 临时不可用Collector 可以缓冲数据避免数据丢失或拖垮应用。数据处理Collector 可以在传输过程中对数据进行处理比如添加统一的属性如集群名称、过滤掉不必要的 Span、采样在高流量时只收集部分数据以控制成本、或转换数据格式。统一接收一个 Collector 可以接收来自多个服务、多种语言Go, Java, Python等的数据是微服务架构中可观测性的枢纽。部署 Collector 很简单通常使用一个配置文件otel-collector-config.yamlreceivers: otlp: protocols: http: endpoint: 0.0.0.0:4318 grpc: endpoint: 0.0.0.0:4317 processors: batch: # 批处理处理器优化性能 timeout: 1s send_batch_size: 1024 # 可以添加其他处理器如 attributes添加标签、filter过滤等 exporters: logging: loglevel: debug otlphttp/elastic: endpoint: https://your-elastic-cloud-endpoint.apm.us-central1.gcp.cloud.es.io:443 # Elastic Cloud APM 端点 headers: Authorization: Bearer your_elastic_api_key # 使用API密钥认证 tls: insecure: false service: pipelines: traces: receivers: [otlp] processors: [batch] exporters: [otlphttp/elastic] metrics: receivers: [otlp] processors: [batch] exporters: [otlphttp/elastic] logs: receivers: [otlp] processors: [batch] exporters: [otlphttp/elastic]然后使用 Docker 运行 Collectordocker run -p 4317:4317 -p 4318:4318 \ -v $(pwd)/otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml \ otel/opentelemetry-collector-contrib:latest配置完成后记得将 Hermes 应用中OTLPSpanExporter的endpoint改为http://localhost:4318/v1/traces。3.4 在 Elastic Stack 中查看数据数据通过 Collector 流入 Elasticsearch 后我们就可以打开 Kibana 进行探索了。访问 APM 应用在 Kibana 左侧导航栏找到并进入“Observability” - “APM”。发现你的服务在“Services”列表中你应该能看到hermes-agent-service即我们在代码中设置的SERVICE_NAME。点击进入。查看概览服务详情页展示了请求吞吐量、延迟、错误率等关键指标以及最新的追踪列表。深入追踪详情点击任意一次请求的追踪你会看到一个完整的瀑布图Waterfall清晰地展示了从 HTTP 入口到 Agent 思考再到各个 Tool 调用如web_search_tool以及其内部网络请求的完整层级和时间消耗。你可以点击每个 Span 查看其详细的属性Attributes这正是我们手动埋点时设置的tool.name、query等信息。使用日志关联如果 Hermes 的应用日志也通过 OTel 或 Filebeat 等方式采集到了 Elasticsearch并且在日志中记录了追踪 IDTrace ID那么在 Kibana 的“Logs”应用中你可以直接通过 Trace ID 过滤出这次请求对应的所有日志行实现追踪与日志的无缝跳转。创建仪表盘在“Dashboard”中你可以自由地组合各种可视化组件。例如一个时序图显示hermes-agent-service过去一小时的请求延迟p95。一个饼图展示不同tool.name的调用次数分布。一个表格列出最近失败的工具调用及其error.message。将系统指标CPU、内存与业务指标错误率放在同一个时间轴上便于定位资源瓶颈导致的业务问题。4. 从数据到洞察基于可观测性的排错与优化实战有了可观测性数据排查问题就从“猜”变成了“查”。我们模拟几个典型场景。4.1 场景一用户报告“Hermes 响应缓慢”传统方式登录服务器看日志猜可能是模型调用慢也可能是网络问题需要逐个手动排查。可观测性方式打开 Kibana APM进入hermes-agent-service。查看“Latency”图表确认延迟确实有飙升。在“Traces”列表中筛选出高延迟例如 10s的追踪。点开一个慢追踪瀑布图立即告诉你瓶颈所在。假设你看到hermes-agent-service总耗时 12s。其下子 Spanweb_search_tool耗时 11.5s。web_search_tool下又有一个requests发起的子 Span 耗时 11.4s。结论一目了然延迟的罪魁祸首是web_search_tool中的某个网络请求。点击这个requestsSpan查看其属性可能发现请求的 URL 或状态码。结合当时的网络指标在 Infrastructure 或 Metrics 应用里查看就能判断是目标网站慢还是自身网络问题。4.2 场景二特定技能Skill频繁失败传统方式在浩如烟海的日志文件中grep错误信息难以统计失败频率和模式。可观测性方式在 Kibana 中进入“Discover”视图索引模式选择 APM 追踪相关的索引如traces-apm*。使用 KQLKibana Query Language编写查询语句processor.event: transaction AND service.name: hermes-agent-service AND transaction.result: error这能找出所有失败的请求。为了进一步分析是哪个工具导致的我们可以添加聚合。点击“Add filter”添加span.name: web_search_tool AND span.status.code: ERROR现在我们可以创建一个可视化。点击“Lens”选择我们筛选后的数据。在“Horizontal axis”放timestamp按时间聚合。在“Vertical axis”放Count of documents计数。在“Break down by”放span.attributes.tool.input.query.keyword按查询词分组。这样你就能看到一个图表显示不同搜索查询的失败次数随时间的变化。也许你会发现查询特定关键词时总是失败这可能触发了搜索服务的风控。更进一步我们可以创建一个告警规则Alerting。在“Management” - “Stack Management” - “Rules and Connectors”中创建一个基于查询的规则当过去5分钟内web_search_tool的错误率超过 20% 时自动发送通知到 Slack 或钉钉。4.3 场景三评估不同大模型LLM的性能与成本如果你为 Hermes 配置了多个模型端点例如同时支持 OpenAI GPT-4 和本地部署的 Llama 3可观测性数据可以帮助你做科学的决策。手动埋点补充在调用 LLM 的代码段为每次调用创建 Span并添加关键属性with tracer.start_as_current_span(llm_inference) as span: span.set_attribute(llm.provider, openai) span.set_attribute(llm.model, gpt-4-turbo) span.set_attribute(llm.input_tokens, input_token_count) span.set_attribute(llm.output_tokens, output_token_count) # ... 调用逻辑在 Kibana 中分析对比不同llm.model的请求平均延迟p50, p95和错误率。通过llm.input_tokens和llm.output_tokens估算成本如果知道单价。分析不同模型对于特定类型任务可通过span.attributes.tool.name或自定义属性区分的质量差异需要结合业务指标如任务完成率。通过这些分析你可以得出数据驱动的结论例如“对于简单的文档总结任务本地 Llama 3 在成本上更有优势且延迟可接受但对于需要复杂推理的编程任务GPT-4 的完成率更高值得付出更高的成本。”5. 进阶配置与生产环境考量将可观测性引入生产环境还需要考虑更多因素。5.1 采样策略平衡数据量与成本全量采集所有追踪数据在高压下会产生海量数据带来存储和成本压力。采样是必须的。OTel 提供了多种采样器头部采样Head-based Sampling在追踪开始时决定是否采样。常用的是TraceIdRatioBased例如设置为 0.110%。优点是决策简单高效缺点是如果采样率低可能错过重要错误因为错误发生在决策之后。尾部采样Tail-based SamplingCollector 等组件先缓存所有追踪数据等一个追踪完成后根据其整体特征如是否包含错误、总耗时是否超长决定是否保留。优点是能确保所有错误和慢追踪都被捕获采样决策更智能缺点是实现复杂需要缓存资源。生产建议对于 Hermes 这类业务应用可以采用组合策略。在 SDK 端使用一个较高的头部采样率如 50%确保足够的数据量用于初步分析。同时在 OTel Collector 中配置尾部采样处理器如tail_samplingprocessor制定类似“保留所有包含错误状态的追踪保留耗时超过 2 秒的追踪对其他追踪随机采样 10%”的策略。这样既能控制总体数据量又不会遗漏关键问题。5.2 安全与敏感信息处理Hermes 处理的用户查询可能包含敏感信息。我们必须防止这些信息被记录到追踪或日志中。在代码层过滤在手动埋点设置span.set_attribute时务必进行脱敏。# 错误做法直接记录原始查询 # span.set_attribute(“user.query”, user_input) # 正确做法记录哈希或脱敏后的信息 import hashlib query_hash hashlib.sha256(user_input.encode()).hexdigest()[:8] span.set_attribute(“user.query_hash”, query_hash) # 或者只记录元数据 span.set_attribute(“query.length”, len(user_input)) span.set_attribute(“query.contains_code”, “def ” in user_input)在 Collector 层过滤使用 OTel Collector 的attributesprocessor 来批量删除或混淆某些属性。在otel-collector-config.yaml中配置processors: attributes/redact: actions: - key: http.request.header.authorization action: delete - key: span.attributes.user.query action: delete - key: span.attributes.tool.input.query action: hash # 使用哈希值替换原值在 Elasticsearch 层控制访问利用 Elasticsearch 的安全特性如基于角色的访问控制 RBAC确保只有授权的运维或开发人员才能访问包含详细追踪数据的索引。5.3 性能开销监控与调优开启可观测性必然引入额外开销CPU、内存、网络。我们需要监控这个开销本身。监控 OTel Collector为 Collector 本身暴露指标它内置了 Prometheus 指标端点并采集到 Elasticsearch 或专门的监控系统。关注其 CPU 使用率、内存占用、队列长度和导出错误率。监控应用性能对比开启和关闭 OTel 仪表化时Hermes 应用的吞吐量RPS和平均响应时间。在测试环境中进行压测评估性能损耗。通常经过良好配置的批量处理器和采样策略性能损耗可以控制在 1-5% 以内这对于大多数应用是可接受的。调整批处理参数BatchSpanProcessor的timeout和send_batch_size参数需要根据流量调整。在低流量场景可以适当增加timeout如 5s以等待更多数据打包减少请求次数。在高流量场景可以减小timeout如 100ms和send_batch_size如 512以降低内存占用和延迟。为 Hermes 构建可观测性体系初期看似增加了复杂度但它带来的价值是长期的运维效率提升和系统稳定性的保障。它让这个强大的“数字员工”变得透明、可信、易于管理。当你能够从容地回答“Hermes 现在在做什么它健康吗为什么刚才那个任务失败了”这些问题时你就真正掌握了运维 AI 应用的主动权。