应用入口与启动校验
Written: 2026.06第 13 章跟敲代码:上一讲:FastAPI 与异步 Web 框架codealong/chapters/ch13_preflight_checks。 这部分代码是本章跟敲版,用来先跑通核心闭环;完整项目源码仍以本讲后文标注的qa_core/、scripts/等路径为准。
下一讲:知识库多版本管理
本讲定位
前 9 讲你一直在用app.py启动项目,但可能没有逐行理解过它。app.py是整个系统的入口——它负责在接收第一个请求之前,验证 LLM 是否可达、Milvus 是否在线、模型文件是否存在、知识库是否有 active 版本。任何一项不满足,进程直接退出,不做”降级启动”。 这个设计选择很关键:它保证了系统不会在运行时出现”咦,怎么某个功能坏了”的诡异问题。本讲拆解这个设计。
1. 本讲目标
- 理解 FastAPI 应用的完整启动流程
- 掌握环境前置校验(Preflight Check)的设计模式
- 理解为什么本项目”不允许降级启动”
- 读懂 app.py 的每一行代码
2. 前置知识 — 软件的”启动校验”模式
2.1 什么是 Preflight Check
Preflight Check(前置校验) 来源于航空术语,指飞机起飞前的地面检查清单。在软件工程中,它指的是在服务正式接受请求之前,验证所有关键依赖是否可用。 类似地,当你开车前会检查油量、轮胎、灯光——你不会开到高速公路上才发现没油了。2.2 为什么 Web 服务需要启动校验
考虑一个没有启动校验的服务:- 健康检查端点可能返回 200 OK
- 但核心业务链路根本没通电
- 等发现问题时已经影响了真实用户
2.3 启动校验 vs 运行时降级
| 方案 | 行为 | 优缺点 |
|---|---|---|
| 启动校验(本项目采用) | 缺依赖→启动失败 | 问题暴露早,但要求环境完整 |
| 运行时降级(常见反模式) | 缺依赖→用到时才报错 | 看起来能启动,但不可靠 |
| 功能开关 | 缺依赖→关闭相关功能 | 适合大规模分布式系统,不适合当前项目 |
3. app.py 逐行详解
3.1 完整代码
3.2 应用实例创建
get_settings() 是一个全局配置单例,返回一个 Settings 对象。它使用 Pydantic 的 BaseSettings,优先读取进程环境变量;本机 API 调试时,再读取项目根目录下的 .env 作为本地配置文件。Docker Compose 模式下,.env.compose 由 Compose 注入到 API 容器的进程环境变量里,Settings 本身不直接读取 .env.compose:
前置知识:如果你不熟悉 Pydantic BaseSettings,请先阅读 附录A:Pydantic 数据校验
3.3 CORS 中间件配置
allow_origins:生产环境应该设置为具体的域名列表,而非["*"]allow_credentials=True:允许前端携带 Cookie/Authorization Header- 本项目前后端同源部署在 8000 端口,CORS 主要是为本地开发场景保留
3.4 静态资源挂载
app.mount() 将一个完整的子应用挂载到路由前缀。这里把 static/ 目录挂载到 /static 路径,所以:
static/index.html→http://127.0.0.1:8000/static/index.htmlstatic/css/base.css→http://127.0.0.1:8000/static/css/base.css
3.5 启动事件
validate_runtime_environment():逐个检查所有前置条件,任何一个不满足就抛RuntimeError,FastAPI 会阻止服务启动。warmup_retrieval_stack():预热全部 8 个场景的 FAQ 和文档 Milvus Collection。这个操作是同步的(涉及模型加载和网络连接),用asyncio.to_thread放到线程池执行。
3.6 路由注册
3.7 启动入口
reload=False 是因为生产环境不需要热重载。本地调试时可以加 --reload 参数。
4. 环境前置校验详解
4.1 校验清单
validate_runtime_environment() 在 qa_core/config/preflight.py 中实现,按以下顺序检查:
与 Lecture 01 (2.3 部署架构) 的流程图对应关系:步骤 1-8 覆盖了 Lecture 01 中展示的各项依赖校验(API Key、模型文件、Milvus/MySQL 可达性)。步骤 9-11 是本项目额外增加的深层校验——MySQL TCP 连通性(区别于 Milvus 的独立检查)、LLM 真实连通性(发送测试请求而非仅检查 Key 格式)、以及 Active 知识库版本存在性(确保上线时的知识库版本已就绪)。
4.2 占位符检测
.env.compose 注入到容器里的值,本机 API 调试模式检查 .env 中的值。如果 API Key 还是 replace-with-real-key,系统会直接拒绝启动并给出明确的错误信息。
4.3 TCP 连接校验
telnet host port),不进行业务读写。这是最快速的检查方式:
- 如果 Milvus 没启动,不用等到查询时才报错
- 如果 MySQL 没启动,不用等到写入历史时才报错
4.4 路径校验
models/bge-m3、models/bge-reranker-large)、场景文档目录、FAQ CSV 文件等本地资源。
4.5 Milvus URI 校验
4.6 LLM 连通性验证
4.7 Active 知识库版本校验
4.8 校验结果
校验通过后返回一个摘要字典,既有场景信息、也有环境配置信息:5. 检索栈预热
5.1 为什么需要预热
BGE-M3 Embedding 模型和 Milvus 的连接初始化都有首次访问延迟:- 模型加载:BGE-M3 模型文件约 2GB,首次加载需要 5-15 秒
- Milvus 连接:首次创建 Collection 对象需要获取 schema 信息
5.2 warmup_retrieval_stack() 实现
5.3 为什么用 asyncio.to_thread 包裹
warmup_retrieval_stack() 内部有:
- 磁盘 I/O(读取模型文件)
- CPU 密集操作(加载模型到内存)
- 网络 I/O(连接 Milvus)
asyncio.to_thread 将这些操作放到线程池中,启动过程不阻塞其他异步任务的执行。
6. 配置管理体系
6.1 配置来源
配置来源的架构解读:这张图展示了项目的两条配置通道,它们在职责上有明确的分工: 左路:运行时环境变量通道(环境变量 / .env → Settings → 各模块)
运行时环境变量存放的是”这个服务怎么跑”的基础设施配置——LLM API Key、Milvus 地址、MySQL 连接串、Admin Token、模型路径。本机 API 调试时,这些值来自 .env;Docker Compose 运行时,这些值来自 .env.compose 注入到容器的环境变量。这些值的特点是:
- 全局唯一:不管切换到哪个业务场景,Milvus 地址和 LLM Key 都不会变
- 启动即加载:通过 Pydantic BaseSettings 在应用启动时一次性读取并校验类型(端口号必须是 int、API Key 不能是占位符)
- 全局单例访问:任何模块需要基础设施配置时,调用
get_settings()就能拿到同一个 Settings 实例,避免多处解析环境变量导致不一致
scenario.toml → ScenarioRegistry → QAService)
scenarios/*/scenario.toml 存放的是”这个场景的业务是什么”的领域配置——scenario_id、valid_sources、FAQ collection 名称、source_patterns。这些值的特点是:
- 按场景变化:
enterprise_knowledge的 sources 是["hr_process", "it_policy"],compliance_qa的 sources 是["privacy", "audit", "contract"] - 启动时扫描:ScenarioRegistry 在启动时遍历
scenarios/目录,把所有scenario.toml解析成ScenarioDefinition(frozen dataclass,创建后不可变) - 每次请求时解析:QAService 根据用户请求中的
scenario_id,从 Registry 中取出对应的 ScenarioDefinition,注入到后续的检索过滤和 Prompt 选择中
scenario.toml 两条通道,后者由 preflight check 在启动时强制执行——详见本讲第四部分。
6.2 运行时环境变量中的必需配置项
运行时环境变量有两个模板,不再提供通用.env.example:
| 运行模式 | 模板 | 实际配置 | 地址视角 |
|---|---|---|---|
| Docker Compose | .env.compose.example | .env.compose | API 在容器内,使用 mysql、milvus、/app/models/... |
| 本机 API 调试 | .env.local.example | .env | API 在宿主机,使用 localhost、models/... |
| 配置项 | 用途 | 缺失后果 |
|---|---|---|
DASHSCOPE_API_KEY | LLM API Key | 启动失败 |
ADMIN_API_TOKEN | 管理接口认证令牌 | 启动失败 |
MILVUS_URI | Milvus 连接地址 | 启动失败 |
MYSQL_HOST / MYSQL_PORT | MySQL 连接 | 启动失败 |
EMBEDDING_MODEL_PATH | BGE-M3 模型路径 | 启动失败 |
RERANKER_MODEL_PATH | Reranker 模型路径 | 启动失败 |
ACTIVE_KB_VERSION | 默认知识库版本 | 启动失败(如版本清单也无 active) |
ACTIVE_SCENARIO_ID | 默认业务场景 | 可选,缺失用第一个场景 |
6.3 当前配置边界
当前版本的配置边界非常明确:所有基础设施配置只从运行时环境变量读取,所有业务场景配置只从scenario.toml 读取。
为什么坚持两条配置通道?
- 环境变量是云原生部署的标准做法
- TOML 更适合表达场景包中的结构化配置,例如 source 列表、collection 名称和文档匹配规则
- 配置来源固定以后,排查问题时只需要检查两处,不会被额外配置入口分散注意力
7. 本讲实践闭环
| 项目 | 内容 |
|---|---|
| 本讲类型 | 系统集成 |
| 实践产物 | app.py 生命周期、preflight check、检索栈 warmup |
| 是否进入最终项目 | 是 |
| 验收方式 | 故意缺关键配置时启动失败,配置完整时启动成功 |
| 后续落点 | 第 19 讲生产启动和事故排查 |
7.1 本讲从 0 到 1 实现闭环
这一讲要建立“服务不能带病启动”的工程习惯。实现顺序如下:- 先用
Settings统一读取运行时环境变量,所有基础设施配置从这里进入系统。 - 再写
validate_runtime_environment(),逐项检查 LLM、Milvus、MySQL、模型目录、场景和 active 版本。 - 然后在
app.py的生命周期里执行 preflight,失败就让服务启动失败。 - 最后执行检索栈 warmup,提前加载模型和检查 Milvus collection schema。
qa_core/config/settings.py。
qa_core/config/preflight.py::validate_runtime_environment()。
app.py 里。
来源:真实代码调用点,见 app.py。
qa_core/retrieval/factory.py::warmup_retrieval_stack()。
| 验证项 | 验证方式 | 期望结果 |
|---|---|---|
| 缺 API Key | 使用占位配置启动 | 启动失败并提示修复 |
| 模型路径缺失 | 改错模型目录 | 启动失败 |
| Milvus/MySQL 不通 | 停止依赖服务 | preflight 拒绝启动 |
| active 版本缺失 | 未初始化知识库 | 明确提示先重建知识库 |
| schema 不兼容 | 旧 collection | warmup 阶段暴露错误 |
| 场景配置缺失 | 删除场景目录或 FAQ | 启动失败并指出缺失路径 |
| LLM 不可用 | Key 错误或网络失败 | 启动阶段失败,不等用户请求 |
8. 重点掌握
| 优先级 | 内容 | 原因 |
|---|---|---|
| ★★★ 必会 | Preflight Check 的概念:服务启动时校验所有核心依赖,缺失即失败 | 防止”假启动”(页面能打开但核心链路不通),面试常问 |
| ★★★ 必会 | validate_runtime_environment() 的 11 项校验清单(API Key → 模型目录 → TCP 连通性 → LLM 连通性 → Active 版本) | 启动校验链的具体实现 |
| ★★★ 必会 | app.py 极薄入口:只做创建应用、CORS、静态资源、预热、路由注册五件事 | 入口文件的设计哲学 |
| ★★ 理解 | 配置管理体系:运行时环境变量(基础设施配置,全局唯一) vs scenario.toml(业务配置,按场景变化) | 理解两条配置通道的职责分工 |
| ★★ 理解 | warmup_retrieval_stack() 预热模型和 Milvus 连接,避免冷启动 | 用户体感优化,面试常见问题 |
| ★★ 理解 | asyncio.to_thread 将阻塞操作放到线程池 | 异步编程中的关键模式 |
| ★ 了解 | _is_placeholder() 占位符检测 | 防呆设计 |
| ★ 了解 | 配置边界:基础设施配置来自运行时环境变量,业务配置来自 scenario.toml | 了解设计原则即可 |
9. 本讲小结
- Preflight Check 在服务启动时验证所有关键依赖,缺失即失败,避免”假启动”
- app.py 保持极薄:创建应用 + CORS + 静态资源 + 启动预热 + 路由注册,总共不到 80 行
- 启动校验链 覆盖 LLM Key、模型目录、Milvus/MySQL 连通性、场景配置和知识库版本
- 检索栈预热 在启动时加载模型和建立连接,避免首个用户承受冷启动延迟
- 配置来源统一为运行时环境变量 +
scenario.toml,环境配置和业务场景配置各自负责一类问题

