AI 写代码时,非常喜欢一种“最快能跑”的方式,,例如:

db = Database()
cache = RedisCache()
mailer = EmailService()
service = UserService()

哪里需要,就在哪里创建。

短期看很方便,但当项目越来越大以后,就会出现:

数据库实例到处创建
缓存客户端到处创建
配置散落
Service 互相 new
测试时无法替换依赖

这类代码最明显的问题是:

对象把自己的依赖写死了。

DI(Dependency Injection,依赖注入) 要解决的,就是这件事。


📌 技术名片

DI / Dependency Injection / 依赖注入

指一个类或函数需要某个对象时,不由它自己创建,而是由外部把已经准备好的对象“注入”进来。

例如:

不推荐:

class UserService:
    def __init__(self):
        self.db = UserDatabase()

推荐:

class UserService:
    def __init__(self, db):
        self.db = db

区别只有一点:

UserService 不再决定数据库对象怎么创建。

它只负责:

使用数据库。


💡 一句话理解

可以把 DI 想象成一家餐厅。

厨师做菜需要:

食材
燃气
调料

正常情况下,是餐厅提前准备好这些东西。

厨师上班以后直接使用,而不是:

每炒一道菜
厨师自己去买锅
自己开燃气账户
自己找供应商买菜

软件也是一样。

业务类真正应该关心的是:

“我要使用什么能力?”

而不是:

“这个能力应该怎么创建?”

所以 DI 的核心可以概括为:

对象负责使用依赖,系统负责创建依赖。

这对 AI 编程尤其重要。因为如果不给规则,AI 很容易为了完成当前任务,直接在任何地方实例化它需要的对象。


一、反面教材:AI 的“自由发挥”

假设我们告诉 AI:

实现一个 AgentService,用于查询 Agent,并在 Agent 修改后清理缓存。

没有架构限制时,AI 很可能写成:

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:数据库被写死了

self.db = AgentDatabase()

意味着:

AgentService 永远只能使用这个 AgentDatabase

以后如果要:

换数据库实现
使用 Mock Database 测试
增加数据库代理
更换连接池

都必须修改 AgentService


问题 2:缓存也被写死了

self.cache = RedisCache()

以后如果项目想从:

Redis

换成:

内存缓存
其他缓存服务
测试用 Fake Cache

业务类也必须跟着修改。


问题 3:每个地方都可能重新创建连接

如果 AI 在很多 Service 中都写:

Database()
Redis()
LLMClient()

可能导致:

连接池重复创建
HTTP Client 重复创建
模型客户端重复创建
缓存实例重复创建

浪费系统资源。


问题 4:测试特别麻烦

假设现在要测试:

AgentService.update_agent()

我们并不想真的:

连接 SQLite
连接 Redis
初始化完整系统

只想给它一个假的数据库,但对象内部已经写死,测试就很难替换。


二、架构规则:不要让业务对象自己创建依赖

DI 最重要的规则其实很简单:

谁使用依赖,谁不要负责创建依赖。

创建工作应该集中到系统入口、容器或者框架的依赖管理机制中。

一个简单结构可以是:

应用启动
创建 Database
创建 Cache
创建 Repository
创建 Service
放入 DI Container
业务代码按需获取

这里的:

DI Container

全称是:

Dependency Injection Container,依赖注入容器。

可以把它理解成:

统一管理对象创建和关系的“总仓库”。


规则 1:业务类不主动 new / 实例化基础设施

例如不要:

class AgentService:

    def __init__(self):
        self.db = AgentDatabase()

而应该:

class AgentService:

    def __init__(self, db):
        self.db = db

这样:

AgentService

只知道:

我有一个 db 可以用。

至于这个 db:

是不是 SQLite
是不是 PostgreSQL
是不是 Mock

都不是它关心的事。


规则 2:共享资源尽量集中创建

例如:

Database Engine
Session Factory
HTTP Client
LLM Client
Cache Registry
Vector Store

这些对象通常不应该每个请求都重新创建。

更合理的是:

应用启动
创建一次
统一复用

这既是 DI,也是资源生命周期管理。


规则 3:Service 的依赖从构造函数或容器获得

例如:

class UserService:

    def __init__(self, user_db):
        self.user_db = user_db

或者:

class AgentService:

    def __init__(self, container):
        self._agent_db = container.agent_db
        self._cache = container.cache

重点不是形式,而是:

依赖来自外部,而不是内部临时创建。


规则 4:API 不自己创建 Service

不要:

@router.get("/agents")
async def list_agents():
    service = AgentService()

而应该:

@router.get("/agents")
async def list_agents(
    service = Depends(get_service)
):
    ...

这里的 Depends 是 FastAPI 提供的:

Dependency Injection,依赖注入机制。

它负责在执行接口之前,把依赖准备好。


规则 5:禁止为了方便绕过容器

这一点对 AI 特别重要。

例如项目已经有:

container.agent_service

AI 却为了方便重新写:

service = AgentService(container)

技术上可能能运行,但会绕开统一生命周期管理。所以应该明确:

已有容器管理的对象必须复用,不允许随意重新实例化。


三、这样做有什么好处?

DI 经常被新人理解成:

“只是换一种传参数的方法。”

其实远不止如此。


1. 更容易替换实现

假设现在:

AgentService
SQLite Repository

以后需要换成:

AgentService
PostgreSQL Repository

如果依赖是注入的:

AgentService 本身可能根本不需要修改。

这就是:

低耦合。

也就是:

一个模块不应该死死绑定某个具体实现。


2. 更容易测试

正式运行时:

AgentService
Real Agent Database

测试时:

AgentService
Fake Agent Database

例如:

fake_db = FakeAgentDatabase()

service = AgentService(
    db=fake_db
)

测试不需要真的启动数据库。

这就是 DI 对:

Testability,可测试性

最大的帮助之一。


3. 系统资源更容易统一管理

例如数据库 Engine:

创建一次
Repository 共用

而不是:

UserService
 → 一个 Engine

AgentService
 → 又一个 Engine

ToolService
 → 再一个 Engine

尤其对于:

数据库连接池
LLM Client
HTTP Client
向量数据库

统一生命周期非常重要。


4. 配置切换更简单

例如开发环境使用:

Local LLM

生产环境使用:

Cloud LLM

如果业务代码直接:

client = OpenAIClient(...)

就会和具体实现绑定。

DI 后可以变成:

开发环境
LocalLLMClient

生产环境
CloudLLMClient

业务逻辑只接收:

LLM Client

而不用自己决定是谁。


5. AI 不容易偷偷制造新的基础设施

这是 AI 编程中特别有价值的一点。

如果没有 DI 规则,AI 遇到:

“这里需要数据库。”

很容易:

db = Database(...)

遇到:

“这里需要缓存。”

又:

cache = Redis(...)

最后整个项目出现很多重复对象。

有 DI 以后,AI 的第一反应应该变成:

项目里已经有这个依赖了吗?

而不是:

我怎么创建一个新的?


四、提示词落地:把 DI 规则告诉 AI

只写:

“使用依赖注入。”

仍然不够具体。可以直接写进项目规则:

## 依赖注入规则

本项目使用依赖注入 (DI)。

规则:

- 业务类不得创建内部基础设施依赖项。
- 不得在业务方法内部直接实例化数据库、存储库、缓存、LLM 客户端、HTTP 客户端、向量存储或服务。
- 重用应用程序 ServiceContainer 管理的依赖项。
- 共享基础设施资源必须集中创建和管理。
- 服务应通过构造函数注入或现有应用程序容器接收所需的依赖项。
- FastAPI 路由必须使用项目现有的依赖项解析机制。
- 不得在 API 路由内部直接实例化 Service 类。
- 创建新对象之前,请检查 ServiceContainer 中是否已存在等效实例。
- 尊重容器管理的单例或共享资源的生命周期。
- 依赖项在测试中必须尽可能可替换。

然后具体任务可以这样写:

为 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 当前有一个非常明确的核心类:

backend/app/core/service_container.py

文件本身的说明就是:

# Application-level service container,
# Implement Dependency Injection.

即:

应用级 Service Container,用于实现依赖注入。


1. 数据库 Engine 只在容器集中创建

ServiceContainer 中先统一创建:

self.engine = create_async_engine(
    database_url,
    echo=False,
    future=True,
)

self.session_factory = async_sessionmaker(
    bind=self.engine,
    ...
)

也就是说:

Service A
Service B
Service C

不需要各自再:

create_async_engine(...)

而是共享容器管理的数据库基础设施。


2. Repository 也统一创建

容器接着创建:

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 等数据访问对象。

形成:

ServiceContainer
       ├── engine
       ├── session_factory
       ├── user_db
       ├── agent_db
       ├── tool_db
       ├── kb_db
       └── ...

所有对象的来源非常清楚。


3. Service 继续由容器统一创建

例如:

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)

于是整体关系变成:

ServiceContainer
       ├── Repository
       ├── Runtime
       ├── Cache
       ├── Registry
       └── Service

也就是说:

对象在哪里创建,由容器决定。


4. 例子:AgentService 自己不创建数据库

再看真实的:

backend/app/services/admin/agent.py

AgentService 的构造函数是:

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

这里没有:

AsyncAgentDatabase(...)

也没有:

CacheRegistry()

而是:

容器已经准备好
AgentService 获取并使用

这就是依赖注入。


5. 例子:FastAPI Router 也不自己创建 Service

再来看 Agent API。

它定义:

def get_service(
    request: Request
) -> AgentService:

    return request.app.state.container.agent_service

接口则:

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
    )

这里的:

Depends(get_service)

就是 FastAPI 的依赖注入机制。

可以理解成:

HTTP Request
FastAPI Depends
get_service()
ServiceContainer
已经存在的 AgentService
Router 使用

Router 没有:

svc = AgentService(...)

6. 最终形成了怎样的依赖关系?

下面是 miniagent 的 依赖注入 / DI 实现原理图:

miniagent DI 架构

可以简化成:

flowchart TD START["Application Startup
应用启动"] CONTAINER["ServiceContainer
依赖注入容器"] DB["Database
数据库"] RUNTIME["Runtime
运行时"] CACHE["Cache
缓存"] REPO["Repository
数据访问"] REGISTRY["Registry
注册中心"] SERVICE["Service
业务服务"] DEPENDS["FastAPI Depends
依赖注入"] ROUTER["Router
路由"] START --> CONTAINER CONTAINER --> DB CONTAINER --> RUNTIME CONTAINER --> CACHE DB --> REPO RUNTIME --> REGISTRY CACHE --> REGISTRY REPO --> SERVICE REGISTRY --> SERVICE SERVICE --> DEPENDS DEPENDS --> ROUTER

这里最重要的一条原则就是:

依赖在外部创建,从外向内注入。

而不是:

Router
自己 new Service
Service 自己 new Repository
Repository 自己 new Database

六、 为什么这比内部实例化更好?

如果直接写成:

class AgentService:

    def __init__(self):
        self._agent_db = AsyncAgentDatabase(...)
        self._tool_db = AsyncToolDatabase(...)
        self._cache = CacheInvalidator(...)

AgentService 就必须知道:

Engine 怎么创建
SessionFactory 从哪里来
Cache Registry 怎么配置
Repository 怎么构造

它承担了太多本不属于自己的职责。而当前结构下:

AgentService

只负责:

使用这些能力完成 Agent 业务。


七、这对 AI 编程意味着什么?

假设以后让 AI:

给系统增加 EmailService。

没有 DI 规则时,它可能:

mailer = SMTPMailer(
    host=...,
    port=...
)

然后在很多地方重复出现。

有 DI 规则后,合理思路应该变成:

1. 这是共享服务吗?
2. 是否应该进入 ServiceContainer?
3. 邮件 Client 在哪里创建?
4. 哪些 Service 需要它?
5. 通过容器注入使用

同样:

Redis
LLM Client
Vector Store
Web Search Client
SQL Agent

都遵循相同原则。

于是 AI 的行为从:

“缺什么,我就现场造什么。”

变成:

“缺什么,我先检查系统是否已经提供。”

这其实是非常大的变化。


结语

DI 依赖注入经常被讲成一个很抽象的设计模式。

但对于 AI 编程,我们完全可以用一句话理解:

不要让业务代码自己创建它依赖的对象。

为什么?

因为一旦 AI 可以自由实例化:

Database
Cache
LLM
HTTP Client
Repository
Service

它很容易为了当前功能快速创建新对象。几十次以后,系统就会出现:

重复实例
重复配置
资源浪费
测试困难
替换困难
生命周期混乱

而 DI 给 AI 规定了一条非常重要的工程纪律:

对象的创建
集中管理

对象的使用
按需注入

于是系统变成:

ServiceContainer
负责“谁是谁、怎么创建”

Service
负责“怎么完成业务”

Router
负责“怎么接收请求”

AI 不再需要到处考虑:

“我要怎么创建 Database?”

它只需要关心:

“项目已经给我的 Database 应该怎么使用?”

所以:

DI 的真正价值,不只是少写几个 new,而是把“对象创建权”从业务代码手里收回来。

对于 AI 编程来说,这一点尤其重要。

禁止 AI 随意实例化,让依赖由架构统一供应。

这样才能获得真正的:

低耦合、可测试、可替换、可扩展。

开源代码


🪐祝您好运🪐