# 第二阶段接口契约与开发规划 > 文档状态:接口冻结草案 > > 更新日期:2026-09-01 > > 依据:`../architecture/第二阶段团队分工表.md`、`../architecture/AI笔记软件技术栈说明-团队版-v2.3.md`、`后端接口契约-开发版.md` 本文统一第二阶段新增能力的 HTTP、SSE、前端 Service、桌面 Host 和内部模块接口。文中标记为“计划新增”的路径尚未实现,不能据此判断当前服务已经支持;实现完成后以 FastAPI `/openapi.json`、TypeScript Wire DTO 和自动化测试共同作为最终依据。 人员分工和任务优先级不在本文重复维护,以第二阶段分工表为准。 --- ## 1. 规划原则 1. 第一阶段已经发布的路径和字段保持兼容,第二阶段优先增加可选字段、子资源和新事件。 2. 前端只消费项目 Contract,不解析 Plugin Manifest、MCP 原始消息或模型厂商响应。 3. Agent、Benchmark 和 Trace 共用同一运行事件,不建立测试专用的旁路执行协议。 4. 长任务统一建模为 Job,创建接口快速返回,进度通过查询或 SSE 获取。 5. 文件、附件、插件包和 Dataset 均使用受控 ID;HTTP 请求不接受任意本地绝对路径。 6. Secret 只经专用写入接口提交,普通读取接口只返回是否配置和引用 ID。 7. Mermaid、函数图像和导出器共享静态渲染 Contract,不从 Vue 组件 DOM 抓取结果。 8. 所有第三方协议在 Adapter 边界转换,错误进入前端前映射为项目错误码。 ## 2. 状态说明 | 标记 | 含义 | | --- | --- | | 已实现 | 第一阶段接口已经存在,第二阶段保持兼容 | | 扩展 | 路径已存在,第二阶段增加字段、事件或行为 | | 计划新增 | 第二阶段需要新增实现 | | 内部 Contract | 不直接暴露 HTTP,由两个模块共同遵守 | | Host Contract | 由 Tauri/Rust Host 提供,Web 开发模式可使用 Mock Adapter | ## 3. 第二阶段接口总览 | 域 | 方法 | 路径或 Contract | 状态 | 用途 | | --- | --- | --- | --- | --- | | Transcription | POST | `/api/media/transcriptions` | 扩展 | 创建真实音频转写任务 | | Transcription | GET | `/api/media/transcriptions` | 计划新增 | 分页获取转写任务 | | Transcription | GET | `/api/media/transcriptions/{job_id}` | 扩展 | 获取分段转写和任务状态 | | Transcription | GET | `/api/media/transcriptions/{job_id}/events` | 计划新增 | 订阅模型加载、分离和转写进度 | | Transcription | POST | `/api/media/transcriptions/{job_id}/cancel` | 计划新增 | 取消音频任务 | | Transcription | POST | `/api/media/transcriptions/{job_id}/notes` | 计划新增 | 将 Transcript 写入 Knowledge Core | | Agent Trace | GET | `/api/agent/runs/{run_id}/events` | 已实现 | 支持游标恢复并增加模型与权限事件 | | Agent Trace | GET | `/api/agent/runs/{run_id}/trace` | 已实现 | 分页读取可回放 Trace 快照 | | Plugin Host | GET | `/api/plugins/{plugin_id}/host` | 已实现 | 获取 MCP Host 健康状态 | | Plugin Host | POST | `/api/plugins/{plugin_id}/host/restart` | 已实现 | 重启异常 Host 并重新发现 Tool | | Plugin Command | GET | `/api/plugin-contributions/commands` | 计划新增 | 获取前端可展示的 Command | | Plugin Command | POST | `/api/plugin-contributions/commands/{command_id}/execute` | 计划新增 | 受控执行 Command | | Plugin Settings | GET | `/api/plugins/{plugin_id}/settings` | 计划新增 | 获取 Schema 与非敏感配置 | | Plugin Settings | PUT | `/api/plugins/{plugin_id}/settings` | 计划新增 | 更新非敏感配置 | | Plugin Settings | PUT/DELETE | `/api/plugins/{plugin_id}/settings/{key}/secret` | 计划新增 | 写入或删除 Secret Reference | | Provider | 现有路径 | `/api/providers/*`、`POST /api/chat` | 扩展 | 补齐协议能力和统一行为 | | Retrieval | GET/POST | `/api/index/status`、`/api/index/rebuild` | 扩展 | 暴露 Embedding 兼容状态并安全重建向量 | | Benchmark | GET | `/api/benchmarks/datasets` | 计划新增 | 枚举受控 Dataset | | Benchmark | POST | `/api/benchmarks/rag/runs` | 计划新增 | 创建 RAG Benchmark | | Benchmark | POST | `/api/benchmarks/agent/runs` | 计划新增 | 创建 Agent Benchmark | | Benchmark | GET | `/api/benchmarks/runs` | 计划新增 | 分页获取 Benchmark Run | | Benchmark | GET/POST | `/api/benchmarks/runs/{run_id}/*` | 计划新增 | 查询、订阅、取消和读取报告 | | Export | POST | `/api/exports` | 计划新增 | 创建 HTML/PDF/DOCX 导出任务 | | Export | GET | `/api/exports` | 计划新增 | 分页获取导出任务 | | Export | GET | `/api/exports/{job_id}` | 计划新增 | 查询导出任务 | | Export | GET | `/api/exports/{job_id}/file` | 计划新增 | 下载已完成产物 | | Export | POST | `/api/exports/{job_id}/cancel` | 计划新增 | 取消导出任务 | | Theme | Host Contract | `ThemePackageService` | 计划新增 | 导入、预览、启停和卸载主题包 | | Renderer | 内部 Contract | `StaticRenderer` | 计划新增 | Mermaid/Function Plot 预览和导出复用 | --- ## 4. 公共协议 ### 4.1 JSON 与时间 - HTTP JSON 字段统一使用 `snake_case`。 - TypeScript Wire DTO 与 JSON 保持同名,View Model 可在 Service 层转换。 - 时间使用 UTC ISO 8601,例如 `2026-08-31T10:30:00Z`。 - 耗时统一使用 `duration_ms`,音视频时间使用秒数浮点值。 - ID 是不透明字符串,调用方不得解析前缀或依赖生成规则。 - 枚举新增值时前端必须提供 unknown fallback,不能使整个页面渲染失败。 ### 4.2 Job 状态 第二阶段异步任务统一使用: ```text queued running completed failed cancelled ``` 第一阶段 Transcription 曾使用 `processing`。迁移期间前端 Wire DTO 同时接受 `processing` 和 `running`,Service 统一映射为 `running`;所有消费者更新后,后端再停止返回旧值。 通用进度结构: ```json { "phase": "transcribing", "current": 42, "total": 100, "percent": 42.0, "message": "正在转写音频" } ``` `percent` 可以为 `null`,调用方不能以 100 代替 `completed`。终态 Job 必须有 `completed_at`;失败 Job 必须提供项目错误码,不直接暴露第三方堆栈。 ### 4.3 分页和游标 普通资源列表继续使用: ```json { "items": [], "page": { "total": 0, "limit": 50, "offset": 0 } } ``` 事件和 Trace 使用序列游标: ```json { "items": [], "next_sequence": 120, "has_more": false } ``` `after_sequence` 表示只返回大于该值的事件。事件序号只保证在一个资源内单调递增,客户端不得假设从 0 或 1 开始。 ### 4.4 SSE SSE 事件统一包含 `id`、`event` 和 JSON `data`: ```text id: 42 event: Progress data: {"sequence":42,"resource_id":"job_123","data":{"percent":50},"timestamp":"2026-08-31T10:30:00Z"} ``` - `id` 等于可恢复的事件 sequence。 - 客户端重连时发送 `Last-Event-ID`;开发环境也可以使用 `?after_sequence=42`。 - 服务端先回放缺失事件,再切换为实时事件。 - 重放与实时交界处允许重复,客户端按资源 ID 与 sequence 去重。 - `Completed`、`Failed`、`Cancelled` 是终止事件;正常断流但没有终止事件时,客户端按可重连处理。 - 心跳使用 SSE 注释行,不创建业务事件。 ### 4.5 统一错误 沿用第一阶段结构: ```json { "error": { "code": "RESOURCE_NOT_FOUND", "message": "Resource was not found.", "details": {} } } ``` `message` 用于用户提示,`details` 只放可安全展示和定位的结构化信息。前端业务分支只判断 `code`。 --- ## 5. Multimodal / Transcription ### 5.1 创建转写任务 `POST /api/media/transcriptions`,扩展现有接口,成功返回 `202 Accepted`。 请求: ```json { "attachment_id": "attachment_123", "language": null, "diarization": true, "word_timestamps": false, "profile_id": "default", "metadata": {} } ``` 约束: - `attachment_id` 必须属于当前 Vault 的 Host 管理附件。 - `language = null` 表示自动识别。 - `profile_id` 引用服务端模型配置,不允许请求直接传 CUDA 路径或任意模型目录。 - `metadata` 只用于关联业务上下文,不进入模型参数。 响应: ```json { "job_id": "transcription_123", "attachment_id": "attachment_123", "status": "queued", "progress": null, "language": null, "diarization_enabled": true, "segments": [], "text": null, "warnings": [], "error": null, "created_at": "2026-08-31T10:30:00Z", "started_at": null, "updated_at": "2026-08-31T10:30:00Z", "completed_at": null } ``` ### 5.2 Transcript Segment ```json { "segment_id": "segment_001", "speaker": "SPEAKER_00", "start_time": 12.4, "end_time": 18.9, "text": "这一部分介绍进程调度。", "confidence": 0.93, "words": [] } ``` `speaker`、`confidence` 和 `words` 可以为空。Segment 按 `start_time` 排序且不得重叠为负时长。没有 pyannote 能力时任务可以完成,但必须在 `warnings` 中返回 `DIARIZATION_UNAVAILABLE`。 ### 5.3 查询、事件和取消 | 方法 | 路径 | 响应 | | --- | --- | --- | | GET | `/api/media/transcriptions?status=&limit=&offset=` | `TranscriptionJobListResponse` | | GET | `/api/media/transcriptions/{job_id}` | 完整 `TranscriptionJob` | | GET | `/api/media/transcriptions/{job_id}/events` | `TranscriptionEvent` SSE | | POST | `/api/media/transcriptions/{job_id}/cancel` | `OperationResponse` | 事件类型: ```text Queued ModelLoading DiarizationStarted DiarizationCompleted TranscriptionStarted Progress Completed Failed Cancelled ``` 取消必须向正在运行的模型任务传播。接口返回 accepted 只表示取消请求已记录,最终状态以 Job 或终止事件为准。 ### 5.4 写入 Knowledge Core `POST /api/media/transcriptions/{job_id}/notes` ```json { "title": "操作系统课程录音", "folder": "课程/操作系统", "include_timestamps": true, "include_speakers": true, "template_id": null } ``` 仅 completed Job 可以转换。响应返回第一阶段 `Note` Contract。生成的 Block Citation 需要保留 `source_audio`、`start_time`、`end_time` 和 `speaker`,使搜索与 Agent 引用能够跳回音频。 ### 5.5 Multimodal 错误码 ```text ATTACHMENT_NOT_FOUND UNSUPPORTED_AUDIO_FORMAT TRANSCRIPTION_PROFILE_NOT_FOUND TRANSCRIPTION_MODEL_UNAVAILABLE DIARIZATION_MODEL_UNAVAILABLE TRANSCRIPTION_FAILED TRANSCRIPTION_CANCELLED TRANSCRIPT_NOT_READY ``` --- ## 6. Agent Trace ### 6.1 兼容现有 AgentEvent 保留现有事件: ```text RunStarted TextDelta ThinkingDelta ToolCall ToolResult PermissionRequired Usage Citation RunCompleted RunFailed RunCancelled ``` 第二阶段增加: ```text ModelCallStarted ModelCallCompleted ModelCallFailed PermissionResolved ``` `GET /api/agent/runs/{run_id}/events` 增加 `Last-Event-ID` 和 `after_sequence` 支持,不改变现有事件 envelope。 ### 6.2 Trace 快照 `GET /api/agent/runs/{run_id}/trace?after_sequence=0&limit=200` ```json { "run_id": "run_123", "status": "running", "items": [], "next_sequence": 42, "has_more": false, "summary": { "model_calls": 2, "tool_calls": 3, "duration_ms": 1530, "token_usage": 2048, "errors": 0 }, "config_snapshot": {} } ``` Trace API 返回事件事实,不返回前端树形布局。前端根据 parent ID 和 sequence 构建时间线或树,Benchmark 使用相同事件计算指标。 ### 6.3 新增事件数据 `ModelCallStarted`: ```json { "model_call_id": "model_call_01", "provider_id": "deepseek", "model": "deepseek-chat", "step": 2 } ``` `ModelCallCompleted`: ```json { "model_call_id": "model_call_01", "duration_ms": 820, "finish_reason": "tool_calls", "input_tokens": 1200, "output_tokens": 240 } ``` `PermissionResolved`: ```json { "request_id": "permission_01", "permission": "notes.write", "decision": "allow_once" } ``` ToolCall 和 ToolResult 增加可选 `parent_model_call_id`、`duration_ms` 和经过截断/净化的摘要字段。完整敏感参数不进入 Trace。 ### 6.4 Trace 保留和脱敏 - `run_id + sequence` 是事件幂等键。 - 运行记录和事件由持久化 Repository 管理,不能只保存在 SSE 订阅队列。 - API Key、Authorization Header、Secret Setting、完整附件正文默认脱敏。 - 大型 Tool Result 保存摘要与受控 artifact 引用,不直接塞入事件 JSON。 - Trace 被 Benchmark 引用时保存运行配置快照,避免后续 Provider 配置变更导致报告不可解释。 ### 6.5 Trace 错误码 ```text AGENT_RUN_NOT_FOUND TRACE_NOT_AVAILABLE TRACE_CURSOR_EXPIRED TRACE_CURSOR_INVALID ``` --- ## 7. MCP Bridge 与 Plugin Contribution ### 7.1 MCP 内部 Adapter Agent Runtime 不使用 MCP 原始类型。MCP Bridge 必须完成: ```text MCP tools/list → JSON Schema 校验 → 项目 ToolDefinition → Tool Registry 项目 ToolCall → MCP tools/call → MCP Content / Error → 项目 ToolResult ``` 进入 Tool Registry 的名称使用 `.` 命名空间。重复名称、无效 Schema、未声明权限和超过结果大小上限时拒绝注册。 内部接口示意: ```python class McpBridge(Protocol): async def start(self, plugin: PluginManifest) -> "PluginHostStatus": ... async def discover_tools(self, plugin_id: str) -> list[ToolDefinition]: ... async def call_tool(self, plugin_id: str, call: ToolCall) -> ToolResult: ... async def cancel(self, plugin_id: str, request_id: str) -> None: ... async def stop(self, plugin_id: str) -> None: ... ``` 首个实现已支持本地 `stdio`,按 MCP `2025-11-25` 发起 initialize,并兼容 `2025-06-18`、`2025-03-26` 和 `2024-11-05` 协商结果。Host 负责 capability negotiation、分页 `tools/list`、进程生命周期、超时取消、stderr 隔离和异常退出后的 Tool 注销。stdio 消息使用 UTF-8 单行 JSON-RPC,并在完整行进入内存前执行有界读取;当前不实现 Streamable HTTP。 MCP Plugin 的 `backend` 增加: ```yaml backend: type: mcp transport: stdio command: uvx args: [--isolated, --from, example-mcp==1.2.3, example-mcp] startup_timeout_seconds: 60 tool_timeout_seconds: 30 ``` 命令通过参数数组直接启动,不经过 Shell。带路径的 executable 必须位于 Plugin 包内;PATH 中的命令可以按名称引用。Python 包形式的 MCP 推荐使用固定版本的 `uvx --isolated --from`,但 `uvx` 只隔离依赖而不是文件/网络/系统调用安全沙箱,非 Python Server 不强制使用。开发模式首次运行未缓存的 uvx 包可能联网解析,因此 startup 示例使用 60 秒;生产安装阶段必须预取并验证,运行阶段不得临时解析依赖。子进程只继承运行所需的系统环境变量,不继承 `OPENAI_API_KEY`、`APP_DB_PATH`、Vault 路径等宿主状态。Secret 注入留给阶段 D 的专用引用接口。 在平台沙箱和可信命令许可完成前,`APP_ENVIRONMENT != development` 时启用 MCP Plugin 必须返回 `403 MCP_TRUST_APPROVAL_REQUIRED`,不得启动进程或注册 Tool。该门禁由后端执行,不能只依赖前端提示或文档约定。 远端 Tool 的可选项目权限放在 MCP `_meta`: ```json { "_meta": { "notesagent/permission": "notes.read" } } ``` 该权限必须属于项目已知权限并同时出现在 Plugin Manifest 中。发现结果必须与 `contributes.tools` 的命名空间 ID 完全一致;校验全部成功后才一次性发布到 Tool Registry。 当前 Web 开发接口 `POST /api/plugins/install` 使用 `package_path`。桌面 Host 接入后,`ExtensionInstallRequest` 增加 `package_id`,由文件选择器产生临时包句柄;`package_path` 只在明确的 development 环境保留并标记 deprecated,生产构建拒绝任意前端路径。 ### 7.2 Plugin Host 状态 `GET /api/plugins/{plugin_id}/host` ```json { "plugin_id": "example-plugin", "backend_type": "mcp", "transport": "stdio", "status": "ready", "tools_count": 3, "started_at": "2026-08-31T10:30:00Z", "last_seen_at": "2026-08-31T10:31:00Z", "error": null } ``` Host 状态: ```text stopped starting ready unhealthy error ``` `POST /api/plugins/{plugin_id}/host/restart` 返回 `202 OperationResponse`。重启期间先注销旧 Tool,发现和校验全部成功后再一次性发布新 Tool 集合,避免半注册状态。 当前实现还返回协商后的 `protocol_version`、`server_name` 和 `server_version`。单条协议消息上限为 2 MiB,单次 Tool Result 上限为 256 KiB;超限分别按 Host/Result 错误处理。Server 异常退出或发送无效 stdout 消息时,Host 进入 `unhealthy`,Plugin 进入 `error`,相关 Tool 立即注销。取消会同时通知 Server、移除 pending request 并唤醒本地等待线程。Restart 不得把 `installed`、`disabled` 或 `permission_required` Plugin 隐式启用,这些状态必须走 Enable。 ### 7.3 Command Contribution 列表 `GET /api/plugin-contributions/commands?location=command_palette` ```json { "items": [ { "command_id": "example-plugin.open-search", "plugin_id": "example-plugin", "title": "打开外部搜索", "description": "在插件服务中搜索当前选区", "icon": "search", "locations": ["command_palette", "context_menu"], "when": ["workspace.has_vault", "editor.has_selection"], "parameters": { "type": "object", "properties": {} }, "enabled": true } ] } ``` `icon` 只能引用宿主图标 ID 或插件包内已验证资源。`when` 是宿主支持的条件 token 列表,不执行插件提供的 JavaScript 表达式。 ### 7.4 执行 Command `POST /api/plugin-contributions/commands/{command_id}/execute` ```json { "arguments": {}, "context": { "note_id": "note_123", "selection": "选中的文本" } } ``` 宿主根据 Command 声明裁剪 Context,插件不能要求前端上传整个 Pinia 状态。响应: ```json { "command_id": "example-plugin.open-search", "status": "completed", "effect": { "type": "notification", "payload": { "level": "success", "message": "命令已完成" } } } ``` 允许的 effect 首批为 `none`、`notification`、`navigate`、`refresh` 和 `job`。前端仅执行白名单 effect;未知类型显示结果但不执行。 ### 7.5 Settings Schema `GET /api/plugins/{plugin_id}/settings` ```json { "plugin_id": "example-plugin", "schema_version": 1, "fields": [ { "key": "result_limit", "label": "结果数量", "description": "每次最多返回的结果数", "type": "number", "required": true, "default": 10, "minimum": 1, "maximum": 100, "options": [] }, { "key": "api_key", "label": "API Key", "type": "secret", "required": true, "options": [] } ], "values": { "result_limit": 10 }, "secrets": { "api_key": { "configured": false } } } ``` 字段类型首批固定为: ```text string number boolean select secret ``` ### 7.6 更新 Settings 和 Secret `PUT /api/plugins/{plugin_id}/settings` ```json { "schema_version": 1, "values": { "result_limit": 20 } } ``` 该接口拒绝 secret 字段。Schema 版本过期返回 `PLUGIN_SETTINGS_VERSION_CONFLICT` 并附当前版本。 Secret 使用: ```text PUT /api/plugins/{plugin_id}/settings/{key}/secret DELETE /api/plugins/{plugin_id}/settings/{key}/secret ``` 写入请求: ```json { "secret": "仅在本次请求中出现的明文" } ``` 响应只返回: ```json { "plugin_id": "example-plugin", "key": "api_key", "configured": true } ``` Secret 明文不进入普通 Settings、日志、Trace、Benchmark Dataset 或前端持久化。 ### 7.7 Plugin/MCP 错误码 ```text PLUGIN_HOST_UNAVAILABLE PLUGIN_HOST_START_FAILED PLUGIN_HOST_UNHEALTHY MCP_INITIALIZE_FAILED MCP_CAPABILITY_UNSUPPORTED MCP_TOOL_SCHEMA_INVALID MCP_TOOL_CALL_FAILED MCP_TOOL_RESULT_TOO_LARGE MCP_TRUST_APPROVAL_REQUIRED PLUGIN_COMMAND_NOT_FOUND PLUGIN_COMMAND_CONTEXT_INVALID PLUGIN_SETTINGS_SCHEMA_INVALID PLUGIN_SETTINGS_VERSION_CONFLICT PLUGIN_SECRET_FIELD_NOT_FOUND ``` --- ## 8. Provider Adapter 扩展 第二阶段不新增平行 Provider CRUD,继续使用第一阶段接口: ```text GET /api/providers GET /api/providers/presets POST /api/providers GET /api/providers/{provider_id} PATCH /api/providers/{provider_id} DELETE /api/providers/{provider_id} GET /api/providers/{provider_id}/models POST /api/providers/test POST /api/chat ``` ### 8.1 ModelInfo 扩展 `GET /api/providers/{provider_id}/models` 的 item 增加可选字段: ```json { "model": "example-model", "display_name": "Example Model", "capabilities": ["chat", "streaming", "tool_calling"], "context_window": 128000, "max_output_tokens": 8192, "deprecated": false, "metadata": {} } ``` 模型列表无法可靠获取 Capability 时返回保守集合,不能默认声称支持 Tool Calling 或 Vision。 ### 8.2 Streaming 行为 继续使用现有 ModelEvent: ```text TextDelta ThinkingDelta ToolCallStart ToolCallDelta ToolCallEnd Usage Error Done ``` 第二阶段统一以下规则: - 每个事件包含单调递增 `sequence`。 - `ToolCallDelta` 按 `tool_call_id` 聚合,完成时参数必须是合法 JSON Object。 - `Usage` 字段统一为 `input_tokens`、`output_tokens`、`total_tokens`。 - Model Stream 在成功或失败后都发送且只发送一个终止事件 `Done`;失败顺序为 `Error` 后 `Done`,`Done.data.status` 为 `completed`、`failed` 或 `cancelled`。 - 浏览器取消 Fetch 或 SSE 后,服务端必须取消上游 Provider 请求。 - 不支持 reasoning 的 Provider 不发送伪造 ThinkingDelta。 ### 8.3 Provider 一致性测试 Contract 每个 Adapter 使用相同 Case 描述: ```yaml id: provider-tool-call-basic capabilities: - chat - streaming - tool_calling request_fixture: tool_call_weather assertions: terminal_event: Done tool_calls: 1 usage_required: false ``` 测试报告记录 Provider 类型、模型、协议、运行时间和跳过原因。真实网络测试与离线 Adapter 单元测试分组,CI 默认不要求外部 API Key。 ### 8.4 Provider 错误码 沿用并补齐: ```text PROVIDER_AUTH_FAILED PROVIDER_RATE_LIMITED PROVIDER_TIMEOUT PROVIDER_UNAVAILABLE PROVIDER_PROTOCOL_ERROR PROVIDER_STREAM_INTERRUPTED MODEL_NOT_FOUND MODEL_CAPABILITY_MISMATCH MODEL_CONTEXT_LENGTH_EXCEEDED ``` --- ## 9. RAG / Agent Benchmark Benchmark Service 同时提供 Python 调用接口和本地 HTTP 接口。CLI、测试和前端报告页调用同一 Service,不各自实现指标。 ### 9.1 Dataset 列表 `GET /api/benchmarks/datasets?kind=rag|agent` ```json { "items": [ { "dataset_id": "rag-core-v1", "kind": "rag", "version": "1.0.0", "description": "基础中文笔记检索集", "case_count": 50, "content_hash": "sha256:..." } ] } ``` Dataset 从仓库或受控导入目录注册。API 不接受调用方提交任意文件路径。 ### 9.2 RAG Dataset Case ```json { "case_id": "rag-os-001", "query": "死锁发生需要什么条件", "expected_note_ids": ["note_deadlock"], "expected_block_ids": ["block_conditions"], "citation_required": true, "tags": ["os", "deadlock"] } ``` ### 9.3 Agent Dataset Case ```json { "case_id": "agent-os-review-001", "prompt": "查找死锁内容并创建三个复习任务", "allowed_tools": ["rag.search", "notes.read", "tasks.create"], "expected_tools": ["rag.search", "notes.read", "tasks.create"], "expected_conditions": { "citation_required": true, "tasks_created": 3 }, "tags": ["os", "write"] } ``` 有写操作的 Agent Case 必须运行在隔离 Vault/数据库中,测试结束后清理 Fixture,禁止作用于用户真实知识库。 ### 9.4 创建 RAG Benchmark `POST /api/benchmarks/rag/runs` ```json { "dataset_id": "rag-core-v1", "modes": ["fts", "vector", "hybrid"], "retrieval": { "top_k": 10, "rrf_k": 60, "rerank": true, "rerank_candidates": 20, "score_threshold": 0.0 }, "repeat": 1, "metadata": {} } ``` 配置快照必须记录 Embedding model ID/version/dimension、Reranker、索引版本、Dataset Hash 和运行环境。 ### 9.5 创建 Agent Benchmark `POST /api/benchmarks/agent/runs` ```json { "dataset_id": "agent-core-v1", "provider_id": "mock", "model": "mock-1", "skill_id": null, "max_steps": 10, "token_budget": 20000, "concurrency": 1, "metadata": {} } ``` Benchmark Runner 通过正式 Agent Runtime 创建 Run,并从 Trace 计算结果,不能直接调用 Tool Executor 绕过权限和步骤控制。 ### 9.6 Benchmark Run RAG 和 Agent 创建接口均返回 `202 BenchmarkRun`: ```json { "run_id": "benchmark_123", "kind": "rag", "dataset_id": "rag-core-v1", "dataset_hash": "sha256:...", "status": "queued", "progress": null, "metrics": null, "config_snapshot": {}, "error": null, "created_at": "2026-08-31T10:30:00Z", "started_at": null, "completed_at": null } ``` 公共接口: | 方法 | 路径 | 用途 | | --- | --- | --- | | GET | `/api/benchmarks/runs?kind=&status=&limit=&offset=` | 分页获取运行记录 | | GET | `/api/benchmarks/runs/{run_id}` | 获取状态和指标摘要 | | GET | `/api/benchmarks/runs/{run_id}/events` | SSE 进度和 Case 结果 | | POST | `/api/benchmarks/runs/{run_id}/cancel` | 取消运行 | | GET | `/api/benchmarks/runs/{run_id}/report` | 获取结构化完整报告 | ### 9.7 指标 Contract RAG: ```json { "hit_at_1": 0.72, "hit_at_5": 0.91, "recall_at_k": 0.88, "mrr": 0.81, "citation_hit_rate": 0.89, "p50_latency_ms": 24.5, "p95_latency_ms": 67.3 } ``` Agent: ```json { "task_success_rate": 0.80, "tool_selection_accuracy": 0.92, "tool_argument_accuracy": 0.86, "invalid_tool_call_rate": 0.02, "average_steps": 4.3, "average_latency_ms": 2300, "average_token_usage": 3200 } ``` 报告同时返回逐 Case 结果、失败原因和 Trace Run ID,汇总指标不能覆盖失败样本。 ### 9.8 Benchmark 错误码 ```text BENCHMARK_DATASET_NOT_FOUND BENCHMARK_DATASET_INVALID BENCHMARK_CONFIG_INVALID BENCHMARK_INDEX_INCOMPATIBLE BENCHMARK_RUN_NOT_FOUND BENCHMARK_RUN_FAILED ``` ### 9.9 Retrieval Profile 与索引兼容 Benchmark、Search 和 RAG Engine 共享内部 `RetrievalProfile`,不得各自硬编码参数: ```json { "profile_id": "default-v2", "version": 2, "fts_recall": 20, "vector_recall": 20, "rrf_k": 60, "rerank": true, "rerank_candidates": 20, "context_top_k": 8, "score_threshold": 0.0 } ``` 第二阶段扩展 `GET /api/index/status`: ```json { "status": "idle", "pending_jobs": 0, "active_job_id": null, "embedding": { "model_id": "bge-m3", "model_version": "...", "dimension": 1024, "normalization": "l2" }, "vector_index": { "model_id": "hash-v1", "dimension": 128, "compatible": false }, "last_completed_at": null, "error_message": null } ``` 模型 ID、版本、维度或归一化方式不一致时,Vector/Hybrid 搜索返回 `INDEX_MODEL_MISMATCH`,前端降级到 FTS;`POST /api/index/rebuild` 使用现有 `scope = vectors` 重建,不能在查询时混用旧向量。 相关错误码: ```text EMBEDDING_MODEL_UNAVAILABLE EMBEDDING_DIMENSION_MISMATCH INDEX_MODEL_MISMATCH VECTOR_INDEX_REBUILD_REQUIRED ``` --- ## 10. Export Service ### 10.1 创建导出任务 `POST /api/exports`,返回 `202 ExportJob`。 ```json { "source": { "type": "note", "note_id": "note_123" }, "format": "pdf", "options": { "theme_id": "light", "include_title": true, "include_metadata": false, "page_size": "A4", "code_theme": "github-light" } } ``` `source.type` 首批支持 `note` 和 `markdown`。`markdown` 来源用于尚未保存的预览,字段大小受限且不持久化到 Trace。`format` 固定为 `html`、`pdf`、`docx`。 响应: ```json { "job_id": "export_123", "status": "queued", "format": "pdf", "progress": null, "file": null, "warnings": [], "error": null, "created_at": "2026-08-31T10:30:00Z", "started_at": null, "completed_at": null } ``` ### 10.2 查询、取消和下载 | 方法 | 路径 | 响应 | | --- | --- | --- | | GET | `/api/exports?status=&format=&limit=&offset=` | `ExportJobListResponse` | | GET | `/api/exports/{job_id}` | `ExportJob` | | POST | `/api/exports/{job_id}/cancel` | `OperationResponse` | | GET | `/api/exports/{job_id}/file` | 文件流 | 下载响应设置正确 `Content-Type`、经过清理的 `Content-Disposition` 文件名和 `Content-Length`。未完成、失败或过期 Job 不返回空文件。 完成 Job 的 file: ```json { "file_name": "操作系统复习.pdf", "mime_type": "application/pdf", "size": 1048576, "sha256": "...", "expires_at": "2026-09-01T10:30:00Z" } ``` ### 10.3 Document AST 内部 Contract ```python class DocumentExporter(Protocol): async def export( self, document: Document, options: ExportOptions, ) -> ExportResult: ... ``` Document Node 使用稳定判别字段: ```json { "node_id": "node_01", "type": "heading", "attributes": { "level": 2 }, "children": [], "text": "进程调度" } ``` 首批 node type: ```text document heading paragraph text emphasis strong link list list_item table image blockquote code_block math_inline math_block mermaid function_plot ``` Exporter 对无法表示的节点添加 warning,不允许静默丢失。严重缺失由 `EXPORT_UNSUPPORTED_CONTENT` 失败。 ### 10.4 Static Render Contract Mermaid 和函数图像共享: ```ts interface StaticRenderRequest { kind: 'mermaid' | 'function_plot' source: string sourceHash: string theme: 'light' | 'dark' width?: number height?: number } interface StaticRenderResult { mimeType: 'image/svg+xml' | 'image/png' content: string | Uint8Array width: number height: number warnings: string[] } ``` 渲染实现可以位于前端共享 Renderer、Host 或后端适配进程,但 Export Service 只依赖上述 Contract。SVG 进入导出器前必须净化;缓存键至少包含 source hash、主题和 renderer version。 ### 10.5 Export 错误码 ```text EXPORT_SOURCE_NOT_FOUND EXPORT_FORMAT_UNSUPPORTED EXPORT_OPTIONS_INVALID EXPORT_RENDER_FAILED EXPORT_UNSUPPORTED_CONTENT EXPORT_JOB_NOT_FOUND EXPORT_FILE_EXPIRED ``` --- ## 11. Theme Package Host Contract Theme 不经过 Python AI Core。正式桌面版通过 Tauri Host,Web 开发模式由相同 TypeScript Service 的 Mock Adapter 提供。 ### 11.1 Theme Manifest `theme.yaml` 对应: ```ts interface ThemeManifest { theme_id: string name: string version: string author: string description?: string min_app_version: string is_dark: boolean css_entry: string preview?: string } ``` ### 11.2 ThemePackageService ```ts interface ThemePackageService { selectPackage(): Promise inspectPackage(packageId: string): Promise install(packageId: string): Promise list(): Promise enable(themeId: string): Promise disable(themeId: string): Promise uninstall(themeId: string): Promise } ``` `packageId` 是 Host 通过文件选择器生成的临时句柄,不是前端传入的绝对路径。 ### 11.3 Inspection ```json { "package_id": "theme_package_123", "manifest": {}, "preview_url": "app-theme-preview://theme_package_123", "warnings": [], "compatible": true } ``` 安装前检查: ```text Manifest Schema Theme ID / Version min_app_version 包内相对路径 CSS 语法与大小 禁止远程 URL、@import、脚本和越界资源 预览资源 MIME ``` 预览运行在隔离容器,不能直接将未验证 CSS 注入主页面。 ### 11.4 Theme 错误码 ```text THEME_PACKAGE_NOT_FOUND THEME_MANIFEST_INVALID THEME_PACKAGE_INCOMPATIBLE THEME_RESOURCE_OUTSIDE_PACKAGE THEME_CSS_UNSAFE THEME_ALREADY_INSTALLED THEME_NOT_FOUND THEME_BUILTIN_PROTECTED ``` --- ## 12. Mermaid 与 Function Plot 前端 Contract ### 12.1 MermaidRenderer ```ts interface MermaidRenderer { render( source: string, options: { theme: 'light' | 'dark' mode: 'interactive' | 'static' }, ): Promise<{ svg: string width: number height: number warnings: string[] }> } ``` Renderer 返回净化后的 SVG 或明确错误,不直接操作调用组件之外的 DOM。组件销毁时释放事件监听和临时节点;编辑快速变化时以前一次请求的 AbortSignal 或 render version 丢弃旧结果。 ### 12.2 FunctionPlot Model ```ts interface FunctionPlot { version: 1 expressions: Array<{ expression: string label?: string color?: string }> domain?: [number, number] range?: [number, number] axes: { xLabel?: string yLabel?: string grid: boolean } } ``` Markdown parser 将 fenced block 转换为 `FunctionPlotParseResult`: ```ts interface FunctionPlotParseResult { plot?: FunctionPlot diagnostics: Array<{ severity: 'warning' | 'error' code: string message: string line?: number column?: number }> } ``` 表达式解析使用白名单数学语法,不执行 `eval`、函数构造器、网络请求或对象属性访问。无法解析时保留原始 fenced block,并在预览中显示可定位诊断。 ### 12.3 FunctionPlotRenderer ```ts interface FunctionPlotRenderer { renderInteractive(plot: FunctionPlot, target: HTMLElement): Promise renderStatic(plot: FunctionPlot, options: StaticRenderOptions): Promise } interface RenderHandle { update(plot: FunctionPlot): Promise resize(width: number, height: number): void destroy(): void } ``` 编辑器、Markdown 预览和 Export Service 只能依赖上述 Model/Renderer,不直接依赖具体绘图库的数据结构。 ### 12.4 可视化错误码 ```text MERMAID_PARSE_FAILED MERMAID_RENDER_FAILED MERMAID_SVG_UNSAFE FUNCTION_PLOT_PARSE_FAILED FUNCTION_PLOT_EXPRESSION_UNSAFE FUNCTION_PLOT_RENDER_FAILED ``` --- ## 13. 跨模块 Contract 对照 | Contract | 提供模块 | 消费模块 | 稳定字段 | | --- | --- | --- | --- | | `TranscriptSegment` | Multimodal | Knowledge、Frontend | speaker、time range、text | | `AgentEvent` / Trace | Agent Runtime | Frontend、Agent Benchmark | run_id、sequence、event、timestamp、data | | `ToolDefinition` / `ToolResult` | Tool Registry | Agent、MCP Bridge | name、parameters、permission、success/error | | `PluginCommand` | Extension Core | Command Palette、Context Menu | command_id、locations、when、parameters | | `PluginSettingsSchema` | Extension Core | Settings UI | schema_version、fields、values、secret status | | `ModelEvent` | Provider Adapter | Chat、Agent、Frontend | event、sequence、data、timestamp | | `BenchmarkRun` | Benchmark Service | CLI、报告页 | dataset hash、config snapshot、metrics、status | | `Document AST` | Export Service | HTML/PDF/DOCX Exporter | node_id、type、attributes、children/text | | `StaticRenderResult` | Mermaid/Function Renderer | Export Service | mime、content、size、warnings | | `ThemeManifest` | Theme Host | Theme UI | ID、version、compatibility、entry、preview | 跨模块字段需要修改时: 1. 先修改本文和对应 Pydantic/TypeScript Contract。 2. 新字段优先可选并提供默认行为。 3. 同一提交增加 Provider/Consumer 两侧契约测试。 4. 若必须破坏兼容,增加版本字段或新路径并记录迁移窗口。 --- ## 14. HTTP 状态码约定 | 状态码 | 使用场景 | | --- | --- | | 200 | 查询、同步更新、已完成命令 | | 201 | 同步创建持久资源 | | 202 | 创建异步 Job、请求取消或 Host 重启 | | 204 | 删除成功且无响应正文 | | 400 | 语义无效但 JSON 结构合法 | | 401/403 | 本地 Session 或权限不足 | | 404 | 资源不存在 | | 409 | 状态冲突、依赖缺失、Schema 版本冲突 | | 413 | 上传正文、结果或包超过限制 | | 422 | Pydantic/Schema 校验失败 | | 429 | 并发或速率容量已满 | | 501 | Contract 已预留但当前宿主能力未实现 | | 502/503/504 | 外部 Provider、MCP Host 或模型服务异常 | `501` 只用于明确存在于当前版本 Contract、但运行环境尚不具备的能力;未定义路由仍返回普通 404。 --- ## 15. 接口冻结与开发顺序 这里描述依赖顺序,不代表人员优先级。 ### 15.1 公共契约先行 先冻结并实现: ```text JobStatus / JobProgress SSE sequence 与恢复规则 Agent Trace 新事件 Document AST StaticRenderResult Plugin Command / Settings Schema Benchmark Dataset / Run ``` 这些类型同时进入后端 Pydantic 和前端 Wire DTO,并增加序列化样例测试。 ### 15.2 Adapter 和 Service 在公共 Contract 后分别实现: ```text Whisper / Pyannote Adapter MCP Bridge / Plugin Host Provider 行为适配 RAG / Agent Benchmark Runner HTML / PDF / DOCX Exporter Mermaid / FunctionPlot Renderer ThemePackageService ``` Service 先以 Fake/Mock Adapter 完成状态机和错误测试,再接真实运行时,便于其他模块并行联调。 ### 15.3 前后端联调 - 前端 Service 只依赖本文 Wire DTO。 - 后端未完成时使用与 Contract 相同的 Mock,不在组件中硬编码第二套字段。 - SSE 联调覆盖分片、重复事件、断线恢复、取消和终止状态。 - Job 页面覆盖刷新后恢复,不依赖只存在于 Pinia 的进度。 - Secret 输入在提交后立即清空,不进入浏览器持久化。 ### 15.4 集成验收链路 ```text Audio Attachment → Transcription Job / Trace → Transcript Note → Index / RAG → Agent Run → MCP Tool → Agent Trace → Markdown with Mermaid / Function Plot → HTML / PDF / DOCX → Community Theme Preview ``` 链路中每个箭头都必须通过本文定义的 Contract 或已有第一阶段接口,禁止测试脚本直接写数据库来伪造完成状态。 --- ## 16. Definition of Done 一个第二阶段接口完成需要同时满足: - [ ] Pydantic Request/Response Model 已实现并进入 OpenAPI; - [ ] 前端 Wire DTO 与 Service 显式映射已实现; - [ ] 成功、校验失败、资源不存在、状态冲突和运行时失败均有测试; - [ ] 异步任务支持查询终态,取消语义明确; - [ ] SSE 支持分片解析、sequence 去重、重连和唯一终止事件; - [ ] 第三方错误已映射,响应和日志不包含 Secret 或堆栈; - [ ] Mock/Fake 与真实 Adapter 遵守相同 Contract; - [ ] 接口已在 `/docs` 和 `/openapi.json` 可见; - [ ] 本文状态由“计划新增”更新为“已实现”或“扩展完成”; - [ ] 相关开发说明、测试手册和问题修复文档已同步。 --- ## 17. 实现文件建议 在不破坏现有目录的前提下,第二阶段可以逐步拆分: ```text backend/app/ ├── contracts.py # 现有公共 Contract;稳定后可按域拆分 ├── api/ │ ├── media.py │ ├── agent_trace.py │ ├── plugin_contributions.py │ ├── benchmarks.py │ └── exports.py ├── media/ │ ├── transcription.py │ ├── whisper_adapter.py │ └── diarization_adapter.py ├── extensions/ │ ├── mcp_bridge.py │ ├── command_registry.py │ └── settings.py ├── benchmarks/ │ ├── rag.py │ ├── agent.py │ └── reports.py └── export/ ├── document.py ├── service.py └── exporters/ frontend/src/ ├── contracts/ ├── services/ │ ├── transcriptionService.ts │ ├── pluginContributionService.ts │ ├── benchmarkService.ts │ ├── exportService.ts │ └── themePackageService.ts └── features/ ├── agent-trace/ ├── themes/ └── export/ ``` 目录调整应按实际代码规模渐进进行。Router 只做参数接收和错误映射,状态机、第三方 SDK 与文件处理继续放在 Service/Adapter 层。