自然语言转PromQL:智能监控查询框架的设计与实践
1. 项目概述:当自然语言遇见PromQL
在云原生和AI驱动的时代,一个中等规模的Kubernetes生产集群每天产生的监控指标数以万计。对于负责保障系统稳定性的工程师而言,从海量数据中快速定位问题,就像在浩瀚星海中寻找一颗特定的星星。Prometheus的查询语言PromQL,功能强大但门槛不低。你需要精确记住container_cpu_usage_seconds_total这样的指标名,理解rate()、histogram_quantile()等函数的微妙差异,还要在查询中正确嵌入时间范围[5m]或[1h]。一个简单的意图——“查看过去一小时vLLM服务的P95延迟”——背后是数行需要精心构造的PromQL代码。
这正是我们构建这个框架的初衷:让工程师用最自然的方式提问,让机器去处理那些繁琐的语法细节。我们不再需要工程师成为PromQL语法专家,而是让他们回归到问题本质——关心系统的健康、性能与趋势。这个框架的核心,是一个“翻译官”,它一端连接着人类模糊、多样的自然语言表达,另一端连接着精确、结构化的PromQL查询。它不仅仅是一个简单的关键词匹配工具,而是一个融合了静态知识、动态发现、语义理解和意图推理的智能管道。接下来,我将带你深入这个框架的每一层设计,拆解我们是如何将一句口语化的提问,变成一行精准、可执行的监控查询的。
2. 核心架构与设计哲学
2.1 为什么是“目录驱动”而非“知识图谱”?
在项目初期,我们面临一个关键抉择:如何构建系统的“知识库”?主流的思路是构建知识图谱(Knowledge Graph),将服务、依赖关系、指标间的因果联系建模成图结构。这听起来很完美,能支持复杂的、跨服务的推理查询,例如“找出导致支付链路延迟的根本服务”。然而,在快速迭代的云原生环境中,维护一个准确、实时更新的知识图谱成本极高。每次服务部署、指标变更或架构调整,都需要人工或复杂的自动化流程去更新图谱,这在实际运维中几乎不可持续。
因此,我们选择了“目录驱动”(Catalog-Driven)这条更务实、更轻量的路径。你可以把我们的混合指标目录想象成一个高度结构化、带索引的“电话簿”,而不是一张描绘城市全貌的“地图”。它的首要目标是:快速、准确地将自然语言中的概念映射到具体的Prometheus指标名。这个目录由两部分组成:一个预先精心编排的静态基础目录,以及一个在运行时动态发现的硬件指标目录。静态目录包含了约1800个经过分类和标注的通用Kubernetes与云原生指标,而运行时发现则专门用于捕获因硬件差异(如不同厂商的GPU)而产生的特定指标。
这种设计的优势在于零配置启动和极低的维护开销。静态目录被打包在容器镜像中,服务启动后15毫秒内即可加载完毕,立即可用。运行时发现以异步后台任务执行,即使失败也不影响核心查询功能。相比之下,知识图谱方案在启动前需要一个漫长的“图谱构建”阶段。我们的权衡很明确:用一定的表达能力(暂时无法处理复杂的跨服务因果查询),换取极高的可用性、可维护性和查询性能。在实际生产环境中,绝大多数运维查询都是针对单一或少数几个指标的现状查询、趋势分析或对比,目录方案已能覆盖90%以上的日常需求。
2.2 系统组件全景与协同流程
整个系统构建在FastAPI之上,部署为Kubernetes中的一个服务。其核心组件像一个精密的翻译流水线,协同工作:
- 混合指标目录(Hybrid Metrics Catalog):系统的基石。它并非简单的列表,而是一个带有分类、优先级、关键词和类型信息的结构化数据库。它提供亚毫秒级的指标查找能力。
- 查询管道(Query Pipeline):核心处理引擎。它接收用户的自然语言问题,依次进行意图检测、时间解析、指标筛选和查询生成。
- 动态时间解析器(Temporal Resolver):这是我们的创新点之一。它专门处理“自从昨天”、“过去6小时”、“上周二”这类时间表达,并将其转换为PromQL能理解的绝对时间戳和
rate()函数所需的窗口语法(如[1h])。 - MCP工具服务器(MCP Tool Server):系统的对外接口。它遵循Model Context Protocol标准,将框架的核心能力(如指标搜索、查询执行)封装成12个工具,供不同的LLM提供商(如Claude、GPT-4、Gemini)或前端API调用。
当一个用户提问“过去3天GPU的平均温度是多少?”时,流程如下:问题首先进入查询管道,意图检测器识别出这是一个关于“趋势”(trend)的查询。同时,时间解析器将“过去3天”解析为具体的起止时间戳和对应的[3d]速率窗口。接着,类别路由器根据关键词“GPU”和“温度”,将搜索范围缩小到gpu_ai类别。然后,语义评分器在该类别下的几十个候选指标中,通过多维评分(关键词匹配、类型匹配、特异性)选出最相关的指标,例如DCGM_FI_DEV_GPU_TEMP。最后,PromQL生成器根据trend意图和指标类型(Gauge),套用模板生成最终的查询:avg_over_time(DCGM_FI_DEV_GPU_TEMP[3d])。整个流程,如果走目录路径,通常在1.1秒内完成。
3. 混合指标目录的深度解析
3.1 静态基础目录:精心编排的指标百科全书
静态目录是一个约840KB的JSON文件,包含了经过运维专家审核和分类的约1800个核心指标。我们将它们归入17个领域类别,例如cluster_health(集群健康)、node_hardware(节点硬件)、pod_container(容器)、gpu_ai(GPU与AI)等。这种分类是后续“类别感知路由”能大幅提升效率的关键。
每个指标条目都包含以下元数据:
- 名称(Name):Prometheus中的原始指标名,如
kube_pod_container_resource_requests。 - 类型(Type):
counter(计数器)、gauge(仪表盘)、histogram(直方图)或summary(摘要)。这对于后续选择正确的PromQL函数至关重要。 - 帮助文本(Help):从Prometheus元数据中提取的描述,用于辅助语义理解。
- 优先级(Priority):
High或Medium。这是根据指标在运维场景中的重要性人工定义的。High优先级指标通常是监控告警的核心,如CPU使用率、内存使用量、请求错误率。 - 关键词(Keywords):这是目录的“智能”所在。每个指标最多有12个关键词,通过一个五层流水线生成:
- 人工精调关键词:对于众所周知的指标,直接添加常用别名。例如,为
DCGM_FI_DEV_GPU_UTIL添加{“gpu utilization”, “nvidia utilization”}。 - 基于类型的关键词:
counter类型指标自动获得{“total”, “count”, “rate”};histogram类型获得{“distribution”, “percentile”, “p95”}。 - 基于模式的关键词扩展:约30条正则规则。例如,任何包含
latency的指标名,都会获得{“duration”, “slow”, “delay”, “response time”}等关键词。 - 基于名称的关键词提取:将指标名按
_或:分割,过滤掉过短的词元。 - 帮助文本提取:从
Help字段中提取除停用词外的 salient words。
- 人工精调关键词:对于众所周知的指标,直接添加常用别名。例如,为
这个分层关键词体系确保了即使用户使用非标准的、口语化的词汇(如问“卡不卡”可能关联到“延迟”),系统也有机会通过关键词匹配找到正确的指标。
3.2 运行时GPU发现:应对硬件异构性的动态策略
云原生环境,尤其是AI集群,硬件异构性是常态。一个集群可能混用NVIDIA、Intel Habana(Gaudi)或AMD的GPU。不同厂商的监控指标前缀截然不同(如NVIDIA是DCGM_*,Intel是habanalabs_*)。静态目录无法穷尽所有可能性。
我们的解决方案是异步运行时发现。在服务启动时,一个后台线程会主动查询Prometheus服务器,获取当前暴露的所有指标列表。然后,根据预定义的厂商前缀模式(见表1)进行过滤和匹配。系统会统计各厂商指标的匹配数量,以此判定集群中的主显卡厂商。对于新发现的GPU指标,我们会用89条厂商特定的模式规则为其分配优先级(例如,包含UTIL或TEMP的通常为High优先级),并为其生成关键词。
表1:支持的GPU厂商及其指标前缀模式
| 厂商 | 默认前缀 | 关键高优先级指标示例 |
|---|---|---|
| NVIDIA | DCGM_*, nvidia_gpu_* |
GPU利用率、温度、功耗、显存 |
| Intel | habanalabs_*, xpu_* |
HPU利用率、能耗、内存、PCIe |
| AMD | amdgpu_*, rocm_* |
GPU繁忙百分比、显存、温度 |
| 推理框架 | vllm:*, gpu_* |
首Token延迟(TTFT)、每次迭代延迟(ITL)、KV缓存、吞吐量 |
注意:运行时发现设计为“尽力而为”。它有一个10秒的超时限制。如果发现失败或超时,系统会记录警告并继续运行,仅使用静态目录中的指标。这确保了即使在纯CPU环境或Prometheus暂时不可达的情况下,核心查询功能依然可用。
3.3 目录验证:保持与真实环境同步
我们预置的静态目录是基于某个特定Kubernetes发行版版本生成的。实际生产集群可能版本更旧(缺少新指标)或安装了更多组件(暴露额外指标)。为此,我们引入了目录验证机制。
另一个后台线程在启动后,会将静态目录中的指标与真实Prometheus实例中当前存在的指标进行比对。它会识别出两类指标:
- 陈旧指标:存在于目录但不存在于Prometheus。这些指标会被临时标记为“不活跃”,在查询时被降权或过滤。
- 新增指标:存在于Prometheus但不存在于目录。这些指标会被自动“吸收”进目录。系统会使用最长前缀匹配算法,尝试将其归类到现有的17个类别中,并保守地赋予
Medium优先级。
这种动态验证机制使得目录成为一个“活”的实体,能够随着集群环境的变化而温和地演进,无需人工干预,始终与真实监控数据源保持基本一致。
4. 自然语言到PromQL的转换管道
4.1 意图检测:理解用户想“干什么”
转换的第一步是理解用户的意图。我们定义了八种核心查询意图,覆盖了绝大多数运维场景:
表2:意图类型、触发关键词及对应的PromQL模式
| 意图 | 触发关键词示例 | PromQL模式含义 |
|---|---|---|
| current_value | “当前”、“现在”、“是多少” | 瞬时查询,获取最新值 |
| count | “有多少个”、“总数” | 使用count()函数计数 |
| average | “平均”、“均值” | 使用avg()函数求平均值 |
| percentile | “p95”、“p99”、“分布” | 对直方图使用histogram_quantile() |
| top_n | “最高的”、“最忙的”、“前5个” | 使用topk()函数 |
| comparison | “对比”、“比较”、“vs” | 按某个标签分组聚合(by (label)) |
| trend | “随时间变化”、“增长趋势” | 使用avg_over_time()等区间函数 |
| rate | “每秒”、“吞吐量”、“速率” | 使用rate()或irate()函数 |
检测器基于关键词模式匹配实现,快速且轻量。例如,问题“显示最忙的5个Pod的CPU使用率”会触发top_n意图,并提取出测量类型“CPU使用率”和实体“Pod”。这些信息将传递给下游的指标选择器,极大地缩小了搜索范围。除了意图,检测器还会提取领域特定术语,如“TTFT”(首Token时间)、“TPOT”(每次输出时间)、“KV缓存”等,这些术语在后续的语义评分中享有很高的权重。
4.2 动态时间解析:将模糊时间变为精确参数
这是框架的一大亮点,也是区别于其他类似工作的关键。在PromQL中,时间范围不是可选的,而是查询语法的核心部分。用户说“昨天的CPU使用率”,我们需要将其转换为rate(container_cpu_usage_seconds_total[1d] offset 1d)这样的查询。我们的解析器采用多优先级策略来处理纷繁复杂的自然语言时间表达:
- 简写格式:直接处理来自UI选择器的字符串,如“15m”、“6h”、“7d”。直接映射为绝对时间戳和对应的
[15m]、[6h]、[7d]语法。 - 自然语言时段模式:识别“过去N个[单位]”、“最近N[单位]”、“N[单位]前”等模式。解析器不仅计算时间窗口,还会根据窗口长度智能选择
rate()函数的子区间。例如,“过去6小时”可能对应rate(metric[1h]),而“过去30天”则对应rate(metric[1d])。这是因为rate()函数需要一个合适的“lookback window”来平滑计算,窗口太长或太短都会导致数据失真。 - 日历参照:解析“昨天”、“本周”、“本月”、“去年三月”等。这里需要处理时区和周起始日(周一/周日)的配置。
- 具体日期表达式:使用
dateparser库解析“2023-10-01”、“October 1st”等,并偏好解析为过去时间(监控通常不查询未来)。 - 显式时间戳:如果API调用者直接提供了起止时间戳,则直接使用,并动态计算一个合理的
rate窗口。 - 默认回退:如果未检测到任何时间信号,则使用一个可配置的默认窗口(我们设置为1小时)。
解析器的输出是一个time_range_info结构,包含人类可读的时长字符串、PromQL速率语法和数字化的时长。这个结构会注入到后续给LLM的提示词中,指导其生成正确的时间范围,也会在查询后处理阶段用于验证和修正生成的结果。
4.3 类别感知的指标选择与语义评分
面对近2000个候选指标,如何快速找到最相关的那一个?我们采用了两阶段策略:先粗筛(类别路由),再精排(语义评分)。
4.3.1 类别提示提取
每个目录类别都维护了一个关键词映射表。例如,gpu_ai类别映射了{“gpu”, “nvidia”, “cuda”, “vllm”, “ttft”, “inference”, …}等关键词。当用户问题中出现这些关键词时,对应的类别就被“激活”。对于有类别提示的问题,系统会返回匹配类别下的所有High和Medium优先级指标(通常30-80个)。对于没有明确类别提示的通用问题(如“系统现在健康吗?”),则只返回所有类别下的High优先级指标(约350个)。这一步能将搜索空间缩小一个数量级。
4.3.2 多维语义评分 在粗筛出的候选指标池中,每个指标会从三个维度进行评分:
- 关键词相关性得分:基于领域特定的模式匹配公式。公式
S_keyword = Σ (w_p * 1[match(q, p)]),其中w_p是预定义模式的权重。例如,“TTFT”完全匹配权重为+20,“GPU”相关关键词权重为+15,“温度”相关权重为+15。这确保了领域术语的高权重。 - 类型相关性得分:将指标类型(
counter,gauge,histogram)与检测到的意图进行匹配。例如,histogram类型的指标对percentile意图得分更高;counter类型对rate意图得分更高。 - 特异性得分:指标名称越具体(包含更多子系统标识符),对于针对性查询的得分越高。例如,
container_cpu_usage_seconds_total比node_cpu_seconds_total对于容器CPU查询更具体。
最后,加上基于优先级的奖励分(High优先级+15,Medium+5),总分最高的指标被选中用于生成查询。这套评分机制在实践中能很好地处理歧义。例如,当用户问“延迟”时,系统会在histogram类型的延迟指标(如http_request_duration_seconds_bucket)和可能误匹配的其他指标间,凭借类型相关性得分做出正确选择。
4.4 PromQL生成与后处理
有了选定的指标、明确的意图和解析好的时间范围,最后一步就是“组装”PromQL。我们采用基于模板的生成方式,这比完全依赖LLM生成更加可控和可靠。
表4:按意图和指标类型生成的PromQL模板示例(M代表指标,R代表速率语法)
| 意图 | Counter类型 | Gauge类型 | Histogram类型 |
|---|---|---|---|
| rate | sum(rate(M[R])) |
rate(M[R]) |
histogram_quantile(0.95, rate(M_bucket[R])) |
| trend | rate(M[R]) |
avg_over_time(M[R]) |
M (通常直接查询分位数) |
| top_n | topk(5, rate(M[R])) |
topk(5, M) |
topk(5, M) |
| comparison | sum by (label)(rate(M[R])) |
avg by (label)(M) |
histogram_quantile(0.95, sum by(label, le)(rate(M_bucket[R]))) |
实操心得:即使有了模板,LLM在生成查询时仍会犯一些语法错误。因此,一个后处理(Post-Processing) 步骤至关重要。我们的后处理器会做以下几件事:修复标签选择器中多余的逗号、平衡括号、确保
rate()函数包含时间范围[R]、将裸的范围向量(如metric[5m])自动转换为正确的rate(metric[5m])调用。这个简单的语法修复层,能将端到端的查询有效性提升约15%。
5. MCP集成与生产部署考量
5.1 通过MCP实现LLM供应商无感化
为了让框架能力能无缝嵌入各种AI助手和运维平台,我们选择了Model Context Protocol作为集成层。MCP是一个用于AI工具调用的开放标准,它定义了一套统一的JSON-RPC接口。我们将框架的核心功能封装成了12个MCP工具,例如search_metrics(搜索指标)、execute_promql(执行查询)、get_metric_metadata(获取元数据)以及一个集成的smart_discovery(智能发现)工具。
这种设计带来了两个清晰的消费路径:
- 面向LLM智能体:通过一个进程内适配器(
MCPServerAdapter)直接调用工具,避免了HTTP开销,延迟极低。我们为Anthropic Claude、OpenAI GPT-4、Google Gemini和本地部署的Llama模型提供了特定的聊天机器人适配器,它们负责将各厂商原生的工具调用格式转换为统一的MCP接口。 - 面向前端/API客户端:通过HTTP JSON-RPC调用工具,这使得任何能发送HTTP请求的客户端(如Web控制台插件、外部自动化脚本)都能轻松集成。
系统提示词(System Prompt)中包含了关于指标类型、PromQL语义以及vLLM特定概念(如延迟分解、KV缓存语义)的领域知识。这为LLM提供了必要的上下文,使其不仅能生成查询,还能对查询结果生成人性化的解释。
5.2 生产环境性能与稳定性
我们在一个运行AI推理工作负载(vLLM)的Kubernetes 1.29生产集群上部署了该框架。以下是一些关键的性能数据(见表6):
表6:生产环境性能指标
| 操作 | 延迟 |
|---|---|
| 冷启动(从JSON加载目录) | ~15 毫秒 |
| 缓存目录访问(单例) | ~0.05 毫秒 |
| 类别过滤 | 3–5 毫秒 |
| GPU发现(异步,后台) | 1–2 秒 |
| 目录验证(异步,后台) | 1–2 秒 |
| 智能指标发现(目录路径) | ~1.1 秒 |
| 智能指标发现(API回退路径) | ~3.7 秒 |
| 时间解析 | < 1 毫秒 |
可以看到,基于目录的查询路径(~1.1秒)比回退到调用Prometheus API逐一查询元数据的路径(~3.7秒)快3.4倍。GPU发现和目录验证是后台异步执行的,在启动后的1-2秒内完成,之后便不会影响查询延迟。这意味着服务在启动15毫秒后就能处理大多数查询,并在1-2秒后获得完整的GPU指标支持。
在资源占用上,由于核心逻辑是规则和模板驱动,对计算资源要求不高。主要的开销在于与LLM的交互(如果使用)以及最终执行PromQL查询时对Prometheus/Thanos的请求。我们建议为服务分配适量的内存(如512MiB-1GiB)以缓存目录和中间结果。
6. 典型问题排查与优化经验
在实际部署和运营中,我们遇到并总结了几类典型问题及其应对策略。
6.1 指标歧义与误选
这是最常见的失败模式。当用户问题过于宽泛,或多个指标在语义上高度相似时,评分系统可能选出一个“合理但错误”的指标。
- 案例:用户问“内存使用情况”。系统可能返回
container_memory_usage_bytes(容器内存),但用户实际想看的是node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes(节点已用内存)。 - 排查与解决:
- 检查查询日志:框架会输出详细的决策日志,包括被激活的类别、候选指标列表及其各项得分。首先查看得分最高的前3个指标是什么。
- 优化关键词:如果发现误选的指标是因为某个通用关键词(如“memory”)匹配了不相关的指标,可以考虑在静态目录中为该指标添加更精确的负向关键词或调整其优先级。
- 引导用户细化问题:在前端交互中,当检测到潜在歧义时(例如,同时匹配到“容器”和“节点”类别的指标),可以主动提示用户:“您是想查看容器内存还是节点内存?”
6.2 时间解析偏差
时间解析器对于明确的时间表达(如“过去6小时”)很准确,但对于需要外部知识的相对参照(如“自从上次部署以来”、“故障期间”)则无能为力。
- 案例:用户问“自从v1.2版本发布后的错误率”。解析器无法知道v1.2是何时发布的。
- 排查与解决:
- 集成事件系统:长期的解决方案是与CI/CD流水线或事件管理系统(如Kubernetes Event、或专门的部署记录数据库)集成,将“版本发布”这类事件转化为具体的时间戳。
- 提供备选方案:在当前阶段,当解析器无法确定时间时,会回退到默认时间(如最近1小时),并在返回结果中明确提示:“未识别到具体时间范围,默认展示最近1小时数据。如需查询特定事件时段,请提供具体时间或确保该事件已录入系统。”
6.3 PromQL函数选择错误
尽管有意图和类型匹配,但在处理复杂意图与复杂指标类型的组合时,LLM或模板仍可能选错函数。
- 案例:对于
histogram类型的指标,用户意图是trend(趋势)。简单的avg_over_time()可能不合适,因为直方图本身是一组分桶数据。更合适的可能是查询某个特定分位数(如P95)随时间的变化趋势。 - 排查与解决:
- 细化模板规则:在模板生成阶段,为
histogram类型且trend意图的查询,默认生成avg_over_time(histogram_quantile(0.95, rate(metric_bucket[time_window]))[resolution])这样的复合查询,以展示P95延迟的趋势。 - 加强后处理验证:在后处理阶段,可以加入一个简单的验证规则:如果指标名包含
_bucket且生成的查询中没有histogram_quantile,则记录警告或尝试自动修正(但这需要谨慎,以免误改)。
- 细化模板规则:在模板生成阶段,为
6.4 性能优化点
对于追求极致性能的场景,可以考虑以下优化:
- 目录预加载与缓存:我们已经做了,将目录加载到内存中。可以进一步考虑使用更高效的数据结构,如前缀树(Trie)来加速关键词匹配。
- 语义评分缓存:对于高频的、模式固定的查询(如“CPU使用率”、“内存”),可以缓存其语义评分结果,避免每次重复计算。
- 异步并行处理:在时间解析、类别路由、语义评分等步骤中,如果它们之间没有严格的先后依赖,可以考虑并行执行以降低整体延迟。
这个框架将我们从繁琐的PromQL语法记忆中解放出来,让我们能更专注于问题本身。它不是一个完美的、全能的解决方案,但在处理云原生环境下的日常指标查询场景中,已经展现出极高的实用价值。未来,我们计划将其与事件流、日志系统更深度地集成,并探索如何自动化地从生产查询日志中生成测试用例,以持续优化其准确性和健壮性。