Elasticsearch:监控 LLM 推理和 Agent Builder 使用 OpenRouter

作者:来自 Elastic Tomás Murúa

学习如何使用 OpenRouter 的 OpenTelemetry 广播和 Elastic APM 来监控 Agent Builder 和推理流水线中的 LLM 使用情况、成本和性能。

Agent Builder 现已作为技术预览提供。通过 Elastic Cloud Trial 开始使用,并在此查看 Agent Builder 的文档。


每周都会发布新模型,在智能性、速度或成本方面超越之前的模型。这使供应商锁定变得有风险,并让管理多个连接器、计费账户和 API 变得不必要地复杂。每个模型在行为上都不同,包括 token 消耗、响应延迟,以及与特定工具集的兼容性。

在本文中,我们将构建一个 AI 增强的音频产品目录,将其连接到 Elastic Agent Builder,并使用 OpenRouter 访问不同模型,同时在整个工作流中监控它们的性能,从数据摄取到 agent 交互。

前提条件

  • Elastic Cloud 9.2 或 Elastic Cloud Serverless
  • 启用 APM 的集成服务器
  • OpenRouter 账户和 API Key
  • Python 3.9+

什么是 OpenRouter?

OpenRouter 是一个平台,通过单一账户和 API 统一访问来自多个提供商的 500 多个模型。你无需分别管理 OpenAI、Anthropic、Google 等的独立账户,而是通过 OpenRouter 访问它们全部。

OpenRouter 负责在不同提供商之间进行负载均衡,自动将请求路由到延迟最低且错误最少的提供商。你也可以手动选择提供商,或配置回退链。OpenRouter 兼容标准 API、代码助手、集成开发环境( IDEs )等。

其中一个关键特性是 Broadcast,它会将你的模型使用情况追踪发送到外部可观测性系统。由于 OpenRouter 支持 OpenTelemetry,我们可以在 Elastic Stack 中监控完整流水线以及任何其他 OpenRouter 使用成本。

架构概览

我们将使用一个音频产品目录,通过推理摄取流水线使用 AI 生成新字段,然后创建一个 agent,使其能够基于已索引的产品数据回答问题

在摄取数据时,摄取流水线使用 OpenRouter 推理端点,基于产品的非结构化描述生成新的属性字段,这会触发 OpenRouter 将该次推理的日志发送到 Elasticsearch。

类似地,当与使用这些数据的 Agent Builder 进行对话时,日志也会被发送到 Elasticsearch 用于可视化。

我们将为 Agent Builder 和摄取流程使用不同的 OpenRouter API key:

  • OPENROUTER_API_KEY 用于 Agent Builder 交互
  • OPENROUTER_INGESTION_KEY 用于推理流水线

这样可以在监控仪表板中区分不同流量,并将成本归因到具体的工作流。

设置

首先,我们需要创建一个 AI 连接器,用于让 agent 与 大语言模型( LLM )交互,并为摄取流水线创建一个推理端点,用于从描述中提取字段。两者都通过同一个 API 连接到 OpenRouter(但可以使用不同的 key 以便监控区分)。

创建 AI 连接器

AI 连接器允许 Agent Builder 与 LLM 通信。我们将其配置为使用 OpenRouter 作为提供商:

复制代码
import requests
import os

ELASTIC_URL = os.getenv("ELASTIC_URL")
KIBANA_URL = os.environ["KIBANA_URL"]
ELASTIC_API_KEY = os.environ["ELASTIC_API_KEY"]
OPENROUTER_API_KEY = os.environ["OPENROUTER_AGENT_KEY"]
OPENROUTER_INGESTION_KEY = os.environ.get("OPENROUTER_INGESTION_KEY", OPENROUTER_API_KEY)

# Create AI Connector for Agent Builder
connector_payload = {
    "name": "OpenRouter Agent Connector",
    "connector_type_id": ".gen-ai",
    "config": {
        "apiProvider": "Other",
        "apiUrl": "https://openrouter.ai/api/v1/chat/completions",
        "defaultModel": "openai/gpt-5.2",
        "enableNativeFunctionCalling": True
    },
    "secrets": {
        "apiKey": OPENROUTER_API_KEY
    }
}

response = requests.post(
    f"{KIBANA_URL}/api/actions/connector",
    headers={
        "kbn-xsrf": "true",
        "Authorization": f"ApiKey {ELASTIC_API_KEY}",
        "Content-Type": "application/json"
    },
    json=connector_payload
)

connector = response.json()
print(f"Connector created: {connector['id']}")

我们为 agent 使用具备推理能力的模型,例如 GPT-5.2,因为它需要处理复杂查询和工具编排。

创建推理端点

推理端点允许 Elasticsearch 在数据处理过程中调用 LLMs:

复制代码
from elasticsearch import Elasticsearch

es = Elasticsearch(
    hosts=[ELASTIC_URL],
    api_key=ELASTIC_API_KEY,
    request_timeout=60  # Higher timeout for inference operations
)

# Create inference endpoint for ingestion
inference_config = {
    "service": "openai",
    "service_settings": {
        "model_id": "openai/gpt-4.1-mini",
        "api_key": OPENROUTER_INGESTION_KEY,
        "url": "https://openrouter.ai/api/v1/chat/completions"
    }
}

response = es.inference.put(
    inference_id="openrouter-inference-endpoint",
    task_type="completion",
    body=inference_config
)

print(f"Inference endpoint created: {response['inference_id']}")

我们为批量摄取任务使用更快、更便宜的模型,例如 GPT-4.1 Mini,因为这些任务不需要高级推理能力。

数据 pipeline

我们来配置摄取 pipeline(ingest pipeline)。它将从产品描述字段读取数据,并提取 Agent Builder 可用于过滤和聚合的结构化类别。

例如,给定以下产品描述:

"Premium wireless Bluetooth headphones with active noise cancellation, 30-hour battery life, and premium leather ear cushions. Perfect for travel and office use."

我们可以提取:

  • **Category/**类别:Headphones
  • **Features/**特性:["wireless","noise_cancellation","long_battery"]
  • **Use case/**使用场景:Travel

关键在于向 LLM 提供可能取值作为 enum,这样它才能进行一致的分组。否则,可能会得到诸如"noise cancellation""ANC""noise-canceling"等不同变体,从而更难进行聚合。

复制代码
# Define the extraction prompt
EXTRACTION_PROMPT = (
    "Extract audio product information from this description. "
    "Return raw JSON only, no markdown, no explanation. Fields: "
    "category (string, one of: Headphones/Earbuds/Speakers/Microphones/Accessories), "
    "features (array of strings from: wireless/noise_cancellation/long_battery/waterproof/voice_assistant/fast_charging/portable/surround_sound), "
    "use_case (string, one of: Travel/Office/Home/Fitness/Gaming/Studio). "
    "Description: "
)

# Create the enrichment pipeline
pipeline_config = {
    "processors": [
        {
            "script": {
                "source": f"ctx.prompt = '{EXTRACTION_PROMPT}' + ctx.description"
            }
        },
        {
            "inference": {
                "model_id": "openrouter-inference-endpoint",
                "input_output": {
                    "input_field": "prompt",
                    "output_field": "ai_response"
                }
            }
        },
        {
            "json": {
                "field": "ai_response",
                "add_to_root": True  # Parses JSON and adds fields to document root
            }
        },
        {
            "remove": {
                "field": ["prompt", "ai_response"]
            }
        }
    ]
}

es.ingest.put_pipeline(
    id="product-enrichment-pipeline",
    body=pipeline_config
)

print("Pipeline created: product-enrichment-pipeline")

在使用 OpenAI 提取包含新属性的 JSON 之后,我们使用json 处理器将它们展开为新的字段。

现在让我们索引一些示例音频产品:

复制代码
# Sample audio product data
products = [
    {
        "name": "Wireless Noise-Canceling Headphones",
        "description": "Premium wireless Bluetooth headphones with active noise cancellation, 30-hour battery life, and premium leather ear cushions. Perfect for travel and office use.",
        "price": 299.99
    },
    {
        "name": "Portable Bluetooth Speaker",
        "description": "Compact waterproof speaker with 360-degree surround sound. 20-hour battery life, perfect for outdoor adventures and pool parties.",
        "price": 149.99
    },
    {
        "name": "Studio Condenser Microphone",
        "description": "Professional USB microphone with noise cancellation and voice assistant compatibility. Ideal for podcasting, streaming, and home studio recording.",
        "price": 199.99
    }
]

# Create index with mapping
es.indices.create(
    index="products-enriched",
    body={
        "mappings": {
            "properties": {
                "name": {"type": "text"},
                "description": {"type": "text"},
                "price": {"type": "float"},
                "category": {"type": "keyword"},
                "features": {"type": "keyword"},
                "use_case": {"type": "keyword"}
            }
        }
    },
    ignore=400  # Ignore if already exists
)

# Index products using the enrichment pipeline
for i, product in enumerate(products):
    es.index(
        index="products-enriched",
        id=i,
        body=product,
        pipeline="product-enrichment-pipeline"
    )
    print(f"Indexed: {product['name']}")

# Refresh to make documents searchable
es.indices.refresh(index="products-enriched")

Agent Builder

现在我们可以创建一个 Agent Builder agent,使用该索引,并利用我们创建的新字段来回答文本问题和分析型查询:

复制代码
# Create Agent Builder agent
agent_payload = {
    "id": "audio-product-assistant",
    "name": "Audio Product Assistant",
    "description": "Answers questions about audio product catalog using semantic search and analytics",
    "labels": ["audio"],
    "avatar_color": "#BFDBFF",
    "avatar_symbol": "AU",
    "configuration": {
        "tools": [
            {
                "tool_ids": [
                    "platform.core.search",
                    "platform.core.list_indices",
                    "platform.core.get_index_mapping",
                    "platform.core.execute_esql"
                ]
            }
        ],
        "instructions": """You are an audio product assistant that helps users find and analyze audio equipment.

Use the products-enriched index for all queries. The extracted fields are:
- category: Headphones, Earbuds, Speakers, Microphones, or Accessories
- features: array of product features like wireless, noise_cancellation, long_battery
- use_case: Travel, Office, Home, Fitness, Gaming, or Studio

For analytical questions, use ESQL to aggregate data.
For product searches, use semantic search on the description field."""
    }
}

response = requests.post(
    f"{KIBANA_URL}/api/agent_builder/agents",
    headers={
        "kbn-xsrf": "true",
        "Authorization": f"ApiKey {ELASTIC_API_KEY}",
        "Content-Type": "application/json"
    },
    json=agent_payload
)

agent = response.json()
print(f"Agent created: {agent['id']}")

对于工具,我们使用 search 进行语义查询,并使用 Elasticsearch Query Language( ES|QL )进行分析型查询:

现在你可以与 agent 对话,并提出如下问题:

  • "What headphones do we have for travel?/我们有哪些适合旅行的耳机?"
  • "Show me products with noise cancellation under 200/展示价格低于 200 且具备降噪的产品"
  • "What's the average price by category?/按类别的平均价格是多少?"

agent 使用 AI 增强字段来提供更好的过滤和聚合。

实现 OpenRouter Broadcast

现在我们来设置推理监控。首先,我们需要 OpenTelemetry 端点 URL。请在 Kibana 中导航到 APM 教程:

复制代码
https://<your_kibana_url>/app/observabilityOnboarding/otel-apm/?category=application

从 OpenTelemetry 选项卡中收集 URL 和认证 token:

重要提示:你的 Kibana server 需要能通过公网访问,以便接收来自 OpenRouter 的数据。

在 OpenRouter 中,进入 Broadcast 设置,并为"OpenTelemetry Collector" 添加一个新目的地:

重要提示:配置端点时使用 /v1/traces 路径和认证 headers:

复制代码
Endpoint: https://xxxxx.ingest.us-east-2.aws.elastic-cloud.com:443/v1/traces

Headers: {"Authorization": "Bearer YOUR_APM_SECRET_TOKEN"}

点击 Test connection,你应该会看到成功消息。

在 Elastic 中监控

使用 OpenRouter 模型后,你应该开始在 Kibana 中看到文档。已索引的文档位于数据流 traces-generic.otel-default 中,service.name 为 "openrouter",并包含以下信息:

  • Request and response details.
  • Token usage (prompt, completion, total).
  • Cost (in USD).
  • Latency (time to first token, total).
  • Model information.

从现在开始,推理流水线和 Agent Builder 与 LLM 使用相关的活动将被记录在 OpenRouter 中,并发送到 Elastic。

默认 APM 仪表板

你可以在 Kibana 中通过 Observability > Applications > Service Inventory > openrouter 查看默认仪表板:

服务视图显示:

  • **Latency/**延迟:所有调用的平均响应时间
  • **Throughput/**吞吐量:每分钟请求数
  • **Failed transactions/**失败事务:错误率
  • **Transactions/**事务:按操作类型的细分

自定义 LLM 监控仪表板

为了更好地控制显示的信息,你可以创建自定义仪表板。我们创建了一个,可以区分摄取和 agent 聊天,并衡量相关参数,如 token 使用量和成本,以及 Elastic 外的使用情况,例如通过 API key 过滤的代码助手:

仪表板显示:

  • 按工作流类型的成功率
  • 按模型的 token 使用量
  • 按 API key 的成本细分
  • 随时间变化的延迟趋势
  • 模型对比指标

你可以在此下载仪表板,并通过 Saved Objects 导入到你的 Kibana 实例中。

结论

OpenRouter 让你可以快速操作,并使用相同的 API 和计费账户测试多个模型和提供商,方便比较不同类型的模型 ------ 大参数、小参数、商业、开源等。

使用 OpenRouter Broadcast,我们可以轻松监控这些模型在摄取流水线中的表现或通过 Agent Builder 聊天时的性能,同时结合 OpenRouter 的其他使用情况,如代码 agent 和应用程序。

原文:https://www.elastic.co/search-labs/blog/llm-monitoring-openrouter-agent-builder

相关推荐
lipiaoshuigood7 小时前
MySQL 数据出海之数据同步方案
数据库·mysql
humors2217 小时前
【分享】传统文化/国学/圣贤视频
大数据·程序人生
逐鹿人生7 小时前
【人工智能工程师系列】一【全面Python3.8入门+进阶】ch.3
人工智能
笨蛋不要掉眼泪7 小时前
Nacos配置中心详解:核心用法、动态刷新与经典面试题解析
java·数据库·后端
@@神农7 小时前
PostgreSQL-SQL语句的执行过程(一)
数据库·sql·postgresql
杨浦老苏7 小时前
本地优先的AI个人助手Moltis
人工智能·docker·ai·群晖
OBS插件网8 小时前
OBS直播如何给人脸加口罩特效?OBS口罩特效插件下载安装教程
人工智能·数码相机·语音识别·产品经理
LitchiCheng8 小时前
Mujoco 如何添加 Apriltag 并获得相机视野进行识别
人工智能·python·开源
想用offer打牌8 小时前
一站式了解Agent Skills
人工智能·后端·ai编程
一切尽在,你来8 小时前
LangGraph快速入门
人工智能·python·langchain·ai编程