Validation & Retry.py

# 核心概念
# 验证和重试 = 确保 LLM 应用的可靠性和数据质量
# 在生产环境中,需要处理三类问题:
#     网络错误 - 临时性连接问题(用 with_retry())
#     模型故障 - 主模型不可用(用 with_fallbacks())
#     输出质量 - LLM 输出不符合要求(用验证 + 重试循环)

# 基本用法
# 1. with_retry() - 自动重试
# 处理临时性网络错误:


import os
import sys
from langchain.chat_models import init_chat_model
from pydantic import BaseModel, Field, ValidationError

sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from init_model import get_chat_model

chat_model = get_chat_model()


def demo1():
    # 添加重试机制
    llm_with_retry = chat_model.with_retry(
        retry_if_exception_type=(ConnectionError, TimeoutError),
        wait_exponential_jitter=True,  # 指数退避 + 随机抖动
        stop_after_attempt=3,  # 最多重试 3 次
    )

    response = llm_with_retry.invoke("你好")


# 工作原理:
#     第 1 次尝试 → 失败 (ConnectionError)
#         ↓ 等待 1s
#     第 2 次尝试 → 失败 (ConnectionError)
#         ↓ 等待 2s
#     第 3 次尝试 → 成功 ✓


# 2. with_fallbacks() - 降级方案
# 主模型失败时切换到备用模型:


def demo2():
    # 主模型
    primary_model = init_chat_model("groq:llama-3.3-70b-versatile")

    # 备用模型(更可靠/更便宜)
    fallback_model = init_chat_model("groq:llama-3.1-8b-instant")

    # 配置降级
    llm_with_fallbacks = primary_model.with_fallbacks([fallback_model])

    response = llm_with_fallbacks.invoke("介绍 Python")
    # 主模型正常 → 使用主模型
    # 主模型失败 → 自动切换到备用模型


# 3. Pydantic 验证
# 使用 Pydantic 约束确保数据质量:
def demo3():
    from pydantic import BaseModel, Field, field_validator

    class User(BaseModel):
        name: str = Field(min_length=2, max_length=20)
        age: int = Field(ge=0, le=150)  # 0-150 岁
        email: str

        @field_validator("email")
        @classmethod
        def validate_email(cls, v):
            if "@" not in v:
                raise ValueError("邮箱必须包含 @")
            return v

    # 使用
    try:
        user = User(name="张三", age=200, email="invalid")  # 失败
    except ValidationError as e:
        print(e.errors())  # 查看错误详情


# 核心组件
# 1. with_retry() 参数
# 参数    说明    默认值
# retry_if_exception_type    哪些异常会触发重试    (Exception,)
# stop_after_attempt    最大重试次数    3
# wait_exponential_jitter    是否使用指数退避 + 抖动    False
# 常见重试异常:
# # 网络相关
# (ConnectionError, TimeoutError, httpx.ConnectError)
# # API 限流
# (RateLimitError, )
# # 所有异常(谨慎使用)
# (Exception, )

# 2. with_fallbacks() 参数
# primary_model.with_fallbacks(
#     fallbacks=[model1, model2],  # 备用模型列表(按顺序尝试)
#     exceptions_to_handle=(Exception,)  # 触发降级的异常
# )
# 工作流程:
# 尝试 primary_model → 失败
#     ↓
# 尝试 model1 → 失败
#     ↓
# 尝试 model2 → 成功 ✓


# 3. Pydantic Field 约束
# 约束    用途    示例
# gt / ge    数值 > / >=    Field(gt=0)
# lt / le    数值 < / <=    Field(le=100)
# min_length / max_length    字符串长度    Field(min_length=2)
# pattern    正则表达式    Field(pattern=r'^\d{11}$')
# class Product(BaseModel):
#     name: str = Field(min_length=2, max_length=50)
#     price: float = Field(gt=0, description="价格必须 > 0")
#     stock: int = Field(ge=0, description="库存 >= 0")


# 实际应用
# 1. LLM 输出验证 + 重试循环
# 当 LLM 输出不符合验证规则时,重新提示:


def demo4():

    from pydantic import ValidationError

    class Product(BaseModel):
        name: str = Field(min_length=2)
        price: float = Field(gt=0)

    structured_llm = chat_model.with_structured_output(Product)

    max_retries = 3
    text = "产品价格是 -100 元"  # 负价格会触发验证失败

    for attempt in range(1, max_retries + 1):
        try:
            result = structured_llm.invoke(f"提取产品信息:{text}")
            # 验证通过
            break
        except ValidationError as e:
            error_msg = e.errors()[0]["msg"]
            # 在提示中加入错误信息
            text = f"{text}\n注意: {error_msg}。请确保价格 > 0"
            if attempt == max_retries:
                raise  # 重试次数用完


# 第 1 次尝试:
#   LLM 输出: price = -100
#   验证: ✗ 失败 (price 必须 > 0)

# 第 2 次尝试(修正提示):
#   提示: "...注意: price 必须 > 0"
#   LLM 输出: price = 100
#   验证: ✓ 通过
添加新评论