本文来自《AI 应用开发课程》月份 1 课程文档,已整理为网站文章版本。
学习目标
学完本节后,你应当能够:
- 使用 FastAPI 提供最小流式响应。
- 为
/health和一个核心接口编写最小测试。 - 理解“服务已启动”不等于“服务可靠”。
前置知识
- 已完成
/chat和/tools/run
1. 为什么这里要补流式响应
因为月份 1 前面你已经学过模型流式输出,这一节要把它变成服务能力。
月份 1 不要求你实现复杂 SSE 协议,但至少要理解:
- 后端不是只能一次性返回完整文本
- 流式能力会直接影响用户体验
2. 最小流式思路
FastAPI 中常见做法是使用 StreamingResponse。
伪代码思路:
from fastapi.responses import StreamingResponse
async def stream_generator():
async for chunk in llm_client.stream(messages):
yield chunk
@router.post("/chat/stream")
async def chat_stream(request: ChatRequest):
return StreamingResponse(stream_generator(), media_type="text/plain")
月份 1 的重点不是协议细节,而是理解“路由 -> service -> stream 生成器”的分层。
3. 最小接口测试
/health
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_health_should_return_ok() -> None:
response = client.get("/health")
assert response.status_code == 200
assert response.json() == {"status": "ok"}
核心接口
对于 /chat,月份 1 建议先 mock 或替换底层 service,而不是每次都打真实模型。
4. 为什么测试不一定直接打真实模型
因为真实模型调用:
- 有成本
- 有随机性
- 依赖外部网络
学习阶段先确保:
- 路由行为正确
- 模型输出经过封装后的接口结构正确
5. 实操任务
- 为
/health写测试 - 为
/chat或/tools/run写一个最小测试 - 如果实现了流式响应,至少手动验证一次
6. 自测题
- 为什么服务接口测试和模型能力测试不是一回事?
- 为什么流式响应要单独设计路径和处理方式?
- 为什么
/health是每个服务型项目的基础接口?
7. 作业与验收
验收标准:
/health有自动化测试/chat或/tools/run至少一个有自动化测试- 流式响应至少完成手动验证
8. 常见错误
- 测试直接依赖真实网络,导致结果不稳定
- 流式接口写出来但从未实际验证
- 只测 happy path,不测异常路径
9. 本章与前文关系
上一章让你完成了:
/chat/tools/run
也就是核心业务链路已经通了。现在要补两件会显著提高项目质量的能力:
- 流式响应
- 接口测试
这两者分别对应“更好的交互体验”和“更稳定的回归验证”。
10. 本章在研发助手项目中的位置
在第 4 周综合项目里:
- 流式响应会让 Demo 体验明显更好
- 接口测试会让你更敢于重构 service 层
所以本章不是“额外补充”,而是在为最终项目打展示质量和稳定性基础。
11. 流式响应的数据流应该怎样理解
你可以把它拆成这条链路:
flowchart LR
A["用户请求 /chat/stream"] --> B["FastAPI 路由"]
B --> C["ChatService / LLMClient.stream"]
C --> D["模型逐块返回 chunk"]
D --> E["StreamingResponse 逐块转发"]
E --> F["客户端持续接收"]
只要你把这条链路理解清楚,流式响应就不会显得神秘。
12. 为什么接口测试不优先打真实模型
因为接口测试的目标是验证:
- 路由是否挂对
- schema 是否匹配
- 基础状态码是否正确
- service 是否被正确调用
而不是验证模型“今天心情如何”。如果一开始就强依赖真实模型,你会同时引入:
- 网络因素
- 成本因素
- 文本随机性
这对月份 1 学习并不划算。
13. 错误示例 vs 正确示例
错误示例:只写接口,不验证
这种做法最大的问题是:你以为它能跑,但很多问题直到综合项目时才集中爆炸。
正确示例:最少也给 /health 和一个核心接口留测试
这能让你对服务壳子和最重要主链路有基本信心。
14. 一个更完整的流式接口示例
from fastapi.responses import StreamingResponse
@router.post("/chat/stream")
async def chat_stream(request: ChatRequest) -> StreamingResponse:
raw_messages = [message.model_dump() for message in request.messages]
async def event_generator():
async for chunk in llm_client.stream(raw_messages, request.temperature):
yield chunk
return StreamingResponse(event_generator(), media_type="text/plain")
这段代码背后的关键点有三个:
event_generator是连接 LLM client 和 HTTP 响应的桥- 路由层不直接手写模型请求细节
StreamingResponse负责把生成器变成可持续输出的响应
15. 完整文件级示例:tests/test_api.py
"""月份 1 API 最小测试示例。"""
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_health_should_return_ok() -> None:
response = client.get("/health")
assert response.status_code == 200
assert response.json() == {"status": "ok"}
def test_docs_should_be_available() -> None:
response = client.get("/docs")
assert response.status_code == 200
16. 逐段解释这份完整示例
为什么先测 /health
因为它是最稳定、最基础、最不依赖外部环境的接口。
为什么测 /docs
因为它不仅能证明 FastAPI 服务正常,也能证明自动文档链路还在。
为什么这里没有直接测真实 /chat
因为月份 1 当前阶段,更重要的是先把“服务层结构”和“最小接口回归验证”打稳。
17. 一个更接近工程的增强测试方向
后续你可以:
- 用依赖替身替换真实
LLMClient - 对
/chat构造一个固定返回的假 client - 测试响应 schema 是否按预期返回
这样做的好处是:你能测试接口行为,而不依赖真实模型输出。
18. 调试与排错:本章最常见问题
问题一:流式接口看似返回了,但客户端没有持续收到内容
说明你要检查:
- 生成器是否真的 yield
llm_client.stream是否真的返回 chunk- 客户端是否按流式方式消费
问题二:测试导入应用失败
这往往暴露的是项目结构或模块路径问题,而不是 FastAPI 本身问题。
问题三:接口测试过于依赖真实模型
这会导致测试不稳定、运行慢、成本高。
19. 本章完成后你应该具备的能力
完成本章后,你应当做到:
- 能解释模型流式结果如何变成 HTTP 流式响应。
- 能为服务写出最小接口测试。
- 能区分接口测试和模型能力测试。
- 能为后续综合项目保留一条更稳的服务化基础。
20. 如果你卡在这里,先回看哪几章
- 流式输出逻辑不稳:回看 04-流式输出与成本统计.md
/chat服务边界不稳:回看 03-chat与tools接口实现.md
21. 从本章过渡到下一章的桥接说明
接下来进入 06-LangChain核心抽象入门 模块。
到这里,你已经把“纯 API + Tool Calling + FastAPI 服务化”全部走通。现在再引入 LangChain,时机才是对的,因为你已经知道框架要抽象掉的到底是什么,而不会把它当黑盒。