LangSmith实战:LLM应用调试、追踪与评估的完整工程指南

4 阅读1分钟

LLM 应用的调试是个黑盒噩梦——输入进去,输出出来,中间发生了什么完全不透明。LangSmith 正是为解决这一痛点而生。本文从工程实践角度,系统讲解 LangSmith 在 LLM 应用开发全生命周期的应用。

一、为什么 LLM 应用需要专门的可观测性工具

1.1 传统监控的局限

传统的 APM(Application Performance Monitoring)工具,如 Datadog、Prometheus,擅长监控 HTTP 响应时间、错误率、数据库查询等结构化指标。但 LLM 应用的核心质量问题无法用这些工具衡量:

传统监控能告诉你:
✅ API 响应时间:2.3s
✅ Token 消耗:1247 tokens
✅ 错误率:0.1%

但无法告诉你:
❌ 这次回答逻辑是否连贯?
❌ 检索到的文档是否真正相关?
❌ 多步骤 Agent 的哪一步出了问题?
❌ 同一问题为何今天的回答质量比昨天差?

1.2 LLM 应用的可观测性需求

LLM 应用需要三个层次的可观测性:

L1 系统层(传统工具可覆盖)
  - 延迟、吞吐量、错误率
  - Token 消耗、API 费用

L2 调用链层(LangSmith 核心能力)
  - 完整的 LLM 调用链路追踪
  - 每一步的输入/输出
  - 工具调用和检索结果可视化

L3 质量层(LangSmith 评估能力)
  - 答案正确性、相关性
  - 幻觉检测
  - 与基准版本的 A/B 对比

二、LangSmith 核心功能概览

LangSmith 是 LangChain 团队推出的 LLM 应用可观测性和评估平台,提供:

  • Tracing:自动捕获每次 LLM 调用的完整执行链路
  • Playground:可视化调试和 Prompt 实验
  • Datasets:管理测试数据集
  • Evaluations:自动化质量评估
  • Monitoring:生产环境监控和告警

三、快速上手:集成 LangSmith 追踪

3.1 基础配置

import os
from langchain_openai import ChatOpenAI
from langsmith import Client

# 设置环境变量
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_ENDPOINT"] = "https://api.smith.langchain.com"
os.environ["LANGCHAIN_API_KEY"] = "your_langsmith_api_key"
os.environ["LANGCHAIN_PROJECT"] = "my-production-app"  # 项目名称

# 仅这几行配置,LangChain 会自动追踪所有调用
llm = ChatOpenAI(model="gpt-4o", temperature=0.7)
response = llm.invoke("解释一下 Transformer 的注意力机制")
# 此次调用会自动出现在 LangSmith 的 Traces 页面

3.2 自定义追踪与元数据

from langsmith import traceable
from langchain_core.tracers import LangChainTracer

@traceable(
    name="RAG检索与生成",
    tags=["production", "rag"],
    metadata={"version": "1.2.0", "user_tier": "premium"}
)
def rag_pipeline(question: str, user_id: str) -> str:
    """带完整追踪的 RAG 流水线"""
    
    # 第一步:检索
    docs = retriever.invoke(question)
    
    # 第二步:生成
    context = "\n".join([d.page_content for d in docs])
    prompt = f"基于以下上下文回答问题:\n{context}\n\n问题:{question}"
    response = llm.invoke(prompt)
    
    return response.content

# 调用时附加追踪元数据
result = rag_pipeline(
    question="什么是 MoE 架构?",
    user_id="user_123"
)

3.3 追踪 Agent 复杂调用链

from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

# LangSmith 自动追踪 Agent 的每一步工具调用
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个有用的AI助手,可以使用工具帮助用户。"),
    ("human", "{input}"),
    MessagesPlaceholder(variable_name="agent_scratchpad"),
])

agent = create_openai_tools_agent(llm, tools, prompt)
agent_executor = AgentExecutor(
    agent=agent, 
    tools=tools,
    verbose=True,  # 配合 LangSmith 追踪
)

# LangSmith 会记录:
# - Agent 的决策过程
# - 每次工具调用的输入输出
# - 最终答案的生成
result = agent_executor.invoke(
    {"input": "查询今天的天气,并生成一份出行建议"},
    config={"run_name": "weather_agent_run"}  # 自定义追踪名称
)

四、调试工作流:用 LangSmith 定位问题

4.1 RAG 检索质量问题定位

最常见的 RAG 问题:模型生成的回答看起来合理,但其实没有使用正确的文档。

from langsmith import Client
from typing import Optional

client = Client()

def analyze_rag_traces(project_name: str, date_from: str) -> dict:
    """
    分析 RAG 追踪数据,找出检索质量问题
    """
    # 获取所有 RAG 追踪
    traces = client.list_runs(
        project_name=project_name,
        start_time=date_from,
        run_type="chain",
        filter='eq(name, "RAG检索与生成")'
    )
    
    analysis = {
        "total_runs": 0,
        "low_relevance_retrievals": [],
        "empty_retrievals": [],
        "avg_retrieval_count": 0,
    }
    
    for trace in traces:
        analysis["total_runs"] += 1
        
        # 检查是否有检索步骤
        retrieval_runs = [
            r for r in trace.child_runs 
            if "retriever" in r.name.lower()
        ]
        
        for ret_run in retrieval_runs:
            docs = ret_run.outputs.get("documents", [])
            
            if not docs:
                analysis["empty_retrievals"].append({
                    "trace_id": trace.id,
                    "question": trace.inputs.get("question"),
                    "timestamp": trace.start_time
                })
            else:
                # 分析检索质量(可以集成评估器)
                avg_score = sum(
                    d.get("relevance_score", 0) 
                    for d in docs
                ) / len(docs)
                
                if avg_score < 0.5:
                    analysis["low_relevance_retrievals"].append({
                        "trace_id": trace.id,
                        "question": trace.inputs.get("question"),
                        "avg_relevance": avg_score
                    })
    
    return analysis

4.2 Playground:交互式 Prompt 调试

LangSmith 的 Playground 功能允许你直接对历史追踪进行"重放"调试:

# 场景:某次 Agent 决策错误,需要在 Playground 中重现和调试

# 1. 在代码中标记需要关注的运行
from langsmith import traceable

@traceable(
    name="complex_reasoning",
    tags=["debug-needed"],  # 打标签便于后续过滤
)
def complex_reasoning_task(input_data: dict) -> str:
    # ... 复杂逻辑
    pass

# 2. 在 LangSmith 界面:
# - 过滤 tags 为 "debug-needed" 的追踪
# - 点击某次失败的运行
# - 使用 "Open in Playground" 直接重放
# - 修改 Prompt 并立即看到新的输出
# - 不需要改代码、重新部署

五、评估体系:自动化质量衡量

LangSmith 的评估功能是其区别于普通追踪工具的核心能力。

5.1 创建评估数据集

from langsmith import Client
from langsmith.schemas import Example

client = Client()

# 创建包含标准答案的测试数据集
examples = [
    {
        "inputs": {"question": "什么是 RAG?"},
        "outputs": {"answer": "RAG(检索增强生成)是一种将检索系统与生成模型结合的AI技术框架..."}
    },
    {
        "inputs": {"question": "Transformer 的核心创新是什么?"},
        "outputs": {"answer": "Transformer 的核心创新是自注意力机制(Self-Attention),它允许模型..."}
    },
    # ... 更多测试用例
]

dataset = client.create_dataset(
    dataset_name="ai-concepts-qa-v1",
    description="AI 核心概念问答评估数据集"
)

client.create_examples(
    inputs=[e["inputs"] for e in examples],
    outputs=[e["outputs"] for e in examples],
    dataset_id=dataset.id
)

5.2 实现自定义评估器

from langsmith.evaluation import EvaluationResult, RunEvaluator
from langchain_openai import ChatOpenAI

class CorrectnessEvaluator(RunEvaluator):
    """
    使用 LLM 作为裁判,评估答案的正确性
    """
    
    def __init__(self):
        self.judge_llm = ChatOpenAI(model="gpt-4o", temperature=0)
    
    def evaluate_run(self, run, example=None) -> EvaluationResult:
        if not example or not example.outputs:
            return EvaluationResult(key="correctness", score=None)
        
        predicted = run.outputs.get("answer", "")
        reference = example.outputs.get("answer", "")
        question = run.inputs.get("question", "")
        
        prompt = f"""请评估以下 AI 回答的正确性。

问题:{question}

参考答案:{reference}

AI 回答:{predicted}

请从以下维度评分(0-1):
1. 事实准确性:关键事实是否正确
2. 完整性:是否覆盖了主要要点
3. 无幻觉:是否包含虚假信息

综合评分(0-1):"""
        
        response = self.judge_llm.invoke(prompt)
        
        # 解析评分
        score = self._parse_score(response.content)
        
        return EvaluationResult(
            key="correctness",
            score=score,
            comment=response.content
        )
    
    def _parse_score(self, text: str) -> float:
        import re
        numbers = re.findall(r'\d+\.?\d*', text[-50:])
        if numbers:
            score = float(numbers[-1])
            return min(score, 1.0) if score <= 1 else score / 10
        return 0.5


class RelevanceEvaluator(RunEvaluator):
    """评估检索文档的相关性"""
    
    def evaluate_run(self, run, example=None) -> EvaluationResult:
        question = run.inputs.get("question", "")
        retrieved_docs = run.outputs.get("retrieved_docs", [])
        
        if not retrieved_docs:
            return EvaluationResult(key="retrieval_relevance", score=0.0)
        
        # 计算每个文档与问题的语义相似度
        scores = []
        for doc in retrieved_docs[:3]:  # 只评估前3个
            similarity = self._compute_similarity(question, doc["content"])
            scores.append(similarity)
        
        avg_relevance = sum(scores) / len(scores)
        
        return EvaluationResult(
            key="retrieval_relevance",
            score=avg_relevance
        )

5.3 运行评估实验

from langsmith.evaluation import evaluate

def my_rag_app(inputs: dict) -> dict:
    """被评估的 RAG 应用"""
    question = inputs["question"]
    
    # 检索
    docs = retriever.invoke(question)
    retrieved_docs = [
        {"content": d.page_content, "score": d.metadata.get("score", 0)}
        for d in docs
    ]
    
    # 生成
    context = "\n".join([d.page_content for d in docs])
    response = llm.invoke(f"上下文:{context}\n问题:{question}")
    
    return {
        "answer": response.content,
        "retrieved_docs": retrieved_docs
    }

# 运行评估
results = evaluate(
    my_rag_app,
    data="ai-concepts-qa-v1",  # 数据集名称
    evaluators=[CorrectnessEvaluator(), RelevanceEvaluator()],
    experiment_prefix="rag-v1.2-eval",
    num_repetitions=3,  # 每个样本运行 3 次(LLM 有随机性)
    max_concurrency=5,
)

# 查看汇总结果
print(f"平均正确性: {results.aggregate_metrics['correctness']:.3f}")
print(f"平均检索相关性: {results.aggregate_metrics['retrieval_relevance']:.3f}")

六、A/B 实验与版本对比

LangSmith 的评估系统支持多版本对比,这是优化 LLM 应用的核心工作流:

# 对比两个不同版本的 Prompt

def rag_app_v1(inputs: dict) -> dict:
    """原始版本"""
    question = inputs["question"]
    docs = retriever.invoke(question)
    context = "\n".join([d.page_content for d in docs])
    prompt = f"请基于以下信息回答:{context}\n问题:{question}"
    response = llm.invoke(prompt)
    return {"answer": response.content}

def rag_app_v2(inputs: dict) -> dict:
    """优化版本:更好的 Prompt 结构"""
    question = inputs["question"]
    docs = retriever.invoke(question)
    context = "\n\n".join([
        f"[来源{i+1}] {d.page_content}" 
        for i, d in enumerate(docs)
    ])
    prompt = f"""你是一个专业的AI知识助手。请基于以下参考信息,用准确、清晰的语言回答用户问题。
如果参考信息中没有相关内容,请明确说明"参考资料中暂无此信息",不要臆造。

参考信息:
{context}

用户问题:{question}

请给出详细、准确的回答:"""
    response = llm.invoke(prompt)
    return {"answer": response.content}

# 在相同数据集上对比两个版本
evaluators = [CorrectnessEvaluator(), RelevanceEvaluator()]

results_v1 = evaluate(rag_app_v1, data="ai-concepts-qa-v1", 
                       evaluators=evaluators, experiment_prefix="rag-v1")
results_v2 = evaluate(rag_app_v2, data="ai-concepts-qa-v1", 
                       evaluators=evaluators, experiment_prefix="rag-v2")

# LangSmith 界面可以直接对比两次实验的评分分布

七、生产监控与告警

7.1 设置生产监控规则

from langsmith import Client
import smtplib
from email.mime.text import MIMEText

client = Client()

def production_health_check(project_name: str) -> dict:
    """
    每小时执行的生产健康检查
    """
    import datetime
    
    one_hour_ago = datetime.datetime.now() - datetime.timedelta(hours=1)
    
    # 统计各类指标
    runs = list(client.list_runs(
        project_name=project_name,
        start_time=one_hour_ago,
        run_type="chain"
    ))
    
    if not runs:
        return {"status": "no_data"}
    
    total = len(runs)
    errors = sum(1 for r in runs if r.error)
    avg_latency = sum(
        (r.end_time - r.start_time).total_seconds() 
        for r in runs if r.end_time
    ) / total
    
    # Token 消耗统计
    total_tokens = sum(
        (r.total_tokens or 0) for r in runs
    )
    
    health = {
        "total_requests": total,
        "error_rate": errors / total,
        "avg_latency_s": avg_latency,
        "total_tokens": total_tokens,
        "estimated_cost_usd": total_tokens * 0.000015,  # GPT-4o 估算
    }
    
    # 告警触发
    if health["error_rate"] > 0.05:  # 错误率 > 5%
        send_alert(
            f"⚠️ LLM 应用错误率告警: {health['error_rate']:.1%}",
            health
        )
    
    if health["avg_latency_s"] > 10:  # 平均延迟 > 10s
        send_alert(
            f"⚠️ LLM 应用延迟告警: {health['avg_latency_s']:.1f}s",
            health
        )
    
    return health

八、最佳实践总结

8.1 追踪设计原则

原则实践
完整性追踪整个请求链路,不要只追踪 LLM 调用
最小信息不要记录用户 PII,只记录对调试有用的信息
合理采样生产环境可以采样 10-20%,降低存储成本
标签规范建立统一的 tag 命名规范,便于过滤分析

8.2 评估数据集维护

# 好的评估数据集维护策略
class EvalDatasetManager:
    
    def expand_from_production(
        self, 
        project_name: str,
        days: int = 7,
        sample_rate: float = 0.01  # 从 1% 的生产流量中采样
    ):
        """
        从生产追踪中自动采样,扩充评估数据集
        优先选择:
        1. 用户反馈为负的请求
        2. 延迟异常高的请求  
        3. 多样性采样(避免重复的问题类型)
        """
        production_runs = self._fetch_production_runs(project_name, days)
        
        candidates = []
        for run in production_runs:
            priority_score = 0
            
            # 负面反馈优先
            if run.feedback_stats.get("user_score", 1) < 0.5:
                priority_score += 10
            
            # 高延迟优先
            if run.total_tokens and run.total_tokens > 2000:
                priority_score += 5
            
            candidates.append((priority_score, run))
        
        # 按优先级采样
        candidates.sort(reverse=True)
        selected = candidates[:int(len(candidates) * sample_rate)]
        
        # 添加到数据集(待人工标注答案)
        self._add_to_pending_annotation(selected)

九、总结

LangSmith 将 LLM 应用开发从"黑盒调试"变成了"白盒工程"。关键价值点:

  1. 开发效率:Playground 让 Prompt 迭代速度提升 5-10 倍
  2. 质量保证:自动化评估替代人工抽查,覆盖率从 1% 提升到 100%
  3. 版本管理:A/B 实验让每次 Prompt 改动都有数据支撑
  4. 生产可信:追踪系统让生产问题可复现、可定位

从原型到生产,LangSmith 应该是 LLM 应用工程师工具箱中的标配。