实验二手记在 CogmAIt 里把 OOP 与 ADT 真正「接上线」这学期软件构造的实验二标题听起来很「架构」——ADT、OOP、可插拔、防具式上下文。说实话我一开始更担心的是环境能不能跑起来、Swagger 点下去会不会又是一片红。真正做完之后我发现实验想练的不是「背概念」而是在真实仓库里找主路径、在脏数据里活下来、在能跑的前提下做最小改造。下面按时间线把我踩过的坑和怎么爬出来的写清楚也给以后的自己留个档。1. 我先搞明白到底改哪里才算「生效」实验文档一开始就提醒仓库里可能有旧代码、路径要确认。我做的第一件事不是写 Provider而是打开app/main.py和app/api/v1/api.py确认路由前缀是/api而不是我脑子里默认的/api/v1。后面所有截图、curl、Swagger我都按这个前缀来避免「文档写得对、我测得不对」这种低级错位。和实验最相关的几条主链路我最后锁定在agents.py聊天主流程models.py模型 CRUD 和测试providers/base.pymanager.py抽象契约与扫描加载utils/model.py统一推理入口这一步看起来「没产出」但后面每次报错我至少知道该去哪个文件对线。2. 任务一接三家厂商之前环境先把我教育了一遍2.1 MySQL、Poetry、依赖我本地用 MySQL。最早.env里还残留过 SQLite 的写法和课程要求不一致后来改成DB_*指向本机实例才算和「模型、智能体落库」这条链路对齐。Poetry 安装依赖时根目录不是一个标准 Python 包装全局会报「找不到 package」。我在pyproject.toml里加了package-mode false这才顺利用poetry install把环境拉齐。登录注册那块缺bcrypt报错信息很直白补上依赖后 Swagger 注册/登录才稳定。2.2 Swagger 登录为什么一直是Bearer undefined这不是模型问题是 OAuth2 配置问题。OAuth2PasswordBearer的tokenUrl必须指向真正返回access_token的接口。我们项目里登录返回字段叫token一类和 Swagger 默认想象的不完全一致把tokenUrl改到/api/auth/token这类正确路径后Authorize 里才能拿到真正的 Bearer。另外还有/auth/me之类接口ORM 字段和响应模型字段对不上会直接 500。那种错误很像「后端炸了」其实是 Pydantic 校验失败。我后来学会先看响应体里的detail再对照 schema。2.3 三个 Provider接口不是「能跑就行」而是要「长得像一家人」我按文档继承了ModelProvider实现了chat_completion、embedding、test_connection等契约方法。实现上我走了 OpenAI 兼容 SDKAsyncOpenAI这条最省心的路DeepSeek、智谱、Moonshot 都按兼容层去调。这里我踩过一个很蠢但很常见的坑把 Swagger 模板里的string、null当真值写进数据库。尤其是base_url写成带引号的null时底层会当成 URL 去解析直接UnsupportedProtocolstatus写成字面量string时推理入口会提示「模型状态不是活动状态: string」——字面意思就是你存进去的状态真的叫string当然不 active。厂商控制台里 402 Insufficient Balance 我也遇到过。那一刻我差点怀疑 Provider 写错了后来才意识到这是钱的问题不是代码问题。换 key 如果还是同一个零余额账号照样 402充值或换「有余额账号」的 key 后同一份代码立刻正常。2.4 对话接口stream: false为什么「200 但 code 500」这个坑很「后端特色」HTTP 200 只是网关层业务包装里code仍然是 500。我们后来定位到非流式路径函数没有 return中间件包一层就变成「服务器内部错误」。修完之后又要面对async for拿到 dict推理层在错误场景会返回{error: ...}但聊天生成器把它当异步迭代器去async forPython 直接抛__aiter__。再往后是「成功但正文为空、tokens 为 0」本质是 chunk 形态多样只认某一种delta.content会漏。把解析写宽一点之后我才在 Swagger 里截到那张最爽的图code200message.content有字tokens_used也大于 0。任务一最后的反思题我也真的想过如果每加一个厂商都要改agents.py的 if-else那确实违反 开闭原则OCP——扩展应该主要靠新增子类与扫描加载而不是改核心路由。3. 任务二我不想做一个「漂亮但挂不上线」的 SessionContext3.1 为什么从final_messages下手agents.py太长我先用全局搜索找final_messages看它怎么「长出来」文件上下文、系统提示、检索结果等前缀先拼最后才把chat_request.messages那段用户可见多轮对话 append 进去。文档要求至少替换一段装配逻辑我就选这段风险最小、又最贴近实验描述的 chat messages。3.2 AF / RI 不是写给老师看的是写给我自己用的我先写SessionContext的 DocstringAF 写清楚「抽象上它代表什么」RI 写清楚「内部允许什么、不允许什么」。后面_check_rep基本就是把这些话翻译成 assert。写细一点的好处很明显实现阶段我会不断问自己——这句规则到底要不要 enforceadd_message先deepcopy再校验最后只保留role和content两个键get_messages再deepcopy整表返回。测试里我故意对返回值append确认内部条数不变——那一刻我才真的理解「防具」不是形容词是能挡住什么。3.3 接入与两条入口主聊天路径改完后我发现还有 API Key 那条聊天入口也在拼消息。只改一条未来一定不一致所以我把那条也统一成同一模式。4. 进阶我挑了两条「能写完、也能讲清楚」的加分项我不想一口吃成胖子去「重构整个记忆子系统」那更像另一个大作业。我选的是不可变KnowledgeChunk在process_text_chunks向量化前把List[str]提升成Tuple[KnowledgeChunk, ...]批内用kc.text/kc.chunk_index去对齐 embedding 与入库字段。它解决的是「块级文本与序号」这一小段流水线里的表示泄露问题。VectorStoreProvider 两种内存实现抽象接口 ListMemoryVectorStoreDictMemoryVectorStore。我没有幻想短期替换 Milvus而是在同一批数据写入 Milvus 之前并行镜像到两套内存后端——足够在报告里讲清楚多态与解耦的意图又不把线上存储结构赌进去。这两项都有独立pytest本地不依赖 Milvus 也能验。5. 写报告时我遇到的「最后一个坑」LaTeX 图乱飘我一开始用figure[htbp]图总跑到奇怪的位置。后来用float包的[H]强制就地第一次还忘了\usepackage{float}编译器直接报Unknown float option H。另外\linewidth超过 1.0 会溢出这些属于「写论文/写报告的肌肉记忆」我也顺便长了一点。6. 我最大的收获比分数更重要实验二让我把几件事打通了扩展尽量靠抽象接口与扫描加载而不是靠改核心路由分支。ADT先写清 AF/RI再写代码比先堆代码再补注释靠谱得多。工程现实402、statusstring、OAuth、非流式 return这些看起来像「玄学」的东西多数都有非常具体的解释路径。最小改造先切最小口、能回归、能解释再考虑「架构师幻想时间」。如果读者你也正在做类似实验建议你把自己的「第一次成功对话」截图留下来——那不是终点但它会是你整个实验二里最像奖励关卡的一帧。加油