## Phase 0(文件層,全部 Accepted) - ADR-106/107:AwoooP 平台架構 + 儲存策略 - ADR-111~118:Bootstrap → RLS 七項核心 ADR - ADR-119~124:SAGA → Singleton Decomposition 六項 ADR - ADR-UI-01~04:Operator Console 四個 UI ADR ## Phase 1(DB schema + migration) - awooop_phase1_control_plane_2026-05-04.sql:7 張新表 + trigger + RLS - Step 1:三角色(platform_admin/migration BYPASSRLS,awooop_app 受 RLS) - Step 13:GRANT awooop_app 最小權限(7 條) - Step 14:RLS fail-closed,移除 __platform__ 後門 - awooop_phase1_batch1_rls_2026-05-04.sql:高流量四表三步式 ADD COLUMN - awooop_phase1_batch1_backfill.py:SKIP LOCKED 分批回填腳本 - awooop_models.py:7 個 SQLAlchemy 2.x models ## Critic 修正(4 Critical + 3 Major) - C-1:ADD CONSTRAINT IF NOT EXISTS → DO 塊 + pg_constraint 查詢 - C-2:__mapper_args__ 字串 list → primary_key=True on mapped_column - C-3:__platform__ RLS 後門 → 全移除,改用 BYPASSRLS role - C-4:awooop_app role 從未建立 → Step 1 + 7 條 GRANT - M-1:active_pointer_guard SECURITY DEFINER(FORCE RLS 跨租戶保護) - M-2:pg_partman create_parent 加冪等防護 - M-3:immutability trigger 新增身份欄位保護(project_id/family/contract_id) ## Task 1.2 修補 - agent_loader.py:硬編碼 Mac 路徑 → AGENTS_DIR 環境變數 - Dockerfile:補 COPY .claude/agents/ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7.5 KiB
7.5 KiB
ADR-121: OTel GenAI Semantic Conventions
狀態:Accepted 日期:2026-05-03(台北) 決策者:統帥 範圍:GenAI span 命名、token attribute、SignOz 上報、現有 telemetry.py 修補 關聯:ADR-110(GCP Ollama)、ADR-120(token budget)、INV-4(hardcoded IP)
背景
OpenTelemetry 已發布 GenAI Semantic Conventions 1.0(2025 Q1),定義了 AI/LLM 應用的標準 span 屬性。
現有 apps/api/src/services/telemetry.py 的問題:
- P0-08:
telemetry.py:71hardcodedif "192.168.0.188" not in endpoint: raise(阻擋 EwoooC) - 不標準:span 名稱和 attribute 不符合 GenAI Semantic Conventions
- token 追蹤缺失:LLM call 的 prompt/completion token 沒有進 OTel span
- OTel 版本:需要 >=1.25.0 支援 GenAI semconv(INV-5 確認目前 >=1.20.0)
決策
D1 — GenAI Span 命名規範
標準格式(GenAI semconv):
{gen_ai.operation.name} {gen_ai.system}
例:
chat ollama ← Ollama chat 呼叫
chat openai ← OpenAI 呼叫(備援)
chat gemini ← Gemini 呼叫
embeddings ollama ← embedding 呼叫
禁止的命名方式:
# 禁止(舊格式,不符合 semconv)
"llm_call"
"openclaw_decision"
"ai_provider_call"
D2 — GenAI Span Attributes(標準屬性)
每個 LLM call span 必須包含以下 attributes:
# 標準 GenAI attributes(OpenTelemetry GenAI semconv 1.0)
{
# 系統識別
"gen_ai.system": "ollama" | "openai" | "gemini" | "nvidia_nim",
"gen_ai.operation.name": "chat" | "embeddings" | "completion",
# 請求
"gen_ai.request.model": "llama3.2", # 請求的模型名稱
"gen_ai.request.max_tokens": 4096,
"gen_ai.request.temperature": 0.7,
"gen_ai.request.top_p": 0.9,
# 回應
"gen_ai.response.model": "llama3.2", # 實際使用的模型(可能因 fallback 不同)
"gen_ai.response.finish_reasons": ["stop"],
# Token 計數(重要:budget 對帳用)
"gen_ai.usage.prompt_tokens": 512,
"gen_ai.usage.completion_tokens": 128,
# AwoooP 自訂 namespace(非標準,但必需)
"awooop.project_id": "awoooi",
"awooop.agent_id": "openclaw-sre",
"awooop.run_id": "uuid",
"awooop.provider_tier": "primary" | "secondary" | "fallback", # ADR-110 三層
# W3C TraceContext(由 OTel SDK 自動注入)
# trace_id, span_id, parent_id
}
D3 — telemetry.py 修補(P0-08 + semconv 升級)
P0-08 修補(PR-01,立即):
# 修補前(telemetry.py:71,不安全)
if "192.168.0.188" not in endpoint:
raise ValueError(f"Unsupported OTEL endpoint: {endpoint}")
# 修補後(允許任何 HTTPS 或設定允許清單的 endpoint)
ALLOWED_OTEL_ENDPOINTS = {
settings.OTEL_EXPORTER_OTLP_ENDPOINT, # 從環境變數讀取
}
# 移除 hardcode assert,直接使用設定值
semconv 升級(Phase 2):
# apps/api/src/services/telemetry.py
from opentelemetry import trace
from opentelemetry.semconv._incubating.attributes import gen_ai_attributes
class GenAITracer:
"""符合 GenAI Semantic Conventions 1.0 的 LLM call tracer"""
def __init__(self, tracer_name: str = "awooop.platform"):
self._tracer = trace.get_tracer(tracer_name)
def start_llm_span(
self,
system: str, # "ollama", "openai", etc.
operation: str, # "chat", "embeddings"
model: str,
project_id: str,
agent_id: str,
run_id: str,
provider_tier: str # "primary", "secondary", "fallback"
) -> trace.Span:
span_name = f"{operation} {system}"
span = self._tracer.start_span(span_name)
span.set_attributes({
gen_ai_attributes.GEN_AI_SYSTEM: system,
gen_ai_attributes.GEN_AI_OPERATION_NAME: operation,
gen_ai_attributes.GEN_AI_REQUEST_MODEL: model,
"awooop.project_id": project_id,
"awooop.agent_id": agent_id,
"awooop.run_id": run_id,
"awooop.provider_tier": provider_tier,
})
return span
def end_llm_span(
self,
span: trace.Span,
response_model: str,
prompt_tokens: int,
completion_tokens: int,
finish_reason: str
) -> None:
span.set_attributes({
gen_ai_attributes.GEN_AI_RESPONSE_MODEL: response_model,
gen_ai_attributes.GEN_AI_USAGE_PROMPT_TOKENS: prompt_tokens,
gen_ai_attributes.GEN_AI_USAGE_COMPLETION_TOKENS: completion_tokens,
gen_ai_attributes.GEN_AI_RESPONSE_FINISH_REASONS: [finish_reason],
})
span.end()
D4 — OTel 依賴版本更新(INV-5)
# pyproject.toml 更新
opentelemetry-sdk = ">=1.25.0" # GenAI semconv 支援
opentelemetry-exporter-otlp-proto-grpc = ">=1.25.0"
opentelemetry-semantic-conventions = ">=0.46b0" # incubating GenAI attrs
D5 — SignOz 上報設定(188:24318)
# apps/api/src/services/telemetry.py
# OTEL_EXPORTER_OTLP_ENDPOINT 從環境變數讀取(不再 hardcode)
# K8s deployment 中設置:
# OTEL_EXPORTER_OTLP_ENDPOINT=http://192.168.0.188:24318
# EwoooC 可設置不同的 endpoint(PR-01 修補後解除封鎖)
K8s ConfigMap(per-tenant 可配置):
apiVersion: v1
kind: ConfigMap
metadata:
name: awooop-telemetry-config
data:
OTEL_EXPORTER_OTLP_ENDPOINT: "http://192.168.0.188:24318"
OTEL_SERVICE_NAME: "awooop-platform"
OTEL_RESOURCE_ATTRIBUTES: "deployment.environment=production"
D6 — GCP Ollama 三層拓撲的 span 標記(ADR-110 整合)
# ADR-110 三層拓撲的 span 標記
PROVIDER_TIER_MAP = {
"34.143.170.20": "primary", # GCP-A
"34.21.145.224": "secondary", # GCP-B
"192.168.0.111": "fallback", # Local
}
def get_provider_tier(ollama_host: str) -> str:
for ip, tier in PROVIDER_TIER_MAP.items():
if ip in ollama_host:
return tier
return "unknown"
Fallback 事件(primary → secondary 或 secondary → fallback)必須在 span 上標記 awooop.provider_tier,讓 SignOz 可以分析 fallback 頻率。
後果
Benefits
- GenAI semconv 標準化:SignOz / Grafana 可用通用 GenAI dashboard
gen_ai.usage.prompt_tokens/completion_tokens進 OTel:budget 對帳有 trace 依據- EwoooC 解封(PR-01 移除 hardcode assert)
- GCP Ollama 三層 fallback 可視化
Costs
- OTel 版本升級(>=1.25.0)需要更新 pyproject.toml + 驗證現有 instrumentation
- 現有 LLM call 程式碼需要加
start_llm_span/end_llm_span包裝
Risks
- OTel GenAI semconv incubating 狀態(not stable),未來可能有 attribute 名稱變更
- 緩解:
awooop.*自訂 namespace 隔離,即使 semconv 變更只需更新 GenAI attributes 部分
驗收標準
telemetry.py:71hardcoded IP assert 移除(PR-01 完成)- OTel 版本升級至 >=1.25.0(Phase 2)
- LLM call span 包含
gen_ai.usage.prompt_tokens/completion_tokens(整合測試) - SignOz 可看到
chat ollamaspan 帶 token 計數(Phase 2 驗收) - GCP Ollama fallback 事件在 SignOz 有
awooop.provider_tier標記可查詢
關聯
- ADR-110(GCP Ollama 三層拓撲,fallback 事件標記)
- ADR-120(token budget,telemetry token 計數對帳)
- INV-4(telemetry.py:71 hardcoded IP,P0-08)
- INV-5(OTel 版本相容性矩陣)