Vibe Coding 全栈实战:章鱼哥解题 07|功能跑通后的架构收敛

作者:小小小小小鹿日期:2026/5/28

Vibe Coding 全栈实战:章鱼哥解题 07|功能跑通后的架构收敛

上一期做完对话持久化以后,章鱼哥已经不只是一个“能回答问题”的接口了。它有了登录态,有了当前对话,有了 LangGraph thread,也能在刷新页面后恢复最近的消息。

但功能跑通以后,我回头看了一下后端模块依赖,发现了两个不太舒服的地方。

一个是 agent 依赖了 chat

1agent.nodes  chat.question_classifier
2

另一个是 infra.llm 依赖了 rag.context_builder

1infra.llm  rag.context_builder
2

这两个问题都不影响功能运行。测试能过,接口能调,页面也能正常对话。但它们会让模块边界慢慢变歪:功能越往后加,越难判断一段代码到底属于业务编排、基础设施,还是 RAG 检索。

所以这一期没有继续加新功能,而是停下来做了一次小范围架构收敛。

这一期的目标很克制:

1不改业务逻辑
2不改函数签名
3只移动模块归属
4只更新 import 路径
5最后用测试和残留引用检查验收
6

一、为什么功能跑通后还要重构

Vibe Coding 做功能很快。

从 RAG 检索、流式输出、鉴权接入,到对话持久化,很多代码都是在“先把链路跑通”的节奏下完成的。这个阶段最重要的是验证方向:用户能不能问,后端能不能答,前端能不能显示,刷新后对话能不能回来。

但功能一多,另一个问题就会出现:AI 往往能把局部代码写对,但它不一定会长期维护模块边界。

比如问题分类这件事,最开始只是 Chat 接口里的一段逻辑,放在 chat/question_classifier.py 里很自然。后来引入 LangGraph 后,agent 也要做意图分类,于是 agent.nodes 直接引用了 chat.question_classifier

代码能跑,但依赖关系变成了这样:

1agent  chat
2

这就有点别扭了。agentchat 都是业务层模块,agent 不应该为了一个通用分类函数去依赖 chat

另一个类似的问题是 context_builder

它的职责是把检索出来的 chunk 组装成给 LLM 使用的上下文,比如带编号的教材片段、引用来源列表。最开始它放在 rag 包里,因为输入来自 RAG 检索结果。但后来 infra.llmagent.graph 都要用它,于是依赖变成:

1infra.llm  rag.context_builder
2agent.graph  rag.context_builder
3

这里的问题不是函数错了,而是位置不对。context_builder 更像是“给 LLM 组装 prompt 上下文”的基础设施能力,不是 RAG 检索本身。

所以这一期做的事情很简单:把模块放回更合适的位置。


二、先看重构前的问题

重构前的两个关键问题可以画成这样:

1flowchart TD
2    Agent[agent.nodes] --> ChatClassifier[chat.question_classifier]
3    InfraLLM[infra.llm] --> RagContext[rag.context_builder]
4    AgentGraph[agent.graph] --> RagContext
5
6    ChatClassifier:::problem
7    RagContext:::problem
8
9    classDef problem fill:#fff4e5,stroke:#e08a00,color:#222;
10

第一条问题是同层耦合。

1agent.nodes  chat.question_classifier
2

agent 负责智能体流程编排,chat 负责对话业务接口和服务。分类器本身是一个无状态纯函数,只依赖正则规则,不属于 Chat 接口专有能力。它应该放在更底层、更通用的位置。

第二条问题是职责归属不清。

1infra.llm  rag.context_builder
2

infra.llm 是 LLM 调用实现,rag 应该更专注于教材读取、分块、向量化、检索这些事情。context_builder 做的是把检索结果格式化成 LLM 可用的 prompt context,更适合跟 LLM 基础设施放在一起。

这类问题如果不处理,短期没事,长期会越来越难改。

后面如果继续加新的智能体节点、评估逻辑、对话策略,大家都会开始到处引用“刚好能用”的函数。最后代码还能跑,但模块边界会变成一张网。


三、把 question_classifier 移到 domain

第一个调整是移动问题分类器。

重构前:

1backend/app/chat/question_classifier.py
2

重构后:

1backend/app/domain/classifier.py
2

为什么放到 domain

因为它满足几个特征:

  • 不依赖 FastAPI
  • 不依赖数据库
  • 不依赖 LLM
  • 不依赖 ChatService
  • 只是根据问题文本判断 textbook / unrelated

也就是说,它是一个通用领域判断函数。chat 可以用,agent 也可以用,后面如果评估或 API 层需要复用,也不应该反向依赖 chat

调整后依赖关系变成:

1agent.nodes  domain.classifier
2chat.service  domain.classifier
3

代码层面没有改分类规则,只改 import:

1# 改前
2from app.chat.question_classifier import classify_question
3
4# 改后
5from app.domain.classifier import classify_question
6

这类重构最重要的是克制。只移动归属,不顺手改逻辑。否则一旦测试失败,就很难判断是迁移出错,还是业务行为被改坏了。


四、把 context_builder 移到 infra

第二个调整是移动上下文构建逻辑。

重构前:

1backend/app/rag/context_builder.py
2

重构后:

1backend/app/infra/context_builder.py
2

这里最容易混淆的是:它处理的是 RAG 检索结果,为什么不放在 rag

我的判断是:看一个模块属于哪里,不只看它的输入,还要看它服务谁。

context_builder 的输入确实是 QueryResult,但它的输出是给 LLM prompt 用的上下文文本和引用来源。它不是在做检索,也不是在做分块,而是在把检索结果变成 LLM 可以消费的格式。

所以它更像 LLM 调用链路的一部分。

调整后依赖变成:

1infra.llm  infra.context_builder
2agent.graph  infra.context_builder
3

对应 import 也只是改路径:

1# 改前
2from app.rag.context_builder import build_numbered_context
3from app.rag.context_builder import chunks_to_sources
4
5# 改后
6from app.infra.context_builder import build_numbered_context
7from app.infra.context_builder import chunks_to_sources
8

函数签名不改,返回结构不改,调用方式不改。这样这次重构就不会影响 RAG 检索质量,也不会影响 LLM 生成结果。


五、重构后的项目结构和依赖关系

这次虽然只移动了两个后端模块,但我还是把前后端放在一起重新看了一遍。原因很简单:架构收敛不是只看某个文件放在哪里,而是要看它在整条产品链路里承担什么职责。

前端这一期没有做结构调整,它主要作为后端能力的调用边界放进图里。重构真正发生在后端:classifier.pychat 移到 domaincontext_builder.pyrag 移到 infra

重构后,项目结构可以简化成这样:

1OctoTutor
2├── frontend/src
3   ├── app                 # Next.js 路由入口
4   ├── chat                # 对话状态、SSE、历史恢复等前端逻辑
5   ├── components          # Chat UI、消息气泡、来源展示等组件
6   ├── contexts            # 登录态、全局状态上下文
7   ├── hooks               # 页面和组件复用的 React hooks
8   └── lib                 # API client、工具函数
9└── backend/app
10    ├── main.py             # FastAPI 应用入口,装配依赖和路由
11    ├── config.py           # 环境配置
12    ├── domain              # 领域模型、协议、通用领域逻辑
13       ├── models.py
14       ├── protocols.py
15       └── classifier.py   #  chat 移入:问题意图分类
16    ├── rag                 # 教材读取、分块、向量化、向量存储
17       ├── models.py
18       ├── embeddings.py
19       ├── vector_store.py
20       ├── chunkers/
21       ├── readers/
22       └── classifiers/
23    ├── infra               # 外部能力和基础设施适配
24       ├── llm.py
25       ├── context_builder.py  #  rag 移入:LLM 上下文格式化
26       ├── bm25.py
27       └── reranker.py
28    ├── agent               # LangGraph 编排和智能体节点
29       ├── graph.py
30       ├── nodes.py
31       └── prompts.py
32    ├── chat                # 对话 API 的业务编排
33       ├── service.py
34       ├── stream_router.py
35       ├── conversation_router.py
36       ├── schemas.py
37       └── dependencies.py
38    ├── api                 # 其他 HTTP API 路由
39    ├── ingestion           # 教材入库流程
40    ├── evaluation          # 检索和回答质量评估
41    └── middleware          # 鉴权等请求中间件
42

模块依赖关系可以简化成这样。虚线表示前后端之间的 HTTP 调用边界,实线表示后端内部的代码依赖:

1flowchart TD
2    subgraph FE[Frontend]
3        App[app 路由] --> UI[components]
4        UI --> ChatState[chat controller/hooks]
5        Hooks[hooks] --> ChatState
6        ChatState --> ApiClient[lib/api-client]
7        Contexts[contexts] --> ApiClient
8    end
9
10    subgraph BE[Backend]
11        Main[main.py] --> Chat[chat]
12        Main --> API[api]
13        Main -. 装配 .-> Agent[agent]
14
15        Chat --> Agent
16        Chat --> Domain[domain]
17        Chat --> Infra[infra]
18        Chat --> Rag[rag]
19        Chat --> Middleware[middleware]
20
21        Agent --> Domain
22        Agent --> Infra
23        Agent --> Rag
24
25        API --> Rag
26        API --> Middleware
27
28        Ingestion[ingestion] --> Rag
29        Evaluation[evaluation] --> Rag
30        Evaluation --> Domain
31
32        Infra --> Domain
33        Infra -. 使用 QueryResult 等数据模型 .-> Rag
34        Rag --> Domain
35        Middleware --> Config[config]
36
37        Domain --> Classifier[classifier.py]
38        Infra --> ContextBuilder[context_builder.py]
39    end
40
41    ApiClient -. HTTP /api .-> Chat
42    ApiClient -. HTTP /api .-> API
43

这张图里最重要的变化有两个。

1agent.nodes    domain.classifier
2chat.service   domain.classifier
3
4infra.llm      infra.context_builder
5agent.graph    infra.context_builder
6

也就是说,agent 不再依赖 chatinfra.llm 也不再跨到 rag 包里拿 prompt 组装逻辑。

这里要说清楚一个边界:这次不是要让所有模块都完全隔离。比如 infra.context_builder 仍然会使用 QueryResult 这类 RAG 数据模型。真正要收敛的是职责不清的路径:LLM 调用不应该从 rag.context_builder 里拿 prompt 格式化逻辑,智能体节点也不应该从 chat 包里拿通用分类函数。


六、怎么验收这类重构

这种重构不需要很复杂的验收,但一定要有验收。

因为它看起来只是“挪文件、改 import”,真正的风险却是:

  • 旧路径有没有漏改
  • 调用方行为有没有变化
  • 模块依赖有没有真的收敛

所以我主要看三件事。

第一,确认旧引用已经清理干净。比如不应该再出现:

1from app.chat.question_classifier
2from app.rag.context_builder
3

第二,先跑和这两个模块直接相关的测试,再跑后端全量测试。分类器迁移后,问题分类结果要保持不变;上下文构建迁移后,LLM 生成链路和 StateGraph 仍然要能正常跑通;最后再用全量测试确认这次移动没有影响其他模块。

第三,回到依赖图上确认目标已经达成:

1agent 不再依赖 chat
2infra 不再通过 context_builder 依赖 rag
3domain 新增 classifier
4infra 新增 context_builder
5

这里的关键不是把验收做得多复杂,而是证明一件事:这次只改变模块归属,没有偷偷改变业务行为。范围越小,验收越清楚,这类架构收敛才不会变成另一场失控的大改。


Vibe Coding 全栈实战:章鱼哥解题 07|功能跑通后的架构收敛》 是转载文章,点击查看原文


相关推荐


开发了一个管理本地开发环境的软件
神奇的程序员2026/5/5

前言 前阵子换了新电脑,我在整理本地开发环境时,看到一堆需要重新装的,顿时感觉好麻烦。想着都过去这么久了,应该有工具可以做到统一管理,实现快速安装、更新、切换版本吧。 经过一番查找后,找到了mise这个东西,只需要简单的一句命令就能安装java、node、redis、go等工具,而且还支持对这些工具做统一管理(更新、删除),支持三大主流平台(macOS/Windows/Linux) 命令行始终不方便,于是我萌生了一个做GUI的想法,花了亿点时间用Flutter把它开发出来了,欢迎各位有需要的开发


Hello 算法:“走一步看一步”的智慧
灵感__idea2026/4/26

每个系列一本前端好书,帮你轻松学重点。 本系列来自上海交通大学硕士,华为高级算法工程师 靳宇栋 的 《Hello,算法》 “走一步看一步”,是我们面对不断变化的世界所采取的应对策略。 多数时候,我们无法对未来做出准确预测,只能根据上一件事的结果对下一件事做决策。介绍“分治”的时候,我们已经接触过这种策略。本篇主角依然如此,但又有所不同。 先看个例子。 爬楼梯 给一个 n 阶楼梯,每步可以上 1 阶或者 2 阶,问有多少种方案可以爬到楼顶? 假设 n 是3,那么方案共 3 种。如下图所示。 这


Linux 驱动开发入门:从最简单的 hello 驱动到硬件交互
4. 嵌入式铲屎官2026/4/17

Linux 驱动开发入门:从最简单的 hello 驱动到硬件交互 🎉 写给未来的自己和领导:本文是 Linux 驱动开发的 入门级保姆教程,从零开始搭建驱动框架,逐行解释代码,记录每一个踩过的坑。无论你是刚接触内核编程,还是想快速上手 GPIO 中断,都能在这里找到清晰的思路和可复现的步骤。 📚 目录 引言:驱动是什么?驱动的基本框架 —— 一切皆文件实战:第一个 hello 驱动 3.1 完整的驱动源码(带详细注释)3.2 编译驱动 —— Makefile 解析3.3 上机测试 ——


深入剖析 Redis 经典面试题
Thomas.Sir2026/4/9

1、什么是Redis?它主要用来什么的? Redis,英文全称是Remote Dictionary Server(远程字典服务),是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。 与MySQL数据库不同的是,Redis的数据是存在内存中的。它的读写速度非常快,每秒可以处理超过10万次读写操作。因此redis被广泛应用于缓存,另外,Redis也经常用来做分布式锁。除此之外,Redis支持事务、持久化、


260331-OpenWebUI统计所有Chat的对话字符个数
GuokLiu2026/4/1

1 OWUI启动脚本 # Open-WebUI Settings export DATA_DIR='data0331' export ENABLE_SIGNUP=True export DEFAULT_USER_ROLE='admin' export DEFAULT_GROUP_ID='xai' export OFFLINE_MODE=false export HF_HUB_OFFLINE=1 # OpenAI API 配置 export ENABLE_OLLAMA_API=false ex


[LangChain智能体本质论]中间件是如何参与Agent、Model和Tool三者交互的?
JaydenAI2026/3/23

LangChain的中间件(Middleware)是围绕Agent执行流程构建的“可插拔钩子系统”。它允许开发者在不修改核心逻辑的情况下,在执行的关键节点(如输入处理、模型调用前后、输出解析等)对数据流进行拦截、修改或验证。中间件类型以AgentMiddleware为基类。 1. AgentMiddleware AgentMiddleware是一个泛型类型,两个泛型参数分别代表状态和静态上下文的类型,我们可以利用state_schema字段得到状态类型。它的name属性返回中间件的名称,默认返回


haproxy案例项目(haproxy+dns+nginx+nfs+keepalived)
爱莉希雅&&&2026/3/15

HAProxy+Nginx+NFS+DNS 部署笔记 一、环境规划 主机名IP 地址安装软件角色说明haproxy192.168.72.100/24haproxy负载均衡器nginx1192.168.72.10/24nginx、nfs-utilsWeb 节点 1(挂载 NFS 共享)nginx2192.168.72.20/24nginx、nfs-utilsWeb 节点 2(挂载 NFS 共享)nfs192.168.72.30/24nfs-utilsNFS 文件共享服务器dns192.168.


Spring Cloud+AI :实现分布式智能推荐系统
我不是呆头2026/3/7

欢迎文末添加好友交流,共同进步! “ 俺はモンキー・D・ルフィ。海贼王になる男だ!” 引言 在当今数字化时代,推荐系统已成为电商平台、内容分发平台、社交网络等互联网产品的核心竞争力之一。从淘宝的"猜你喜欢"、抖音的精准内容推送,到 Netflix 的影视推荐,优秀的推荐系统不仅能显著提升用户留存率和转化率,更能为企业带来可观的商业价值。据统计,亚马逊约 35% 的销售额来自推荐系统,Netflix 则通过推荐算法为用户节省了每年约 10 亿美元的搜索成本。 然而,随着业


一文搞懂激活函数!
aicoting2026/2/27

推荐直接网站在线阅读:aicoting.cn 在深度学习中,激活函数(Activation Function)是神经网络的灵魂。它不仅赋予网络非线性能力,还决定了训练的稳定性和模型性能。那么,激活函数到底是什么?为什么我们非用不可?有哪些经典函数?又该如何选择? 所有相关源码示例、流程图、模型配置与知识库构建技巧,我也将持续更新在Github:AIHub,欢迎关注收藏! 1. 什么是激活函数,为什么需要激活函数 激活函数的核心作用就是为神经网络引入非线性。 为什么需要非线性? 想象一下,如果


【Python练习五】Python 正则与网络爬虫实战:专项练习(2道经典练习带你巩固基础——看完包会)
纯.Pure_Jin(g)2026/2/18

第一题 题目: 使用正则完成下列内容的匹配 匹配陕西省区号 029-12345匹配邮政编码 745100匹配邮箱 lijian@xianoupeng.com匹配身份证号 62282519960504337X 代码: import re # 1. 匹配陕西省区号 029-12345 pattern_area = r'^029-\d{5}$' # 精确匹配 029- 开头,后接5位数字 test_area = '029-12345' print("区号匹配:", re.match(pattern_

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 聚合阅读