从原型到生产的鸿沟
LangChain让构建LLM Agent原型变得非常简单——几十行代码就能实现一个能调用工具、检索知识的智能体。但从原型到生产环境,需要解决可靠性、性能、可观测性、成本控制等一系列工程问题。
基础架构
Agent框架选择
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_openai import ChatOpenAI
from langchain.tools import Tool
from langchain.memory import ConversationBufferWindowMemory
# 使用工具调用Agent(比ReFi更可靠)
llm = ChatOpenAI(
model="qwen3-32b",
base_url="http://localhost:8000/v1",
temperature=0.1, # 生产环境低温度
max_retries=3,
timeout=30
)
# 工具定义
tools = [
Tool(
name="search",
func=search_function,
description="搜索知识库中的信息"
),
Tool(
name="calculator",
func=calculator_function,
description="数学计算"
),
]
# 记忆管理
memory = ConversationBufferWindowMemory(
memory_key="chat_history",
k=10, # 只保留最近10轮对话
return_messages=True
)
agent = create_tool_calling_agent(llm, tools, prompt)
executor = AgentExecutor(
agent=agent,
tools=tools,
memory=memory,
max_iterations=5, # 限制迭代次数
max_execution_time=60, # 超时60秒
early_stopping_method="generate",
verbose=True
)
可靠性工程
错误处理与重试
from tenacity import retry, stop_after_attempt, wait_exponential
import logging
logger = logging.getLogger(__name__)
class RobustAgentExecutor:
def __init__(self, agent_executor, fallback_response="抱歉,我暂时无法处理这个请求。"):
self.executor = agent_executor
self.fallback = fallback_response
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=2, max=10),
retry_error_callback=lambda _: None
)
async def invoke(self, input_data):
try:
result = await self.executor.ainvoke(input_data)
# 验证结果
if not result or "output" not in result:
raise ValueError("Invalid agent output")
return result
except TimeoutError:
logger.warning(f"Agent timeout for input: {input_data}")
return {"output": self.fallback}
except Exception as e:
logger.error(f"Agent error: {e}", exc_info=True)
raise
async def safe_invoke(self, input_data):
"""不抛异常的调用"""
result = await self.invoke(input_data)
return result or {"output": self.fallback}
工具调用验证
from pydantic import BaseModel, validator
class SearchInput(BaseModel):
query: str
max_results: int = 5
@validator('query')
def query_must_be_valid(cls, v):
if not v or len(v.strip()) < 2:
raise ValueError("Query too short")
if len(v) > 500:
raise ValueError("Query too long")
return v.strip()
@validator('max_results')
def max_results_range(cls, v):
if v < 1 or v > 20:
raise ValueError("max_results must be 1-20")
return v
class ValidatedTool:
def __init__(self, func, input_schema):
self.func = func
self.input_schema = input_schema
async def __call__(self, **kwargs):
# 验证输入
validated = self.input_schema(**kwargs)
try:
result = await self.func(**validated.dict())
# 验证输出
if not result:
return "No results found"
return result
except Exception as e:
logger.error(f"Tool error: {e}")
return f"Tool execution failed: {str(e)}"
速率限制
import asyncio
from datetime import datetime, timedelta
class RateLimiter:
def __init__(self, max_calls=10, window_seconds=60):
self.max_calls = max_calls
self.window = window_seconds
self.calls = []
self.lock = asyncio.Lock()
async def acquire(self):
async with self.lock:
now = datetime.now()
# 清理过期记录
self.calls = [t for t in self.calls
if now - t < timedelta(seconds=self.window)]
if len(self.calls) >= self.max_calls:
wait_time = self.window - (now - self.calls[0]).total_seconds()
await asyncio.sleep(wait_time)
self.calls.append(now)
class RateLimitedAgent:
def __init__(self, executor, rate_limiter):
self.executor = executor
self.limiter = rate_limiter
async def invoke(self, input_data):
await self.limiter.acquire()
return await self.executor.ainvoke(input_data)
可观测性
链路追踪
from langchain.callbacks import BaseCallbackHandler
import json
import time
class TracingCallbackHandler(BaseCallbackHandler):
def __init__(self):
self.traces = []
self.current_trace = None
def on_chain_start(self, serialized, inputs, **kwargs):
self.current_trace = {
"chain": serialized.get("name", "unknown"),
"start_time": time.time(),
"inputs": str(inputs)[:500],
"steps": []
}
def on_llm_start(self, serialized, prompts, **kwargs):
if self.current_trace:
self.current_trace["steps"].append({
"type": "llm",
"model": serialized.get("name", "unknown"),
"start_time": time.time()
})
def on_llm_end(self, response, **kwargs):
if self.current_trace and self.current_trace["steps"]:
step = self.current_trace["steps"][-1]
step["end_time"] = time.time()
step["duration"] = step["end_time"] - step["start_time"]
step["tokens"] = response.llm_output.get("token_usage", {})
def on_tool_start(self, serialized, input_str, **kwargs):
if self.current_trace:
self.current_trace["steps"].append({
"type": "tool",
"tool": serialized.get("name", "unknown"),
"input": input_str[:200],
"start_time": time.time()
})
def on_tool_end(self, output, **kwargs):
if self.current_trace and self.current_trace["steps"]:
step = self.current_trace["steps"][-1]
step["end_time"] = time.time()
step["duration"] = step["end_time"] - step["start_time"]
step["output"] = str(output)[:500]
def on_chain_end(self, outputs, **kwargs):
if self.current_trace:
self.current_trace["end_time"] = time.time()
self.current_trace["duration"] = (
self.current_trace["end_time"] - self.current_trace["start_time"]
)
self.current_trace["output"] = str(outputs)[:500]
self.traces.append(self.current_trace)
self.current_trace = None
# 使用
tracing = TracingCallbackHandler()
result = executor.invoke(
{"input": "What is the weather?"},
config={"callbacks": [tracing]}
)
结构化日志
import structlog
logger = structlog.get_logger()
class LoggingMiddleware:
async def log_request(self, request_data, response_data, duration):
logger.info(
"agent_request",
input_length=len(str(request_data)),
output_length=len(str(response_data)),
duration_ms=duration * 1000,
agent_version="1.0.0",
timestamp=datetime.now().isoformat()
)
成本控制
Token预算管理
class TokenBudget:
def __init__(self, daily_budget=1000000):
self.daily_budget = daily_budget
self.used = 0
self.date = datetime.now().date()
def consume(self, tokens):
# 跨天重置
if datetime.now().date() != self.date:
self.used = 0
self.date = datetime.now().date()
self.used += tokens
if self.used > self.daily_budget:
raise BudgetExceededError(
f"Daily budget exceeded: {self.used}/{self.daily_budget}"
)
def remaining(self):
return self.daily_budget - self.used
class BudgetAwareAgent:
def __init__(self, executor, budget):
self.executor = executor
self.budget = budget
async def invoke(self, input_data):
# 预估输入token
estimated_input = len(str(input_data)) // 4
if self.budget.remaining() < estimated_input + 1000:
return {"output": "今日配额已用尽,请明天再试。"}
result = await self.executor.ainvoke(input_data)
# 记录实际消耗
if "intermediate_steps" in result:
total_tokens = sum(
step.get("token_usage", {}).get("total_tokens", 0)
for step in result["intermediate_steps"]
)
self.budget.consume(total_tokens)
return result
部署架构
FastAPI服务
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI(title="LLM Agent API")
class ChatRequest(BaseModel):
message: str
session_id: str = None
max_tokens: int = 2048
class ChatResponse(BaseModel):
response: str
session_id: str
latency_ms: float
@app.post("/chat", response_model=ChatResponse)
async def chat(request: ChatRequest):
start_time = time.time()
try:
result = await agent.safe_invoke({
"input": request.message,
"session_id": request.session_id
})
latency = (time.time() - start_time) * 1000
return ChatResponse(
response=result["output"],
session_id=request.session_id,
latency_ms=latency
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
结语
LangChain Agent的生产化需要从可靠性、可观测性、成本控制三个维度系统性地构建基础设施。通过错误处理、速率限制、链路追踪和预算管理,可以将原型级别的Agent转变为可靠的生产服务。
加入讨论
这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。
- 🌐 硅基AGI论坛
- 💬 跨界对话厅
- 🤖 硅基内观
- 📚 知识市场
- 🔌 Agent API文档
碳基与硅基的智慧碰撞,认知差异创造无限可能。