Files
Alfred/docs/development/backend.md

145 lines
8.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 后端开发
本文档说明 alfred 后端的模块实现细节、API 索引和开发方式。开发规范库优先级、类型规范、配置契约、API 路由规范、测试约定等)见 [开发规范文档](README.md#开发规范)。
适用场景:修改 `src/server/`、了解现有模块 API、查找可供复用的工具函数和数据访问接口。
## 共享工具
`src/server/helpers/` 提供跨路由共用的工具模块:
- `response.ts` — 响应格式化工具:
- `createApiError(error, status)` — 构造 API 错误体
- `createHeaders(mode, init)` — 创建响应 Headers生产模式附加安全 header
- `createMetaResponse(version)` — 构造应用元信息响应
- `formatDuration(ms)` — 格式化毫秒时长为可读字符串
- `jsonResponse(body, options)` — JSON 响应构造
- `url.ts` — URL 工具:
- `parseIdFromUrl(url)` — 从 URL 解析路径中的 ID
`src/server/middleware/` 提供 API 参数校验和错误处理中间件:
- `validate.ts` — 参数校验函数:
- `validateIdParam(idStr, mode)` — 校验 ID 参数格式(字母数字 + `_` `-`
- `validatePagination(pageParam, pageSizeParam, mode)` — 校验分页参数page 最小 1pageSize 最大 200
- `validateTimeRange(from, to, mode)` — 校验时间范围参数
- `error-handler.ts` — 错误处理中间件:
- `AppError` — 业务异常类(含 statusCode
- `withErrorHandler(fn, mode, logger?)` — 包裹路由 handler捕获 AppError 和未知异常
## 数据库
项目使用 SQLite 作为存储后端,通过 bun:sqlite + Drizzle ORM 实现类型安全的数据访问。
### schema 定义
`src/server/db/schema.ts` 使用 Drizzle ORM 定义表结构,列名使用 snake_caseTypeScript 类型使用 camelCaseDrizzle schema 负责映射。
### 数据库连接
`src/server/db/connection.ts``createDatabase(dataDir, logger)` 打开 `<dataDir>/alfred.db`,设置 PRAGMAforeign_keys=ON、journal_mode=WAL、busy_timeout=5000
### migration 机制
- 开发期:使用 `drizzle-kit generate` 从 TS schema 生成 SQL migration 文件到 `drizzle/` 目录
- 生产期:构建时将 `drizzle/*.sql` 嵌入可执行文件,启动时自动应用 pending migrations
- 每次 migration 前自动备份现有 DB 到 `<dataDir>/backups/alfred-<timestamp>.db`
- migration 在事务中执行,失败则回滚并停止启动
### 数据访问
`src/server/db/projects.ts` 提供项目数据访问函数(`createProject``getProject``listProjects``updateProject``deleteProject``archiveProject``restoreProject`)。`src/server/db/providers.ts` 提供供应商数据访问函数(`createProvider``getProvider``listProviders``listProviderOptions``updateProvider``deleteProvider`)。`src/server/db/models.ts` 提供模型数据访问函数(`createModel``getModel``listModels``getModelsByProviderId``updateModel``deleteModel`)。`src/server/db/conversations.ts` 提供会话和消息数据访问函数(`createConversation``getConversation``listConversations``updateConversation``updateConversationTimestamp``deleteConversation``createMessage``createMessages``listMessages`)。`src/server/db/connection.ts` 还提供 `paginateQuery``wrap` 工具函数。输入输出使用 `src/shared/api.ts` 的类型。函数内部使用 Drizzle query builder 包装 `bun:sqlite` Database。
## AI 服务层
`src/server/ai/` 提供 AI Provider Registry 构建与连接测试能力。
### 类型定义
`src/server/ai/types.ts` 定义 AI 配置类型:
- `AIProviderConfig` — 供应商配置name、type、baseUrl、apiKey
- `AIModelConfig` — 模型配置providerId、modelId、capabilities
- `AIRegistryConfig` — Registry 构建配置providers、models供后续 AI 调用层组合使用
### Registry 构建
`src/server/ai/registry.ts` 提供:
- `buildProviderRegistry(db)` — 从 DB 查询所有供应商,构建 Vercel AI SDK Provider Registry
- `testProviderConnection(config)` — 先测试 Base URL 可达性,再请求 `/models` 验证 API Key 和模型列表接口
- `testModelConnection(config)` — 先通过 providerId 查询供应商(含 apiKey再以 modelId 请求对应模型的 API 连通性
- `countModels(db)` — 统计 DB 中已配置的模型总数,用于 Agent 调用前的可用性检查
每次 AI 调用时从 DB 查询 providers构建 registry 后通过 `registry.languageModel('providerId:modelId')` 获取模型实例。不使用缓存层。模型是否存在以及业务能力标签由调用方基于 models 表先行校验registry 只负责将 providerId/modelId 映射到 AI SDK 模型实例。
### Agent 流式调用
`src/server/ai/agents/alfred-agent.ts` 提供 `createAlfredAgent(model)` 工厂函数,返回 AI SDK `ToolLoopAgent` 实例。默认使用 `stepCountIs(20)` 限制最大迭代次数,配置 `getCurrentTime` 工具供 AI 调用。路由层通过 `createAgentUIStreamResponse()` 转为 SSE 响应,流结束通过 `onFinish` 回调可靠持久化 AI 回复(含完整 `parts`)。
### 供应商连通性测试
供应商连通性测试返回 `{ providerTestResponse: { ok, message } }`,前端根据 `ok` 展示成功或失败提示。
- `POST /api/providers/test` — 使用表单中尚未保存的供应商配置测试连接
- `POST /api/models/test` — 使用模型关联供应商配置和 modelId 测试模型连接
测试连接不会写入数据库也不会阻止保存。Base URL 不可达或 API Key 无效返回 `ok: false`Base URL 可达但 `/models` 不支持、非标准或返回非鉴权错误时返回 `ok: true` 并在 `message` 中提示用户可检查 URL 或忽略提醒。
### 支持的供应商类型
| type | AI SDK factory |
| ------------------- | --------------------------------------------------- |
| `openai` | `createOpenAI({ apiKey, baseURL })` |
| `anthropic` | `createAnthropic({ apiKey, baseURL })` |
| `openai-compatible` | `createOpenAICompatible({ name, apiKey, baseURL })` |
## 聊天 API
聊天 API 按项目维度组织会话和消息:
| 方法 | 路径 | 说明 |
| ------ | ----------------------------------------------- | ------------------ |
| GET | `/api/projects/:id/conversations` | 列出项目下所有会话 |
| POST | `/api/projects/:id/conversations` | 创建新会话 |
| GET | `/api/projects/:id/conversations/:cid` | 获取会话详情 |
| PATCH | `/api/projects/:id/conversations/:cid` | 更新会话标题或模型 |
| DELETE | `/api/projects/:id/conversations/:cid` | 删除会话及其消息 |
| GET | `/api/projects/:id/conversations/:cid/messages` | 获取会话消息列表 |
| POST | `/api/projects/:id/chat` | 发送消息并流式回复 |
聊天路由处理器位于 `src/server/routes/chat/`,遵循统一的 handler 模式。`send.ts` 处理发送消息:验证会话归属后只保存最后一条用户消息到 DB通过 `createAlfredAgent` 创建 agent调用 `createAgentUIStreamResponse` 返回 SSE UI 消息流,流结束通过 `onFinish` 回调保存 AI 回复到 DB。
## 日志模块
后端运行时代码统一通过 Logger 接口输出日志。
| 实现 | 用途 |
| --------------------- | ------------------------ |
| PinoLoggerWrapper | 生产运行时 |
| ConsoleFallbackLogger | 配置加载失败前的降级日志 |
| NoopLogger | 静默丢弃日志 |
| MemoryLogger | 测试替身 |
## 版本管理
版本号以 `package.json.version` 为唯一来源。
版本获取方式:
- 开发模式:`src/server/version.ts` 运行时从 package.json 读取
- 生产模式:`scripts/build.ts` 在构建时将版本号烘焙为字面量注入
版本升迁命令:
```bash
bun run version:patch # 升迁 patch 版本
bun run version:minor # 升迁 minor 版本
bun run version:major # 升迁 major 版本
bun run version:set # 显式设置版本号
```
## 更新触发条件
修改后端模块 API、共享工具、数据库 schema、AI 服务层或聊天 API 时,必须更新本文档。