5个让LangChain调用更稳的小技巧

5个让LangChain调用更稳的小技巧:自动重试、限流、结构化输出、缓存、错误隔离


用LangChain调大模型API时,有一些小坑会让你线上服务不稳定——API超时、token耗尽、JSON解析失败、并发超限。这里分享5个我在生产环境里用到的技巧,代码可以直接拿去用。


技巧1:API调用自动重试 + 指数退避


大模型API调用不稳定是常态。OpenAI/DeepSeek经常偶发502或限流错误。加自动重试是最基本的防护。


from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
import openai
from openai import APIError, RateLimitError, Timeout

@retry(
    stop=stop_after_attempt(3),
    wait=wait_exponential(multiplier=1, min=2, max=10),
    retry=retry_if_exception_type((APIError, RateLimitError, Timeout)),
    before_sleep=lambda retry_state: print(
        f"重试第{retry_state.attempt_number}次,等待{retry_state.next_action.sleep}秒"
    )
)
def safe_chat_completion(messages, model="deepseek-chat"):
    client = openai.OpenAI(
        api_key="your-api-key",
        base_url="https://api.deepseek.com/v1",
        timeout=30
    )
    response = client.chat.completions.create(
        model=model,
        messages=messages,
        temperature=0.7
    )
    return response.choices[0].message.content

3次重试 + 指数退避(2秒→4秒→8秒)+ 只重试特定异常类型。这样普通网络波动不会炸,但真正挂了也不会死循环。


技巧2:结构性输出 + 异常兜底


让LLM返回JSON是很常见的需求,但它偶尔会返回残缺的JSON。加一个兜底逻辑:


import json
import re

def safe_parse_json(llm_response: str) -> dict:
    """安全解析LLM返回的JSON,包含兜底逻辑"""
    # 尝试直接解析
    try:
        return json.loads(llm_response)
    except json.JSONDecodeError:
        pass
    
    # 尝试从markdown代码块中提取
    code_block = re.search(r'```(?:json)?\s*\n(.*?)\n```', llm_response, re.DOTALL)
    if code_block:
        try:
            return json.loads(code_block.group(1))
        except json.JSONDecodeError:
            pass
    
    # 尝试从花括号提取
    brace_content = re.search(r'\{.*\}', llm_response, re.DOTALL)
    if brace_content:
        try:
            return json.loads(brace_content.group())
        except json.JSONDecodeError:
            pass
    
    # 全部失败,返回默认值
    print(f"⚠️ JSON解析失败,返回默认值。原始输出: {llm_response[:200]}")
    return {"error": "parse_failed", "raw": llm_response[:500]}

这个函数有4层回退:直接解析→markdown代码块→花括号内容→默认值。线上跑了几个月,99%以上的非标准JSON输出都能兜住。


技巧3:多模型降级


主模型挂了自动切备用模型:


from functools import wraps

def model_fallback(func):
    """装饰器:主模型失败时自动降级到备用模型"""
    @wraps(func)
    def wrapper(*args, **kwargs):
        primary_model = kwargs.get('model', 'deepseek-chat')
        fallback_models = ['gpt-4o-mini', 'qwen-plus']
        
        for i, model in enumerate([primary_model] + fallback_models):
            try:
                kwargs['model'] = model
                result = func(*args, **kwargs)
                if i > 0:
                    print(f"⚠️ 降级使用备用模型: {model}")
                return result
            except Exception as e:
                if i == len(fallback_models):
                    raise  # 全部失败才抛出
                print(f"❌ 模型{model}失败: {e},尝试降级...")
                continue
    return wrapper

@model_fallback
def chat_with_fallback(messages, model="deepseek-chat"):
    # 你的调用代码
    pass

这个装饰器可以加在任何调用函数上。主模型挂了自动尝试备选模型,整个过程对调用方透明。


技巧4:请求级缓存


同样的Prompt重复请求大模型,浪费钱:


import hashlib
import json
from functools import lru_cache

def request_cache(ttl_seconds: int = 3600):
    """LRU缓存装饰器,带TTL"""
    cache = {}
    
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            key = hashlib.md5(
                json.dumps({"args": str(args), "kwargs": str(kwargs)}, 
                          sort_keys=True, default=str).encode()
            ).hexdigest()
            
            if key in cache:
                cached_time, cached_result = cache[key]
                if time.time() - cached_time < ttl_seconds:
                    return cached_result
            
            result = func(*args, **kwargs)
            cache[key] = (time.time(), result)
            return result
        return wrapper
    return decorator

@request_cache(ttl_seconds=300)
def classify_intent(user_message: str) -> str:
    """意图分类(相同的输入5分钟内只调一次API)"""
    # 调用LLM做分类
    pass

意图分类这种场景特别适合缓存——同样的问题问10遍,只有第1次真正调了API。


技巧5:请求级超时控制和并发限流


LangChain默认没有超时控制,一个卡住的请求可能拖垮整个服务:


import asyncio
import time

class APIRateLimiter:
    """简单的漏桶算法限流器"""
    def __init__(self, max_per_minute=30):
        self.max_per_minute = max_per_minute
        self.interval = 60.0 / max_per_minute
        self.last_call = 0
    
    def wait_if_needed(self):
        elapsed = time.time() - self.last_call
        if elapsed < self.interval:
            sleep_time = self.interval - elapsed
            time.sleep(sleep_time)
        self.last_call = time.time()

# 使用
limiter = APIRateLimiter(max_per_minute=30)

def rate_limited_call(messages):
    limiter.wait_if_needed()
    return client.chat.completions.create(
        model="deepseek-chat",
        messages=messages,
        timeout=25  # 单次请求超时25秒
    )

每分钟限制30次调用,每次请求超时25秒。这个组合在你同时跑多个Agent任务时特别有用。


---


以上5个技巧可以单独用也可以组合用。我目前在线上服务的核心流程是:限流 → 超时控制 → 自动重试 → 降级 → JSON兜底,跑了大半年没出过大的稳定性问题。


如果你的生产环境也遇到类似问题,可以直接把代码拿去用。