W1-Summary-notes
docker-compose和docker-compose.yml这两个东西一个是“软件工具”一个是“配置文件”。它们的关系就像 “播放器” 和 “视频文件”。docker-compose工具/命令它是什么它原本是一个独立的命令行工具现在新版 Docker已经内置到了 Docker 里变成了 docker compose 命令。它的作用它是一个“包工头”。当你在终端敲下 docker compose up 时这个包工头就会去读取配置文件然后按照图纸把一个个容器建好、启动好、连好网线。有没有它以前你需要手动敲 docker run postgres…、docker run redis… 一个个启动还要手动建网络。有了这个包工头一键就能拉起一堆服务。docker-compose.yml配置文件/图纸它是什么它是一个后缀为 .yml 的文本文件YAML 格式就是你项目 infra/ 目录下那个文件。它的作用它是写给包工头看的“施工图纸”。里面清清楚楚地写明了要拉取哪些镜像PostgreSQL, Redis, Silo容器的名字叫什么端口怎么映射比如把容器的 5432 映射到电脑的 5432数据存在哪里挂载数据卷环境变量密码是多少为什么用 YAML 格式因为 YAML 靠空格缩进来表示层级阅读起来非常像人类语言比写长长的命令行参数直观多了。 总结一下整个流程结合 make up在 infra/docker-compose.yml 文件里写好了三个数据库的“施工图纸”。在 Makefile 里写下了一行命令docker compose -f infra/docker-compose.yml up -d。在终端敲下 make up。系统调用 docker-compose 这个工具去找 infra 目录下的 .yml 文件。工具看了一眼图纸说“懂了”然后自动帮你下载镜像、配置网络、启动容器。一句话区别docker-compose.yml 是写的代码静态的。docker-compose或 docker compose是运行这段代码的程序动态的。MakefileMakefile 就是你的“专属快捷键面板”或“总控开关”。它是干嘛的Makefile 是程序员为了解决“命令太长、记不住、不想每次都敲”而发明的一个工具。你可以把你经常用的、长串的命令起一个简短的别名比如叫 up、dev写进 Makefile 里。以后你只需要在终端敲 make up它就会自动帮你执行那一长串命令。为什么不能直接敲长命令假设没有 Makefile你每次想启动底层数据库得敲dockercompose-finfra/docker-compose.yml up-d如果你想同时启动数据库然后再启动 FastAPI 后端你可能得敲两三次。而且如果团队里有 5 个人有人忘了加 -d有人把文件路径写错了项目就跑不起来。有了 Makefile团队所有人只需要记住 make up 这一个短命令就行了。长什么样其实非常像 Python 的字典它就是一个叫 Makefile 的纯文本文件。你直接打开它里面大概长这样# 这是一个注释写给人看的 up: docker compose -f infra/docker-compose.yml up -d dev: uv run uvicorn apps.api.main:app --reload test: uv run pytest tests/ lint: uv run ruff check . uv run ruff format .它的语法非常死板你要注意两点up:、dev: 这些是指令的名字前面没有空格。下面那行真正执行的命令前面必须是一个 Tab 键制表符绝对不能敲空格 这是 Makefile 最大的坑敲空格就会报错。它是怎么工作的当你在终端敲下 make dev 时系统会在当前文件夹找叫 Makefile 的文件。找到名叫 dev: 的那一块。执行它下面的那行命令也就是启动 Uvicorn 服务。 总结三剑客的关系docker-compose.yml图纸写着要启动哪些容器。docker-compose包工头看图纸干活的工具。Makefile你手里的对讲机你按一下 make up包工头就立刻去拿图纸干活。Uvicorn 服务Uvicorn 就是你用 Python 写的后端程序的“发动机”。它是一个基于 Python 的 ASGI异步服务器网关接口Web 服务器。为什么需要这个“发动机”结合你正在做的 servicepilot 项目你会在 apps/api 里面用 FastAPI 写一堆代码。比如app.post(/v1/chat)defchat():return{reply:你好}你写完了这段代码它只是硬盘上的一个死文件。它自己不会监听网络端口前端发来的请求它也接收不到。这时候Uvicorn 就登场了。它是一个基于 Python 的 ASGI异步服务器网关接口Web 服务器。当你敲下 make dev底层其实是运行了 uvicorn main:appUvicorn 就会做三件事占领端口它会在你的电脑上占用一个端口比如 8000。当门卫一旦有用户在浏览器里访问 http://localhost:8000Uvicorn 就会接收到这个网络请求。传话给 FastAPIUvicorn 把请求转交给你写的 FastAPI 代码去处理FastAPI 处理完给出结果Uvicorn 再把结果打包发回给浏览器。它的核心优势是什么Python 有很多 Web 服务器比如传统的 Gunicorn、uWSGI。为什么现在 AI 圈全都在用 Uvicorn极速异步Async大模型应用的特点是什么是等。等大模型思考可能 2 秒等 TTS 合成可能 1 秒。如果不用异步服务器一个人提问服务器就卡死了别人进不来。Uvicorn 是专门为异步设计的你等大模型的时候它可以去服务其他人。支持 SSE 流式输出Uvicorn 天生完美支持 SSE 和 WebSocket能像挤牙膏一样把大模型吐出来的字实时推给前端这也就是 ChatGPT 打字机效果 的底层基础。在你的 Makefile 里make dev 对应的命令通常是uvicorn apps.api.main:app--reload--port8000apps.api.main告诉它去哪里找你的代码。app是你代码里 app FastAPI() 的那个变量名。–reload开发神器。只要你的代码一保存Uvicorn 自动帮你重启服务你立刻就能看到修改结果。–port 8000指定它在 8000 号门接待客人。SEE透传SSE (Server-Sent Events)最核心的“单行道”流式传输SSE 是一种网络技术。平时我们上网是你点一下网页服务器回一整个网页给你一次性传完。但大模型回复字数多、速度慢如果等整段话生成完再传给你你要干等十几秒。SSE 就是专门用来解决这个问题的服务器Server会建立一条单向的通道像流水一样生成一个字就推给你一个字生成一段话就推给你一段话。透传原封不动地“转交”“透传Pass-through”的意思是 Web 后端FastAPI在这个环节里不做任何“多余加工”。大模型比如 vLLM 或 Qwen吐出什么格式的 SSE 数据流你的后端就原封不动地直接转发给前端。不改变里面的内容也不等它全部收集完再发而是像一根水管一样直接连到前端。WebSocketWebSocket 是一种让浏览器前端和服务器后端建立“永久双向通话”的网络技术。传统的 HTTP像“寄信”平时你上网无论是看网页还是用你刚学的 app.post 发请求底层都是 HTTP 协议。规则客户端你发一封信服务器收到后回一封信然后连接直接断开。缺点服务器不能主动给你发消息。你必须先问它才能答。如果你想知道大模型有没有生成新字你得每隔 1 秒发个请求去问“好了没好了没”这叫轮询极其浪费资源。WebSocket像“打电话”WebSocket 专门为了解决实时通信而生。规则客户端先发个请求“咱们连个专线吧”服务器同意后这条连接永远不断开变成了一条双向高速公路。优势无论是你说话发给服务器还是服务器出字发给你都可以随时随地、同时进行不需要再重新建立连接。这就是所谓的全双工通信。WebSocket VS SEESSEServer-Sent Events单向。就像收音机服务器单向往外播大模型吐字你只能听不能通过它说话。适合纯文本的 ChatGPT 回复。WebSocket双向。就像电话能听也能说。适合语音实时对话、复杂的 Agent 交互。postgres(pgvector)PostgreSQL 是个存表格和文字的关系型数据库pgvector 是 PostgreSQL 的一个开源扩展专门用来做向量相似度搜索不用多装一个数据库你本来就要用 PostgreSQL 存用户信息和聊天记录现在连向量也一起存了不用额外维护 Milvus 这种专门的向量库。混合查询很自然你可以用一句 SQL 同时做“按用户 ID 过滤”和“按向量相似度排序”这是独立向量库很难做到的。够用对于百万级数据量以内的 RAG 应用pgvector 的性能完全足够延迟通常在几十毫秒级别。redisRedis 的全称是 Remote Dictionary Server远程字典服务器。可以把它理解为一个“速度极快、把数据存在内存里”的超强版字典Key-Value 数据库它到底是个啥极其快因为它是把数据放在内存RAM里的而不是硬盘里所以读写速度是微秒级别的比传统硬盘快几万倍。现在全球最快的数据库之一。键值对存储它不像 PostgreSQL 那样有复杂的表和行它就是一个巨大的字典。你存入一个键比如 user:1001:name它返回一个值“张三”。丰富的数据结构除了存普通字符串它还能存列表List、集合Set、哈希表Hash甚至能实现排行榜和消息队列。PostgreSQL 负责“永久保存”Redis 负责“极速响应”短期记忆缓存大模型的上下文用户和 Agent 聊天时Agent 需要记住用户刚才说了什么。如果每次对话都去查 PostgreSQL速度慢而且数据库容易扛不住。把最近几轮的聊天记录放在 Redis 里Agent 每次思考时直接从内存里“秒拿”上下文对话极其丝滑。会话状态与分布式锁假设你正在用语音跟 AI 聊天系统正在“听”你的声音。如果别人也接入了系统怎么区分你俩通过 Redis 存一个临时的 Session ID。谁的请求来了先对一下 Redis 里的通行证。消息队列任务排队用户上传了一份 100 页的 PDF 让你做 RAG切分、向量化。这个过程很慢不能把前端卡死。后端会把任务扔进 Redis 的队列里后台的 Worker 像流水线工人一样一件件拿出来慢慢处理。它的“致命缺陷”与解决办法因为数据在内存里一旦你拔掉电源或者重启电脑内存里的数据就全没了这叫易失性。但是我们刚才敲的 make up 里Redis 容器挂载了数据卷Volume并且 Redis 本身支持持久化RDB 快照和 AOF 日志。它会定期把内存里的数据“写”到硬盘上。下次重启时它又能从硬盘把数据恢复回内存。一个常见的误区很多人觉得 Redis 只能做缓存。其实它除了做缓存还能做排行榜ZSet、发布订阅Pub/Sub、限流器比如限制用户一分钟只能发 5 句话防止恶意刷接口。总结一句话Redis 就是你 AI 系统里的“超高速临时办公桌”什么高频、急需、不怕丢的临时数据都往它上面放。RDB 快照和 AOF 日志RDBRedis Database—— 快照原理Redis 会定期比如每隔 5 分钟或者修改了 1000 个键后把当前内存里所有的数据打包压缩成一个二进制文件默认叫 dump.rdb保存到硬盘上。优点文件小、恢复速度极快直接把文件读进内存就行非常适合做备份。缺点会丢数据。如果它 5 分钟存一次档你在第 4 分 59 秒写入了重要数据结果电脑断电了那这 5 分钟内的数据就全丢了。AOFAppend Only File—— 日志原理它不存全量数据而是像一个“录音机”。你每执行一条写命令比如 SET name “张三”它就把这条命令原封不动地追加写进一个日志文件里。优点数据最安全。你可以设置“每秒保存一次”或者“每次操作都保存”。这样就算断电最多也就丢 1 秒的数据。重启时Redis 会把这个文件里的命令重新执行一遍数据就回来了。缺点文件会越来越大恢复速度比 RDB 慢因为要一条条重新跑命令。RDB 快照 ≈ MySQL 的物理全量备份 (mysqldump)AOF 日志 ≈ MySQL 的 Binlog二进制日志灾难恢复Recovery的底层逻辑:第一步回放“全量照片”RDB第二步快进“监控录像”AOF但在实际工程配置中很少会真的去手动“跑”,因为运行几年的业务可能AOF日志文件会有几十个G所以 Redis 会在后台偷偷做一件事AOF 重写AOF Rewrite。当 AOF 文件变得太大时Redis 会在后台把这三年的流水账“精简”一下。比如你改名字 100 次它只保留最后一次SET name “张三”。或者当 AOF 特别大时Redis 会直接把当前内存的数据生成一个新的 RDB 快照然后把从这一刻起的操作写进新的 AOF。SILO它是 MinIO 官方停止维护后由社区Pigsty 团队接手继续维护的“续命版”分支。MiniOMinIO 是一个高性能、开源的对象存储系统专为云原生和 AI 场景设计。可以把它理解为一个“装在你自己服务器上的私有阿里云 OSS 或 AWS S3”用来存放海量的非结构化数据图片、PDF、音视频、模型文件等。在Agent项目中MinIO 扮演的是“原始文件仓库”的角色MinIO 的存储逻辑非常像文件系统但更简洁桶Bucket类似于文件系统里的“顶层文件夹”或“C 盘”。你需要先创建一个桶比如叫 documents才能往里面放东西。对象Object就是实际存储的文件PDF、图片等。对象的名字可以带“/”来模拟文件夹结构比如 2026/report.pdfMinIO 会自动帮你生成层级。MinIO 之所以能成为 AI 基础设施的热门选择靠的是两个关键技术纠删码Erasure Coding它不会像传统 RAID 那样浪费大量硬盘做备份。而是把文件切分成数据块和校验块。即使坏掉几块硬盘数据也能自动恢复。生产环境下最少 4 个节点就能搭起高可用集群。S3 API 兼容你的代码里不需要专门写一套连 MinIO 的逻辑。直接用任何支持 AWS S3 的 SDKPython、Java、Go 等就能操作它。这意味着你的应用不会被“锁死”在 MinIO 上随时可以迁移到云上的 S3 或 OSS。pydanticPydantic 是 Python 生态里最火、最强大的“数据校验与类型提示”库。可以把它理解为“数据保安”或“智能模具”。在构建大模型应用比如 LangChain/LangGraph和写后端 API 时它几乎无处不在。它解决什么痛点在 Python 里变量类型是动态的比如你定义一个 age 18别人非传个字符串 age “十八” 给你Python 也不会报错直到程序跑崩了你才发现。但在做后端或 AI 应用时数据是脏的。大模型吐出来的 JSON 可能缺字段前端发来的请求可能把数字传成了字符串。Pydantic 就是用来强制规定数据长什么样并自动帮你纠错的。它是怎么用的只需继承 BaseModel你只要写一个类继承 BaseModel用 Python 原生的类型提示str, int定义字段Pydantic 在后台就会自动帮你做三件事类型校验如果要求是 int你传了 “123”它会尝试帮你强转成 123如果你传了 “abc”它会立刻报错阻止。数据转换自动把 JSON 字符串解析成 Python 字典。错误提示一旦数据不对它会返回极其清晰的错误信息告诉你第几个字段、因为什么原因错了。代码例子frompydanticimportBaseModel# 定义一个“模具”classUser(BaseModel):name:strage:intis_active:boolTrue# 可以设置默认值# 正常传入user1User(name张三,age25)# 神奇的是即使传字符串数字它也会自动帮你转成 intuser2User(name李四,age30)# 错误传入age 不是数字user3User(name王五,age不知道)# 报错Input should be a valid integer输入必须是一个有效整数在你的项目里FastAPI 和大模型中为什么重要在 FastAPI 里还记得你之前问的 app.post 吗你只要把 Pydantic 模型作为参数传给函数FastAPI 就会自动去校验前端发来的数据。如果前端少传了字段FastAPI 直接返回 422 错误你的核心业务代码根本不需要写一堆 if not xxx: return “错误” 的脏代码。在大模型LLM里你肯定知道大模型可以“调用工具Function Calling”。大模型给出的参数就是 JSON。通过 Pydantic你可以完美地限制大模型必须按照什么格式返回参数一旦大模型瞎编格式Pydantic 就会拦住它甚至可以把错误信息扔回给大模型让它重新生成这叫 Self-Correction。在 LangChain / LangGraph 里你构建的 Agent 状态State几乎全都是用 Pydantic 模型来定义的因为 Agent 在各个节点流转时数据必须绝对规范。 额外小知识它和 Rust 的奇妙联系Pydantic 的核心V1 版本是纯 Python但 V2 版本发生了质变是用 Rust 语言重写的底层。所以你调用 Pydantic 校验数据时速度极快这也是为什么现在 AI 框架都在重度依赖它的原因。总结Pydantic 就是一个极其好用的“数据模具”你给它画个框它就能保证流经这里的数据绝对不出错Pytestpytest 是 Python 生态里最主流、最受欢迎的自动化测试框架。可以把它理解成一个不知疲倦的“质检员”专门用来验证代码有没有写错、功能正不正常。写测试很简单你只需要在普通的 Python 函数里用 assert 语句断言结果就行不需要像以前那样死记硬背一堆 self.assert* 的复杂名字。自动找测试你不需要手动指定要跑哪个文件。只要你的文件名是 test_*.py 或 *test.py函数名以 test开头它就会自动扫描并运行所有测试。出错了会“说人话”如果 assert 失败了它不会只甩给你一个冰冷的报错而是会把当时变量的实际值、期望值全打印出来帮你快速定位 Bug。OpenAI 兼容客户端 流式 astreamOpenAI 兼容客户端OpenAI-Compatible Client背景现在市面上有成百上千个大模型Qwen、DeepSeek、Llama 等。如果每个模型厂商都搞一套自己的代码规范程序员会疯掉。什么是“OpenAI 兼容”因为 OpenAI 最早定义了行业标准它的 API 格式现在几乎所有的主流大模型包括你用 vLLM 部署的 Qwen、或者 DeepSeek 官方都支持直接套用 OpenAI 的代码格式。怎么用你不需要专门去学 Qwen 的 SDK。你只需要在代码里写 client OpenAI(base_url“你的模型地址”, api_key“xxx”)然后就能像调用 GPT-4 一样调用 Qwen。这就叫“兼容客户端”。流式 astreamAsynchronous Stream流式Stream就是打字机效果。大模型不是一个字一个字算的吗如果等它整段话算完再返回你网页要卡好几秒。流式就是它算出一个字立马通过网络推给你一个字。a 代表 Async异步astream 是 Python尤其是 LangChain 或 OpenAI SDK里的标准写法。加了 a代表这个操作是“非阻塞”的。意思是你在等大模型吐字的时候你的程序没有死等它还可以去干别的事比如去查数据库、或者去处理另一个用户的请求。# 1. 创建一个兼容 OpenAI 的客户端连向你的 vLLM 或 DeepSeekclientOpenAI(base_url...,api_key...)# 2. 发起流式请求responseawaitclient.chat.completions.create(modelqwen2.5-7b,messages[{role:user,content:你好}],streamTrue# 开启流式)# 3. 用 astream 异步循环接收数据asyncforchunkinresponse:print(chunk.choices[0].delta.content,end)# 边接收边打印# orasyncforchunkinstream:piecechunk.choices[0].delta.contentifpiece:yieldpieceyield / 生成器普通函数 return 结束生成器 yield 暂停并交出一个值下次继续。所以 astream 是“边跑边吐”不是“跑完再返回”。之前在 LangChain 里看到的没有 a 的写法比如 stream 或者 invoke叫做同步Synchronous调用。同步 stream没有 a像“排队打饭”当你写下 for chunk in llm.stream(…) 时程序会死死卡在这一行。它在等大模型吐字在字吐完之前这行代码后面的逻辑绝对不会执行。如果有第二个用户此时发来请求服务器只能干瞪眼说“等会儿我还在处理第一个人呢。”(这在单人测试或者脚本里完全没问题但一旦做成 Web 服务并发量一高就废了。)异步 astream加了 a像“饭店服务员”当你写下 async for chunk in llm.astream(…) 时程序在这一行是不会卡住的。它向大模型发出请求后立刻就去干别的了比如去接待第二个用户。当大模型在后台吐出一个字时系统会通知它“嘿字来了”它再回来处理这个字处理完继续去干别的。(注意因为用了异步所以它必须配合 async for 和 await 一起使用。)探针Probe探针就是一段用来“探测系统状态、收集数据或检查是否存活”的微型程序或工具。容器健康探针Kubernetes / Docker 中最常见“健康检查”在 Kubernetes 或 Docker 里这个“体检动作”就是由探针Probe来执行的。容器编排系统会定期派探针去问你存活探针Liveness Probe定期问“你还活着吗”。如果不回应系统就会把你“杀掉”重启容器。就绪探针Readiness Probe定期问“你准备好接收用户的请求了吗”。如果不回应系统就不把流量分给你免得用户报错。启动探针Startup Probe给刚出生的程序一点时间慢慢初始化防止还没启动好就被误杀。监控系统探针APM 探针在复杂的系统中为了知道代码哪一行跑得慢开发者会在代码里植入一段“探针程序”比如 SkyWalking、Prometheus 探针。这个探针会悄悄地记录“第 10 行代码花了 2 毫秒第 15 行查数据库花了 500 毫秒”。它不会影响业务逻辑但能把系统的各个细节扒得干干净净画成监控图表给你看。AI 领域的评测探针Probing在大型语言模型LLM的研究中“探针Probing”是一种常用的分析方法。因为大模型是个黑盒研究人员会训练一个简单的分类器这个分类器就叫探针去“刺探”大模型在生成某个词的时候它隐藏层里的神经元到底学到了什么比如它到底知不知道“巴黎是法国的首都”。*FastAPIapp.get 和 app.post 是 Python FastAPI 框架中最核心的语法叫路由装饰器。app.get(“/路径”) —— “我要看东西”含义GET 请求用于获取数据。当你直接在浏览器地址栏输入网址并回车发起的全是 GET 请求。用法场景查询列表、获取详情、打开网页。代码例子fromfastapiimportFastAPI appFastAPI()# 当用户访问 http://localhost:8000/hello 时执行下面的函数app.get(/hello)defsay_hello():return{message:你好欢迎光临}app.post(/路径)—— “我要交东西给你”含义POST 请求用于提交数据。通常用户会带着一堆数据比如账号密码、聊天内容塞进包裹里发给你。用法场景用户登录、提交表单、把聊天记录发给大模型。代码例子frompydanticimportBaseModel# 定义用户发来的数据长什么样classChatRequest(BaseModel):text:str# 当用户向 http://localhost:8000/v1/chat 发送数据时触发app.post(/v1/chat)defchat_with_ai(request:ChatRequest):# 接收用户发来的文本准备交给大模型user_textrequest.textreturn{reply:f我收到了你的消息{user_text}} 还有几个你以后会碰到的变种app.put()通常用于更新数据比如修改个人资料。app.delete()通常用于删除数据。app.websocket()用于建立双向长连接实时语音聊天经常用。lru_cache全称是 Least Recently Used Cache最近最少使用缓存。你可以把它理解为一个“极其聪明的自动备忘录”。它的作用是把函数执行过的结果直接记在脑子里内存里下次遇到一模一样的参数直接秒给答案绝不再重新算一遍。括号里的 maxsize 是啥意思lru_cache(maxsize128) # 最多记 128 个不同的参数内存不是无限的不能什么结果都死记硬背。maxsize128 意思是“你最多只能记 128 个不同的结果”。如果记满了又来一个新参数它会自动把最久没被用过Least Recently Used的那个缓存踢出去腾出位置给新的。这就是它名字的由来。在你写的代码里哪里会用到它非常典型的一个场景就是读取环境变量或配置文件。lru_cache()defget_settings():# 读取 .env 文件很慢returnSettings()因为 .env 文件里的配置通常程序跑起来之后就不会变了。有了这个装饰器无论全项目里有多少个地方调用了 get_settings()它只会在第一次调用时真正去读文件后面全是从内存里直接拿现成的 Settings 对象。这样极大地提高了性能也保证了全局配置是同一个对象。Swagger UI 是一个能让你直接在网页上“点击测试”后端接口的交互式文档界面。用app.get和app.post写了 FastAPI 的后端代码。但是怎么知道这些接口到底通不通怎么给它们传参数没有 Swagger UI 时要用 curl 敲命令或者写 Python 脚本发请求非常麻烦。有了 Swagger UIFastAPI 会自动生成一个极其漂亮的网页。在浏览器里点几下、填几个空就能直接把请求发给后端并且立刻看到返回的 JSON 数据。FastAPI / Uvicorn 启动时 --reload 参数它的意思是“自动热重载”。程序在后台像一个保安一样死死盯着你项目里的所有 .py 代码文件。它是怎么工作的监听文件变化启动时你敲下uvicorn main:app --reloadUvicorn不仅启动了你的 Web 服务还在后台开了一个小“监控器”。你改代码当你在 VS Code 里改了某行 Python 代码按下 Ctrl S 保存的那一瞬间监控器立刻发现了“哎文件变了”自动重启监控器立刻杀掉原来正在运行的旧服务然后瞬间用你刚改好的新代码重新启动一个新的 Web 服务。monkey patch猴子补丁Monkey Patch猴子补丁 是 Python 中一种非常“暴力”且实用的编程技巧。它的核心意思是在程序运行的时候不修改源代码直接动态地替换掉某个模块、类或函数的功能。它长什么样最简单的例子假设你引入了一个别人写好的模块里面有个函数算加法# 别人写的模块 module.pydefadd(a,b):returnab# 在你的代码里你导入了它importmodule# 正常调用是 3print(module.add(1,2))# 【猴子补丁来了】# 你直接在外面把这个函数给“劫持”并替换掉defnew_add(a,b):returna*b# 我偷偷把加法改成了乘法module.addnew_add# 再次调用结果变成了 2print(module.add(1,2))你没去改 module.py 的源代码但是在运行的时候你把它里面的 add 换成了你的逻辑。这就是猴子补丁。你在 AI 开发中会在哪里碰到它测试Mocking—— 最常见场景你写了一个 Agent它需要调用 OpenAI 的 API。但 API 是收费的而且网络很慢测试的时候不能每次都真调。你就可以用 monkeypatch 把 OpenAI 的请求函数“替换”成一个假的函数每次调用直接返回一段写死的假数据比如“我是一个假回复”。这样你就能免费、极快地测试你的代码逻辑。修复第三方库的 Bug你下载了一个开源包发现里面有个小 Bug但原作者还没修复。你总不能去改人家的源代码吧这时候你就可以在你的入口文件里用 Monkey Patch 把那个出错的方法替换成你写好的正确方法。动态替换模型在 LangChain 里你可能想在不修改业务代码的前提下把 ChatOpenAI 临时替换成 ChatQwen。有些开发者会通过猴子补丁动态替换类的方法。deftest_readyz_ok(monkeypatch)-None:asyncdeffake_ok(host:str,port:int)-bool:returnTruemonkeypatch.setattr(main,_tcp_ok,fake_ok)respclient.get(/readyz)assertresp.status_code200assertresp.json()[ready]isTrue这是 Monkeypatch 的经典应用场景readyz 接口原本的逻辑可能是去真实的网络上“Ping”一下数据库端口通过调用 main._tcp_ok 函数。但在跑测试时数据库可能根本没启动或者网络不通。monkeypatch.setattr(main, _tcp_ok, fake_ok)这就是猴子补丁。它强制把真实的网络请求函数换成了上面的 fake_ok让它永远返回 True。这样就保证了无论在哪台电脑上跑测试结果都绝对稳定不会因为没网而报错。test_readyz_degraded则是反过来打补丁让它永远返回 False测试当数据库挂掉时接口能不能正确返回 503服务不可用错误。测试金字塔概念它涉及软件工程里最经典的“测试金字塔”理论。我用通俗的话给你把这几层剥开什么是测试金字塔想象一个金字塔从下到上分为三层。越往下测试跑得越快、成本越低越往上越接近真实用户但跑得越慢、越容易崩。底层塔基单元测试Unit Test数量最多。测什么只测代码里的一个极小的函数比如一个算加法的函数或者一个格式化时间的方法。特点极快几毫秒跑完、极稳。中层集成测试Integration Test数量适中。测什么测几个模块连在一起能不能跑通。比如测试“数据库读取 后端逻辑”。特点比较慢因为可能要连真实的数据库。顶层塔尖端到端测试E2E Test数量最少。测什么模拟真实用户在浏览器里点击、输入、翻页。特点极慢可能跑几分钟、极易碎网络抖动一下或者页面加载慢一点就报错而且维护成本极高。为什么是金字塔形状因为如果你写了一大堆顶层的 E2E 测试每次改一行代码等测试跑完天都亮了而且天天报错。所以业界的共识是多写底层的单元测试少写顶层的端到端测试。为什么说“是接口层测试”W1写的代码属于金字塔里的中层集成测试的范围更准确地说叫 API 层测试或者组件测试。用了 FastAPI 的 TestClient直接对接口发起请求测试路由/v1/chat对不对、Pydantic 校验空消息报 422对不对、SSE 格式对不对。它比“单元测试”宏观跨了路由、验证、业务逻辑但又比“E2E 测试”轻量不需要真的打开浏览器去点按钮。最核心的一句“外部依赖全部隔离”是什么意思这就是代码里用到的 monkeypatch猴子补丁 的功劳假设你要测试 /readyz探针接口真实的代码逻辑是去 Ping 一下真实的 PostgreSQL 数据库。如果跑测试的时候你的电脑没开 Docker或者数据库正好卡了测试就会失败。但这不是你的代码写错了而是环境问题。这就叫“依赖了外部环境”。什么叫“隔离”就是骗过你的代码。你写了monkeypatch.setattr(main, _tcp_ok, fake_ok)强行让 Ping 数据库的函数永远返回 True。这样一来外部真实的数据库被“隔离”掉了。测试时根本不需要启动 PostgreSQL、Redis 和 MinIO也不需要连真实的大模型。 总结一个测试原则W1写的接口测试不会真的去连数据库、也不会真的去调大模型。通过“打补丁隔离外部依赖”让测试跑得飞快、且 100% 稳定。只要接口测试通过就说明“骨架”没问题可以放心提 PR 了。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →