feat(otel #876): 双 Claw 强制标签 + 入口接线 + Grafana 双 Claw 对比看板
#876 OpenTelemetry 可观测(双 Claw 标签): - app/core/otel.py: 新增 set_otel_ctx/get_otel_ctx(ContextVar 三元组 tenant_id+claw_type+ trace_id);current_span_cm 自动注入双 Claw 强制标签,全部 13 个 span 调用点零签名侵入带维度; record_span_metrics 指标维度补 tenant_id+claw_type(trace_id 因高基数不计入指标标签)。 - app/core/llm_router.py: set_route_ctx 桥接转调 set_otel_ctx(单次入口注入双端共享: 路由上下文 + OTel 标签),失败安全。 - app/api/chat.py: send_message 与 send_message_stream 入口注入 set_route_ctx(web), stream 经 asyncio.create_task 子协程自动传播 contextvars,使 _send_message_core 内 所有 span 均带 tenant_id+claw_type。 - app/api/im_llm.py: _call_real_llm 入口最早注入 set_route_ctx(im_wechat),并显式开启 im.send_message span(此前 IM Claw 完全无 span,Grafana 无 IM 数据点),finally 关闭。 - tests/test_dual_claw.py: R-DUAL-6 由 skip 转为确定性断言(Web/IM 双 Claw span 必带 tenant_id+claw_type),作为持续哨兵。 - 交付 Grafana 双 Claw 对比看板 JSON(aiflow_span_* / aiflow_llm_route_total 按 claw_type 分组:P50/P95 延迟、成功率、QPS、租户健康 Top10)。Alloy OTLP 仅桥接 metrics→Prometheus, trace 无 pipeline(#800 已压制 UNIMPLEMENTED 噪声),看板基于 metrics 出图。 safe-align: 仅复制 5 个 git-tracked 文件,未触碰 secrets/venv/node_modules/caches。本地提交不 push。
This commit is contained in:
1 parent
180409d317
commit
04a0641202
5 files changed
+153
-13
No files matched your search
@@ -30,7 +30,7 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from ..capability_registry import resolve_auto_model
|
||||
from ..models import AIModel, ModelProvider, TokenUsageLog, gen_uuid
|
||||
from app.core.observability import get_trace_id
|
||||
from app.core.observability import get_trace_id, span
|
||||
# #875 LLM Router:统一模型路由层(影子模式先行,收敛 litellm 主密钥硬编码)
|
||||
from ..core.llm_router import (
|
||||
route as _llm_route,
|
||||
@@ -137,6 +137,13 @@ def _log_media_usage(db: Session, user_id: str, ai_model, media_type: str, promp
|
||||
|
||||
def _call_real_llm(db: Session, user=None, prompt: str = "", platform: str = "im", user_id: str = None) -> "str | dict":
|
||||
"""调用七虎AI 生成回复,供 IM Bot 复用。支持媒体意图路由、已安装技能自动发现、模型 failover。"""
|
||||
# #876 双 Claw 维度:入口最早注入 tenant_id + claw_type,确保下方所有 span / metric
|
||||
# 均带双 Claw 标签(此前 IM Claw 完全无 span,Grafana 无 IM 数据点)。
|
||||
_set_route_ctx(tenant_id=getattr(user, "tenant_id", "") or "", claw_type="im_wechat")
|
||||
# #876:IM Claw 此前完全无 span,Grafana 无 IM 数据点。此处显式开启一个 im.send_message
|
||||
# span(手动 enter/exit 以避免整段重缩进),使 IM 链路在双 Claw 对比看板出图。
|
||||
_im_span = span("im.send_message", platform=platform, user_id=user_id)
|
||||
_im_span.__enter__()
|
||||
try:
|
||||
from app.api.chat import (
|
||||
_generate_media,
|
||||
@@ -224,7 +231,6 @@ def _call_real_llm(db: Session, user=None, prompt: str = "", platform: str = "im
|
||||
# 计算决策并打 claw_type 标签 metrics;影子模式下仅观测,不改流量。
|
||||
# 切主路径:LLM_ROUTER_SHADOW=0 时改用 Router 候选链(已含文本过滤,等同此处逻辑)。
|
||||
try:
|
||||
_set_route_ctx(tenant_id=getattr(user, "tenant_id", "") or "", claw_type="im_wechat")
|
||||
_rm = _llm_route("text", db=db, legacy_primary=_try_models[0].name if _try_models else None)
|
||||
if not _LLM_ROUTER_SHADOW and _rm.models:
|
||||
_try_models = _rm.models
|
||||
@@ -441,6 +447,12 @@ def _call_real_llm(db: Session, user=None, prompt: str = "", platform: str = "im
|
||||
return f"(已尝试 {len(_try_models)} 个模型均调用失败。最后错误: {_summary}。请稍后重试。)"
|
||||
except Exception as e:
|
||||
return f"(AI 回复生成失败:{e})"
|
||||
finally:
|
||||
# #876:关闭 im.send_message span(记录耗时 + 指标,强制带 tenant_id + claw_type)
|
||||
try:
|
||||
_im_span.__exit__(None, None, None)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
async def _call_engine_core(db, user_id, prompt: str, platform: str, user=None) -> "str | dict":
|
||||
|
||||
Reference in new issue
Block a user