LangSmith与LLM应用可观测性完全教程

教程简介

零基础LangSmith与LLM应用可观测性完全教程,涵盖LLM可观测性概念、LangSmith架构与接入、Trace追踪机制、Prompt版本管理、评估体系(自动+人工)、成本监控、LangFuse开源替代方案、生产级监控告警、CI/CD集成、企业级LLMOps平台搭建等核心技能,适合AI运维工程师和SRE系统学习。

LangSmith与LLM应用可观测性完全教程

从概念理解到企业级LLMOps平台搭建,构建生产级LLM应用的可观测性体系

目录

  1. LLM可观测性概述
  2. LangSmith架构与核心概念
  3. 快速接入与环境配置
  4. Trace追踪机制
  5. Prompt版本管理
  6. 评估体系:自动与人工
  7. 成本监控与优化
  8. LangFuse开源替代方案
  9. 生产级监控与告警
  10. CI/CD集成
  11. 企业级LLMOps平台搭建
  12. 最佳实践总结

1. LLM可观测性概述

1.1 为什么LLM应用需要可观测性

传统软件的可观测性建立在确定性逻辑之上——相同的输入永远产生相同的输出。LLM应用打破了这一假设:

维度 传统应用 LLM应用
输出确定性 确定性 概率性
错误类型 逻辑错误、异常 幻觉、偏见、格式错误
性能瓶颈 CPU/内存/IO Token延迟、API限流
成本模型 固定基础设施 按Token计费,不可预测
质量评估 单元测试/集成测试 主观评估 + 自动指标
调试方式 堆栈跟踪 完整的推理链追踪

LLM应用面临的核心挑战:

1. 黑盒推理:用户问了什么 → 模型"想"了什么 → 为什么给出这个回答?
2. 质量漂移:同一提示词在不同模型版本下表现可能截然不同
3. 成本失控:一次RAG调用可能消耗数千Token,成本难以预测
4. 延迟波动:模型响应时间从100ms到30s不等
5. 幻觉检测:模型"自信地"给出错误信息

1.2 可观测性三大支柱在LLM中的映射

传统可观测性          LLM可观测性
─────────────        ─────────────
Logs (日志)     →    Traces (推理链追踪)
                     - 完整的调用链路
                     - 每一步的输入/输出
                     - Token使用详情

Metrics (指标)  →    Metrics (LLM指标)
                     - 延迟分布 (P50/P95/P99)
                     - Token消耗趋势
                     - 成本仪表板
                     - 质量评分趋势

Traces (链路)   →    Evaluations (评估)
                     - 自动评估(相似度、正确性)
                     - 人工标注与反馈
                     - A/B测试对比

1.3 主流LLM可观测性工具对比

工具 类型 核心优势 适用场景
LangSmith 商业SaaS LangChain深度集成,评估体系完善 LangChain生态用户
LangFuse 开源自托管 完全控制数据,社区活跃 数据敏感/自定义需求
Helicone 商业SaaS 简单易用,代理模式接入 快速上手,轻量需求
Arize Phoenix 商业+开源 可视化强大,嵌入向量分析 RAG应用调试
Weights & Biases 商业SaaS 实验跟踪,与ML流程统一 ML团队统一平台
Datadog LLM 商业SaaS 与现有APM集成 已用Datadog的企业

2. LangSmith架构与核心概念

2.1 LangSmith架构概览

┌─────────────────────────────────────────────────────────┐
│                    LangSmith Platform                     │
│                                                           │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐ │
│  │  Traces   │  │ Datasets │  │Evaluations│  │ Prompts  │ │
│  │  追踪引擎 │  │ 数据管理 │  │  评估系统 │  │ 版本管理 │ │
│  └──────────┘  └──────────┘  └──────────┘  └──────────┘ │
│                                                           │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐               │
│  │  Monitor  │  │  Rules   │  │  Play-   │               │
│  │  监控告警 │  │  规则引擎 │  │  ground  │               │
│  └──────────┘  └──────────┘  └──────────┘               │
└─────────────────────────────────────────────────────────┘
         ↑                ↑                ↑
    ┌────────┐      ┌────────┐      ┌────────┐
    │Python  │      │  JS/TS │      │  REST  │
    │  SDK   │      │  SDK   │      │  API   │
    └────────┘      └────────┘      └────────┘
         ↑                ↑                ↑
    ┌────────┐      ┌────────┐      ┌────────┐
    │LangChain│     │LlamaIndex│    │ 自定义  │
    │ 应用    │      │  应用   │      │ 应用   │
    └────────┘      └────────┘      └────────┘

2.2 核心概念

Trace(追踪):一次完整的用户请求链路,包含所有子步骤。

Trace: "用户询问天气"
├── Chain: 处理用户输入
│   ├── LLM: 意图识别 (200ms, 150 tokens)
│   └── Tool: 天气API调用 (500ms)
├── LLM: 生成回答 (1.2s, 300 tokens)
└── 总计: 1.9s, 450 tokens, $0.003

Run(运行):Trace中的单个步骤,类型包括:

  • llm:模型调用
  • chain:链式调用
  • tool:工具调用
  • retriever:文档检索
  • parser:输出解析

Dataset(数据集):用于评估的输入/输出对集合。

Evaluation(评估):对模型输出质量的量化衡量。

Experiment(实验):在数据集上运行的评估批次。


3. 快速接入与环境配置

3.1 注册与API密钥

# 1. 访问 https://smith.langchain.com 注册账户
# 2. 在 Settings → API Keys 创建密钥

# 设置环境变量
export LANGCHAIN_TRACING_V2=true
export LANGCHAIN_API_KEY="lsv2_pt_xxxxxxxxxxxxxxxxxxxx"
export LANGCHAIN_PROJECT="my-project"  # 项目名称
export LANGCHAIN_ENDPOINT="https://api.smith.langchain.com"

3.2 Python SDK安装

pip install langsmith

# 如果使用LangChain生态
pip install langchain langchain-openai langchain-community

3.3 最小化接入示例

import os
from langsmith import Client
from langsmith.run_helpers import traceable

# 初始化客户端
client = Client()

# 方式1:使用装饰器自动追踪
@traceable(
    name="My LLM Call",
    run_type="chain",
    project_name="my-project"
)
def ask_question(question: str) -> str:
    from openai import OpenAI
    
    oai_client = OpenAI()  # 自动读取OPENAI_API_KEY
    
    response = oai_client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": "你是一个有帮助的助手。"},
            {"role": "user", "content": question}
        ]
    )
    
    return response.choices[0].message.content

# 调用函数 - 自动创建Trace并上报
answer = ask_question("什么是LLM可观测性?")
print(answer)

3.4 LangChain集成

import os
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

# 设置环境变量后,LangChain自动上报所有调用到LangSmith
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = "lsv2_pt_xxx"

# 创建链
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个专业的技术文档翻译助手。"),
    ("user", "请将以下内容翻译成英文:{text}")
])

chain = prompt | llm | StrOutputParser()

# 执行 - 自动追踪到LangSmith
result = chain.invoke({"text": "LangSmith是一个LLM应用可观测性平台"})
print(result)

3.5 LlamaIndex集成

import os
from llama_index.core import Settings
from llama_index.llms.openai import OpenAI
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader

# 启用LangSmith追踪
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = "lsv2_pt_xxx"

# LlamaIndex也支持自动追踪
Settings.llm = OpenAI(model="gpt-4o-mini")

documents = SimpleDirectoryReader("./data").load_data()
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine()

# 所有检索和LLM调用都会被追踪
response = query_engine.query("这个文档的主要内容是什么?")
print(response)

4. Trace追踪机制

4.1 手动创建Trace

from langsmith import Client, traceable
from langsmith.run_helpers import tracing_context

client = Client()

# 方式1:使用上下文管理器
with tracing_context(project_name="my-project") as run:
    # 所有在此上下文中的@traceable调用都归属到这个trace
    result = my_function("input")

# 方式2:手动创建和管理Run
def manual_trace_example():
    # 创建根Run
    root_run = client.create_run(
        name="Root Chain",
        run_type="chain",
        inputs={"question": "什么是RAG?"},
        project_name="my-project"
    )
    
    # 创建子Run - LLM调用
    llm_run = client.create_run(
        name="GPT-4o Call",
        run_type="llm",
        inputs={
            "messages": [{"role": "user", "content": "解释RAG"}],
            "model": "gpt-4o"
        },
        parent_run_id=root_run.id
    )
    
    # ... 执行LLM调用 ...
    
    # 更新子Run结果
    client.update_run(
        run_id=llm_run.id,
        outputs={
            "content": "RAG是检索增强生成...",
            "usage": {"prompt_tokens": 50, "completion_tokens": 200}
        },
        end_time=datetime.now()
    )
    
    # 更新根Run结果
    client.update_run(
        run_id=root_run.id,
        outputs={"answer": "RAG是检索增强生成..."},
        end_time=datetime.now()
    )

4.2 复杂链路追踪

from langsmith import traceable
from typing import Dict, Any
import time

@traceable(run_type="chain", name="RAG Pipeline")
def rag_pipeline(question: str) -> str:
    """完整的RAG管道追踪"""
    
    # 步骤1:查询改写
    rewritten = rewrite_query(question)
    
    # 步骤2:文档检索
    docs = retrieve_documents(rewritten)
    
    # 步骤3:上下文构建
    context = build_context(docs)
    
    # 步骤4:生成回答
    answer = generate_answer(question, context)
    
    return answer

@traceable(run_type="llm", name="Query Rewriting")
def rewrite_query(question: str) -> str:
    """查询改写 - 被自动追踪为子步骤"""
    from openai import OpenAI
    client = OpenAI()
    
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": "将用户问题改写为更适合搜索的形式。"},
            {"role": "user", "content": question}
        ]
    )
    return response.choices[0].message.content

@traceable(run_type="retriever", name="Document Retrieval")
def retrieve_documents(query: str) -> list:
    """文档检索 - 被自动追踪为retriever类型"""
    # 模拟向量检索
    from openai import OpenAI
    client = OpenAI()
    
    # 获取embedding
    embedding = client.embeddings.create(
        model="text-embedding-3-small",
        input=query
    )
    
    # 模拟检索结果
    return [
        {"content": "文档片段1...", "score": 0.92},
        {"content": "文档片段2...", "score": 0.87},
        {"content": "文档片段3...", "score": 0.81}
    ]

@traceable(run_type="llm", name="Answer Generation")
def generate_answer(question: str, context: str) -> str:
    """生成回答"""
    from openai import OpenAI
    client = OpenAI()
    
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": f"基于以下上下文回答问题:\n{context}"},
            {"role": "user", "content": question}
        ]
    )
    return response.choices[0].message.content

# 调用 - 完整的调用链会被追踪
result = rag_pipeline("LangSmith的主要功能是什么?")

4.3 Trace元数据与标签

from langsmith import traceable

@traceable(
    name="Product Search",
    run_type="chain",
    metadata={
        "environment": "production",
        "version": "1.2.0",
        "feature_flag": "new_search_v2"
    },
    tags=["search", "production", "v1.2"]
)
def product_search(query: str, user_id: str) -> dict:
    # 可以在运行时动态添加元数据
    from langsmith.context import get_current_run_tree
    
    run_tree = get_current_run_tree()
    if run_tree:
        run_tree.metadata["user_id"] = user_id
        run_tree.metadata["query_length"] = len(query)
    
    # ... 执行搜索逻辑 ...
    return {"results": [], "total": 0}

4.4 错误追踪

from langsmith import traceable
from langsmith.context import get_current_run_tree

@traceable(run_type="chain", name="Fragile API Call")
def call_external_api(prompt: str) -> str:
    try:
        from openai import OpenAI
        client = OpenAI()
        
        response = client.chat.completions.create(
            model="gpt-4o",
            messages=[{"role": "user", "content": prompt}]
        )
        return response.choices[0].message.content
        
    except Exception as e:
        # 错误会被自动记录到LangSmith
        run_tree = get_current_run_tree()
        if run_tree:
            run_tree.metadata["error_type"] = type(e).__name__
            run_tree.metadata["error_message"] = str(e)
        raise  # 重新抛出,LangSmith会记录错误状态

5. Prompt版本管理

5.1 LangSmith Hub

LangSmith提供了Prompt Hub功能,用于集中管理和版本化提示词:

from langsmith import Client

client = Client()

# 推送提示词到Hub
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个专业的代码审查助手。请用中文回答,"
              "并遵循以下原则:\n1. 指出潜在的bug\n"
              "2. 建议性能优化\n3. 评估代码可读性"),
    ("user", "请审查以下代码:\n```{language}\n{code}\n```")
])

# 创建或更新提示词
client.push_prompt(
    "code-review-assistant",
    object=prompt,
    description="代码审查助手提示词",
    tags=["code", "review", "chinese"],
    is_public=False  # 私有提示词
)

5.2 提示词版本控制

from langsmith import Client
from langchain_core.prompts import ChatPromptTemplate

client = Client()

# 获取特定版本的提示词
prompt_v1 = client.pull_prompt("code-review-assistant", version=1)
prompt_v2 = client.pull_prompt("code-review-assistant", version=2)

# 获取最新版本
prompt_latest = client.pull_prompt("code-review-assistant")

# 列出所有版本
versions = client.list_prompt_versions("code-review-assistant")
for v in versions:
    print(f"Version {v.version}: {v.created_at} - {v.description}")

5.3 提示词A/B测试

from langsmith import Client, traceable
import random

client = Client()

# 拉取两个版本的提示词
prompt_v1 = client.pull_prompt("customer-service-v1")
prompt_v2 = client.pull_prompt("customer-service-v2")

@traceable(name="A/B Test Router")
def route_to_prompt(user_message: str, user_id: str) -> str:
    """基于用户ID的A/B测试路由"""
    # 使用用户ID的哈希确保同一用户始终看到同一版本
    variant = hash(user_id) % 2
    
    if variant == 0:
        prompt = prompt_v1
        variant_name = "v1"
    else:
        prompt = prompt_v2
        variant_name = "v2"
    
    from openai import OpenAI
    oai_client = OpenAI()
    
    messages = prompt.format_messages(user_message=user_message)
    response = oai_client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": m.type, "content": m.content} for m in messages]
    )
    
    result = response.choices[0].message.content
    
    # 记录A/B测试元数据
    from langsmith.context import get_current_run_tree
    run_tree = get_current_run_tree()
    if run_tree:
        run_tree.metadata["ab_variant"] = variant_name
        run_tree.metadata["prompt_version"] = variant + 1
    
    return result

5.4 提示词与代码分离的最佳实践

# project/prompts.py - 集中管理所有提示词
from langsmith import Client
from functools import lru_cache

_client = None

def get_client():
    global _client
    if _client is None:
        _client = Client()
    return _client

@lru_cache(maxsize=32)
def get_prompt(name: str, version: int = None):
    """获取提示词,带缓存"""
    client = get_client()
    if version:
        return client.pull_prompt(f"{name}:{version}")
    return client.pull_prompt(name)

# 在业务代码中使用
def answer_customer_question(question: str) -> str:
    prompt = get_prompt("customer-service")
    chain = prompt | get_llm()
    return chain.invoke({"question": question})

6. 评估体系:自动与人工

6.1 评估框架概述

LangSmith的评估体系分为三个层次:

┌─────────────────────────────────────────────┐
│           Layer 3: 人工评估                   │
│  - 众包标注  - 专家评审  - 用户反馈           │
├─────────────────────────────────────────────┤
│           Layer 2: LLM-as-Judge             │
│  - 模型评分  - 对比评估  - 多维度打分         │
├─────────────────────────────────────────────┤
│           Layer 1: 自动指标                   │
│  - 字符匹配  - 语义相似度  - 格式校验         │
└─────────────────────────────────────────────┘

6.2 创建评估数据集

from langsmith import Client

client = Client()

# 创建数据集
dataset = client.create_dataset(
    dataset_name="customer-service-eval-v2",
    description="客服助手评估数据集 - 2024Q4"
)

# 添加测试用例
test_cases = [
    {
        "inputs": {"question": "如何重置密码?"},
        "outputs": {
            "expected": "您可以通过以下步骤重置密码:1. 点击登录页面的'忘记密码';2. 输入注册邮箱;3. 查收重置邮件;4. 设置新密码。"
        },
        "metadata": {"difficulty": "easy", "category": "account"}
    },
    {
        "inputs": {"question": "我的订单显示已发货但物流信息三天没更新"},
        "outputs": {
            "expected": "很抱歉给您带来不便。物流信息延迟更新可能有以下原因:1. 快递公司系统同步延迟;2. 包裹正在中转站;3. 天气等不可抗力因素。建议您先等待24小时,如仍未更新,请提供订单号,我为您联系快递公司查询。"
        },
        "metadata": {"difficulty": "medium", "category": "order"}
    },
    {
        "inputs": {"question": "你们的产品质量太差了,我要投诉!"},
        "outputs": {
            "expected": "非常抱歉给您带来不好的体验。我理解您的感受,我们会认真对待每一位客户的反馈。请您提供具体的订单号和遇到的问题,我会立即为您记录并转交给专门的售后团队处理。我们承诺会在24小时内给您回复。"
        },
        "metadata": {"difficulty": "hard", "category": "complaint"}
    }
]

for case in test_cases:
    client.create_example(
        inputs=case["inputs"],
        outputs=case["outputs"],
        dataset_id=dataset.id,
        metadata=case.get("metadata")
    )

print(f"数据集创建完成,共 {len(test_cases)} 条用例")

6.3 自动评估器

from langsmith import Client, evaluate
from langsmith.evaluation import EvaluationResult

client = Client()

# 评估器1:字符串精确匹配
def exact_match_evaluator(run, example):
    """精确匹配评估"""
    prediction = run.outputs.get("output", "")
    expected = example.outputs.get("expected", "")
    
    score = 1.0 if prediction.strip() == expected.strip() else 0.0
    
    return EvaluationResult(
        key="exact_match",
        score=score,
        comment="完全匹配" if score == 1.0 else "不匹配"
    )

# 评估器2:语义相似度
def semantic_similarity_evaluator(run, example):
    """语义相似度评估(使用embedding)"""
    from openai import OpenAI
    
    oai_client = OpenAI()
    
    prediction = run.outputs.get("output", "")
    expected = example.outputs.get("expected", "")
    
    # 获取两段文本的embedding
    emb_pred = oai_client.embeddings.create(
        model="text-embedding-3-small",
        input=prediction
    ).data[0].embedding
    
    emb_expected = oai_client.embeddings.create(
        model="text-embedding-3-small",
        input=expected
    ).data[0].embedding
    
    # 计算余弦相似度
    import numpy as np
    similarity = np.dot(emb_pred, emb_expected) / (
        np.linalg.norm(emb_pred) * np.linalg.norm(emb_expected)
    )
    
    return EvaluationResult(
        key="semantic_similarity",
        score=float(similarity),
        comment=f"语义相似度: {similarity:.3f}"
    )

# 评估器3:LLM-as-Judge
def llm_judge_evaluator(run, example):
    """使用LLM评估回答质量"""
    from openai import OpenAI
    
    oai_client = OpenAI()
    
    question = example.inputs.get("question", "")
    prediction = run.outputs.get("output", "")
    expected = example.outputs.get("expected", "")
    
    judge_prompt = f"""请评估以下AI回答的质量。

用户问题:{question}

AI回答:{prediction}

参考答案:{expected}

请从以下维度评分(1-5分):
1. 准确性:信息是否正确
2. 完整性:是否涵盖关键点
3. 友好度:语气是否专业友善

请以JSON格式返回:{{"accuracy": X, "completeness": X, "friendliness": X, "overall": X, "reason": "..."}}"""
    
    response = oai_client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": judge_prompt}],
        response_format={"type": "json_object"}
    )
    
    import json
    scores = json.loads(response.choices[0].message.content)
    
    return EvaluationResult(
        key="llm_judge",
        score=scores.get("overall", 0) / 5.0,  # 归一化到0-1
        comment=json.dumps(scores, ensure_ascii=False)
    )

# 运行评估
def target_function(inputs):
    """被评估的函数"""
    from openai import OpenAI
    oai_client = OpenAI()
    
    response = oai_client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": "你是一个专业的客服助手。"},
            {"role": "user", "content": inputs["question"]}
        ]
    )
    
    return {"output": response.choices[0].message.content}

# 执行评估实验
experiment_results = evaluate(
    target_function,
    data="customer-service-eval-v2",
    evaluators=[
        exact_match_evaluator,
        semantic_similarity_evaluator,
        llm_judge_evaluator
    ],
    experiment_prefix="gpt4o-mini-baseline",
    metadata={
        "model": "gpt-4o-mini",
        "temperature": 0,
        "version": "1.0"
    }
)

print(f"评估完成!平均分数:{experiment_results}")

6.4 人工标注与反馈

from langsmith import Client

client = Client()

# 创建标注队列
queue = client.create_annotation_queue(
    name="客服回答质量审核",
    description="人工审核AI客服回答的质量",
    dataset_id="customer-service-eval-v2"
)

# 提交需要人工标注的Run
def submit_for_review(run_id: str):
    client.create_annotation(
        run_id=run_id,
        annotation_queue_id=queue.id,
        metadata={"priority": "high", "reason": "低置信度回答"}
    )

# 通过API收集用户反馈
@traceable(name="Collect Feedback")
def collect_user_feedback(run_id: str, feedback_type: str, comment: str = ""):
    """收集终端用户的反馈"""
    score_map = {"positive": 1.0, "neutral": 0.5, "negative": 0.0}
    
    client.create_feedback(
        run_id=run_id,
        key="user_feedback",
        score=score_map.get(feedback_type, 0.5),
        comment=comment,
        source_info={"source": "user_widget", "version": "1.0"}
    )

# 在应用中嵌入反馈收集
@traceable(name="Chat Response")
def chat_response(user_message: str) -> dict:
    from openai import OpenAI
    oai_client = OpenAI()
    
    response = oai_client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": user_message}]
    )
    
    answer = response.choices[0].message.content
    
    return {
        "answer": answer,
        "run_id": "从上下文获取当前run_id"  # 实际从traceable上下文获取
    }

7. 成本监控与优化

7.1 Token使用追踪

from langsmith import Client, traceable
from openai import OpenAI

client = Client()
oai_client = OpenAI()

@traceable(name="Cost Tracked LLM Call")
def cost_tracked_call(prompt: str, model: str = "gpt-4o") -> dict:
    """带有详细成本追踪的LLM调用"""
    
    response = oai_client.chat.completions.create(
        model=model,
        messages=[{"role": "user", "content": prompt}]
    )
    
    usage = response.usage
    
    # 价格表(美元/1M tokens)
    pricing = {
        "gpt-4o": {"input": 2.50, "output": 10.00},
        "gpt-4o-mini": {"input": 0.15, "output": 0.60},
        "gpt-4-turbo": {"input": 10.00, "output": 30.00},
        "claude-3-5-sonnet": {"input": 3.00, "output": 15.00},
    }
    
    model_pricing = pricing.get(model, {"input": 0, "output": 0})
    
    input_cost = (usage.prompt_tokens / 1_000_000) * model_pricing["input"]
    output_cost = (usage.completion_tokens / 1_000_000) * model_pricing["output"]
    total_cost = input_cost + output_cost
    
    # 记录到LangSmith元数据
    from langsmith.context import get_current_run_tree
    run_tree = get_current_run_tree()
    if run_tree:
        run_tree.metadata.update({
            "model": model,
            "prompt_tokens": usage.prompt_tokens,
            "completion_tokens": usage.completion_tokens,
            "total_tokens": usage.total_tokens,
            "input_cost_usd": round(input_cost, 6),
            "output_cost_usd": round(output_cost, 6),
            "total_cost_usd": round(total_cost, 6)
        })
    
    return {
        "content": response.choices[0].message.content,
        "usage": {
            "prompt_tokens": usage.prompt_tokens,
            "completion_tokens": usage.completion_tokens,
            "total_tokens": usage.total_tokens,
            "cost_usd": round(total_cost, 6)
        }
    }

7.2 成本仪表板查询

from langsmith import Client
from datetime import datetime, timedelta

client = Client()

def get_cost_report(project_name: str, days: int = 7) -> dict:
    """获取项目成本报告"""
    
    start_time = datetime.now() - timedelta(days=days)
    
    # 获取所有Run
    runs = client.list_runs(
        project_name=project_name,
        start_time=start_time.isoformat(),
        run_type="llm"
    )
    
    total_cost = 0
    total_tokens = 0
    model_costs = {}
    daily_costs = {}
    
    for run in runs:
        if run.metadata:
            cost = run.metadata.get("total_cost_usd", 0)
            tokens = run.metadata.get("total_tokens", 0)
            model = run.metadata.get("model", "unknown")
            
            total_cost += cost
            total_tokens += tokens
            
            # 按模型统计
            if model not in model_costs:
                model_costs[model] = {"cost": 0, "calls": 0, "tokens": 0}
            model_costs[model]["cost"] += cost
            model_costs[model]["calls"] += 1
            model_costs[model]["tokens"] += tokens
            
            # 按天统计
            day = run.start_time.strftime("%Y-%m-%d") if run.start_time else "unknown"
            if day not in daily_costs:
                daily_costs[day] = 0
            daily_costs[day] += cost
    
    return {
        "period": f"Last {days} days",
        "total_cost_usd": round(total_cost, 4),
        "total_tokens": total_tokens,
        "total_calls": sum(m["calls"] for m in model_costs.values()),
        "avg_cost_per_call": round(total_cost / max(sum(m["calls"] for m in model_costs.values()), 1), 6),
        "by_model": model_costs,
        "daily_breakdown": daily_costs
    }

# 生成报告
report = get_cost_report("my-project", days=30)
print(f"过去30天总成本: ${report['total_cost_usd']}")
print(f"总调用次数: {report['total_calls']}")
print(f"平均每次调用: ${report['avg_cost_per_call']}")

7.3 成本优化策略

# 策略1:智能模型路由
def smart_model_router(query: str, complexity_threshold: float = 0.7) -> str:
    """根据查询复杂度选择模型"""
    from openai import OpenAI
    
    oai_client = OpenAI()
    
    # 使用小模型评估复杂度
    eval_response = oai_client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{
            "role": "user",
            "content": f"评估以下问题的复杂度(0-1的JSON):{query}"
        }],
        response_format={"type": "json_object"}
    )
    
    complexity = json.loads(eval_response.choices[0].message.content).get("score", 0.5)
    
    # 根据复杂度选择模型
    if complexity < 0.3:
        return "gpt-4o-mini"      # 简单问题用便宜模型
    elif complexity < 0.7:
        return "gpt-4o-mini"      # 中等问题用中等模型
    else:
        return "gpt-4o"           # 复杂问题用强模型

# 策略2:语义缓存
import hashlib
import numpy as np
from typing import Optional

class SemanticCache:
    """基于语义相似度的缓存"""
    
    def __init__(self, similarity_threshold: float = 0.95):
        self.cache = []  # [(embedding, response, query)]
        self.threshold = similarity_threshold
        self.openai_client = OpenAI()
    
    def _get_embedding(self, text: str) -> list:
        response = self.openai_client.embeddings.create(
            model="text-embedding-3-small",
            input=text
        )
        return response.data[0].embedding
    
    def get(self, query: str) -> Optional[str]:
        """查找语义相似的缓存"""
        if not self.cache:
            return None
        
        query_emb = self._get_embedding(query)
        
        for cached_emb, cached_response, cached_query in self.cache:
            similarity = np.dot(query_emb, cached_emb) / (
                np.linalg.norm(query_emb) * np.linalg.norm(cached_emb)
            )
            if similarity >= self.threshold:
                return cached_response
        
        return None
    
    def set(self, query: str, response: str):
        """缓存新的问答对"""
        embedding = self._get_embedding(query)
        self.cache.append((embedding, response, query))
        
        # 限制缓存大小
        if len(self.cache) > 1000:
            self.cache = self.cache[-500:]

8. LangFuse开源替代方案

8.1 LangFuse概述

LangFuse是一个完全开源的LLM可观测性平台,可以自托管,提供与LangSmith类似的功能:

特性 LangSmith LangFuse
部署方式 SaaS 自托管 / Cloud
开源 是 (MIT)
数据控制 第三方托管 完全自控
LangChain集成 原生 优秀
LlamaIndex集成 原生 优秀
评估系统 完善 完善
社区 LangChain官方 活跃开源社区

8.2 自托管部署

# 使用Docker Compose部署LangFuse
# docker-compose.yml
version: '3.8'

services:
  langfuse:
    image: langfuse/langfuse:latest
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgresql://postgres:postgres@db:5432/langfuse
      - NEXTAUTH_URL=http://localhost:3000
      - NEXTAUTH_SECRET=your-secret-key-here
      - SALT=your-salt-here
    depends_on:
      - db
  
  db:
    image: postgres:15
    environment:
      - POSTGRES_DB=langfuse
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
    volumes:
      - langfuse_data:/var/lib/postgresql/data

volumes:
  langfuse_data:
# 启动
docker-compose up -d

# 访问 http://localhost:3000 创建账户

8.3 LangFuse Python SDK

pip install langfuse
import os
from langfuse import Langfuse
from langfuse.decorators import observe, langfuse_context

# 初始化
langfuse = Langfuse(
    public_key="pk-lf-xxx",
    secret_key="sk-lf-xxx",
    host="http://localhost:3000"  # 自托管地址
)

# 使用装饰器追踪
@observe(as_type="generation")
def call_llm(prompt: str, model: str = "gpt-4o-mini") -> str:
    from openai import OpenAI
    client = OpenAI()
    
    response = client.chat.completions.create(
        model=model,
        messages=[{"role": "user", "content": prompt}]
    )
    
    # 记录到LangFuse
    langfuse_context.update_current_observation(
        usage={
            "input": response.usage.prompt_tokens,
            "output": response.usage.completion_tokens,
            "total": response.usage.total_tokens
        },
        model=model,
        metadata={"temperature": 0}
    )
    
    return response.choices[0].message.content

@observe(as_type="chain")
def rag_pipeline(question: str) -> str:
    """完整的RAG管道"""
    # 检索
    docs = retrieve_docs(question)
    
    # 生成
    context = "\n".join(docs)
    answer = call_llm(
        f"基于以下上下文回答:\n{context}\n\n问题:{question}"
    )
    
    return answer

# 调用
result = rag_pipeline("什么是LLMOps?")

# 确保所有事件都已发送
langfuse.flush()

8.4 LangFuse评估

from langfuse import Langfuse
from langfuse.decorators import observe

langfuse = Langfuse()

# 创建评估数据集
dataset = langfuse.create_dataset(
    name="qa-evaluation",
    description="问答系统评估"
)

# 添加数据点
langfuse.create_dataset_item(
    dataset_name="qa-evaluation",
    input={"question": "什么是机器学习?"},
    expected_output={"answer": "机器学习是人工智能的一个分支..."}
)

# 运行评估
@observe()
def run_evaluation():
    dataset = langfuse.get_dataset("qa-evaluation")
    
    for item in dataset.items:
        # 在每个数据点上运行你的应用
        with item.observe(run_name="gpt4o-eval") as trace:
            answer = call_llm(item.input["question"])
            
            # 记录评估分数
            langfuse.score(
                trace_id=trace.id,
                name="accuracy",
                value=1.0 if "机器学习" in answer else 0.0
            )

run_evaluation()
langfuse.flush()

9. 生产级监控与告警

9.1 监控指标体系

from dataclasses import dataclass
from typing import Dict, List
from datetime import datetime

@dataclass
class LLMMetric:
    """LLM应用核心监控指标"""
    
    # 性能指标
    latency_p50_ms: float      # 中位延迟
    latency_p95_ms: float      # P95延迟
    latency_p99_ms: float      # P99延迟
    throughput_rps: float      # 每秒请求数
    
    # 质量指标
    avg_score: float           # 平均质量评分
    low_score_rate: float      # 低分回答比例
    hallucination_rate: float  # 幻觉率
    error_rate: float          # 错误率
    
    # 成本指标
    total_cost_usd: float      # 总成本
    avg_cost_per_request: float # 单请求平均成本
    total_tokens: int          # 总Token数
    
    # 业务指标
    user_satisfaction: float   # 用户满意度
    task_completion_rate: float # 任务完成率

# 监控阈值配置
ALERT_THRESHOLDS = {
    "latency_p95_ms": {"warning": 5000, "critical": 10000},
    "error_rate": {"warning": 0.05, "critical": 0.10},
    "low_score_rate": {"warning": 0.15, "critical": 0.30},
    "hallucination_rate": {"warning": 0.10, "critical": 0.20},
    "daily_cost_usd": {"warning": 100, "critical": 500},
    "avg_cost_per_request": {"warning": 0.05, "critical": 0.20}
}

9.2 实时监控实现

import time
from collections import deque
from dataclasses import dataclass, field
from threading import Lock
from typing import Optional
import json

class LLMMonitor:
    """实时LLM监控系统"""
    
    def __init__(self, window_size: int = 1000):
        self.window_size = window_size
        self.latencies = deque(maxlen=window_size)
        self.costs = deque(maxlen=window_size)
        self.errors = deque(maxlen=window_size)
        self.scores = deque(maxlen=window_size)
        self.lock = Lock()
        
        # 告警历史
        self.alerts = []
    
    def record_request(
        self,
        latency_ms: float,
        cost_usd: float,
        tokens: int,
        success: bool,
        score: Optional[float] = None,
        metadata: Optional[dict] = None
    ):
        """记录一次请求"""
        with self.lock:
            self.latencies.append(latency_ms)
            self.costs.append({"cost": cost_usd, "tokens": tokens})
            self.errors.append(0 if success else 1)
            if score is not None:
                self.scores.append(score)
        
        # 检查告警
        self._check_alerts()
    
    def _check_alerts(self):
        """检查是否需要触发告警"""
        metrics = self.get_metrics()
        
        # 延迟告警
        if metrics["latency_p95"] > ALERT_THRESHOLDS["latency_p95_ms"]["critical"]:
            self._trigger_alert("CRITICAL", f"P95延迟过高: {metrics['latency_p95']:.0f}ms")
        
        # 错误率告警
        if metrics["error_rate"] > ALERT_THRESHOLDS["error_rate"]["critical"]:
            self._trigger_alert("CRITICAL", f"错误率过高: {metrics['error_rate']:.2%}")
        
        # 成本告警
        daily_cost = sum(c["cost"] for c in self.costs)
        if daily_cost > ALERT_THRESHOLDS["daily_cost_usd"]["warning"]:
            self._trigger_alert("WARNING", f"日成本过高: ${daily_cost:.2f}")
    
    def _trigger_alert(self, level: str, message: str):
        """触发告警"""
        alert = {
            "level": level,
            "message": message,
            "timestamp": datetime.now().isoformat()
        }
        self.alerts.append(alert)
        
        # 发送通知(邮件、Slack、PagerDuty等)
        self._send_notification(alert)
    
    def _send_notification(self, alert: dict):
        """发送告警通知"""
        # Slack webhook示例
        import requests
        
        webhook_url = "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
        
        payload = {
            "text": f"🚨 LLM告警 [{alert['level']}]\n{alert['message']}",
            "username": "LLM Monitor"
        }
        
        try:
            requests.post(webhook_url, json=payload, timeout=5)
        except:
            pass  # 告警发送失败不影响主流程
    
    def get_metrics(self) -> dict:
        """获取当前指标"""
        with self.lock:
            if not self.latencies:
                return {}
            
            sorted_latencies = sorted(self.latencies)
            n = len(sorted_latencies)
            
            return {
                "latency_p50": sorted_latencies[int(n * 0.5)],
                "latency_p95": sorted_latencies[int(n * 0.95)],
                "latency_p99": sorted_latencies[int(n * 0.99)],
                "error_rate": sum(self.errors) / len(self.errors),
                "avg_cost": sum(c["cost"] for c in self.costs) / len(self.costs),
                "total_tokens": sum(c["tokens"] for c in self.costs),
                "avg_score": sum(self.scores) / len(self.scores) if self.scores else None,
                "request_count": n
            }

# 全局监控实例
monitor = LLMMonitor()

# 在应用中使用
@traceable(name="Monitored LLM Call")
def monitored_llm_call(prompt: str) -> str:
    start_time = time.time()
    
    try:
        from openai import OpenAI
        client = OpenAI()
        
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": prompt}]
        )
        
        latency_ms = (time.time() - start_time) * 1000
        
        # 记录指标
        monitor.record_request(
            latency_ms=latency_ms,
            cost_usd=calculate_cost(response.usage, "gpt-4o-mini"),
            tokens=response.usage.total_tokens,
            success=True
        )
        
        return response.choices[0].message.content
        
    except Exception as e:
        latency_ms = (time.time() - start_time) * 1000
        
        monitor.record_request(
            latency_ms=latency_ms,
            cost_usd=0,
            tokens=0,
            success=False
        )
        
        raise

9.3 Grafana集成

# 使用Prometheus指标暴露监控数据
from prometheus_client import Counter, Histogram, Gauge, start_http_server

# 定义指标
REQUEST_COUNT = Counter(
    'llm_requests_total',
    'Total LLM requests',
    ['model', 'status', 'endpoint']
)

REQUEST_LATENCY = Histogram(
    'llm_request_duration_seconds',
    'LLM request latency',
    ['model', 'endpoint'],
    buckets=[0.1, 0.5, 1.0, 2.0, 5.0, 10.0, 30.0]
)

TOKEN_USAGE = Counter(
    'llm_tokens_total',
    'Total tokens used',
    ['model', 'type']  # type: input/output
)

COST_TOTAL = Counter(
    'llm_cost_dollars_total',
    'Total cost in dollars',
    ['model']
)

ACTIVE_REQUESTS = Gauge(
    'llm_active_requests',
    'Number of active LLM requests'
)

# 在应用中使用
@traceable(name="Prometheus Monitored Call")
def prometheus_monitored_call(prompt: str, model: str = "gpt-4o-mini") -> str:
    ACTIVE_REQUESTS.inc()
    
    try:
        with REQUEST_LATENCY.labels(model=model, endpoint="/chat").time():
            from openai import OpenAI
            client = OpenAI()
            
            response = client.chat.completions.create(
                model=model,
                messages=[{"role": "user", "content": prompt}]
            )
        
        # 记录指标
        REQUEST_COUNT.labels(model=model, status="success", endpoint="/chat").inc()
        TOKEN_USAGE.labels(model=model, type="input").inc(response.usage.prompt_tokens)
        TOKEN_USAGE.labels(model=model, type="output").inc(response.usage.completion_tokens)
        
        cost = calculate_cost(response.usage, model)
        COST_TOTAL.labels(model=model).inc(cost)
        
        return response.choices[0].message.content
        
    except Exception as e:
        REQUEST_COUNT.labels(model=model, status="error", endpoint="/chat").inc()
        raise
    finally:
        ACTIVE_REQUESTS.dec()

# 启动Prometheus指标服务器
start_http_server(8000)
# Grafana Dashboard JSON 片段
{
  "panels": [
    {
      "title": "LLM请求延迟分布",
      "type": "heatmap",
      "targets": [
        {
          "expr": "rate(llm_request_duration_seconds_bucket[5m])",
          "legendFormat": "{{le}}"
        }
      ]
    },
    {
      "title": "Token使用趋势",
      "type": "timeseries",
      "targets": [
        {
          "expr": "rate(llm_tokens_total[5m])",
          "legendFormat": "{{model}} - {{type}}"
        }
      ]
    },
    {
      "title": "累计成本",
      "type": "stat",
      "targets": [
        {
          "expr": "llm_cost_dollars_total",
          "legendFormat": "{{model}}"
        }
      ]
    }
  ]
}

10. CI/CD集成

10.1 评估作为测试

# .github/workflows/llm-eval.yml
name: LLM Quality Gate

on:
  pull_request:
    paths:
      - 'prompts/**'
      - 'chains/**'
      - 'eval/**'

jobs:
  evaluate:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.10'
      
      - name: Install dependencies
        run: pip install -r requirements.txt
      
      - name: Run evaluation
        env:
          LANGCHAIN_API_KEY: ${{ secrets.LANGCHAIN_API_KEY }}
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        run: python eval/run_evaluation.py
      
      - name: Check quality gate
        run: |
          python -c "
          import json
          with open('eval/results.json') as f:
              results = json.load(f)
          
          # 质量门禁
          min_score = 0.7
          max_error_rate = 0.1
          
          assert results['avg_score'] >= min_score, \
              f'Score {results[\"avg_score\"]} below minimum {min_score}'
          assert results['error_rate'] <= max_error_rate, \
              f'Error rate {results[\"error_rate\"]} above maximum {max_error_rate}'
          
          print('✅ Quality gate passed!')
          "
      
      - name: Comment on PR
        if: always()
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const results = JSON.parse(fs.readFileSync('eval/results.json', 'utf8'));
            
            const body = `## LLM Evaluation Results
            
            | Metric | Value | Threshold | Status |
            |--------|-------|-----------|--------|
            | Avg Score | ${results.avg_score.toFixed(3)} | ≥0.7 | ${results.avg_score >= 0.7 ? '✅' : '❌'} |
            | Error Rate | ${(results.error_rate * 100).toFixed(1)}% | ≤10% | ${results.error_rate <= 0.1 ? '✅' : '❌'} |
            | Avg Latency | ${results.avg_latency_ms.toFixed(0)}ms | ≤5000ms | ${results.avg_latency_ms <= 5000 ? '✅' : '❌'} |
            
            [View full results in LangSmith](${results.langsmith_url})
            `;
            
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: body
            });

10.2 评估脚本

# eval/run_evaluation.py
import json
import os
from langsmith import Client, evaluate

def run_evaluation():
    client = Client()
    
    # 定义评估目标
    def target(inputs):
        # 这里调用你的应用
        from chains.customer_service import get_chain
        chain = get_chain()
        result = chain.invoke(inputs)
        return {"output": result}
    
    # 定义评估器
    def accuracy_eval(run, example):
        prediction = run.outputs.get("output", "")
        expected = example.outputs.get("expected", "")
        
        # 使用简单的关键词匹配作为基线
        keywords = expected.lower().split()
        matches = sum(1 for kw in keywords if kw in prediction.lower())
        score = matches / len(keywords) if keywords else 0
        
        return {"key": "accuracy", "score": score}
    
    # 运行评估
    results = evaluate(
        target,
        dataset="customer-service-eval",
        evaluators=[accuracy_eval],
        metadata={"pr": os.environ.get("GITHUB_PR_NUMBER", "local")}
    )
    
    # 保存结果
    summary = {
        "avg_score": results["accuracy"],
        "error_rate": 0,  # 从results中提取
        "avg_latency_ms": 0,  # 从results中提取
        "langsmith_url": f"https://smith.langchain.com/projects/{results.get('project_id', '')}"
    }
    
    with open("eval/results.json", "w") as f:
        json.dump(summary, f, indent=2)
    
    print(f"评估完成: {summary}")

if __name__ == "__main__":
    run_evaluation()

10.3 Prompt版本发布流程

# scripts/publish_prompt.py
"""提示词发布脚本 - 在CI/CD中使用"""

import sys
from langsmith import Client

def publish_prompt(prompt_name: str, version: int, environment: str = "production"):
    client = Client()
    
    # 1. 验证评估结果
    # 获取最新实验结果
    experiments = client.list_experiments(
        dataset_name=f"{prompt_name}-eval"
    )
    
    latest = next(iter(experiments), None)
    if not latest:
        print("❌ No evaluation results found")
        sys.exit(1)
    
    # 2. 检查质量门禁
    # ... 检查各项指标 ...
    
    # 3. 标记版本为已发布
    client.update_prompt_version(
        prompt_name=prompt_name,
        version=version,
        metadata={
            "environment": environment,
            "released_at": datetime.now().isoformat(),
            "evaluation_score": latest.results.get("avg_score", 0)
        }
    )
    
    print(f"✅ Prompt '{prompt_name}' v{version} published to {environment}")

if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument("--name", required=True)
    parser.add_argument("--version", required=True, type=int)
    parser.add_argument("--env", default="production")
    args = parser.parse_args()
    
    publish_prompt(args.name, args.version, args.env)

11. 企业级LLMOps平台搭建

11.1 平台架构设计

┌─────────────────────────────────────────────────────────────────┐
│                    LLMOps Platform Architecture                   │
│                                                                   │
│  ┌─────────────────────────────────────────────────────────────┐ │
│  │                    API Gateway (Kong/Nginx)                   │ │
│  └─────────────────────────────────────────────────────────────┘ │
│                                    ↓                              │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐        │
│  │ Trace    │  │ Eval     │  │ Prompt   │  │ Cost     │        │
│  │ Service  │  │ Service  │  │ Service  │  │ Service  │        │
│  └──────────┘  └──────────┘  └──────────┘  └──────────┘        │
│                                    ↓                              │
│  ┌─────────────────────────────────────────────────────────────┐ │
│  │              Message Queue (Kafka/RabbitMQ)                   │ │
│  └─────────────────────────────────────────────────────────────┘ │
│                                    ↓                              │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐        │
│  │ClickHouse│  │ Postgres │  │  Redis   │  │   S3     │        │
│  │(Traces)  │  │(Metadata)│  │ (Cache)  │  │(Artifacts)│       │
│  └──────────┘  └──────────┘  └──────────┘  └──────────┘        │
│                                    ↓                              │
│  ┌─────────────────────────────────────────────────────────────┐ │
│  │              Monitoring (Grafana + Prometheus)                │ │
│  └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

11.2 数据存储设计

-- PostgreSQL: 元数据存储
CREATE TABLE projects (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name VARCHAR(255) NOT NULL,
    description TEXT,
    created_at TIMESTAMP DEFAULT NOW(),
    settings JSONB DEFAULT '{}'
);

CREATE TABLE prompts (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    project_id UUID REFERENCES projects(id),
    name VARCHAR(255) NOT NULL,
    description TEXT,
    created_at TIMESTAMP DEFAULT NOW(),
    UNIQUE(project_id, name)
);

CREATE TABLE prompt_versions (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    prompt_id UUID REFERENCES prompts(id),
    version INTEGER NOT NULL,
    template TEXT NOT NULL,
    config JSONB DEFAULT '{}',
    metadata JSONB DEFAULT '{}',
    created_at TIMESTAMP DEFAULT NOW(),
    UNIQUE(prompt_id, version)
);

CREATE TABLE evaluations (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    project_id UUID REFERENCES projects(id),
    dataset_name VARCHAR(255),
    prompt_version_id UUID REFERENCES prompt_versions(id),
    model VARCHAR(100),
    results JSONB,
    created_at TIMESTAMP DEFAULT NOW()
);

-- ClickHouse: 高性能追踪存储
CREATE TABLE traces (
    trace_id UUID,
    run_id UUID,
    parent_run_id Nullable(UUID),
    project_id UUID,
    name String,
    run_type String,  -- llm, chain, tool, retriever
    inputs String,    -- JSON
    outputs String,   -- JSON
    metadata String,  -- JSON
    status String,    -- success, error, pending
    start_time DateTime64(3),
    end_time Nullable(DateTime64(3)),
    latency_ms Nullable(UInt32),
    tokens_input Nullable(UInt32),
    tokens_output Nullable(UInt32),
    cost_usd Nullable(Float64),
    error_message Nullable(String)
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(start_time)
ORDER BY (project_id, start_time, trace_id)
TTL start_time + INTERVAL 90 DAY;

-- 物化视图:自动聚合统计
CREATE MATERIALIZED VIEW trace_stats_hourly
ENGINE = SummingMergeTree()
PARTITION BY toYYYYMM(hour)
ORDER BY (project_id, hour, model)
AS SELECT
    project_id,
    toStartOfHour(start_time) AS hour,
    metadata:model AS model,
    count() AS request_count,
    sum(tokens_input) AS total_input_tokens,
    sum(tokens_output) AS total_output_tokens,
    sum(cost_usd) AS total_cost,
    avg(latency_ms) AS avg_latency,
    quantile(0.95)(latency_ms) AS p95_latency
FROM traces
GROUP BY project_id, hour, model;

11.3 统一SDK封装

# llmops_sdk/client.py
"""企业级LLMOps统一SDK"""

from typing import Optional, Dict, Any, Callable
from contextlib import contextmanager
from dataclasses import dataclass
from abc import ABC, abstractmethod
import time
import uuid

@dataclass
class LLMCall:
    """LLM调用记录"""
    trace_id: str
    run_id: str
    model: str
    prompt: str
    response: str
    latency_ms: float
    tokens_input: int
    tokens_output: int
    cost_usd: float
    metadata: Dict[str, Any]

class LLMOpsBackend(ABC):
    """后端接口抽象"""
    
    @abstractmethod
    def log_trace(self, call: LLMCall) -> None:
        pass
    
    @abstractmethod
    def get_prompt(self, name: str, version: Optional[int] = None) -> str:
        pass
    
    @abstractmethod
    def log_evaluation(self, run_id: str, scores: Dict[str, float]) -> None:
        pass

class LLMOpsClient:
    """统一LLMOps客户端"""
    
    def __init__(
        self,
        backend: LLMOpsBackend,
        project: str,
        enable_tracing: bool = True,
        enable_cost_tracking: bool = True
    ):
        self.backend = backend
        self.project = project
        self.enable_tracing = enable_tracing
        self.enable_cost_tracking = enable_cost_tracking
        self._pricing = self._load_pricing()
    
    def _load_pricing(self) -> Dict[str, Dict[str, float]]:
        return {
            "gpt-4o": {"input": 2.50, "output": 10.00},
            "gpt-4o-mini": {"input": 0.15, "output": 0.60},
            "claude-3-5-sonnet": {"input": 3.00, "output": 15.00},
        }
    
    def track(self, func: Callable = None, *, name: str = None):
        """装饰器:自动追踪函数调用"""
        def decorator(fn):
            def wrapper(*args, **kwargs):
                if not self.enable_tracing:
                    return fn(*args, **kwargs)
                
                run_id = str(uuid.uuid4())
                start = time.time()
                
                try:
                    result = fn(*args, **kwargs)
                    latency = (time.time() - start) * 1000
                    
                    # 记录调用
                    call = LLMCall(
                        trace_id=str(uuid.uuid4()),
                        run_id=run_id,
                        model=kwargs.get("model", "unknown"),
                        prompt=str(args),
                        response=str(result),
                        latency_ms=latency,
                        tokens_input=0,
                        tokens_output=0,
                        cost_usd=0,
                        metadata={"function": name or fn.__name__}
                    )
                    self.backend.log_trace(call)
                    
                    return result
                except Exception as e:
                    # 记录错误
                    raise
            
            return wrapper
        
        if func is not None:
            return decorator(func)
        return decorator
    
    @contextmanager
    def trace_context(self, name: str):
        """上下文管理器:追踪代码块"""
        trace_id = str(uuid.uuid4())
        start = time.time()
        
        try:
            yield {"trace_id": trace_id}
        finally:
            latency = (time.time() - start) * 1000
            # 记录到后端

# 使用示例
class LangSmithBackend(LLMOpsBackend):
    """LangSmith后端实现"""
    
    def __init__(self, api_key: str):
        from langsmith import Client
        self.client = Client(api_key=api_key)
    
    def log_trace(self, call: LLMCall) -> None:
        self.client.create_run(
            name="LLM Call",
            run_type="llm",
            inputs={"prompt": call.prompt},
            outputs={"response": call.response},
            trace_id=call.trace_id,
            run_id=call.run_id
        )
    
    def get_prompt(self, name: str, version: Optional[int] = None) -> str:
        return self.client.pull_prompt(name, version=version)
    
    def log_evaluation(self, run_id: str, scores: Dict[str, float]) -> None:
        for key, value in scores.items():
            self.client.create_feedback(
                run_id=run_id,
                key=key,
                score=value
            )

# 初始化
ops = LLMOpsClient(
    backend=LangSmithBackend(api_key="xxx"),
    project="production"
)

# 使用
@ops.track(name="Chat Service")
def chat(message: str, model: str = "gpt-4o-mini") -> str:
    from openai import OpenAI
    client = OpenAI()
    response = client.chat.completions.create(
        model=model,
        messages=[{"role": "user", "content": message}]
    )
    return response.choices[0].message.content

11.4 部署配置

# k8s/llmops-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: llmops-api
  namespace: llmops
spec:
  replicas: 3
  selector:
    matchLabels:
      app: llmops-api
  template:
    metadata:
      labels:
        app: llmops-api
    spec:
      containers:
        - name: llmops-api
          image: your-registry/llmops-api:latest
          ports:
            - containerPort: 8000
          env:
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: llmops-secrets
                  key: database-url
            - name: LANGCHAIN_API_KEY
              valueFrom:
                secretKeyRef:
                  name: llmops-secrets
                  key: langchain-api-key
          resources:
            requests:
              memory: "512Mi"
              cpu: "250m"
            limits:
              memory: "1Gi"
              cpu: "500m"
          livenessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 30
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /ready
              port: 8000
            initialDelaySeconds: 5
            periodSeconds: 5

---
apiVersion: v1
kind: Service
metadata:
  name: llmops-api
  namespace: llmops
spec:
  selector:
    app: llmops-api
  ports:
    - port: 80
      targetPort: 8000
  type: ClusterIP

---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: llmops-api-hpa
  namespace: llmops
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: llmops-api
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

12. 最佳实践总结

12.1 接入最佳实践

  1. 渐进式接入:从装饰器自动追踪开始,逐步添加自定义元数据
  2. 命名规范:建立统一的Trace/Run命名规范,便于搜索和过滤
  3. 元数据标准化:定义统一的元数据schema(模型、版本、环境等)
  4. 采样策略:生产环境可按比例采样(如10%),避免存储爆炸
  5. 敏感数据脱敏:在上报前对PII信息进行脱敏处理

12.2 评估最佳实践

  1. 评估先行:在修改提示词之前,先建立评估基线
  2. 多维度评估:同时使用自动指标和LLM-as-Judge
  3. 黄金数据集:维护一套高质量的评估数据集,定期更新
  4. 持续评估:将评估集成到CI/CD,每次变更自动验证
  5. 人工校准:定期用人工标注结果校准自动评估器

12.3 成本最佳实践

  1. 预算告警:设置日/周/月成本预算,超支自动告警
  2. 模型路由:根据任务复杂度自动选择合适成本的模型
  3. 语义缓存:对相似查询使用缓存,减少重复调用
  4. Token优化:精简系统提示词,使用更高效的格式
  5. 定期审计:每月分析成本分布,识别优化机会

12.4 监控最佳实践

  1. 分层告警:区分Warning和Critical级别,避免告警疲劳
  2. SLA定义:明确LLM应用的SLA(延迟、可用性、质量)
  3. 根因分析:建立从告警到根因的快速定位流程
  4. 仪表板标准化:为不同角色(研发/产品/运维)定制仪表板
  5. 演练机制:定期进行故障演练,验证告警和恢复流程

12.5 安全与合规

# PII脱敏示例
import re
from typing import str

class PIISanitizer:
    """个人信息脱敏工具"""
    
    PATTERNS = {
        "phone": (r'\b1[3-9]\d{9}\b', "***手机号***"),
        "id_card": (r'\b\d{17}[\dXx]\b', "***身份证号***"),
        "email": (r'\b[\w.-]+@[\w.-]+\.\w+\b', "***邮箱***"),
        "bank_card": (r'\b\d{16,19}\b', "***银行卡号***"),
    }
    
    @classmethod
    def sanitize(cls, text: str) -> str:
        """脱敏文本中的PII信息"""
        result = text
        for name, (pattern, replacement) in cls.PATTERNS.items():
            result = re.sub(pattern, replacement, result)
        return result
    
    @classmethod
    def sanitize_dict(cls, data: dict) -> dict:
        """递归脱敏字典中的PII信息"""
        result = {}
        for key, value in data.items():
            if isinstance(value, str):
                result[key] = cls.sanitize(value)
            elif isinstance(value, dict):
                result[key] = cls.sanitize_dict(value)
            elif isinstance(value, list):
                result[key] = [
                    cls.sanitize_dict(v) if isinstance(v, dict)
                    else cls.sanitize(v) if isinstance(v, str)
                    else v
                    for v in value
                ]
            else:
                result[key] = value
        return result

# 在追踪中使用
@traceable(name="Secure Pipeline")
def secure_pipeline(user_input: str) -> str:
    # 脱敏后再上报
    sanitized_input = PIISanitizer.sanitize(user_input)
    
    from langsmith.context import get_current_run_tree
    run_tree = get_current_run_tree()
    if run_tree:
        run_tree.inputs = {"question": sanitized_input}
    
    # ... 正常处理 ...
    return response

参考资源


本教程最后更新:2025年1月。LLM可观测性领域发展迅速,建议关注各工具的官方文档获取最新信息。

内容声明

本文内容为AI技术学习教程,仅供学习参考。如涉及技术问题,欢迎通过 xurj005@163.com 与我们交流。

目录