AI 写代码时,非常喜欢一种"最快能跑"的方式,,例如:
python
db = Database()
cache = RedisCache()
mailer = EmailService()
service = UserService()
哪里需要,就在哪里创建。
短期看很方便,但当项目越来越大以后,就会出现:
text
数据库实例到处创建
缓存客户端到处创建
配置散落
Service 互相 new
测试时无法替换依赖
这类代码最明显的问题是:
对象把自己的依赖写死了。
而 DI(Dependency Injection,依赖注入) 要解决的,就是这件事。
📌 技术名片
DI / Dependency Injection / 依赖注入
指一个类或函数需要某个对象时,不由它自己创建,而是由外部把已经准备好的对象"注入"进来。
例如:
不推荐:
python
class UserService:
def __init__(self):
self.db = UserDatabase()
推荐:
python
class UserService:
def __init__(self, db):
self.db = db
区别只有一点:
UserService不再决定数据库对象怎么创建。
它只负责:
使用数据库。
💡 一句话理解
可以把 DI 想象成一家餐厅。
厨师做菜需要:
text
食材
锅
燃气
调料
正常情况下,是餐厅提前准备好这些东西。
厨师上班以后直接使用,而不是:
text
每炒一道菜
↓
厨师自己去买锅
↓
自己开燃气账户
↓
自己找供应商买菜
软件也是一样。
业务类真正应该关心的是:
"我要使用什么能力?"
而不是:
"这个能力应该怎么创建?"
所以 DI 的核心可以概括为:
对象负责使用依赖,系统负责创建依赖。
这对 AI 编程尤其重要。因为如果不给规则,AI 很容易为了完成当前任务,直接在任何地方实例化它需要的对象。
一、反面教材:AI 的"自由发挥"
假设我们告诉 AI:
实现一个 AgentService,用于查询 Agent,并在 Agent 修改后清理缓存。
没有架构限制时,AI 很可能写成:
python
class AgentService:
def __init__(self):
self.db = AgentDatabase()
self.cache = RedisCache()
async def get_agent(self, agent_id: int):
return await self.db.get_agent(agent_id)
async def update_agent(self, agent_id: int, data):
agent = await self.db.update_agent(
agent_id,
data
)
await self.cache.delete(
f"agent:{agent_id}"
)
return agent
乍一看完全合理,但问题已经出现了...
问题 1:数据库被写死了
python
self.db = AgentDatabase()
意味着:
AgentService永远只能使用这个AgentDatabase。
以后如果要:
text
换数据库实现
使用 Mock Database 测试
增加数据库代理
更换连接池
都必须修改 AgentService。
问题 2:缓存也被写死了
python
self.cache = RedisCache()
以后如果项目想从:
text
Redis
换成:
text
内存缓存
其他缓存服务
测试用 Fake Cache
业务类也必须跟着修改。
问题 3:每个地方都可能重新创建连接
如果 AI 在很多 Service 中都写:
python
Database()
Redis()
LLMClient()
可能导致:
text
连接池重复创建
HTTP Client 重复创建
模型客户端重复创建
缓存实例重复创建
浪费系统资源。
问题 4:测试特别麻烦
假设现在要测试:
python
AgentService.update_agent()
我们并不想真的:
text
连接 SQLite
连接 Redis
初始化完整系统
只想给它一个假的数据库,但对象内部已经写死,测试就很难替换。
二、架构规则:不要让业务对象自己创建依赖
DI 最重要的规则其实很简单:
谁使用依赖,谁不要负责创建依赖。
创建工作应该集中到系统入口、容器或者框架的依赖管理机制中。
一个简单结构可以是:
text
应用启动
↓
创建 Database
↓
创建 Cache
↓
创建 Repository
↓
创建 Service
↓
放入 DI Container
↓
业务代码按需获取
这里的:
DI Container
全称是:
Dependency Injection Container,依赖注入容器。
可以把它理解成:
统一管理对象创建和关系的"总仓库"。
规则 1:业务类不主动 new / 实例化基础设施
例如不要:
python
class AgentService:
def __init__(self):
self.db = AgentDatabase()
而应该:
python
class AgentService:
def __init__(self, db):
self.db = db
这样:
text
AgentService
只知道:
我有一个 db 可以用。
至于这个 db:
text
是不是 SQLite
是不是 PostgreSQL
是不是 Mock
都不是它关心的事。
规则 2:共享资源尽量集中创建
例如:
text
Database Engine
Session Factory
HTTP Client
LLM Client
Cache Registry
Vector Store
这些对象通常不应该每个请求都重新创建。
更合理的是:
text
应用启动
↓
创建一次
↓
统一复用
这既是 DI,也是资源生命周期管理。
规则 3:Service 的依赖从构造函数或容器获得
例如:
python
class UserService:
def __init__(self, user_db):
self.user_db = user_db
或者:
python
class AgentService:
def __init__(self, container):
self._agent_db = container.agent_db
self._cache = container.cache
重点不是形式,而是:
依赖来自外部,而不是内部临时创建。
规则 4:API 不自己创建 Service
不要:
python
@router.get("/agents")
async def list_agents():
service = AgentService()
而应该:
python
@router.get("/agents")
async def list_agents(
service = Depends(get_service)
):
...
这里的 Depends 是 FastAPI 提供的:
Dependency Injection,依赖注入机制。
它负责在执行接口之前,把依赖准备好。
规则 5:禁止为了方便绕过容器
这一点对 AI 特别重要。
例如项目已经有:
python
container.agent_service
AI 却为了方便重新写:
python
service = AgentService(container)
技术上可能能运行,但会绕开统一生命周期管理。所以应该明确:
已有容器管理的对象必须复用,不允许随意重新实例化。
三、这样做有什么好处?
DI 经常被新人理解成:
"只是换一种传参数的方法。"
其实远不止如此。
1. 更容易替换实现
假设现在:
text
AgentService
↓
SQLite Repository
以后需要换成:
text
AgentService
↓
PostgreSQL Repository
如果依赖是注入的:
AgentService 本身可能根本不需要修改。
这就是:
低耦合。
也就是:
一个模块不应该死死绑定某个具体实现。
2. 更容易测试
正式运行时:
text
AgentService
↓
Real Agent Database
测试时:
text
AgentService
↓
Fake Agent Database
例如:
python
fake_db = FakeAgentDatabase()
service = AgentService(
db=fake_db
)
测试不需要真的启动数据库。
这就是 DI 对:
Testability,可测试性
最大的帮助之一。
3. 系统资源更容易统一管理
例如数据库 Engine:
text
创建一次
↓
Repository 共用
而不是:
text
UserService
→ 一个 Engine
AgentService
→ 又一个 Engine
ToolService
→ 再一个 Engine
尤其对于:
text
数据库连接池
LLM Client
HTTP Client
向量数据库
统一生命周期非常重要。
4. 配置切换更简单
例如开发环境使用:
text
Local LLM
生产环境使用:
text
Cloud LLM
如果业务代码直接:
python
client = OpenAIClient(...)
就会和具体实现绑定。
DI 后可以变成:
text
开发环境
↓
LocalLLMClient
生产环境
↓
CloudLLMClient
业务逻辑只接收:
text
LLM Client
而不用自己决定是谁。
5. AI 不容易偷偷制造新的基础设施
这是 AI 编程中特别有价值的一点。
如果没有 DI 规则,AI 遇到:
"这里需要数据库。"
很容易:
python
db = Database(...)
遇到:
"这里需要缓存。"
又:
python
cache = Redis(...)
最后整个项目出现很多重复对象。
有 DI 以后,AI 的第一反应应该变成:
项目里已经有这个依赖了吗?
而不是:
我怎么创建一个新的?
四、AI 最容易出现的"失控现场"
假设让 AI:
给 Agent 增加知识库检索能力。
没有 DI 规则时,很可能写:
python
class AgentService:
async def run(self, agent_id, query):
llm = LLMClient(
api_key=settings.api_key
)
vector_db = Chroma(
persist_directory="./db"
)
retriever = RetrievalPipeline(
vector_db=vector_db
)
agent = AgentRunner(
llm=llm,
retriever=retriever
)
return await agent.run(query)
它真的可能工作。但每调用一次:
text
创建 LLMClient
创建 Vector DB
创建 RetrievalPipeline
创建 AgentRunner
同时:
text
配置硬编码
对象生命周期混乱
缓存失效
测试困难
全部出现。
更麻烦的是,下一个 AI 看到这段代码以后可能继续模仿:
text
Web Search
自己创建 Client
SQL Agent
自己创建 DuckDB
Knowledge Base
自己创建 Vector Store
最后系统彻底失去统一资源管理。
五、提示词落地:把 DI 规则告诉 AI
只写:
"使用依赖注入。"
仍然不够具体。可以直接写进项目规则:
text
## 依赖注入规则
本项目使用依赖注入 (DI)。
规则:
- 业务类不得创建内部基础设施依赖项。
- 不得在业务方法内部直接实例化数据库、存储库、缓存、LLM 客户端、HTTP 客户端、向量存储或服务。
- 重用应用程序 ServiceContainer 管理的依赖项。
- 共享基础设施资源必须集中创建和管理。
- 服务应通过构造函数注入或现有应用程序容器接收所需的依赖项。
- FastAPI 路由必须使用项目现有的依赖项解析机制。
- 不得在 API 路由内部直接实例化 Service 类。
- 创建新对象之前,请检查 ServiceContainer 中是否已存在等效实例。
- 尊重容器管理的单例或共享资源的生命周期。
- 依赖项在测试中必须尽可能可替换。
然后具体任务可以这样写:
text
为 Agent 增加新的业务能力。
严格遵守项目 Dependency Injection 规则:
1. 不要在 Router 或 Service 内创建数据库实例;
2. 不要直接实例化已有 Service;
3. 不要重新创建缓存、LLM Client、Vector Store 等共享资源;
4. 优先从 ServiceContainer 获取已有依赖;
5. FastAPI API 使用现有 Depends / get_service 方式获取 Service;
6. 如果确实需要新增共享依赖,应统一在 ServiceContainer 中创建并管理;
7. 业务类只负责使用依赖,不负责决定依赖如何构造。
实现前先检查:
app/core/service_container.py
相关 Service
相关 Router
现有 Repository / Runtime 实现。
这会明显改变 AI 的编码习惯。它不再问:
"怎么 new 一个?"
而是先问:
"项目已经把它放在哪里了?"
六、正面产出:miniagent 的 DI 是怎么做的?
miniagent 当前有一个非常明确的核心类:
text
backend/app/core/service_container.py
文件本身的说明就是:
python
# Application-level service container,
# Implement Dependency Injection.
即:
应用级 Service Container,用于实现依赖注入。
1. 数据库 Engine 只在容器集中创建
ServiceContainer 中先统一创建:
python
self.engine = create_async_engine(
database_url,
echo=False,
future=True,
)
self.session_factory = async_sessionmaker(
bind=self.engine,
...
)
也就是说:
text
Service A
Service B
Service C
不需要各自再:
python
create_async_engine(...)
而是共享容器管理的数据库基础设施。
2. Repository 也统一创建
容器接着创建:
python
self.user_db = AsyncUserDatabase(
self.engine,
self.session_factory
)
self.agent_db = AsyncAgentDatabase(
self.engine,
self.session_factory
)
self.tool_db = AsyncToolDatabase(
self.engine,
self.session_factory
)
以及知识库、Document、Chat、Role 等数据访问对象。
形成:
text
ServiceContainer
│
├── engine
├── session_factory
│
├── user_db
├── agent_db
├── tool_db
├── kb_db
└── ...
所有对象的来源非常清楚。
3. Service 继续由容器统一创建
例如:
python
self.agent_service = AgentService(self)
self.llm_service = LLMService(self)
self.user_service = UserService(self)
self.tool_service = ToolService(self)
self.kb_service = KnowledgeBaseService(self)
于是整体关系变成:
text
ServiceContainer
│
├── Repository
├── Runtime
├── Cache
├── Registry
└── Service
也就是说:
对象在哪里创建,由容器决定。
4. 例子:AgentService 自己不创建数据库
再看真实的:
text
backend/app/services/admin/agent.py
AgentService 的构造函数是:
python
def __init__(
self,
container: ServiceContainer,
) -> None:
self._agent_db = container.agent_db
self._user_agent_relation_db = (
container.user_agent_relation_db
)
self._agent_tool_relation_db = (
container.agent_tool_relation_db
)
self._tool_db = container.tool_db
self._cache = container.object_cache_invalidator
这里没有:
python
AsyncAgentDatabase(...)
也没有:
python
CacheRegistry()
而是:
text
容器已经准备好
↓
AgentService 获取并使用
这就是依赖注入。
5. 例子:FastAPI Router 也不自己创建 Service
再来看 Agent API。
它定义:
python
def get_service(
request: Request
) -> AgentService:
return request.app.state.container.agent_service
接口则:
python
async def create_agent(
payload: AgentCreate,
svc: AgentService = Depends(get_service),
caller_id: int = Depends(_add),
):
agent_out = await svc.create_agent(payload)
return ApiResponse(
data=agent_out
)
这里的:
python
Depends(get_service)
就是 FastAPI 的依赖注入机制。
可以理解成:
text
HTTP Request
↓
FastAPI Depends
↓
get_service()
↓
ServiceContainer
↓
已经存在的 AgentService
↓
Router 使用
Router 没有:
python
svc = AgentService(...)
6. 最终形成了怎样的依赖关系?
下面是 miniagent 的 依赖注入 / DI 实现原理图:

可以简化成:
这里最重要的一条原则就是:
依赖在外部创建,从外向内注入。
而不是:
text
Router
↓
自己 new Service
↓
Service 自己 new Repository
↓
Repository 自己 new Database
七、 为什么这比内部实例化更好?
如果直接写成:
python
class AgentService:
def __init__(self):
self._agent_db = AsyncAgentDatabase(...)
self._tool_db = AsyncToolDatabase(...)
self._cache = CacheInvalidator(...)
AgentService 就必须知道:
text
Engine 怎么创建
SessionFactory 从哪里来
Cache Registry 怎么配置
Repository 怎么构造
它承担了太多本不属于自己的职责。而当前结构下:
text
AgentService
只负责:
使用这些能力完成 Agent 业务。
八、这对 AI 编程意味着什么?
假设以后让 AI:
给系统增加 EmailService。
没有 DI 规则时,它可能:
python
mailer = SMTPMailer(
host=...,
port=...
)
然后在很多地方重复出现。
有 DI 规则后,合理思路应该变成:
text
1. 这是共享服务吗?
↓
2. 是否应该进入 ServiceContainer?
↓
3. 邮件 Client 在哪里创建?
↓
4. 哪些 Service 需要它?
↓
5. 通过容器注入使用
同样:
text
Redis
LLM Client
Vector Store
Web Search Client
SQL Agent
都遵循相同原则。
于是 AI 的行为从:
"缺什么,我就现场造什么。"
变成:
"缺什么,我先检查系统是否已经提供。"
这其实是非常大的变化。
结语
DI 依赖注入经常被讲成一个很抽象的设计模式。
但对于 AI 编程,我们完全可以用一句话理解:
不要让业务代码自己创建它依赖的对象。
为什么?
因为一旦 AI 可以自由实例化:
text
Database
Cache
LLM
HTTP Client
Repository
Service
它很容易为了当前功能快速创建新对象。几十次以后,系统就会出现:
text
重复实例
重复配置
资源浪费
测试困难
替换困难
生命周期混乱
而 DI 给 AI 规定了一条非常重要的工程纪律:
text
对象的创建
↓
集中管理
对象的使用
↓
按需注入
于是系统变成:
text
ServiceContainer
负责"谁是谁、怎么创建"
Service
负责"怎么完成业务"
Router
负责"怎么接收请求"
AI 不再需要到处考虑:
"我要怎么创建 Database?"
它只需要关心:
"项目已经给我的 Database 应该怎么使用?"
所以:
DI 的真正价值,不只是少写几个
new,而是把"对象创建权"从业务代码手里收回来。
对于 AI 编程来说,这一点尤其重要。
禁止 AI 随意实例化,让依赖由架构统一供应。
这样才能获得真正的:
低耦合、可测试、可替换、可扩展。
开源代码
🪐祝您好运🪐