生产化

第31课:可观测性

📚 可观测性概述

本课学习可观测性(Observability)——生产环境LLM应用的眼睛。没有可观测性,线上问题就是黑盒:不知道哪里慢、哪里错、花了多少钱。可观测性三支柱:日志(Logs) + 指标(Metrics) + 追踪(Traces)

🎯 核心要点

第31课: 可观测性
├── 三支柱
│   ├── Logs: 结构化日志
│   ├── Metrics: 指标聚合
│   └── Traces: 分布式追踪
├── LLMObservability
│   ├── LLM调用记录
│   ├── Token用量追踪
│   ├── 延迟分析
│   └── 成本估算
└── 告警系统
    ├── 错误率告警
    ├── 延迟告警
    └── 成本告警

🔍 三支柱: Logs/Metrics/Traces

# 三支柱: Logs/Metrics/Traces
import json
import time
from typing import Dict, List, Optional, Any
from dataclasses import dataclass, field
from collections import defaultdict

@dataclass
class LogEntry:
    timestamp: float
    level: str       # INFO/WARN/ERROR
    component: str
    message: str
    metadata: dict = field(default_factory=dict)

@dataclass
class MetricPoint:
    timestamp: float
    name: str
    value: float
    tags: Dict[str, str] = field(default_factory=dict)

@dataclass
class Span:
    """分布式追踪的一个跨度"""
    trace_id: str
    span_id: str
    parent_span_id: Optional[str] = None
    operation: str = ""
    start_time: float = 0.0
    end_time: float = 0.0
    status: str = "ok"  # ok/error
    attributes: Dict[str, Any] = field(default_factory=dict)

class LoggingSystem:
    """结构化日志系统"""
    def __init__(self): self.entries: List[LogEntry] = []
    def log(self, level, component, message, **meta):
        self.entries.append(LogEntry(timestamp=time.time(), level=level, component=component, message=message, metadata=meta))
    def query(self, level=None, component=None, since=None):
        results = self.entries
        if level: results = [e for e in results if e.level == level]
        if component: results = [e for e in results if e.component == component]
        if since: results = [e for e in results if e.timestamp >= since]
        return results

class MetricsSystem:
    """指标系统"""
    def __init__(self): self.metrics: List[MetricPoint] = []
    def record(self, name, value, **tags):
        self.metrics.append(MetricPoint(timestamp=time.time(), name=name, value=value, tags=tags))
    def aggregate(self, name, window_seconds=60):
        now = time.time()
        points = [m for m in self.metrics if m.name == name and now - m.timestamp < window_seconds]
        if not points: return {}
        values = [p.value for p in points]
        return {"count": len(values), "avg": sum(values)/len(values), "min": min(values), "max": max(values), "p99": sorted(values)[int(len(values)*0.99)]}

class TracingSystem:
    """分布式追踪系统"""
    def __init__(self): self.traces: Dict[str, List[Span]] = defaultdict(list)
    def start_span(self, trace_id, span_id, operation, parent=None, **attrs):
        span = Span(trace_id=trace_id, span_id=span_id, parent_span_id=parent, operation=operation, start_time=time.time(), attributes=attrs)
        self.traces[trace_id].append(span)
        return span
    def end_span(self, trace_id, span_id, status="ok"):
        for span in self.traces.get(trace_id, []):
            if span.span_id == span_id:
                span.end_time = time.time()
                span.status = status
                break
    def get_trace(self, trace_id):
        return self.traces.get(trace_id, [])

✅ 验证通过:三支柱系统完整实现——LoggingSystem/MetricsSystem/TracingSystem

🛠 LLMObservability专用可观测性

# LLMObservability: LLM应用专用可观测性
from openai import OpenAI
import json
import time
from typing import Dict, List, Optional
from dataclasses import dataclass, field

client = OpenAI()

@dataclass
class LLMCallRecord:
    """LLM调用记录"""
    call_id: str
    model: str
    prompt_tokens: int = 0
    completion_tokens: int = 0
    total_tokens: int = 0
    latency_ms: float = 0.0
    status: str = "success"  # success/error/timeout
    error: Optional[str] = None
    timestamp: float = field(default_factory=time.time)

class LLMObservability:
    """LLM应用可观测性 - 专用的指标/追踪/告警"""
    def __init__(self):
        self.call_records: List[LLMCallRecord] = []
        self.alerts: List[dict] = []

    def record_call(self, call_id, model, usage, latency_ms, status="success", error=None):
        record = LLMCallRecord(
            call_id=call_id, model=model,
            prompt_tokens=usage.get("prompt_tokens", 0),
            completion_tokens=usage.get("completion_tokens", 0),
            total_tokens=usage.get("total_tokens", 0),
            latency_ms=latency_ms, status=status, error=error
        )
        self.call_records.append(record)
        self._check_alerts(record)
        return record

    def _check_alerts(self, record):
        """检查是否触发告警"""
        if record.latency_ms > 10000:
            self.alerts.append({"type": "high_latency", "call_id": record.call_id, "value": record.latency_ms, "threshold": 10000})
        if record.status == "error":
            self.alerts.append({"type": "call_error", "call_id": record.call_id, "error": record.error})
        if record.total_tokens > 4000:
            self.alerts.append({"type": "high_token_usage", "call_id": record.call_id, "value": record.total_tokens})

    def get_metrics(self, window_minutes=60) -> dict:
        """获取聚合指标"""
        now = time.time()
        recent = [r for r in self.call_records if now - r.timestamp < window_minutes * 60]
        if not recent: return {"total_calls": 0}
        latencies = [r.latency_ms for r in recent if r.status == "success"]
        return {
            "total_calls": len(recent),
            "success_rate": sum(1 for r in recent if r.status == "success") / len(recent) * 100,
            "avg_latency_ms": sum(latencies) / len(latencies) if latencies else 0,
            "p99_latency_ms": sorted(latencies)[int(len(latencies)*0.99)] if latencies else 0,
            "total_tokens": sum(r.total_tokens for r in recent),
            "avg_tokens_per_call": sum(r.total_tokens for r in recent) / len(recent),
        }

    def get_cost_estimate(self, price_per_1k_input=0.00015, price_per_1k_output=0.0006):
        """估算成本"""
        total_input = sum(r.prompt_tokens for r in self.call_records)
        total_output = sum(r.completion_tokens for r in self.call_records)
        return {
            "total_input_tokens": total_input,
            "total_output_tokens": total_output,
            "estimated_cost_usd": total_input / 1000 * price_per_1k_input + total_output / 1000 * price_per_1k_output,
        }

✅ 验证通过:LLMObservability实现了LLM调用记录、指标聚合和成本估算

🔔 告警系统

# 告警系统: 多级告警与通知
from typing import Callable, Dict, List
from dataclasses import dataclass, field
from enum import Enum

class AlertSeverity(Enum):
    INFO = "info"
    WARNING = "warning"
    CRITICAL = "critical"

@dataclass
class AlertRule:
    name: str
    condition: Callable
    severity: AlertSeverity
    message_template: str
    cooldown_seconds: int = 300

@dataclass
class Alert:
    rule_name: str
    severity: AlertSeverity
    message: str
    timestamp: float = field(default_factory=__import__("time").time)

class AlertSystem:
    """多级告警系统"""
    def __init__(self):
        self.rules: List[AlertRule] = []
        self.alerts: List[Alert] = []
        self.last_fired: Dict[str, float] = {}

    def add_rule(self, name, condition, severity, message_template, cooldown=300):
        self.rules.append(AlertRule(name=name, condition=condition, severity=severity, message_template=message_template, cooldown_seconds=cooldown))

    def evaluate(self, metrics: dict):
        """评估所有规则"""
        import time
        now = time.time()
        for rule in self.rules:
            if rule.name in self.last_fired and now - self.last_fired[rule.name] < rule.cooldown_seconds:
                continue
            if rule.condition(metrics):
                alert = Alert(rule_name=rule.name, severity=rule.severity, message=rule.message_template.format(**metrics))
                self.alerts.append(alert)
                self.last_fired[rule.name] = now
                print(f"[{alert.severity.value.upper()}] {alert.message}")

# 预定义规则
alert_system = AlertSystem()
alert_system.add_rule("high_error_rate", lambda m: m.get("success_rate", 100) < 95, AlertSeverity.CRITICAL, "错误率过高: {success_rate:.1f}%")
alert_system.add_rule("high_latency", lambda m: m.get("p99_latency_ms", 0) > 5000, AlertSeverity.WARNING, "P99延迟过高: {p99_latency_ms:.0f}ms")
alert_system.add_rule("high_cost", lambda m: m.get("estimated_cost_usd", 0) > 10, AlertSeverity.WARNING, "成本过高: ${estimated_cost_usd:.2f}")

✅ 验证通过:告警系统实现了多级规则评估、冷却时间和自动通知

支柱回答什么成本用途
Logs发生了什么高(存储)审计/调试
Metrics什么趋势低(聚合)监控/告警
Traces请求经过了哪里性能分析

💡 最佳实践

⚠️ 常见陷阱

🔗 与其他课程的关系

构建可观测性完整系统

# 挑战: 构建LLM应用监控Dashboard
# - 实时Token用量
# - 错误率趋势
# - P50/P95/P99延迟
# - 成本追踪

进阶挑战

实现异常检测——自动发现指标异常并告警

🏅🏅 可观测性实践者

🔄 可观测性最佳实践

可观测性不是"记录一切",而是在正确的位置记录正确的信息。以下是生产环境的实战经验:

📐 日志分层策略

🧪 关键指标选择

不是所有指标都值得监控。以下是最关键的LLM应用指标:

指标类别具体指标告警阈值监控频率
性能TTFT(首字延迟)> 2s实时
性能总延迟P99> 10s实时
质量成功率< 95%5分钟
质量用户满意度< 3.5/5每小时
成本每请求Token> 5000实时
成本日均成本> 预算120%每天
安全注入尝试次数> 10/min实时

💡 Dashboard设计原则