AI 应用为什么也需要 Repository 层?不要让 Agent 直接操作数据库 📅 2026/7/12 1:41:50 上一篇文章讨论了 Tool。Tool 让 Agent 具备了读取文件、查询系统、调用接口和执行动作的能力。但当 Tool、Agent、Workflow 真正开始运行之后另一个问题会马上出现这些运行过程中的数据放在哪里用户会话要不要保存任务状态要不要记录上传文档的元信息要不要管理模型调用结果要不要追踪工具调用日志要不要落库失败任务能不能重新执行这些问题都不是单纯调用 LLM 能解决的。它们属于数据访问和数据管理问题。所以AI 应用不是只有 Prompt 和模型。只要它进入真实业务场景就一定会产生数据。而只要产生数据就需要清晰的数据访问边界。这就是 Repository 层存在的意义。一、AI 应用不是无状态脚本很多 AI Demo 看起来是无状态的。用户输入一个问题。程序调用一次模型。模型返回一个答案。代码打印结果。到这里为止似乎不需要数据库也不需要 Repository。但真实 AI 应用通常不是这样。例如一个知识库问答系统至少会涉及用户上传了哪些文档。文档是否已经解析完成。文档被切成了哪些 chunk。chunk 是否已经写入向量库。用户发起了哪一次问答。本次问答检索到了哪些内容。模型最后返回了什么答案。调用过程中有没有报错。再比如一个 Agent 任务系统还会涉及任务是谁创建的。当前任务处于什么状态。Agent 执行了哪些步骤。调用了哪些 Tool。每个 Tool 的输入输出是什么。任务失败后能不能恢复。用户能不能查看历史执行记录。这些都是状态。只要系统有状态就不能把所有数据都放在内存里。也不能让每个模块随手写 SQL。二、为什么不能到处直接操作数据库最常见的写法是defrun_workflow(user_id:str,question:str):sessiondb.query(Session).filter(Session.user_iduser_id).first()docsdb.query(Document).filter(Document.statusready).all()answerllm.invoke(question)db.add(Message(user_iduser_id,contentanswer))db.commit()returnanswer这段代码在 Demo 阶段能跑。但问题也很明显。Workflow 里混进了数据库查询。模型调用和数据写入混在一起。表结构细节暴露给了业务流程。后面如果要把 SQLite 换成 MySQL就会牵动业务代码。如果要加缓存也不知道加在哪里。如果要写单元测试就必须准备真实数据库。如果要调整会话表结构很多 Workflow 和 Agent 都可能受影响。当项目规模变大后这种写法会让代码越来越难改。所以问题不是能不能直接查库。问题是直接查库会让数据访问逻辑扩散。Repository 层的作用就是把这些扩散的逻辑收回来。三、Repository 层解决什么问题Repository 可以理解为数据访问对象。它负责把业务代码需要的数据操作封装起来。例如classConversationRepository:defget_by_id(self,conversation_id:str):...deflist_messages(self,conversation_id:str):...defadd_message(self,conversation_id:str,role:str,content:str):...业务代码不需要知道消息表叫什么。不需要知道字段怎么拼。不需要知道 ORM 查询怎么写。它只需要表达意图conversation_repo.add_message(conversation_idconversation_id,roleassistant,contentanswer,)Repository 层主要解决几个问题隔离数据库细节。收敛数据读写逻辑。让业务流程更清晰。方便替换存储实现。方便单元测试。方便后续加入缓存、事务和审计。它不是为了增加代码层级。而是为了让数据访问有固定入口。四、Repository 不应该负责业务决策Repository 很重要但不能把它写成万能层。它不应该决定业务流程。不应该调用 LLM。不应该调度 Agent。不应该决定任务下一步走向。不应该把复杂业务规则都塞进去。例如下面这种写法就不太合适classTaskRepository:defrun_task_with_agent(self,task_id:str):taskself.get(task_id)resultagent.run(task.goal)self.update_result(task_id,result)returnresult这个方法看起来方便但 Repository 已经开始调度 Agent 了。边界变乱之后后面会越来越难维护。更合理的做法是tasktask_repo.get(task_id)resultagent.run(task.goal)task_repo.update_result(task_id,result)Repository 负责取数据和存数据。Agent 负责执行任务。Workflow 负责组织流程。边界清楚代码才更稳定。五、AI 应用里常见的数据对象AI 应用里的数据对象和传统后端有重叠但也有自己的特点。常见对象包括User用户信息。 Conversation会话。 Message对话消息。 Task异步任务。 Document上传文档。 DocumentChunk文档切片。 EmbeddingRecord向量化记录。 ToolCallLog工具调用日志。 ModelCallLog模型调用日志。 WorkflowRun工作流运行记录。 AgentStepAgent 执行步骤。 ConfigSnapshot配置快照。这些对象不一定一开始都要实现。但脚手架应该给它们预留清晰的位置。例如最小版本可以先支持Conversation。Message。Document。Task。ToolCallLog。等项目复杂后再扩展 WorkflowRun、AgentStep、ModelCallLog 等记录。这样既不会一开始过度设计也不会把未来的路堵死。六、Repository 和数据模型的关系Repository 通常会依赖数据模型。如果使用 SQLAlchemy可以有类似结构classConversationModel(Base):__tablename__conversationsidColumn(String,primary_keyTrue)user_idColumn(String,nullableFalse)titleColumn(String)created_atColumn(DateTime)Repository 负责操作这个模型classConversationRepository:def__init__(self,session):self.sessionsessiondefget_by_id(self,conversation_id:str):return(self.session.query(ConversationModel).filter(ConversationModel.idconversation_id).first())这里要注意一个边界数据模型不一定等于业务对象。数据库模型关注表结构。业务对象关注业务含义。在小项目里可以直接返回 ORM 对象。但在复杂项目里更建议把数据库模型转换成业务 DTO 或实体对象。这样数据库字段变化时不会直接影响所有业务层代码。七、SQLite 适合作为脚手架默认选择脚手架要降低启动成本。所以默认使用 SQLite 是合理的。因为 SQLite 不需要单独安装数据库服务。不需要配置用户名密码。不需要先建库。一个本地文件就可以跑起来。对于新项目、Demo、教学、原型验证来说SQLite 很友好。例如data/ └── ai_scaffold.db开发者可以先把核心流程跑通。等项目进入多人协作或生产环境再切换到 MySQL 或 PostgreSQL。但这里有一个关键点默认用 SQLite不等于代码只能绑定 SQLite。Repository 接口要提前设计好。底层数据库可以变但上层调用方式尽量不变。八、从 SQLite 切换到 MySQL 或 PostgreSQL当 AI 应用进入生产环境时SQLite 往往不够。例如多实例部署。并发写入增加。数据量变大。需要更强的事务能力。需要备份和运维工具。需要和已有业务数据库集成。这时就需要 MySQL 或 PostgreSQL。如果业务代码到处直接写 SQLite 相关逻辑迁移会很痛苦。如果数据访问都通过 Repository就可以把变化收敛在基础设施层。例如配置可以这样设计DATABASE_URLsqlite:///data/ai_scaffold.db切换生产环境时DATABASE_URLpostgresql://user:xxxxhost:5432/ai_app上层代码仍然调用task_repo.create_task(...)conversation_repo.add_message(...)document_repo.mark_ready(...)这就是接口稳定带来的好处。九、Repository 和 SQLAlchemy 的边界如果项目使用 SQLAlchemy是否还需要 Repository这个问题很常见。SQLAlchemy 已经提供了 ORM 能力。但 ORM 不是业务数据访问边界。ORM 解决的是 Python 对象和数据库表之间的映射。Repository 解决的是业务代码如何访问数据。两者关注点不同。可以这样理解SQLAlchemy怎么查表、怎么映射、怎么管理 session。 Repository业务需要什么数据操作入口。例如 SQLAlchemy 允许你写session.query(DocumentModel).filter(DocumentModel.statusready).all()但业务层更应该看到document_repo.list_ready_documents()前者是技术表达。后者是业务表达。Repository 可以内部使用 SQLAlchemy。但不要让 SQLAlchemy 查询语句散落在 Agent、Workflow 和 Tool 中。十、Repository 和 Agent 的关系Agent 不应该直接操作数据库。因为 Agent 的职责是围绕目标进行任务规划和执行。如果 Agent 直接写数据库它很容易同时承担调度、推理、持久化和审计职责。这会让 Agent 变得越来越重。更合理的关系是Agent ↓ Service / Workflow ↓ Repository ↓ Database或者在某些简单项目里Workflow ↓ RepositoryAgent 可以知道任务上下文。但不应该知道数据库表结构。Agent 可以产生执行步骤。但步骤如何保存应该交给 Repository 或 Service。例如stepagent.plan_next_step(context)agent_step_repo.save(task_idtask_id,stepstep)这样 Agent 的输出可以被记录。但 Agent 本身不用关心数据库细节。十一、Repository 和 Tool 的关系Tool 有时需要读取或写入业务数据。例如查询用户订单。创建工单。更新任务状态。检索文档元数据。保存工具调用结果。但 Tool 也不应该绕过业务边界直接操作底层表。如果 Tool 直接写 SQL会带来两个问题。第一权限不好控制。第二日志和审计不好统一。更合理的做法是classQueryOrderTool:def__init__(self,order_repository):self.order_repositoryorder_repositorydefrun(self,arguments:dict):order_idarguments[order_id]returnself.order_repository.get_order_summary(order_id)Tool 调用 Repository 提供的受控方法。Repository 控制数据访问。Tool 控制工具输入输出。Agent 控制是否使用这个工具。每一层都有自己的职责。十二、Repository 要方便测试Repository 层还有一个很实际的价值它让测试更容易写。如果 Workflow 直接依赖真实数据库测试会比较麻烦。但如果 Workflow 依赖 Repository 接口就可以替换成内存实现。例如classInMemoryTaskRepository:def__init__(self):self.tasks{}defcreate(self,task):self.tasks[task.id]taskdefget(self,task_id):returnself.tasks.get(task_id)测试时可以这样task_repoInMemoryTaskRepository()workflowRagWorkflow(task_repositorytask_repo)这样不需要启动数据库。也不需要准备复杂的测试数据。对于脚手架来说这一点很重要。因为脚手架应该鼓励开发者从一开始就写可测试的 AI 应用。十三、Repository 在 AI Scaffold 里的位置在 AI Scaffold 中Repository 可以作为独立模块存在。项目结构可以这样设计app/ ├── agents/ │ └── assistant_agent.py ├── workflows/ │ └── rag_workflow.py ├── tools/ │ └── document_tools.py ├── repositories/ │ ├── __init__.py │ ├── base.py │ ├── conversation_repository.py │ ├── document_repository.py │ ├── task_repository.py │ └── tool_call_repository.py ├── models/ │ ├── conversation.py │ ├── document.py │ └── task.py ├── db/ │ ├── session.py │ └── migrations/ └── config/ └── settings.py其中models/放数据库模型。repositories/放数据访问接口。db/放数据库连接、session 和迁移。config/放数据库配置。workflows/和agents/只调用 Repository 暴露的方法。这样项目结构会更清楚。后面如果要加缓存可以在 Repository 内部加。如果要换数据库也主要改 db 和 models 层。如果要记录审计日志也可以在 Repository 或 Service 层统一处理。十四、总结AI 应用不是只有模型调用。只要进入真实项目它就会产生大量状态和数据。这些数据包括会话、消息、任务、文档、向量化记录、模型调用记录、工具调用日志和工作流执行记录。如果没有 Repository 层数据访问逻辑很容易散落在 Agent、Workflow 和 Tool 里。这样项目早期看起来简单后期会越来越难维护。一个合理的 Repository 设计至少应该做到隔离数据库细节。提供稳定的数据访问接口。不承担业务调度职责。不直接调用 LLM 或 Agent。支持 SQLite 作为默认本地存储。支持后续切换 MySQL 或 PostgreSQL。能和 SQLAlchemy 等成熟工具配合。方便单元测试和替换实现。让 Agent、Workflow、Tool 不直接依赖底层表结构。对于 AI Scaffold 来说Repository 层是 AI 应用从 Demo 走向工程化的重要基础。它不一定是最显眼的模块。但它决定了项目能不能长期维护、能不能稳定演进、能不能从本地原型走向生产系统。下一篇文章可以继续讨论 MemoryAI 应用里的记忆能力到底应该保存什么什么不应该保存以及如何在能力和隐私之间划清边界。