4. 推理接口调用:API服务搭建与调用实战
推理接口,说白了就是让大模型“开口说话”的通道。我见过太多团队模型训得不错,结果API一压就崩。今天咱们就把这块硬骨头啃下来。
4.1 选Flask还是FastAPI?我的建议
我个人习惯用FastAPI。为什么?因为它原生支持异步,对大模型的流式输出特别友好。Flask当然也能做,但需要额外配一堆东西。
来看个对比:
| 特性 | Flask | FastAPI |
|---|---|---|
| 异步支持 | 需额外插件 | 原生支持 |
| 流式输出 | 较复杂 | 简单优雅 |
| 性能 | 一般 | 高并发优秀 |
| 学习成本 | 低 | 中等 |
嗯,这里要注意:如果你们团队对Flask特别熟,也不是不能用。但我建议新项目直接上FastAPI,省得后面重构。
4.2 请求与响应格式设计
接口设计这块,我踩过不少坑。最典型的就是请求体太复杂,前端同学骂娘。
我推荐的标准格式:
{
"model": "law-llm-v1",
"messages": [
{"role": "user", "content": "请分析这个合同条款"}
],
"temperature": 0.3,
"max_tokens": 2048,
"stream": true
}
响应格式:
{
"id": "chatcmpl-xxx",
"object": "chat.completion",
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": "根据《合同法》第XX条..."
},
"finish_reason": "stop"
}],
"usage": {
"prompt_tokens": 45,
"completion_tokens": 120,
"total_tokens": 165
}
}
避坑指南: 我曾经把usage字段放在最前面,结果前端解析时总报错。后来才明白——流式输出时,usage应该在最后一条消息里返回。
4.3 流式输出实现
大模型回答慢,用户等不了。流式输出就是解决方案——一个字一个字往外吐,用户体验好很多。
FastAPI实现流式输出,其实就几行代码:
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import asyncio
app = FastAPI()
async def generate_stream(prompt):
# 模拟大模型逐字输出
response = "根据《民法典》第XXX条规定..."
for char in response:
yield f"data: {char}\n\n"
await asyncio.sleep(0.05)
yield "data: [DONE]\n\n"
@app.post("/v1/chat/completions")
async def chat_completion(request: dict):
prompt = request.get("messages", [])[-1]["content"]
return StreamingResponse(
generate_stream(prompt),
media_type="text/event-stream"
)
你想想看,如果不用流式,用户得等十几秒才能看到第一个字。用了流式,200毫秒就开始出字了。这差距,用户感知非常明显。
注意: 流式输出时,一定要设置正确的Content-Type。我见过有人设成application/json,结果前端SSE解析不了,折腾了半天。
4.4 超时与重试机制
大模型推理不稳定,这是常态。我遇到过模型推理到一半卡住,也遇到过GPU显存不够直接OOM。所以超时和重试必须配好。
我的配置方案:
import httpx
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(
stop=stop_after_attempt(3), # 最多重试3次
wait=wait_exponential(multiplier=1, min=2, max=10) # 指数退避
)
async def call_llm(prompt: str):
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.post(
"http://localhost:8000/v1/chat/completions",
json={"messages": [{"role": "user", "content": prompt}]},
timeout=httpx.Timeout(30.0, read=60.0) # 连接超时30秒,读取超时60秒
)
return response.json()
这里有几个关键点:
- 连接超时:一般设10-30秒,太久会拖垮整个服务
- 读取超时:流式输出建议设60-120秒,因为大模型生成慢
- 重试策略:用指数退避,别一失败就猛冲
我的经验: 重试次数别超过3次。超过3次还失败,说明模型或服务本身有问题,重试也没用。这时候应该返回503,让上游降级处理。
4.5 完整架构图
下面这张图,是我在实际项目中总结出来的推理接口调用流程:
这张图里,我特别想强调两点:
- 流式路径:不走常规HTTP响应,而是走SSE通道。这样用户能实时看到模型在“思考”
- 错误处理分支:超时后先重试,重试3次还不行就走降级。别让用户干等
4.6 实战避坑清单
最后,分享几个我踩过的坑:
- 连接池耗尽:高并发时,每个请求都新建连接,很快就把端口耗光了。一定要用连接池复用。
- 流式输出不flush:有些框架默认会缓冲输出,导致流式变成“块式”。记得每次yield后flush一下。
- 超时时间设太短:法律大模型要分析长文本,生成时间可能超过30秒。我一般设60秒起步。
- 重试导致重复:如果模型已经生成了部分内容,重试时要不要重新生成?我建议幂等设计,每次重试都重新开始。
小技巧: 可以在响应头里加个X-Request-Id,方便排查问题。我每次排查线上故障,第一件事就是看这个ID。
好了,推理接口这块就聊到这儿。API服务搭建不难,难的是把各种边界情况都处理好。记住一句话:用户感知到的稳定性,取决于系统最薄弱的那个环节。
公众号:蓝海资料掘金营,微信deep3321