From 321dd6fdaf683416f8d30b91b5b2d29d39861e0f Mon Sep 17 00:00:00 2001 From: caoxiaozhu Date: Mon, 11 May 2026 05:18:16 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E5=96=84=E5=90=8E=E7=AB=AF=20?= =?UTF-8?q?API=20OpenAPI=20=E6=96=87=E6=A1=A3=E4=B8=8E=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E5=93=8D=E5=BA=94=20schema?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- document/development/backend_api/README.md | 44 + .../backend_api/interface_inventory.md | 100 + document/development/backend_api/openapi.json | 6242 +++++++++++++++++ server/scripts/export_openapi.py | 73 + server/src/app/api/deps.py | 24 +- .../src/app/api/v1/endpoints/agent_assets.py | 165 +- server/src/app/api/v1/endpoints/agent_runs.py | 41 +- server/src/app/api/v1/endpoints/audit_logs.py | 27 +- server/src/app/api/v1/endpoints/auth.py | 14 +- server/src/app/api/v1/endpoints/bootstrap.py | 15 +- server/src/app/api/v1/endpoints/employees.py | 82 +- server/src/app/api/v1/endpoints/health.py | 20 +- server/src/app/api/v1/endpoints/knowledge.py | 404 +- .../app/api/v1/endpoints/reimbursements.py | 29 +- server/src/app/api/v1/endpoints/settings.py | 58 +- server/src/app/core/openapi.py | 80 + server/src/app/main.py | 81 +- server/src/app/schemas/common.py | 28 + server/src/app/schemas/knowledge.py | 28 +- server/tests/test_openapi_schema.py | 29 + 20 files changed, 7359 insertions(+), 225 deletions(-) create mode 100644 document/development/backend_api/README.md create mode 100644 document/development/backend_api/interface_inventory.md create mode 100644 document/development/backend_api/openapi.json create mode 100644 server/scripts/export_openapi.py create mode 100644 server/src/app/core/openapi.py create mode 100644 server/src/app/schemas/common.py create mode 100644 server/tests/test_openapi_schema.py diff --git a/document/development/backend_api/README.md b/document/development/backend_api/README.md new file mode 100644 index 0000000..8ad8cb0 --- /dev/null +++ b/document/development/backend_api/README.md @@ -0,0 +1,44 @@ +# Backend API Swagger 文档 + +本目录用于沉淀后端接口的 Swagger / OpenAPI 产物,给开发、联调和后续 Agent 接口调用统一对照。 + +## 目录说明 + +- `openapi.json` + - 由 FastAPI `app.openapi()` 导出的完整 OpenAPI 规范。 +- `interface_inventory.md` + - 基于 OpenAPI 自动整理的接口清单,按 tag 分组查看方法、路径和摘要。 + +## 在线入口 + +- Swagger UI:`/docs` +- ReDoc:`/redoc` +- OpenAPI JSON:`/openapi.json` + +如果本地默认端口不变,完整地址通常是: + +- `http://127.0.0.1:8000/docs` +- `http://127.0.0.1:8000/redoc` +- `http://127.0.0.1:8000/openapi.json` + +## 重新生成 + +在 `/app/server` 下执行: + +```bash +PYTHONPATH=/app/server/src /app/server/.venv/bin/python /app/server/scripts/export_openapi.py +``` + +## 当前约定 + +- 全部业务接口前缀:`/api/v1` +- 知识库接口使用请求头模拟登录用户: + - `X-Auth-Username` + - `X-Auth-Name` + - `X-Auth-Role-Codes` + - `X-Auth-Is-Admin` +- Agent 资产写接口支持审计头: + - `X-Actor` + - `X-Request-Id` +- Hermes 运行时模型接口使用: + - `Authorization: Bearer ` diff --git a/document/development/backend_api/interface_inventory.md b/document/development/backend_api/interface_inventory.md new file mode 100644 index 0000000..e103109 --- /dev/null +++ b/document/development/backend_api/interface_inventory.md @@ -0,0 +1,100 @@ +# Backend API Interface Inventory + +- Generated at: `2026-05-11 04:14:05 UTC` +- API title: `X-Financial` +- API version: `0.1.0` +- Total paths: `28` + +## Tag Overview + +### agent-assets + +| Method | Path | Summary | +| --- | --- | --- | +| `GET` | `/api/v1/agent-assets` | 查询 Agent 资产列表 | +| `POST` | `/api/v1/agent-assets` | 创建 Agent 资产 | +| `GET` | `/api/v1/agent-assets/{asset_id}` | 读取 Agent 资产详情 | +| `PATCH` | `/api/v1/agent-assets/{asset_id}` | 更新 Agent 资产 | +| `POST` | `/api/v1/agent-assets/{asset_id}/activate` | 激活资产当前版本 | +| `POST` | `/api/v1/agent-assets/{asset_id}/reviews` | 创建资产审核记录 | +| `GET` | `/api/v1/agent-assets/{asset_id}/versions` | 查询资产版本列表 | +| `POST` | `/api/v1/agent-assets/{asset_id}/versions` | 创建资产版本 | + +### agent-runs + +| Method | Path | Summary | +| --- | --- | --- | +| `GET` | `/api/v1/agent-runs` | 查询 Agent 运行日志 | +| `GET` | `/api/v1/agent-runs/{run_id}` | 读取单次 Agent 运行详情 | + +### audit-logs + +| Method | Path | Summary | +| --- | --- | --- | +| `GET` | `/api/v1/audit-logs` | 查询审计日志 | + +### auth + +| Method | Path | Summary | +| --- | --- | --- | +| `POST` | `/api/v1/auth/login` | 用户登录 | + +### bootstrap + +| Method | Path | Summary | +| --- | --- | --- | +| `GET` | `/api/v1/bootstrap` | 读取初始化状态 | +| `POST` | `/api/v1/bootstrap` | 写入初始化配置 | + +### employees + +| Method | Path | Summary | +| --- | --- | --- | +| `GET` | `/api/v1/employees` | 查询员工列表 | +| `POST` | `/api/v1/employees` | 创建员工 | +| `GET` | `/api/v1/employees/meta` | 读取员工目录元数据 | +| `GET` | `/api/v1/employees/{employee_id}` | 读取员工详情 | +| `PATCH` | `/api/v1/employees/{employee_id}` | 更新员工 | +| `POST` | `/api/v1/employees/{employee_id}/disable` | 停用员工 | + +### health + +| Method | Path | Summary | +| --- | --- | --- | +| `GET` | `/api/v1/health` | 服务健康检查 | + +### knowledge + +| Method | Path | Summary | +| --- | --- | --- | +| `POST` | `/api/v1/knowledge/documents` | 上传知识库文档 | +| `DELETE` | `/api/v1/knowledge/documents/{document_id}` | 删除知识库文档 | +| `GET` | `/api/v1/knowledge/documents/{document_id}` | 读取知识库文档详情 | +| `GET` | `/api/v1/knowledge/documents/{document_id}/content` | 下载或预览知识库原文 | +| `GET` | `/api/v1/knowledge/documents/{document_id}/onlyoffice-config` | 读取 ONLYOFFICE 预览配置 | +| `POST` | `/api/v1/knowledge/documents/{document_id}/onlyoffice/callback` | 接收 ONLYOFFICE 回调 | +| `GET` | `/api/v1/knowledge/documents/{document_id}/onlyoffice/content` | 读取 ONLYOFFICE 文档源文件 | +| `GET` | `/api/v1/knowledge/library` | 查询知识库目录 | + +### reimbursements + +| Method | Path | Summary | +| --- | --- | --- | +| `GET` | `/api/v1/reimbursements` | 查询报销申请列表 | +| `POST` | `/api/v1/reimbursements` | 创建报销申请 | +| `GET` | `/api/v1/reimbursements/{request_id}` | 读取报销申请详情 | + +### root + +| Method | Path | Summary | +| --- | --- | --- | +| `GET` | `/` | 服务根检查 | + +### settings + +| Method | Path | Summary | +| --- | --- | --- | +| `GET` | `/api/v1/settings` | 读取系统设置 | +| `PUT` | `/api/v1/settings` | 保存系统设置 | +| `POST` | `/api/v1/settings/model-connectivity` | 测试模型连通性 | +| `GET` | `/api/v1/settings/runtime-models/{slot}` | 读取 Hermes 运行时模型配置 | diff --git a/document/development/backend_api/openapi.json b/document/development/backend_api/openapi.json new file mode 100644 index 0000000..ca9afc2 --- /dev/null +++ b/document/development/backend_api/openapi.json @@ -0,0 +1,6242 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "X-Financial", + "description": "X-Financial 后端 OpenAPI 文档。\n\n## 基本信息\n\n- 所有业务接口都挂在 `/api/v1` 前缀下。\n- 交互式 Swagger 页面默认位于 `/docs`,ReDoc 页面位于 `/redoc`。\n- 仓库内导出的静态规范文件位于 `document/development/backend_api/openapi.json`。\n\n## 鉴权约定\n\n- 知识库接口依赖以下请求头模拟当前用户:\n - `X-Auth-Username`\n - `X-Auth-Name`\n - `X-Auth-Role-Codes`\n - `X-Auth-Is-Admin`\n- Agent 资产写接口支持以下审计头:\n - `X-Actor`\n - `X-Request-Id`\n- Hermes 运行时模型配置接口需要:\n - `Authorization: Bearer `\n\n## 当前模块范围\n\n- 系统初始化与健康检查\n- 登录认证\n- 员工目录\n- 报销单\n- 知识库与 ONLYOFFICE\n- 系统设置与模型连通性\n- Agent 资产、运行日志、审计日志", + "version": "0.1.0" + }, + "paths": { + "/api/v1/health": { + "get": { + "tags": [ + "health" + ], + "summary": "服务健康检查", + "description": "检查服务基础状态,并在系统初始化完成后验证数据库连通性。", + "operationId": "health_check_api_v1_health_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HealthCheckRead" + } + } + } + } + } + } + }, + "/api/v1/bootstrap": { + "get": { + "tags": [ + "bootstrap" + ], + "summary": "读取初始化状态", + "description": "返回当前系统是否已完成初始化,以及公司、数据库和缓存配置快照。", + "operationId": "get_bootstrap_state_api_v1_bootstrap_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BootstrapStateRead" + } + } + } + } + } + }, + "post": { + "tags": [ + "bootstrap" + ], + "summary": "写入初始化配置", + "description": "保存系统初始化配置,并刷新运行时数据库连接。", + "operationId": "initialize_bootstrap_api_v1_bootstrap_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BootstrapSetupPayload" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BootstrapStateRead" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/auth/login": { + "post": { + "tags": [ + "auth" + ], + "summary": "用户登录", + "description": "支持管理员账号和员工账号登录,成功后返回前端会话所需的用户信息。", + "operationId": "login_api_v1_auth_login_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoginRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoginResponse" + } + } + } + }, + "401": { + "description": "账号或密码错误。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/agent-assets": { + "get": { + "tags": [ + "agent-assets" + ], + "summary": "查询 Agent 资产列表", + "description": "按资产类型、状态、领域和关键字筛选规则、技能、MCP 与任务资产。", + "operationId": "list_agent_assets_api_v1_agent_assets_get", + "parameters": [ + { + "name": "asset_type", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "资产类型:`rule`、`skill`、`mcp`、`task`。", + "title": "Asset Type" + }, + "description": "资产类型:`rule`、`skill`、`mcp`、`task`。" + }, + { + "name": "status", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "资产状态筛选。", + "title": "Status" + }, + "description": "资产状态筛选。" + }, + { + "name": "domain", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "业务领域筛选,例如 `expense`、`ar`、`ap`。", + "title": "Domain" + }, + "description": "业务领域筛选,例如 `expense`、`ar`、`ap`。" + }, + { + "name": "keyword", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "资产编码、名称关键字模糊查询。", + "title": "Keyword" + }, + "description": "资产编码、名称关键字模糊查询。" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AgentAssetListItem" + }, + "title": "Response List Agent Assets Api V1 Agent Assets Get" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "post": { + "tags": [ + "agent-assets" + ], + "summary": "创建 Agent 资产", + "description": "创建新的规则、技能、MCP 或任务资产,并自动记录审计日志。", + "operationId": "create_agent_asset_api_v1_agent_assets_post", + "parameters": [ + { + "name": "x-actor", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "审计操作者。未传时回退到请求体中的 owner / reviewer 或 `system`。", + "title": "X-Actor" + }, + "description": "审计操作者。未传时回退到请求体中的 owner / reviewer 或 `system`。" + }, + { + "name": "x-request-id", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "外部请求 ID,用于串联审计日志和上游调用链。", + "title": "X-Request-Id" + }, + "description": "外部请求 ID,用于串联审计日志和上游调用链。" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentAssetCreate" + } + } + } + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentAssetRead" + } + } + } + }, + "400": { + "description": "资产编码冲突或请求字段不合法。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/agent-assets/{asset_id}": { + "get": { + "tags": [ + "agent-assets" + ], + "summary": "读取 Agent 资产详情", + "description": "返回资产当前版本正文、最近版本列表和最近一次审核信息。", + "operationId": "get_agent_asset_api_v1_agent_assets__asset_id__get", + "parameters": [ + { + "name": "asset_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Asset Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentAssetRead" + } + } + } + }, + "404": { + "description": "资产不存在。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "patch": { + "tags": [ + "agent-assets" + ], + "summary": "更新 Agent 资产", + "description": "更新资产基础信息、当前版本、状态和配置,并写入审计日志。", + "operationId": "update_agent_asset_api_v1_agent_assets__asset_id__patch", + "parameters": [ + { + "name": "asset_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Asset Id" + } + }, + { + "name": "x-actor", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "审计操作者。未传时回退到请求体中的 owner / reviewer 或 `system`。", + "title": "X-Actor" + }, + "description": "审计操作者。未传时回退到请求体中的 owner / reviewer 或 `system`。" + }, + { + "name": "x-request-id", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "外部请求 ID,用于串联审计日志和上游调用链。", + "title": "X-Request-Id" + }, + "description": "外部请求 ID,用于串联审计日志和上游调用链。" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentAssetUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentAssetRead" + } + } + } + }, + "400": { + "description": "状态更新非法或请求字段不合法。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "资产或指定版本不存在。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/agent-assets/{asset_id}/versions": { + "get": { + "tags": [ + "agent-assets" + ], + "summary": "查询资产版本列表", + "description": "返回指定资产的版本历史,默认按最近版本优先排序。", + "operationId": "list_agent_asset_versions_api_v1_agent_assets__asset_id__versions_get", + "parameters": [ + { + "name": "asset_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Asset Id" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "description": "返回版本数量上限。", + "default": 20, + "title": "Limit" + }, + "description": "返回版本数量上限。" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AgentAssetVersionRead" + }, + "title": "Response List Agent Asset Versions Api V1 Agent Assets Asset Id Versions Get" + } + } + } + }, + "404": { + "description": "资产不存在。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "post": { + "tags": [ + "agent-assets" + ], + "summary": "创建资产版本", + "description": "为指定资产创建新版本;规则使用 Markdown,其他资产使用 JSON 快照。", + "operationId": "create_agent_asset_version_api_v1_agent_assets__asset_id__versions_post", + "parameters": [ + { + "name": "asset_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Asset Id" + } + }, + { + "name": "x-actor", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "审计操作者。未传时回退到请求体中的 owner / reviewer 或 `system`。", + "title": "X-Actor" + }, + "description": "审计操作者。未传时回退到请求体中的 owner / reviewer 或 `system`。" + }, + { + "name": "x-request-id", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "外部请求 ID,用于串联审计日志和上游调用链。", + "title": "X-Request-Id" + }, + "description": "外部请求 ID,用于串联审计日志和上游调用链。" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentAssetVersionCreate" + } + } + } + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentAssetVersionRead" + } + } + } + }, + "400": { + "description": "版本号重复或内容类型不匹配。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "资产不存在。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/agent-assets/{asset_id}/reviews": { + "post": { + "tags": [ + "agent-assets" + ], + "summary": "创建资产审核记录", + "description": "为指定资产版本写入审核结果,并联动更新资产状态。", + "operationId": "create_agent_asset_review_api_v1_agent_assets__asset_id__reviews_post", + "parameters": [ + { + "name": "asset_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Asset Id" + } + }, + { + "name": "x-actor", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "审计操作者。未传时回退到请求体中的 owner / reviewer 或 `system`。", + "title": "X-Actor" + }, + "description": "审计操作者。未传时回退到请求体中的 owner / reviewer 或 `system`。" + }, + { + "name": "x-request-id", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "外部请求 ID,用于串联审计日志和上游调用链。", + "title": "X-Request-Id" + }, + "description": "外部请求 ID,用于串联审计日志和上游调用链。" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentAssetReviewCreate" + } + } + } + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentAssetReviewRead" + } + } + } + }, + "400": { + "description": "审核参数不合法。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "资产或版本不存在。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/agent-assets/{asset_id}/activate": { + "post": { + "tags": [ + "agent-assets" + ], + "summary": "激活资产当前版本", + "description": "将资产当前版本切换为上线状态;规则资产必须已有 `approved` 审核记录。", + "operationId": "activate_agent_asset_api_v1_agent_assets__asset_id__activate_post", + "parameters": [ + { + "name": "asset_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Asset Id" + } + }, + { + "name": "x-actor", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "审计操作者。未传时回退到请求体中的 owner / reviewer 或 `system`。", + "title": "X-Actor" + }, + "description": "审计操作者。未传时回退到请求体中的 owner / reviewer 或 `system`。" + }, + { + "name": "x-request-id", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "外部请求 ID,用于串联审计日志和上游调用链。", + "title": "X-Request-Id" + }, + "description": "外部请求 ID,用于串联审计日志和上游调用链。" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentAssetRead" + } + } + } + }, + "400": { + "description": "审核未通过或当前版本未设置。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "资产不存在。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/agent-runs": { + "get": { + "tags": [ + "agent-runs" + ], + "summary": "查询 Agent 运行日志", + "description": "按 Agent、运行状态、来源和数量限制筛选运行日志。", + "operationId": "list_agent_runs_api_v1_agent_runs_get", + "parameters": [ + { + "name": "agent", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Agent 名称筛选。", + "title": "Agent" + }, + "description": "Agent 名称筛选。" + }, + { + "name": "status", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "运行状态筛选。", + "title": "Status" + }, + "description": "运行状态筛选。" + }, + { + "name": "source", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "运行来源筛选。", + "title": "Source" + }, + "description": "运行来源筛选。" + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "description": "返回记录上限。", + "default": 20, + "title": "Limit" + }, + "description": "返回记录上限。" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AgentRunRead" + }, + "title": "Response List Agent Runs Api V1 Agent Runs Get" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/agent-runs/{run_id}": { + "get": { + "tags": [ + "agent-runs" + ], + "summary": "读取单次 Agent 运行详情", + "description": "按 `run_id` 返回单次执行的路由结果、工具调用和语义解析信息。", + "operationId": "get_agent_run_api_v1_agent_runs__run_id__get", + "parameters": [ + { + "name": "run_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Run Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentRunRead" + } + } + } + }, + "404": { + "description": "运行记录不存在。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/audit-logs": { + "get": { + "tags": [ + "audit-logs" + ], + "summary": "查询审计日志", + "description": "按资源类型、资源 ID、动作类型和数量限制筛选审计日志。", + "operationId": "list_audit_logs_api_v1_audit_logs_get", + "parameters": [ + { + "name": "resource_type", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "资源类型筛选,例如 `rule`、`task`。", + "title": "Resource Type" + }, + "description": "资源类型筛选,例如 `rule`、`task`。" + }, + { + "name": "resource_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "资源主键或业务编码筛选。", + "title": "Resource Id" + }, + "description": "资源主键或业务编码筛选。" + }, + { + "name": "action", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "动作名称筛选。", + "title": "Action" + }, + "description": "动作名称筛选。" + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 200, + "minimum": 1, + "description": "返回日志上限。", + "default": 50, + "title": "Limit" + }, + "description": "返回日志上限。" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AuditLogRead" + }, + "title": "Response List Audit Logs Api V1 Audit Logs Get" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/knowledge/library": { + "get": { + "tags": [ + "knowledge" + ], + "summary": "查询知识库目录", + "description": "返回固定知识库目录与当前已上传文档列表。", + "operationId": "get_knowledge_library_api_v1_knowledge_library_get", + "parameters": [ + { + "name": "x-auth-username", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "当前登录用户名。知识库接口至少需要提供用户名或姓名。", + "title": "X-Auth-Username" + }, + "description": "当前登录用户名。知识库接口至少需要提供用户名或姓名。" + }, + { + "name": "x-auth-name", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "当前登录人展示姓名。未传时默认回退到用户名。", + "title": "X-Auth-Name" + }, + "description": "当前登录人展示姓名。未传时默认回退到用户名。" + }, + { + "name": "x-auth-role-codes", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "角色编码列表,多个角色使用英文逗号分隔,例如 `manager,finance`。", + "title": "X-Auth-Role-Codes" + }, + "description": "角色编码列表,多个角色使用英文逗号分隔,例如 `manager,finance`。" + }, + { + "name": "x-auth-is-admin", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "是否管理员,支持 `true/false/1/0`。", + "title": "X-Auth-Is-Admin" + }, + "description": "是否管理员,支持 `true/false/1/0`。" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KnowledgeLibraryRead" + } + } + } + }, + "401": { + "description": "未提供知识库访问用户头。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/knowledge/documents/{document_id}": { + "get": { + "tags": [ + "knowledge" + ], + "summary": "读取知识库文档详情", + "description": "返回单个知识库文档的元信息、预览类型和预览内容。", + "operationId": "get_knowledge_document_api_v1_knowledge_documents__document_id__get", + "parameters": [ + { + "name": "document_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Document Id" + } + }, + { + "name": "x-auth-username", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "当前登录用户名。知识库接口至少需要提供用户名或姓名。", + "title": "X-Auth-Username" + }, + "description": "当前登录用户名。知识库接口至少需要提供用户名或姓名。" + }, + { + "name": "x-auth-name", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "当前登录人展示姓名。未传时默认回退到用户名。", + "title": "X-Auth-Name" + }, + "description": "当前登录人展示姓名。未传时默认回退到用户名。" + }, + { + "name": "x-auth-role-codes", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "角色编码列表,多个角色使用英文逗号分隔,例如 `manager,finance`。", + "title": "X-Auth-Role-Codes" + }, + "description": "角色编码列表,多个角色使用英文逗号分隔,例如 `manager,finance`。" + }, + { + "name": "x-auth-is-admin", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "是否管理员,支持 `true/false/1/0`。", + "title": "X-Auth-Is-Admin" + }, + "description": "是否管理员,支持 `true/false/1/0`。" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KnowledgeDocumentDetailRead" + } + } + } + }, + "401": { + "description": "未提供知识库访问用户头。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "知识库文件不存在。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "tags": [ + "knowledge" + ], + "summary": "删除知识库文档", + "description": "删除知识库文档及其索引记录。", + "operationId": "delete_knowledge_document_api_v1_knowledge_documents__document_id__delete", + "parameters": [ + { + "name": "document_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Document Id" + } + }, + { + "name": "x-auth-username", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "当前登录用户名。知识库接口至少需要提供用户名或姓名。", + "title": "X-Auth-Username" + }, + "description": "当前登录用户名。知识库接口至少需要提供用户名或姓名。" + }, + { + "name": "x-auth-name", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "当前登录人展示姓名。未传时默认回退到用户名。", + "title": "X-Auth-Name" + }, + "description": "当前登录人展示姓名。未传时默认回退到用户名。" + }, + { + "name": "x-auth-role-codes", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "角色编码列表,多个角色使用英文逗号分隔,例如 `manager,finance`。", + "title": "X-Auth-Role-Codes" + }, + "description": "角色编码列表,多个角色使用英文逗号分隔,例如 `manager,finance`。" + }, + { + "name": "x-auth-is-admin", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "是否管理员,支持 `true/false/1/0`。", + "title": "X-Auth-Is-Admin" + }, + "description": "是否管理员,支持 `true/false/1/0`。" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KnowledgeActionResponse" + } + } + } + }, + "401": { + "description": "未提供知识库访问用户头。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "403": { + "description": "只有管理员可以删除知识库文件。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "知识库文件不存在。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/knowledge/documents/{document_id}/onlyoffice-config": { + "get": { + "tags": [ + "knowledge" + ], + "summary": "读取 ONLYOFFICE 预览配置", + "description": "为支持的 Office 文档生成 ONLYOFFICE 前端配置和临时访问令牌。", + "operationId": "get_knowledge_document_onlyoffice_config_api_v1_knowledge_documents__document_id__onlyoffice_config_get", + "parameters": [ + { + "name": "document_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Document Id" + } + }, + { + "name": "x-auth-username", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "当前登录用户名。知识库接口至少需要提供用户名或姓名。", + "title": "X-Auth-Username" + }, + "description": "当前登录用户名。知识库接口至少需要提供用户名或姓名。" + }, + { + "name": "x-auth-name", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "当前登录人展示姓名。未传时默认回退到用户名。", + "title": "X-Auth-Name" + }, + "description": "当前登录人展示姓名。未传时默认回退到用户名。" + }, + { + "name": "x-auth-role-codes", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "角色编码列表,多个角色使用英文逗号分隔,例如 `manager,finance`。", + "title": "X-Auth-Role-Codes" + }, + "description": "角色编码列表,多个角色使用英文逗号分隔,例如 `manager,finance`。" + }, + { + "name": "x-auth-is-admin", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "是否管理员,支持 `true/false/1/0`。", + "title": "X-Auth-Is-Admin" + }, + "description": "是否管理员,支持 `true/false/1/0`。" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KnowledgeOnlyOfficeConfigRead" + } + } + } + }, + "400": { + "description": "ONLYOFFICE 未启用、配置不完整或文件格式不支持。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "未提供知识库访问用户头。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "知识库文件不存在。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/knowledge/documents": { + "post": { + "tags": [ + "knowledge" + ], + "summary": "上传知识库文档", + "description": "上传原始文件二进制内容到指定知识库目录。已有同名文件会覆盖并提升版本号。", + "operationId": "upload_knowledge_document_api_v1_knowledge_documents_post", + "parameters": [ + { + "name": "folder", + "in": "query", + "required": true, + "schema": { + "type": "string", + "minLength": 1, + "description": "目标知识库目录名称。", + "title": "Folder" + }, + "description": "目标知识库目录名称。" + }, + { + "name": "filename", + "in": "query", + "required": true, + "schema": { + "type": "string", + "minLength": 1, + "description": "原始文件名。", + "title": "Filename" + }, + "description": "原始文件名。" + }, + { + "name": "x-auth-username", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "当前登录用户名。知识库接口至少需要提供用户名或姓名。", + "title": "X-Auth-Username" + }, + "description": "当前登录用户名。知识库接口至少需要提供用户名或姓名。" + }, + { + "name": "x-auth-name", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "当前登录人展示姓名。未传时默认回退到用户名。", + "title": "X-Auth-Name" + }, + "description": "当前登录人展示姓名。未传时默认回退到用户名。" + }, + { + "name": "x-auth-role-codes", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "角色编码列表,多个角色使用英文逗号分隔,例如 `manager,finance`。", + "title": "X-Auth-Role-Codes" + }, + "description": "角色编码列表,多个角色使用英文逗号分隔,例如 `manager,finance`。" + }, + { + "name": "x-auth-is-admin", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "是否管理员,支持 `true/false/1/0`。", + "title": "X-Auth-Is-Admin" + }, + "description": "是否管理员,支持 `true/false/1/0`。" + } + ], + "requestBody": { + "required": true, + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "contentMediaType": "application/octet-stream", + "description": "待上传的文件二进制内容。", + "title": "Content" + } + } + } + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KnowledgeDocumentDetailRead" + } + } + } + }, + "400": { + "description": "目录、文件名或文件内容不合法。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "未提供知识库访问用户头。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "403": { + "description": "只有管理员可以上传知识库文件。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/knowledge/documents/{document_id}/content": { + "get": { + "tags": [ + "knowledge" + ], + "summary": "下载或预览知识库原文", + "description": "根据文档 ID 返回原始文件内容,可用于浏览器内联预览或下载。", + "operationId": "get_knowledge_document_content_api_v1_knowledge_documents__document_id__content_get", + "parameters": [ + { + "name": "document_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Document Id" + } + }, + { + "name": "disposition", + "in": "query", + "required": false, + "schema": { + "type": "string", + "pattern": "^(inline|attachment)$", + "description": "内容展示方式,支持 `inline` 或 `attachment`。", + "default": "inline", + "title": "Disposition" + }, + "description": "内容展示方式,支持 `inline` 或 `attachment`。" + }, + { + "name": "x-auth-username", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "当前登录用户名。知识库接口至少需要提供用户名或姓名。", + "title": "X-Auth-Username" + }, + "description": "当前登录用户名。知识库接口至少需要提供用户名或姓名。" + }, + { + "name": "x-auth-name", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "当前登录人展示姓名。未传时默认回退到用户名。", + "title": "X-Auth-Name" + }, + "description": "当前登录人展示姓名。未传时默认回退到用户名。" + }, + { + "name": "x-auth-role-codes", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "角色编码列表,多个角色使用英文逗号分隔,例如 `manager,finance`。", + "title": "X-Auth-Role-Codes" + }, + "description": "角色编码列表,多个角色使用英文逗号分隔,例如 `manager,finance`。" + }, + { + "name": "x-auth-is-admin", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "是否管理员,支持 `true/false/1/0`。", + "title": "X-Auth-Is-Admin" + }, + "description": "是否管理员,支持 `true/false/1/0`。" + } + ], + "responses": { + "200": { + "description": "文件内容。", + "content": { + "application/octet-stream": {} + } + }, + "401": { + "description": "未提供知识库访问用户头。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "知识库文件不存在。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/knowledge/documents/{document_id}/onlyoffice/content": { + "get": { + "tags": [ + "knowledge" + ], + "summary": "读取 ONLYOFFICE 文档源文件", + "description": "供 ONLYOFFICE 服务通过短时访问令牌拉取原始文件内容。", + "operationId": "get_knowledge_document_onlyoffice_content_api_v1_knowledge_documents__document_id__onlyoffice_content_get", + "parameters": [ + { + "name": "document_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Document Id" + } + }, + { + "name": "access_token", + "in": "query", + "required": true, + "schema": { + "type": "string", + "minLength": 1, + "description": "ONLYOFFICE 临时访问令牌。", + "title": "Access Token" + }, + "description": "ONLYOFFICE 临时访问令牌。" + } + ], + "responses": { + "200": { + "description": "文件内容。", + "content": { + "application/octet-stream": {} + } + }, + "401": { + "description": "ONLYOFFICE 访问令牌无效。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "知识库文件不存在。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/knowledge/documents/{document_id}/onlyoffice/callback": { + "post": { + "tags": [ + "knowledge" + ], + "summary": "接收 ONLYOFFICE 回调", + "description": "接收 ONLYOFFICE 文档回写回调,在状态满足要求时更新知识库文件内容。", + "operationId": "handle_knowledge_document_onlyoffice_callback_api_v1_knowledge_documents__document_id__onlyoffice_callback_post", + "parameters": [ + { + "name": "document_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Document Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KnowledgeOnlyOfficeCallbackWrite" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KnowledgeOnlyOfficeCallbackRead" + } + } + } + }, + "400": { + "description": "回调载荷不合法。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "知识库文件不存在。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/employees/meta": { + "get": { + "tags": [ + "employees" + ], + "summary": "读取员工目录元数据", + "description": "返回员工总数、状态汇总和可选角色列表,供员工管理页面初始化使用。", + "operationId": "get_employee_meta_api_v1_employees_meta_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmployeeMetaRead" + } + } + } + } + } + } + }, + "/api/v1/employees": { + "get": { + "tags": [ + "employees" + ], + "summary": "查询员工列表", + "description": "按状态和关键字筛选员工目录。", + "operationId": "list_employees_api_v1_employees_get", + "parameters": [ + { + "name": "status", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "员工状态筛选值。", + "title": "Status" + }, + "description": "员工状态筛选值。" + }, + { + "name": "keyword", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "姓名、工号、邮箱等关键字模糊查询。", + "title": "Keyword" + }, + "description": "姓名、工号、邮箱等关键字模糊查询。" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EmployeeRead" + }, + "title": "Response List Employees Api V1 Employees Get" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "post": { + "tags": [ + "employees" + ], + "summary": "创建员工", + "description": "创建新的员工目录记录,并初始化基础角色与组织归属。", + "operationId": "create_employee_api_v1_employees_post", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmployeeCreate" + } + } + } + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmployeeRead" + } + } + } + }, + "400": { + "description": "员工数据校验失败。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/employees/{employee_id}": { + "get": { + "tags": [ + "employees" + ], + "summary": "读取员工详情", + "description": "根据员工主键读取员工完整档案信息。", + "operationId": "get_employee_api_v1_employees__employee_id__get", + "parameters": [ + { + "name": "employee_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Employee Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmployeeRead" + } + } + } + }, + "404": { + "description": "员工不存在。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "patch": { + "tags": [ + "employees" + ], + "summary": "更新员工", + "description": "更新员工基础信息、角色、密码等可维护字段。", + "operationId": "update_employee_api_v1_employees__employee_id__patch", + "parameters": [ + { + "name": "employee_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Employee Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmployeeUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmployeeRead" + } + } + } + }, + "400": { + "description": "请求字段不合法。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "员工不存在。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/employees/{employee_id}/disable": { + "post": { + "tags": [ + "employees" + ], + "summary": "停用员工", + "description": "将员工状态切换为停用,阻止其继续登录系统。", + "operationId": "disable_employee_api_v1_employees__employee_id__disable_post", + "parameters": [ + { + "name": "employee_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Employee Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmployeeRead" + } + } + } + }, + "404": { + "description": "员工不存在。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/reimbursements": { + "get": { + "tags": [ + "reimbursements" + ], + "summary": "查询报销申请列表", + "description": "返回当前系统中的报销申请列表。", + "operationId": "list_reimbursements_api_v1_reimbursements_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/ReimbursementRead" + }, + "type": "array", + "title": "Response List Reimbursements Api V1 Reimbursements Get" + } + } + } + } + } + }, + "post": { + "tags": [ + "reimbursements" + ], + "summary": "创建报销申请", + "description": "创建一条新的报销申请记录,初始状态为 `draft`。", + "operationId": "create_reimbursement_api_v1_reimbursements_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReimbursementCreate" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReimbursementRead" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/reimbursements/{request_id}": { + "get": { + "tags": [ + "reimbursements" + ], + "summary": "读取报销申请详情", + "description": "根据报销申请主键读取单据详情。", + "operationId": "get_reimbursement_api_v1_reimbursements__request_id__get", + "parameters": [ + { + "name": "request_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Request Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReimbursementRead" + } + } + } + }, + "404": { + "description": "报销申请不存在。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/settings": { + "get": { + "tags": [ + "settings" + ], + "summary": "读取系统设置", + "description": "返回公司、管理员、模型、日志、邮件和 ONLYOFFICE 的设置快照。", + "operationId": "get_settings_api_v1_settings_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SettingsRead" + } + } + } + } + } + }, + "put": { + "tags": [ + "settings" + ], + "summary": "保存系统设置", + "description": "保存系统设置,并同步运行时模型配置与 Hermes 使用的模型路由。", + "operationId": "update_settings_api_v1_settings_put", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SettingsWrite" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SettingsRead" + } + } + } + }, + "400": { + "description": "设置字段校验失败。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/settings/model-connectivity": { + "post": { + "tags": [ + "settings" + ], + "summary": "测试模型连通性", + "description": "验证指定模型服务端点是否可用;当未传 API Key 且提供 slot 时会尝试复用已保存密钥。", + "operationId": "test_model_connectivity_api_v1_settings_model_connectivity_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ModelConnectivityTestRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ModelConnectivityTestRead" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/settings/runtime-models/{slot}": { + "get": { + "tags": [ + "settings" + ], + "summary": "读取 Hermes 运行时模型配置", + "description": "供 Hermes 进程读取主模型、备用模型、VLM 或 Embedding 模型的运行时配置。", + "operationId": "get_runtime_model_config_api_v1_settings_runtime_models__slot__get", + "parameters": [ + { + "name": "slot", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Slot" + } + }, + { + "name": "authorization", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Hermes 读取运行时模型配置时使用的 Bearer Token。", + "title": "Authorization" + }, + "description": "Hermes 读取运行时模型配置时使用的 Bearer Token。" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuntimeModelConfigRead" + } + } + } + }, + "401": { + "description": "Hermes 令牌校验失败。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "指定模型槽位不存在。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "503": { + "description": "Hermes 集成令牌尚未配置。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/": { + "get": { + "tags": [ + "root" + ], + "summary": "服务根检查", + "description": "用于快速确认后端服务进程已经启动。", + "operationId": "root__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RootStatusRead" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "AgentAssetContentType": { + "type": "string", + "enum": [ + "markdown", + "json" + ], + "title": "AgentAssetContentType" + }, + "AgentAssetCreate": { + "properties": { + "asset_type": { + "$ref": "#/components/schemas/AgentAssetType" + }, + "code": { + "type": "string", + "maxLength": 100, + "minLength": 1, + "title": "Code" + }, + "name": { + "type": "string", + "maxLength": 200, + "minLength": 1, + "title": "Name" + }, + "description": { + "type": "string", + "title": "Description", + "default": "" + }, + "domain": { + "$ref": "#/components/schemas/AgentAssetDomain" + }, + "scenario_json": { + "items": {}, + "type": "array", + "title": "Scenario Json" + }, + "owner": { + "type": "string", + "maxLength": 100, + "minLength": 1, + "title": "Owner" + }, + "reviewer": { + "anyOf": [ + { + "type": "string", + "maxLength": 100 + }, + { + "type": "null" + } + ], + "title": "Reviewer" + }, + "status": { + "$ref": "#/components/schemas/AgentAssetStatus", + "default": "draft" + }, + "config_json": { + "additionalProperties": true, + "type": "object", + "title": "Config Json" + } + }, + "type": "object", + "required": [ + "asset_type", + "code", + "name", + "domain", + "owner" + ], + "title": "AgentAssetCreate" + }, + "AgentAssetDomain": { + "type": "string", + "enum": [ + "expense", + "ar", + "ap", + "knowledge", + "system" + ], + "title": "AgentAssetDomain" + }, + "AgentAssetListItem": { + "properties": { + "id": { + "type": "string", + "title": "Id" + }, + "asset_type": { + "type": "string", + "title": "Asset Type" + }, + "code": { + "type": "string", + "title": "Code" + }, + "name": { + "type": "string", + "title": "Name" + }, + "description": { + "type": "string", + "title": "Description" + }, + "domain": { + "type": "string", + "title": "Domain" + }, + "scenario_json": { + "items": {}, + "type": "array", + "title": "Scenario Json" + }, + "owner": { + "type": "string", + "title": "Owner" + }, + "reviewer": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Reviewer" + }, + "status": { + "type": "string", + "title": "Status" + }, + "current_version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Current Version" + }, + "config_json": { + "additionalProperties": true, + "type": "object", + "title": "Config Json" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "title": "Updated At" + } + }, + "type": "object", + "required": [ + "id", + "asset_type", + "code", + "name", + "description", + "domain", + "scenario_json", + "owner", + "reviewer", + "status", + "current_version", + "config_json", + "created_at", + "updated_at" + ], + "title": "AgentAssetListItem" + }, + "AgentAssetRead": { + "properties": { + "id": { + "type": "string", + "title": "Id" + }, + "asset_type": { + "type": "string", + "title": "Asset Type" + }, + "code": { + "type": "string", + "title": "Code" + }, + "name": { + "type": "string", + "title": "Name" + }, + "description": { + "type": "string", + "title": "Description" + }, + "domain": { + "type": "string", + "title": "Domain" + }, + "scenario_json": { + "items": {}, + "type": "array", + "title": "Scenario Json" + }, + "owner": { + "type": "string", + "title": "Owner" + }, + "reviewer": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Reviewer" + }, + "status": { + "type": "string", + "title": "Status" + }, + "current_version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Current Version" + }, + "config_json": { + "additionalProperties": true, + "type": "object", + "title": "Config Json" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "title": "Updated At" + }, + "current_version_content": { + "anyOf": [ + {}, + { + "type": "null" + } + ], + "title": "Current Version Content" + }, + "current_version_content_type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Current Version Content Type" + }, + "current_version_change_note": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Current Version Change Note" + }, + "recent_versions": { + "items": { + "$ref": "#/components/schemas/AgentAssetVersionRead" + }, + "type": "array", + "title": "Recent Versions" + }, + "latest_review": { + "anyOf": [ + { + "$ref": "#/components/schemas/AgentAssetReviewRead" + }, + { + "type": "null" + } + ] + } + }, + "type": "object", + "required": [ + "id", + "asset_type", + "code", + "name", + "description", + "domain", + "scenario_json", + "owner", + "reviewer", + "status", + "current_version", + "config_json", + "created_at", + "updated_at" + ], + "title": "AgentAssetRead" + }, + "AgentAssetReviewCreate": { + "properties": { + "version": { + "type": "string", + "maxLength": 30, + "minLength": 1, + "title": "Version" + }, + "reviewer": { + "type": "string", + "maxLength": 100, + "minLength": 1, + "title": "Reviewer" + }, + "review_status": { + "$ref": "#/components/schemas/AgentReviewStatus" + }, + "review_note": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Review Note" + } + }, + "type": "object", + "required": [ + "version", + "reviewer", + "review_status" + ], + "title": "AgentAssetReviewCreate" + }, + "AgentAssetReviewRead": { + "properties": { + "id": { + "type": "string", + "title": "Id" + }, + "asset_id": { + "type": "string", + "title": "Asset Id" + }, + "version": { + "type": "string", + "title": "Version" + }, + "reviewer": { + "type": "string", + "title": "Reviewer" + }, + "review_status": { + "type": "string", + "title": "Review Status" + }, + "review_note": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Review Note" + }, + "reviewed_at": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Reviewed At" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + } + }, + "type": "object", + "required": [ + "id", + "asset_id", + "version", + "reviewer", + "review_status", + "review_note", + "reviewed_at", + "created_at" + ], + "title": "AgentAssetReviewRead" + }, + "AgentAssetStatus": { + "type": "string", + "enum": [ + "draft", + "review", + "active", + "disabled" + ], + "title": "AgentAssetStatus" + }, + "AgentAssetType": { + "type": "string", + "enum": [ + "rule", + "skill", + "mcp", + "task" + ], + "title": "AgentAssetType" + }, + "AgentAssetUpdate": { + "properties": { + "name": { + "anyOf": [ + { + "type": "string", + "maxLength": 200, + "minLength": 1 + }, + { + "type": "null" + } + ], + "title": "Name" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description" + }, + "domain": { + "anyOf": [ + { + "$ref": "#/components/schemas/AgentAssetDomain" + }, + { + "type": "null" + } + ] + }, + "scenario_json": { + "anyOf": [ + { + "items": {}, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Scenario Json" + }, + "owner": { + "anyOf": [ + { + "type": "string", + "maxLength": 100, + "minLength": 1 + }, + { + "type": "null" + } + ], + "title": "Owner" + }, + "reviewer": { + "anyOf": [ + { + "type": "string", + "maxLength": 100 + }, + { + "type": "null" + } + ], + "title": "Reviewer" + }, + "status": { + "anyOf": [ + { + "$ref": "#/components/schemas/AgentAssetStatus" + }, + { + "type": "null" + } + ] + }, + "current_version": { + "anyOf": [ + { + "type": "string", + "maxLength": 30 + }, + { + "type": "null" + } + ], + "title": "Current Version" + }, + "config_json": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Config Json" + } + }, + "type": "object", + "title": "AgentAssetUpdate" + }, + "AgentAssetVersionCreate": { + "properties": { + "version": { + "type": "string", + "maxLength": 30, + "minLength": 1, + "title": "Version" + }, + "content": { + "title": "Content" + }, + "content_type": { + "$ref": "#/components/schemas/AgentAssetContentType" + }, + "change_note": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Change Note" + }, + "created_by": { + "type": "string", + "maxLength": 100, + "minLength": 1, + "title": "Created By" + } + }, + "type": "object", + "required": [ + "version", + "content", + "content_type", + "created_by" + ], + "title": "AgentAssetVersionCreate" + }, + "AgentAssetVersionRead": { + "properties": { + "id": { + "type": "string", + "title": "Id" + }, + "asset_id": { + "type": "string", + "title": "Asset Id" + }, + "version": { + "type": "string", + "title": "Version" + }, + "content": { + "title": "Content" + }, + "content_type": { + "type": "string", + "title": "Content Type" + }, + "change_note": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Change Note" + }, + "created_by": { + "type": "string", + "title": "Created By" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + }, + "is_current": { + "type": "boolean", + "title": "Is Current", + "default": false + } + }, + "type": "object", + "required": [ + "id", + "asset_id", + "version", + "content", + "content_type", + "change_note", + "created_by", + "created_at" + ], + "title": "AgentAssetVersionRead" + }, + "AgentReviewStatus": { + "type": "string", + "enum": [ + "pending", + "approved", + "rejected" + ], + "title": "AgentReviewStatus" + }, + "AgentRunRead": { + "properties": { + "id": { + "type": "string", + "title": "Id" + }, + "run_id": { + "type": "string", + "title": "Run Id" + }, + "agent": { + "type": "string", + "title": "Agent" + }, + "source": { + "type": "string", + "title": "Source" + }, + "user_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "User Id" + }, + "task_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Task Id" + }, + "ontology_json": { + "additionalProperties": true, + "type": "object", + "title": "Ontology Json" + }, + "route_json": { + "additionalProperties": true, + "type": "object", + "title": "Route Json" + }, + "permission_level": { + "type": "string", + "title": "Permission Level" + }, + "status": { + "type": "string", + "title": "Status" + }, + "result_summary": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Result Summary" + }, + "error_message": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Error Message" + }, + "started_at": { + "type": "string", + "format": "date-time", + "title": "Started At" + }, + "finished_at": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Finished At" + }, + "tool_calls": { + "items": { + "$ref": "#/components/schemas/AgentToolCallRead" + }, + "type": "array", + "title": "Tool Calls" + }, + "semantic_parse": { + "anyOf": [ + { + "$ref": "#/components/schemas/SemanticParseRead" + }, + { + "type": "null" + } + ] + } + }, + "type": "object", + "required": [ + "id", + "run_id", + "agent", + "source", + "user_id", + "task_id", + "ontology_json", + "route_json", + "permission_level", + "status", + "result_summary", + "error_message", + "started_at", + "finished_at" + ], + "title": "AgentRunRead" + }, + "AgentToolCallRead": { + "properties": { + "id": { + "type": "string", + "title": "Id" + }, + "run_id": { + "type": "string", + "title": "Run Id" + }, + "tool_type": { + "type": "string", + "title": "Tool Type" + }, + "tool_name": { + "type": "string", + "title": "Tool Name" + }, + "request_json": { + "additionalProperties": true, + "type": "object", + "title": "Request Json" + }, + "response_json": { + "additionalProperties": true, + "type": "object", + "title": "Response Json" + }, + "status": { + "type": "string", + "title": "Status" + }, + "duration_ms": { + "type": "integer", + "title": "Duration Ms" + }, + "error_message": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Error Message" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + } + }, + "type": "object", + "required": [ + "id", + "run_id", + "tool_type", + "tool_name", + "request_json", + "response_json", + "status", + "duration_ms", + "error_message", + "created_at" + ], + "title": "AgentToolCallRead" + }, + "AuditLogRead": { + "properties": { + "id": { + "type": "string", + "title": "Id" + }, + "actor": { + "type": "string", + "title": "Actor" + }, + "action": { + "type": "string", + "title": "Action" + }, + "resource_type": { + "type": "string", + "title": "Resource Type" + }, + "resource_id": { + "type": "string", + "title": "Resource Id" + }, + "before_json": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Before Json" + }, + "after_json": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "After Json" + }, + "request_id": { + "type": "string", + "title": "Request Id" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + } + }, + "type": "object", + "required": [ + "id", + "actor", + "action", + "resource_type", + "resource_id", + "before_json", + "after_json", + "request_id", + "created_at" + ], + "title": "AuditLogRead" + }, + "AuthUserRead": { + "properties": { + "username": { + "type": "string", + "title": "Username" + }, + "name": { + "type": "string", + "title": "Name" + }, + "role": { + "type": "string", + "title": "Role" + }, + "roleCodes": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Rolecodes" + }, + "email": { + "anyOf": [ + { + "type": "string", + "format": "email" + }, + { + "type": "string" + } + ], + "title": "Email" + }, + "avatar": { + "type": "string", + "title": "Avatar" + }, + "isAdmin": { + "type": "boolean", + "title": "Isadmin", + "default": false + } + }, + "type": "object", + "required": [ + "username", + "name", + "role", + "email", + "avatar" + ], + "title": "AuthUserRead" + }, + "BootstrapCacheRead": { + "properties": { + "enabled": { + "type": "boolean", + "title": "Enabled" + }, + "url": { + "type": "string", + "title": "Url" + } + }, + "type": "object", + "required": [ + "enabled", + "url" + ], + "title": "BootstrapCacheRead" + }, + "BootstrapCompanyRead": { + "properties": { + "name": { + "type": "string", + "title": "Name" + }, + "code": { + "type": "string", + "title": "Code" + }, + "admin_email": { + "type": "string", + "title": "Admin Email" + } + }, + "type": "object", + "required": [ + "name", + "code", + "admin_email" + ], + "title": "BootstrapCompanyRead" + }, + "BootstrapConnectionRead": { + "properties": { + "host": { + "type": "string", + "title": "Host" + }, + "port": { + "type": "integer", + "title": "Port" + } + }, + "type": "object", + "required": [ + "host", + "port" + ], + "title": "BootstrapConnectionRead" + }, + "BootstrapDatabaseRead": { + "properties": { + "driver": { + "type": "string", + "title": "Driver" + }, + "host": { + "type": "string", + "title": "Host" + }, + "port": { + "type": "integer", + "title": "Port" + }, + "name": { + "type": "string", + "title": "Name" + }, + "username": { + "type": "string", + "title": "Username" + }, + "password_configured": { + "type": "boolean", + "title": "Password Configured" + } + }, + "type": "object", + "required": [ + "driver", + "host", + "port", + "name", + "username", + "password_configured" + ], + "title": "BootstrapDatabaseRead" + }, + "BootstrapSetupPayload": { + "properties": { + "company_name": { + "type": "string", + "maxLength": 80, + "minLength": 2, + "title": "Company Name" + }, + "company_code": { + "type": "string", + "maxLength": 32, + "title": "Company Code", + "default": "" + }, + "admin_email": { + "anyOf": [ + { + "type": "string", + "format": "email" + }, + { + "type": "null" + } + ], + "title": "Admin Email" + }, + "postgres_host": { + "type": "string", + "maxLength": 255, + "minLength": 1, + "title": "Postgres Host" + }, + "postgres_port": { + "type": "integer", + "maximum": 65535.0, + "minimum": 1.0, + "title": "Postgres Port", + "default": 5432 + }, + "postgres_db": { + "type": "string", + "maxLength": 128, + "minLength": 1, + "title": "Postgres Db" + }, + "postgres_user": { + "type": "string", + "maxLength": 128, + "minLength": 1, + "title": "Postgres User" + }, + "postgres_password": { + "type": "string", + "maxLength": 255, + "minLength": 1, + "title": "Postgres Password" + }, + "redis_url": { + "anyOf": [ + { + "type": "string", + "maxLength": 255 + }, + { + "type": "null" + } + ], + "title": "Redis Url" + } + }, + "type": "object", + "required": [ + "company_name", + "postgres_host", + "postgres_db", + "postgres_user", + "postgres_password" + ], + "title": "BootstrapSetupPayload" + }, + "BootstrapStateRead": { + "properties": { + "initialized": { + "type": "boolean", + "title": "Initialized" + }, + "company": { + "$ref": "#/components/schemas/BootstrapCompanyRead" + }, + "web": { + "$ref": "#/components/schemas/BootstrapConnectionRead" + }, + "server": { + "$ref": "#/components/schemas/BootstrapConnectionRead" + }, + "database": { + "$ref": "#/components/schemas/BootstrapDatabaseRead" + }, + "redis": { + "$ref": "#/components/schemas/BootstrapCacheRead" + } + }, + "type": "object", + "required": [ + "initialized", + "company", + "web", + "server", + "database", + "redis" + ], + "title": "BootstrapStateRead" + }, + "EmployeeCreate": { + "properties": { + "employee_no": { + "type": "string", + "maxLength": 50, + "minLength": 1, + "title": "Employee No" + }, + "name": { + "type": "string", + "maxLength": 100, + "minLength": 1, + "title": "Name" + }, + "email": { + "type": "string", + "format": "email", + "title": "Email" + }, + "gender": { + "anyOf": [ + { + "type": "string", + "maxLength": 20 + }, + { + "type": "null" + } + ], + "title": "Gender" + }, + "birth_date": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Birth Date" + }, + "phone": { + "anyOf": [ + { + "type": "string", + "maxLength": 30 + }, + { + "type": "null" + } + ], + "title": "Phone" + }, + "join_date": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Join Date" + }, + "location": { + "anyOf": [ + { + "type": "string", + "maxLength": 100 + }, + { + "type": "null" + } + ], + "title": "Location" + }, + "position": { + "type": "string", + "maxLength": 100, + "title": "Position", + "default": "员工" + }, + "grade": { + "type": "string", + "maxLength": 20, + "title": "Grade", + "default": "P3" + }, + "cost_center": { + "anyOf": [ + { + "type": "string", + "maxLength": 50 + }, + { + "type": "null" + } + ], + "title": "Cost Center" + }, + "finance_owner_name": { + "anyOf": [ + { + "type": "string", + "maxLength": 100 + }, + { + "type": "null" + } + ], + "title": "Finance Owner Name" + }, + "employment_status": { + "type": "string", + "maxLength": 30, + "title": "Employment Status", + "default": "在职" + }, + "sync_state": { + "type": "string", + "maxLength": 30, + "title": "Sync State", + "default": "已同步" + }, + "spotlight": { + "type": "boolean", + "title": "Spotlight", + "default": false + }, + "organization_unit_code": { + "anyOf": [ + { + "type": "string", + "maxLength": 50 + }, + { + "type": "null" + } + ], + "title": "Organization Unit Code" + }, + "manager_employee_no": { + "anyOf": [ + { + "type": "string", + "maxLength": 50 + }, + { + "type": "null" + } + ], + "title": "Manager Employee No" + }, + "role_codes": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Role Codes" + } + }, + "type": "object", + "required": [ + "employee_no", + "name", + "email" + ], + "title": "EmployeeCreate" + }, + "EmployeeHistoryRead": { + "properties": { + "action": { + "type": "string", + "title": "Action" + }, + "owner": { + "type": "string", + "title": "Owner" + }, + "time": { + "type": "string", + "title": "Time" + }, + "occurredAt": { + "type": "string", + "title": "Occurredat" + } + }, + "type": "object", + "required": [ + "action", + "owner", + "time", + "occurredAt" + ], + "title": "EmployeeHistoryRead" + }, + "EmployeeMetaRead": { + "properties": { + "totalEmployees": { + "type": "integer", + "title": "Totalemployees" + }, + "statusSummary": { + "items": { + "$ref": "#/components/schemas/EmployeeStatusSummaryRead" + }, + "type": "array", + "title": "Statussummary" + }, + "roleOptions": { + "items": { + "$ref": "#/components/schemas/EmployeeRoleOptionRead" + }, + "type": "array", + "title": "Roleoptions" + } + }, + "type": "object", + "required": [ + "totalEmployees", + "statusSummary", + "roleOptions" + ], + "title": "EmployeeMetaRead" + }, + "EmployeeOrganizationRead": { + "properties": { + "id": { + "type": "string", + "title": "Id" + }, + "code": { + "type": "string", + "title": "Code" + }, + "name": { + "type": "string", + "title": "Name" + }, + "unitType": { + "type": "string", + "title": "Unittype" + }, + "costCenter": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Costcenter" + }, + "location": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Location" + }, + "managerName": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Managername" + } + }, + "type": "object", + "required": [ + "id", + "code", + "name", + "unitType" + ], + "title": "EmployeeOrganizationRead" + }, + "EmployeeRead": { + "properties": { + "id": { + "type": "string", + "title": "Id" + }, + "avatar": { + "type": "string", + "title": "Avatar" + }, + "name": { + "type": "string", + "title": "Name" + }, + "employeeNo": { + "type": "string", + "title": "Employeeno" + }, + "department": { + "type": "string", + "title": "Department" + }, + "position": { + "type": "string", + "title": "Position" + }, + "grade": { + "type": "string", + "title": "Grade" + }, + "manager": { + "type": "string", + "title": "Manager" + }, + "financeOwner": { + "type": "string", + "title": "Financeowner" + }, + "roles": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Roles" + }, + "roleCodes": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Rolecodes" + }, + "status": { + "type": "string", + "title": "Status" + }, + "statusTone": { + "type": "string", + "title": "Statustone" + }, + "gender": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Gender" + }, + "age": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Age" + }, + "birthDate": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Birthdate" + }, + "email": { + "type": "string", + "format": "email", + "title": "Email" + }, + "phone": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Phone" + }, + "joinDate": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Joindate" + }, + "location": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Location" + }, + "costCenter": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Costcenter" + }, + "updatedAt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Updatedat" + }, + "lastSync": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Lastsync" + }, + "syncState": { + "type": "string", + "title": "Syncstate" + }, + "spotlight": { + "type": "boolean", + "title": "Spotlight", + "default": false + }, + "permissions": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Permissions" + }, + "history": { + "items": { + "$ref": "#/components/schemas/EmployeeHistoryRead" + }, + "type": "array", + "title": "History" + }, + "organization": { + "anyOf": [ + { + "$ref": "#/components/schemas/EmployeeOrganizationRead" + }, + { + "type": "null" + } + ] + } + }, + "type": "object", + "required": [ + "id", + "avatar", + "name", + "employeeNo", + "department", + "position", + "grade", + "manager", + "financeOwner", + "status", + "statusTone", + "email", + "syncState" + ], + "title": "EmployeeRead" + }, + "EmployeeRoleOptionRead": { + "properties": { + "id": { + "type": "string", + "title": "Id" + }, + "code": { + "type": "string", + "title": "Code" + }, + "label": { + "type": "string", + "title": "Label" + }, + "desc": { + "type": "string", + "title": "Desc" + }, + "permissions": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Permissions" + } + }, + "type": "object", + "required": [ + "id", + "code", + "label", + "desc" + ], + "title": "EmployeeRoleOptionRead" + }, + "EmployeeStatusSummaryRead": { + "properties": { + "id": { + "type": "string", + "title": "Id" + }, + "label": { + "type": "string", + "title": "Label" + }, + "count": { + "type": "integer", + "title": "Count" + } + }, + "type": "object", + "required": [ + "id", + "label", + "count" + ], + "title": "EmployeeStatusSummaryRead" + }, + "EmployeeUpdate": { + "properties": { + "name": { + "anyOf": [ + { + "type": "string", + "maxLength": 100, + "minLength": 1 + }, + { + "type": "null" + } + ], + "title": "Name" + }, + "gender": { + "anyOf": [ + { + "type": "string", + "maxLength": 20 + }, + { + "type": "null" + } + ], + "title": "Gender" + }, + "birth_date": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Birth Date" + }, + "phone": { + "anyOf": [ + { + "type": "string", + "maxLength": 30 + }, + { + "type": "null" + } + ], + "title": "Phone" + }, + "email": { + "anyOf": [ + { + "type": "string", + "format": "email" + }, + { + "type": "null" + } + ], + "title": "Email" + }, + "join_date": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Join Date" + }, + "location": { + "anyOf": [ + { + "type": "string", + "maxLength": 100 + }, + { + "type": "null" + } + ], + "title": "Location" + }, + "position": { + "anyOf": [ + { + "type": "string", + "maxLength": 100, + "minLength": 1 + }, + { + "type": "null" + } + ], + "title": "Position" + }, + "grade": { + "anyOf": [ + { + "type": "string", + "maxLength": 20, + "minLength": 1 + }, + { + "type": "null" + } + ], + "title": "Grade" + }, + "cost_center": { + "anyOf": [ + { + "type": "string", + "maxLength": 50 + }, + { + "type": "null" + } + ], + "title": "Cost Center" + }, + "finance_owner_name": { + "anyOf": [ + { + "type": "string", + "maxLength": 100 + }, + { + "type": "null" + } + ], + "title": "Finance Owner Name" + }, + "role_codes": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Role Codes" + }, + "password": { + "anyOf": [ + { + "type": "string", + "maxLength": 128, + "minLength": 5 + }, + { + "type": "null" + } + ], + "title": "Password" + } + }, + "type": "object", + "title": "EmployeeUpdate" + }, + "ErrorResponse": { + "properties": { + "detail": { + "type": "string", + "title": "Detail" + } + }, + "type": "object", + "required": [ + "detail" + ], + "title": "ErrorResponse" + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail" + } + }, + "type": "object", + "title": "HTTPValidationError" + }, + "HealthCheckRead": { + "properties": { + "status": { + "type": "string", + "title": "Status" + }, + "database": { + "$ref": "#/components/schemas/HealthDatabaseStatusRead" + }, + "redis": { + "$ref": "#/components/schemas/HealthRedisStatusRead" + } + }, + "type": "object", + "required": [ + "status", + "database", + "redis" + ], + "title": "HealthCheckRead" + }, + "HealthDatabaseStatusRead": { + "properties": { + "configured": { + "type": "boolean", + "title": "Configured" + }, + "ok": { + "type": "boolean", + "title": "Ok" + }, + "error": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Error" + } + }, + "type": "object", + "required": [ + "configured", + "ok" + ], + "title": "HealthDatabaseStatusRead" + }, + "HealthRedisStatusRead": { + "properties": { + "configured": { + "type": "boolean", + "title": "Configured" + }, + "enabled": { + "type": "boolean", + "title": "Enabled" + } + }, + "type": "object", + "required": [ + "configured", + "enabled" + ], + "title": "HealthRedisStatusRead" + }, + "KnowledgeActionResponse": { + "properties": { + "ok": { + "type": "boolean", + "title": "Ok", + "default": true + }, + "detail": { + "type": "string", + "title": "Detail" + } + }, + "type": "object", + "required": [ + "detail" + ], + "title": "KnowledgeActionResponse" + }, + "KnowledgeDocumentDetailRead": { + "properties": { + "id": { + "type": "string", + "title": "Id" + }, + "name": { + "type": "string", + "title": "Name" + }, + "folder": { + "type": "string", + "title": "Folder" + }, + "tag": { + "type": "string", + "title": "Tag" + }, + "time": { + "type": "string", + "title": "Time" + }, + "version": { + "type": "string", + "title": "Version" + }, + "state": { + "type": "string", + "title": "State" + }, + "stateTone": { + "type": "string", + "title": "Statetone" + }, + "owner": { + "type": "string", + "title": "Owner" + }, + "icon": { + "type": "string", + "title": "Icon" + }, + "fileType": { + "type": "string", + "title": "Filetype" + }, + "fileTypeLabel": { + "type": "string", + "title": "Filetypelabel" + }, + "summary": { + "type": "string", + "title": "Summary" + }, + "mimeType": { + "type": "string", + "title": "Mimetype" + }, + "extension": { + "type": "string", + "title": "Extension" + }, + "sizeBytes": { + "type": "integer", + "title": "Sizebytes" + }, + "canPreview": { + "type": "boolean", + "title": "Canpreview", + "default": false + }, + "previewKind": { + "type": "string", + "title": "Previewkind" + }, + "previewPages": { + "items": { + "$ref": "#/components/schemas/KnowledgePreviewPageRead" + }, + "type": "array", + "title": "Previewpages" + } + }, + "type": "object", + "required": [ + "id", + "name", + "folder", + "tag", + "time", + "version", + "state", + "stateTone", + "owner", + "icon", + "fileType", + "fileTypeLabel", + "summary", + "mimeType", + "extension", + "sizeBytes", + "previewKind" + ], + "title": "KnowledgeDocumentDetailRead" + }, + "KnowledgeDocumentRead": { + "properties": { + "id": { + "type": "string", + "title": "Id" + }, + "name": { + "type": "string", + "title": "Name" + }, + "folder": { + "type": "string", + "title": "Folder" + }, + "tag": { + "type": "string", + "title": "Tag" + }, + "time": { + "type": "string", + "title": "Time" + }, + "version": { + "type": "string", + "title": "Version" + }, + "state": { + "type": "string", + "title": "State" + }, + "stateTone": { + "type": "string", + "title": "Statetone" + }, + "owner": { + "type": "string", + "title": "Owner" + }, + "icon": { + "type": "string", + "title": "Icon" + }, + "fileType": { + "type": "string", + "title": "Filetype" + }, + "fileTypeLabel": { + "type": "string", + "title": "Filetypelabel" + }, + "summary": { + "type": "string", + "title": "Summary" + }, + "mimeType": { + "type": "string", + "title": "Mimetype" + }, + "extension": { + "type": "string", + "title": "Extension" + }, + "sizeBytes": { + "type": "integer", + "title": "Sizebytes" + }, + "canPreview": { + "type": "boolean", + "title": "Canpreview", + "default": false + } + }, + "type": "object", + "required": [ + "id", + "name", + "folder", + "tag", + "time", + "version", + "state", + "stateTone", + "owner", + "icon", + "fileType", + "fileTypeLabel", + "summary", + "mimeType", + "extension", + "sizeBytes" + ], + "title": "KnowledgeDocumentRead" + }, + "KnowledgeFolderRead": { + "properties": { + "name": { + "type": "string", + "title": "Name" + }, + "count": { + "type": "integer", + "title": "Count" + }, + "icon": { + "type": "string", + "title": "Icon", + "default": "mdi mdi-folder" + } + }, + "type": "object", + "required": [ + "name", + "count" + ], + "title": "KnowledgeFolderRead" + }, + "KnowledgeLibraryRead": { + "properties": { + "folders": { + "items": { + "$ref": "#/components/schemas/KnowledgeFolderRead" + }, + "type": "array", + "title": "Folders" + }, + "documents": { + "items": { + "$ref": "#/components/schemas/KnowledgeDocumentRead" + }, + "type": "array", + "title": "Documents" + } + }, + "type": "object", + "title": "KnowledgeLibraryRead" + }, + "KnowledgeOnlyOfficeCallbackRead": { + "properties": { + "error": { + "type": "integer", + "title": "Error", + "default": 0 + } + }, + "type": "object", + "title": "KnowledgeOnlyOfficeCallbackRead" + }, + "KnowledgeOnlyOfficeCallbackWrite": { + "properties": { + "status": { + "type": "integer", + "title": "Status", + "description": "ONLYOFFICE 回调状态码。" + }, + "url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Url", + "description": "文档下载地址,状态为 2 或 6 时使用。" + }, + "users": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Users", + "description": "当前编辑用户列表。" + } + }, + "additionalProperties": true, + "type": "object", + "required": [ + "status" + ], + "title": "KnowledgeOnlyOfficeCallbackWrite" + }, + "KnowledgeOnlyOfficeConfigRead": { + "properties": { + "documentServerUrl": { + "type": "string", + "title": "Documentserverurl" + }, + "config": { + "additionalProperties": true, + "type": "object", + "title": "Config" + } + }, + "type": "object", + "required": [ + "documentServerUrl" + ], + "title": "KnowledgeOnlyOfficeConfigRead" + }, + "KnowledgePreviewBlockRead": { + "properties": { + "heading": { + "type": "string", + "title": "Heading" + }, + "lines": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Lines" + } + }, + "type": "object", + "required": [ + "heading" + ], + "title": "KnowledgePreviewBlockRead" + }, + "KnowledgePreviewPageRead": { + "properties": { + "title": { + "type": "string", + "title": "Title" + }, + "subtitle": { + "type": "string", + "title": "Subtitle" + }, + "stats": { + "items": { + "$ref": "#/components/schemas/KnowledgePreviewStatRead" + }, + "type": "array", + "title": "Stats" + }, + "blocks": { + "items": { + "$ref": "#/components/schemas/KnowledgePreviewBlockRead" + }, + "type": "array", + "title": "Blocks" + } + }, + "type": "object", + "required": [ + "title", + "subtitle" + ], + "title": "KnowledgePreviewPageRead" + }, + "KnowledgePreviewStatRead": { + "properties": { + "label": { + "type": "string", + "title": "Label" + }, + "value": { + "type": "string", + "title": "Value" + } + }, + "type": "object", + "required": [ + "label", + "value" + ], + "title": "KnowledgePreviewStatRead" + }, + "LoginRequest": { + "properties": { + "username": { + "type": "string", + "maxLength": 255, + "minLength": 1, + "title": "Username" + }, + "password": { + "type": "string", + "maxLength": 128, + "minLength": 1, + "title": "Password" + } + }, + "type": "object", + "required": [ + "username", + "password" + ], + "title": "LoginRequest" + }, + "LoginResponse": { + "properties": { + "ok": { + "type": "boolean", + "title": "Ok", + "default": true + }, + "detail": { + "type": "string", + "title": "Detail", + "default": "登录成功。" + }, + "user": { + "$ref": "#/components/schemas/AuthUserRead" + } + }, + "type": "object", + "required": [ + "user" + ], + "title": "LoginResponse" + }, + "ModelConnectivityTestRead": { + "properties": { + "ok": { + "type": "boolean", + "title": "Ok" + }, + "provider": { + "type": "string", + "title": "Provider" + }, + "model": { + "type": "string", + "title": "Model" + }, + "endpoint": { + "type": "string", + "title": "Endpoint" + }, + "capability": { + "type": "string", + "title": "Capability" + }, + "detail": { + "type": "string", + "title": "Detail" + }, + "status_code": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Status Code" + }, + "checked_at": { + "type": "string", + "format": "date-time", + "title": "Checked At" + } + }, + "type": "object", + "required": [ + "ok", + "provider", + "model", + "endpoint", + "capability", + "detail", + "checked_at" + ], + "title": "ModelConnectivityTestRead" + }, + "ModelConnectivityTestRequest": { + "properties": { + "provider": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "title": "Provider" + }, + "endpoint": { + "type": "string", + "maxLength": 512, + "minLength": 1, + "title": "Endpoint" + }, + "model": { + "type": "string", + "maxLength": 255, + "minLength": 1, + "title": "Model" + }, + "api_key": { + "anyOf": [ + { + "type": "string", + "maxLength": 1024 + }, + { + "type": "null" + } + ], + "title": "Api Key" + }, + "capability": { + "type": "string", + "enum": [ + "chat", + "embedding" + ], + "title": "Capability", + "default": "chat" + }, + "slot": { + "anyOf": [ + { + "type": "string", + "enum": [ + "main", + "backup", + "vlm", + "embedding" + ] + }, + { + "type": "null" + } + ], + "title": "Slot" + } + }, + "type": "object", + "required": [ + "provider", + "endpoint", + "model" + ], + "title": "ModelConnectivityTestRequest" + }, + "ReimbursementCreate": { + "properties": { + "request_no": { + "type": "string", + "title": "Request No" + }, + "employee_id": { + "type": "string", + "title": "Employee Id" + }, + "title": { + "type": "string", + "title": "Title" + }, + "category": { + "type": "string", + "title": "Category" + }, + "amount": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$" + } + ], + "title": "Amount" + }, + "reason": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Reason" + } + }, + "type": "object", + "required": [ + "request_no", + "employee_id", + "title", + "category", + "amount" + ], + "title": "ReimbursementCreate" + }, + "ReimbursementRead": { + "properties": { + "id": { + "type": "string", + "title": "Id" + }, + "request_no": { + "type": "string", + "title": "Request No" + }, + "employee_id": { + "type": "string", + "title": "Employee Id" + }, + "title": { + "type": "string", + "title": "Title" + }, + "category": { + "type": "string", + "title": "Category" + }, + "status": { + "type": "string", + "title": "Status" + }, + "amount": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Amount" + }, + "reason": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Reason" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "title": "Updated At" + } + }, + "type": "object", + "required": [ + "id", + "request_no", + "employee_id", + "title", + "category", + "status", + "amount", + "reason", + "created_at", + "updated_at" + ], + "title": "ReimbursementRead" + }, + "RootStatusRead": { + "properties": { + "message": { + "type": "string", + "title": "Message" + } + }, + "type": "object", + "required": [ + "message" + ], + "title": "RootStatusRead" + }, + "RuntimeModelConfigRead": { + "properties": { + "slot": { + "type": "string", + "enum": [ + "main", + "backup", + "vlm", + "embedding" + ], + "title": "Slot" + }, + "provider": { + "type": "string", + "title": "Provider" + }, + "model": { + "type": "string", + "title": "Model" + }, + "endpoint": { + "type": "string", + "title": "Endpoint" + }, + "apiKey": { + "type": "string", + "title": "Apikey" + }, + "capability": { + "type": "string", + "enum": [ + "chat", + "embedding" + ], + "title": "Capability" + } + }, + "type": "object", + "required": [ + "slot", + "provider", + "model", + "endpoint", + "apiKey", + "capability" + ], + "title": "RuntimeModelConfigRead" + }, + "SemanticParseRead": { + "properties": { + "id": { + "type": "string", + "title": "Id" + }, + "run_id": { + "type": "string", + "title": "Run Id" + }, + "user_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "User Id" + }, + "raw_query": { + "type": "string", + "title": "Raw Query" + }, + "scenario": { + "type": "string", + "title": "Scenario" + }, + "intent": { + "type": "string", + "title": "Intent" + }, + "entities_json": { + "items": {}, + "type": "array", + "title": "Entities Json" + }, + "time_range_json": { + "additionalProperties": true, + "type": "object", + "title": "Time Range Json" + }, + "metrics_json": { + "items": {}, + "type": "array", + "title": "Metrics Json" + }, + "constraints_json": { + "items": {}, + "type": "array", + "title": "Constraints Json" + }, + "risk_flags_json": { + "items": {}, + "type": "array", + "title": "Risk Flags Json" + }, + "permission_json": { + "additionalProperties": true, + "type": "object", + "title": "Permission Json" + }, + "confidence": { + "type": "number", + "title": "Confidence" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + } + }, + "type": "object", + "required": [ + "id", + "run_id", + "user_id", + "raw_query", + "scenario", + "intent", + "entities_json", + "time_range_json", + "metrics_json", + "constraints_json", + "risk_flags_json", + "permission_json", + "confidence", + "created_at" + ], + "title": "SemanticParseRead" + }, + "SettingsAdminForm": { + "properties": { + "adminAccount": { + "type": "string", + "maxLength": 120, + "minLength": 1, + "title": "Adminaccount" + }, + "adminEmail": { + "type": "string", + "maxLength": 255, + "minLength": 1, + "title": "Adminemail" + }, + "newPassword": { + "type": "string", + "maxLength": 128, + "title": "Newpassword", + "default": "" + }, + "confirmPassword": { + "type": "string", + "maxLength": 128, + "title": "Confirmpassword", + "default": "" + }, + "sessionTimeout": { + "type": "integer", + "maximum": 240.0, + "minimum": 5.0, + "title": "Sessiontimeout", + "default": 30 + }, + "noticeEmail": { + "type": "string", + "maxLength": 255, + "title": "Noticeemail", + "default": "" + }, + "mfaEnabled": { + "type": "boolean", + "title": "Mfaenabled", + "default": true + }, + "strongPassword": { + "type": "boolean", + "title": "Strongpassword", + "default": true + }, + "loginAlertEnabled": { + "type": "boolean", + "title": "Loginalertenabled", + "default": true + }, + "adminPasswordConfigured": { + "type": "boolean", + "title": "Adminpasswordconfigured", + "default": false + } + }, + "type": "object", + "required": [ + "adminAccount", + "adminEmail" + ], + "title": "SettingsAdminForm" + }, + "SettingsCompanyForm": { + "properties": { + "companyName": { + "type": "string", + "maxLength": 120, + "minLength": 1, + "title": "Companyname" + }, + "displayName": { + "type": "string", + "maxLength": 120, + "minLength": 1, + "title": "Displayname" + }, + "companyCode": { + "type": "string", + "maxLength": 64, + "title": "Companycode", + "default": "" + }, + "recordNumber": { + "type": "string", + "maxLength": 120, + "title": "Recordnumber", + "default": "" + }, + "copyright": { + "type": "string", + "maxLength": 255, + "title": "Copyright", + "default": "" + } + }, + "type": "object", + "required": [ + "companyName", + "displayName" + ], + "title": "SettingsCompanyForm" + }, + "SettingsLlmForm": { + "properties": { + "mainProvider": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "title": "Mainprovider" + }, + "mainModel": { + "type": "string", + "maxLength": 255, + "minLength": 1, + "title": "Mainmodel" + }, + "mainEndpoint": { + "type": "string", + "maxLength": 512, + "minLength": 1, + "title": "Mainendpoint" + }, + "mainApiKey": { + "type": "string", + "maxLength": 1024, + "title": "Mainapikey", + "default": "" + }, + "mainApiKeyConfigured": { + "type": "boolean", + "title": "Mainapikeyconfigured", + "default": false + }, + "backupProvider": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "title": "Backupprovider" + }, + "backupModel": { + "type": "string", + "maxLength": 255, + "minLength": 1, + "title": "Backupmodel" + }, + "backupEndpoint": { + "type": "string", + "maxLength": 512, + "minLength": 1, + "title": "Backupendpoint" + }, + "backupApiKey": { + "type": "string", + "maxLength": 1024, + "title": "Backupapikey", + "default": "" + }, + "backupApiKeyConfigured": { + "type": "boolean", + "title": "Backupapikeyconfigured", + "default": false + }, + "vlmProvider": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "title": "Vlmprovider" + }, + "vlmModel": { + "type": "string", + "maxLength": 255, + "minLength": 1, + "title": "Vlmmodel" + }, + "vlmEndpoint": { + "type": "string", + "maxLength": 512, + "minLength": 1, + "title": "Vlmendpoint" + }, + "vlmApiKey": { + "type": "string", + "maxLength": 1024, + "title": "Vlmapikey", + "default": "" + }, + "vlmApiKeyConfigured": { + "type": "boolean", + "title": "Vlmapikeyconfigured", + "default": false + }, + "embeddingProvider": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "title": "Embeddingprovider" + }, + "embeddingModel": { + "type": "string", + "maxLength": 255, + "minLength": 1, + "title": "Embeddingmodel" + }, + "embeddingEndpoint": { + "type": "string", + "maxLength": 512, + "minLength": 1, + "title": "Embeddingendpoint" + }, + "embeddingApiKey": { + "type": "string", + "maxLength": 1024, + "title": "Embeddingapikey", + "default": "" + }, + "embeddingApiKeyConfigured": { + "type": "boolean", + "title": "Embeddingapikeyconfigured", + "default": false + } + }, + "type": "object", + "required": [ + "mainProvider", + "mainModel", + "mainEndpoint", + "backupProvider", + "backupModel", + "backupEndpoint", + "vlmProvider", + "vlmModel", + "vlmEndpoint", + "embeddingProvider", + "embeddingModel", + "embeddingEndpoint" + ], + "title": "SettingsLlmForm" + }, + "SettingsLogForm": { + "properties": { + "level": { + "type": "string", + "maxLength": 16, + "minLength": 1, + "title": "Level" + }, + "retentionDays": { + "type": "integer", + "maximum": 3650.0, + "minimum": 1.0, + "title": "Retentiondays", + "default": 180 + }, + "archiveCycle": { + "type": "string", + "maxLength": 32, + "title": "Archivecycle", + "default": "weekly" + }, + "logPath": { + "type": "string", + "maxLength": 255, + "minLength": 1, + "title": "Logpath" + }, + "alertEmail": { + "type": "string", + "maxLength": 255, + "title": "Alertemail", + "default": "" + }, + "operationAudit": { + "type": "boolean", + "title": "Operationaudit", + "default": true + }, + "loginAudit": { + "type": "boolean", + "title": "Loginaudit", + "default": true + }, + "maskSensitive": { + "type": "boolean", + "title": "Masksensitive", + "default": true + } + }, + "type": "object", + "required": [ + "level", + "logPath" + ], + "title": "SettingsLogForm" + }, + "SettingsMailForm": { + "properties": { + "smtpHost": { + "type": "string", + "maxLength": 255, + "minLength": 1, + "title": "Smtphost" + }, + "port": { + "type": "integer", + "maximum": 65535.0, + "minimum": 1.0, + "title": "Port", + "default": 465 + }, + "encryption": { + "type": "string", + "maxLength": 32, + "title": "Encryption", + "default": "SSL/TLS" + }, + "senderName": { + "type": "string", + "maxLength": 120, + "title": "Sendername", + "default": "" + }, + "senderAddress": { + "type": "string", + "maxLength": 255, + "title": "Senderaddress", + "default": "" + }, + "username": { + "type": "string", + "maxLength": 255, + "title": "Username", + "default": "" + }, + "password": { + "type": "string", + "maxLength": 1024, + "title": "Password", + "default": "" + }, + "passwordConfigured": { + "type": "boolean", + "title": "Passwordconfigured", + "default": false + }, + "alertEnabled": { + "type": "boolean", + "title": "Alertenabled", + "default": true + }, + "digestEnabled": { + "type": "boolean", + "title": "Digestenabled", + "default": false + }, + "digestTime": { + "type": "string", + "maxLength": 16, + "title": "Digesttime", + "default": "09:00" + }, + "defaultReceiver": { + "type": "string", + "maxLength": 255, + "title": "Defaultreceiver", + "default": "" + } + }, + "type": "object", + "required": [ + "smtpHost" + ], + "title": "SettingsMailForm" + }, + "SettingsRead": { + "properties": { + "companyForm": { + "$ref": "#/components/schemas/SettingsCompanyForm" + }, + "adminForm": { + "$ref": "#/components/schemas/SettingsAdminForm" + }, + "llmForm": { + "$ref": "#/components/schemas/SettingsLlmForm" + }, + "renderForm": { + "$ref": "#/components/schemas/SettingsRenderForm" + }, + "logForm": { + "$ref": "#/components/schemas/SettingsLogForm" + }, + "mailForm": { + "$ref": "#/components/schemas/SettingsMailForm" + } + }, + "type": "object", + "required": [ + "companyForm", + "adminForm", + "llmForm", + "renderForm", + "logForm", + "mailForm" + ], + "title": "SettingsRead" + }, + "SettingsRenderForm": { + "properties": { + "enabled": { + "type": "boolean", + "title": "Enabled", + "default": false + }, + "publicUrl": { + "type": "string", + "maxLength": 512, + "title": "Publicurl", + "default": "" + }, + "jwtSecret": { + "type": "string", + "maxLength": 1024, + "title": "Jwtsecret", + "default": "" + }, + "jwtSecretConfigured": { + "type": "boolean", + "title": "Jwtsecretconfigured", + "default": false + } + }, + "type": "object", + "title": "SettingsRenderForm" + }, + "SettingsWrite": { + "properties": { + "companyForm": { + "$ref": "#/components/schemas/SettingsCompanyForm" + }, + "adminForm": { + "$ref": "#/components/schemas/SettingsAdminForm" + }, + "llmForm": { + "$ref": "#/components/schemas/SettingsLlmForm" + }, + "renderForm": { + "$ref": "#/components/schemas/SettingsRenderForm" + }, + "logForm": { + "$ref": "#/components/schemas/SettingsLogForm" + }, + "mailForm": { + "$ref": "#/components/schemas/SettingsMailForm" + } + }, + "type": "object", + "required": [ + "companyForm", + "adminForm", + "llmForm", + "renderForm", + "logForm", + "mailForm" + ], + "title": "SettingsWrite" + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "type": "array", + "title": "Location" + }, + "msg": { + "type": "string", + "title": "Message" + }, + "type": { + "type": "string", + "title": "Error Type" + }, + "input": { + "title": "Input" + }, + "ctx": { + "type": "object", + "title": "Context" + } + }, + "type": "object", + "required": [ + "loc", + "msg", + "type" + ], + "title": "ValidationError" + } + } + }, + "tags": [ + { + "name": "health", + "description": "服务健康检查与数据库 / Redis 连通性状态。" + }, + { + "name": "bootstrap", + "description": "系统初始化配置,包括公司信息、数据库和缓存配置。" + }, + { + "name": "auth", + "description": "后台登录认证接口,支持管理员和员工账号登录。" + }, + { + "name": "employees", + "description": "员工目录管理,包括员工列表、详情、创建、更新和停用。" + }, + { + "name": "reimbursements", + "description": "报销申请基础接口,包含列表、创建和详情查询。" + }, + { + "name": "knowledge", + "description": "知识库文件管理、内容访问与 ONLYOFFICE 集成接口。" + }, + { + "name": "settings", + "description": "系统设置、模型配置、模型连通性探测和 Hermes 运行时模型配置。" + }, + { + "name": "agent-assets", + "description": "Agent 资产中心,覆盖规则、技能、MCP、任务及其版本、审核和上线流程。" + }, + { + "name": "agent-runs", + "description": "Agent 运行日志查询,包括工具调用和语义解析结果。" + }, + { + "name": "audit-logs", + "description": "系统审计日志查询接口,用于追踪资产和任务写操作。" + }, + { + "name": "root", + "description": "服务根入口,用于确认应用已启动。" + } + ] +} \ No newline at end of file diff --git a/server/scripts/export_openapi.py b/server/scripts/export_openapi.py new file mode 100644 index 0000000..0ba2909 --- /dev/null +++ b/server/scripts/export_openapi.py @@ -0,0 +1,73 @@ +from __future__ import annotations + +import json +from collections import defaultdict +from datetime import UTC, datetime +from pathlib import Path + +from app.main import create_app + +SERVER_DIR = Path(__file__).resolve().parents[1] +ROOT_DIR = SERVER_DIR.parent +OUTPUT_DIR = ROOT_DIR / "document" / "development" / "backend_api" +OPENAPI_PATH = OUTPUT_DIR / "openapi.json" +INVENTORY_PATH = OUTPUT_DIR / "interface_inventory.md" + + +def build_inventory(schema: dict[str, object]) -> str: + paths = schema.get("paths", {}) + tag_groups: dict[str, list[tuple[str, str, str]]] = defaultdict(list) + + for path, operations in sorted(paths.items()): + if not isinstance(operations, dict): + continue + for method, operation in sorted(operations.items()): + if not isinstance(operation, dict): + continue + summary = str(operation.get("summary") or operation.get("operationId") or "-") + tags = operation.get("tags") or ["untagged"] + primary_tag = str(tags[0]) + tag_groups[primary_tag].append((method.upper(), str(path), summary)) + + generated_at = datetime.now(UTC).strftime("%Y-%m-%d %H:%M:%S UTC") + lines = [ + "# Backend API Interface Inventory", + "", + f"- Generated at: `{generated_at}`", + f"- API title: `{schema['info']['title']}`", + f"- API version: `{schema['info']['version']}`", + f"- Total paths: `{len(paths)}`", + "", + "## Tag Overview", + "", + ] + + for tag_name in sorted(tag_groups): + lines.append(f"### {tag_name}") + lines.append("") + lines.append("| Method | Path | Summary |") + lines.append("| --- | --- | --- |") + for method, path, summary in tag_groups[tag_name]: + lines.append(f"| `{method}` | `{path}` | {summary} |") + lines.append("") + + return "\n".join(lines).strip() + "\n" + + +def main() -> None: + app = create_app() + schema = app.openapi() + + OUTPUT_DIR.mkdir(parents=True, exist_ok=True) + OPENAPI_PATH.write_text( + json.dumps(schema, ensure_ascii=False, indent=2), + encoding="utf-8", + ) + INVENTORY_PATH.write_text(build_inventory(schema), encoding="utf-8") + + print(f"OpenAPI exported to: {OPENAPI_PATH}") + print(f"Interface inventory exported to: {INVENTORY_PATH}") + + +if __name__ == "__main__": + main() diff --git a/server/src/app/api/deps.py b/server/src/app/api/deps.py index 957a24f..9e9de14 100644 --- a/server/src/app/api/deps.py +++ b/server/src/app/api/deps.py @@ -24,12 +24,24 @@ class CurrentUserContext: is_admin: bool -def get_current_user( - x_auth_username: Annotated[str | None, Header()] = None, - x_auth_name: Annotated[str | None, Header()] = None, - x_auth_role_codes: Annotated[str | None, Header()] = None, - x_auth_is_admin: Annotated[str | None, Header()] = None, -) -> CurrentUserContext: +def get_current_user( + x_auth_username: Annotated[ + str | None, + Header(description="当前登录用户名。知识库接口至少需要提供用户名或姓名。"), + ] = None, + x_auth_name: Annotated[ + str | None, + Header(description="当前登录人展示姓名。未传时默认回退到用户名。"), + ] = None, + x_auth_role_codes: Annotated[ + str | None, + Header(description="角色编码列表,多个角色使用英文逗号分隔,例如 `manager,finance`。"), + ] = None, + x_auth_is_admin: Annotated[ + str | None, + Header(description="是否管理员,支持 `true/false/1/0`。"), + ] = None, +) -> CurrentUserContext: role_codes = [item.strip() for item in (x_auth_role_codes or "").split(",") if item.strip()] is_admin = str(x_auth_is_admin or "").strip().lower() in {"1", "true", "yes", "on"} diff --git a/server/src/app/api/v1/endpoints/agent_assets.py b/server/src/app/api/v1/endpoints/agent_assets.py index b37baac..0217e49 100644 --- a/server/src/app/api/v1/endpoints/agent_assets.py +++ b/server/src/app/api/v1/endpoints/agent_assets.py @@ -16,10 +16,19 @@ from app.schemas.agent_asset import ( AgentAssetVersionCreate, AgentAssetVersionRead, ) +from app.schemas.common import ErrorResponse from app.services.agent_assets import AgentAssetService router = APIRouter(prefix="/agent-assets") DbSession = Annotated[Session, Depends(get_db)] +ActorHeader = Annotated[ + str | None, + Header(description="审计操作者。未传时回退到请求体中的 owner / reviewer 或 `system`。"), +] +RequestIdHeader = Annotated[ + str | None, + Header(description="外部请求 ID,用于串联审计日志和上游调用链。"), +] def _handle_asset_error(exc: Exception) -> None: @@ -32,13 +41,30 @@ def _handle_asset_error(exc: Exception) -> None: raise exc -@router.get("", response_model=list[AgentAssetListItem]) +@router.get( + "", + response_model=list[AgentAssetListItem], + summary="查询 Agent 资产列表", + description="按资产类型、状态、领域和关键字筛选规则、技能、MCP 与任务资产。", +) def list_agent_assets( db: DbSession, - asset_type: str | None = Query(default=None), - status_value: str | None = Query(default=None, alias="status"), - domain: str | None = Query(default=None), - keyword: str | None = Query(default=None), + asset_type: Annotated[ + str | None, + Query(description="资产类型:`rule`、`skill`、`mcp`、`task`。"), + ] = None, + status_value: Annotated[ + str | None, + Query(alias="status", description="资产状态筛选。"), + ] = None, + domain: Annotated[ + str | None, + Query(description="业务领域筛选,例如 `expense`、`ar`、`ap`。"), + ] = None, + keyword: Annotated[ + str | None, + Query(description="资产编码、名称关键字模糊查询。"), + ] = None, ) -> list[AgentAssetListItem]: return AgentAssetService(db).list_assets( asset_type=asset_type, @@ -48,7 +74,18 @@ def list_agent_assets( ) -@router.get("/{asset_id}", response_model=AgentAssetRead) +@router.get( + "/{asset_id}", + response_model=AgentAssetRead, + summary="读取 Agent 资产详情", + description="返回资产当前版本正文、最近版本列表和最近一次审核信息。", + responses={ + status.HTTP_404_NOT_FOUND: { + "model": ErrorResponse, + "description": "资产不存在。", + } + }, +) def get_agent_asset(asset_id: str, db: DbSession) -> AgentAssetRead: asset = AgentAssetService(db).get_asset(asset_id) if asset is None: @@ -56,12 +93,24 @@ def get_agent_asset(asset_id: str, db: DbSession) -> AgentAssetRead: return asset -@router.post("", response_model=AgentAssetRead, status_code=status.HTTP_201_CREATED) +@router.post( + "", + response_model=AgentAssetRead, + status_code=status.HTTP_201_CREATED, + summary="创建 Agent 资产", + description="创建新的规则、技能、MCP 或任务资产,并自动记录审计日志。", + responses={ + status.HTTP_400_BAD_REQUEST: { + "model": ErrorResponse, + "description": "资产编码冲突或请求字段不合法。", + } + }, +) def create_agent_asset( payload: AgentAssetCreate, db: DbSession, - x_actor: Annotated[str | None, Header()] = None, - x_request_id: Annotated[str | None, Header()] = None, + x_actor: ActorHeader = None, + x_request_id: RequestIdHeader = None, ) -> AgentAssetRead: try: return AgentAssetService(db).create_asset( @@ -73,13 +122,28 @@ def create_agent_asset( _handle_asset_error(exc) -@router.patch("/{asset_id}", response_model=AgentAssetRead) +@router.patch( + "/{asset_id}", + response_model=AgentAssetRead, + summary="更新 Agent 资产", + description="更新资产基础信息、当前版本、状态和配置,并写入审计日志。", + responses={ + status.HTTP_400_BAD_REQUEST: { + "model": ErrorResponse, + "description": "状态更新非法或请求字段不合法。", + }, + status.HTTP_404_NOT_FOUND: { + "model": ErrorResponse, + "description": "资产或指定版本不存在。", + }, + }, +) def update_agent_asset( asset_id: str, payload: AgentAssetUpdate, db: DbSession, - x_actor: Annotated[str | None, Header()] = None, - x_request_id: Annotated[str | None, Header()] = None, + x_actor: ActorHeader = None, + x_request_id: RequestIdHeader = None, ) -> AgentAssetRead: try: return AgentAssetService(db).update_asset( @@ -92,9 +156,25 @@ def update_agent_asset( _handle_asset_error(exc) -@router.get("/{asset_id}/versions", response_model=list[AgentAssetVersionRead]) +@router.get( + "/{asset_id}/versions", + response_model=list[AgentAssetVersionRead], + summary="查询资产版本列表", + description="返回指定资产的版本历史,默认按最近版本优先排序。", + responses={ + status.HTTP_404_NOT_FOUND: { + "model": ErrorResponse, + "description": "资产不存在。", + } + }, +) def list_agent_asset_versions( - asset_id: str, db: DbSession, limit: int = Query(default=20, ge=1, le=100) + asset_id: str, + db: DbSession, + limit: Annotated[ + int, + Query(ge=1, le=100, description="返回版本数量上限。"), + ] = 20, ) -> list[AgentAssetVersionRead]: try: return AgentAssetService(db).list_versions(asset_id, limit=limit) @@ -106,13 +186,25 @@ def list_agent_asset_versions( "/{asset_id}/versions", response_model=AgentAssetVersionRead, status_code=status.HTTP_201_CREATED, + summary="创建资产版本", + description="为指定资产创建新版本;规则使用 Markdown,其他资产使用 JSON 快照。", + responses={ + status.HTTP_400_BAD_REQUEST: { + "model": ErrorResponse, + "description": "版本号重复或内容类型不匹配。", + }, + status.HTTP_404_NOT_FOUND: { + "model": ErrorResponse, + "description": "资产不存在。", + }, + }, ) def create_agent_asset_version( asset_id: str, payload: AgentAssetVersionCreate, db: DbSession, - x_actor: Annotated[str | None, Header()] = None, - x_request_id: Annotated[str | None, Header()] = None, + x_actor: ActorHeader = None, + x_request_id: RequestIdHeader = None, ) -> AgentAssetVersionRead: try: return AgentAssetService(db).create_version( @@ -126,14 +218,28 @@ def create_agent_asset_version( @router.post( - "/{asset_id}/reviews", response_model=AgentAssetReviewRead, status_code=status.HTTP_201_CREATED + "/{asset_id}/reviews", + response_model=AgentAssetReviewRead, + status_code=status.HTTP_201_CREATED, + summary="创建资产审核记录", + description="为指定资产版本写入审核结果,并联动更新资产状态。", + responses={ + status.HTTP_400_BAD_REQUEST: { + "model": ErrorResponse, + "description": "审核参数不合法。", + }, + status.HTTP_404_NOT_FOUND: { + "model": ErrorResponse, + "description": "资产或版本不存在。", + }, + }, ) def create_agent_asset_review( asset_id: str, payload: AgentAssetReviewCreate, db: DbSession, - x_actor: Annotated[str | None, Header()] = None, - x_request_id: Annotated[str | None, Header()] = None, + x_actor: ActorHeader = None, + x_request_id: RequestIdHeader = None, ) -> AgentAssetReviewRead: try: return AgentAssetService(db).create_review( @@ -146,12 +252,27 @@ def create_agent_asset_review( _handle_asset_error(exc) -@router.post("/{asset_id}/activate", response_model=AgentAssetRead) +@router.post( + "/{asset_id}/activate", + response_model=AgentAssetRead, + summary="激活资产当前版本", + description="将资产当前版本切换为上线状态;规则资产必须已有 `approved` 审核记录。", + responses={ + status.HTTP_400_BAD_REQUEST: { + "model": ErrorResponse, + "description": "审核未通过或当前版本未设置。", + }, + status.HTTP_404_NOT_FOUND: { + "model": ErrorResponse, + "description": "资产不存在。", + }, + }, +) def activate_agent_asset( asset_id: str, db: DbSession, - x_actor: Annotated[str | None, Header()] = None, - x_request_id: Annotated[str | None, Header()] = None, + x_actor: ActorHeader = None, + x_request_id: RequestIdHeader = None, ) -> AgentAssetRead: try: return AgentAssetService(db).activate_asset( diff --git a/server/src/app/api/v1/endpoints/agent_runs.py b/server/src/app/api/v1/endpoints/agent_runs.py index d98739b..a58b4be 100644 --- a/server/src/app/api/v1/endpoints/agent_runs.py +++ b/server/src/app/api/v1/endpoints/agent_runs.py @@ -7,26 +7,55 @@ from sqlalchemy.orm import Session from app.api.deps import get_db from app.schemas.agent_run import AgentRunRead +from app.schemas.common import ErrorResponse from app.services.agent_runs import AgentRunService router = APIRouter(prefix="/agent-runs") DbSession = Annotated[Session, Depends(get_db)] -@router.get("", response_model=list[AgentRunRead]) +@router.get( + "", + response_model=list[AgentRunRead], + summary="查询 Agent 运行日志", + description="按 Agent、运行状态、来源和数量限制筛选运行日志。", +) def list_agent_runs( db: DbSession, - agent: str | None = Query(default=None), - status_value: str | None = Query(default=None, alias="status"), - source: str | None = Query(default=None), - limit: int = Query(default=20, ge=1, le=100), + agent: Annotated[ + str | None, + Query(description="Agent 名称筛选。"), + ] = None, + status_value: Annotated[ + str | None, + Query(alias="status", description="运行状态筛选。"), + ] = None, + source: Annotated[ + str | None, + Query(description="运行来源筛选。"), + ] = None, + limit: Annotated[ + int, + Query(ge=1, le=100, description="返回记录上限。"), + ] = 20, ) -> list[AgentRunRead]: return AgentRunService(db).list_runs( agent=agent, status=status_value, source=source, limit=limit ) -@router.get("/{run_id}", response_model=AgentRunRead) +@router.get( + "/{run_id}", + response_model=AgentRunRead, + summary="读取单次 Agent 运行详情", + description="按 `run_id` 返回单次执行的路由结果、工具调用和语义解析信息。", + responses={ + status.HTTP_404_NOT_FOUND: { + "model": ErrorResponse, + "description": "运行记录不存在。", + } + }, +) def get_agent_run(run_id: str, db: DbSession) -> AgentRunRead: run = AgentRunService(db).get_run(run_id) if run is None: diff --git a/server/src/app/api/v1/endpoints/audit_logs.py b/server/src/app/api/v1/endpoints/audit_logs.py index 24b1db1..b340c92 100644 --- a/server/src/app/api/v1/endpoints/audit_logs.py +++ b/server/src/app/api/v1/endpoints/audit_logs.py @@ -13,13 +13,30 @@ router = APIRouter(prefix="/audit-logs") DbSession = Annotated[Session, Depends(get_db)] -@router.get("", response_model=list[AuditLogRead]) +@router.get( + "", + response_model=list[AuditLogRead], + summary="查询审计日志", + description="按资源类型、资源 ID、动作类型和数量限制筛选审计日志。", +) def list_audit_logs( db: DbSession, - resource_type: str | None = Query(default=None), - resource_id: str | None = Query(default=None), - action: str | None = Query(default=None), - limit: int = Query(default=50, ge=1, le=200), + resource_type: Annotated[ + str | None, + Query(description="资源类型筛选,例如 `rule`、`task`。"), + ] = None, + resource_id: Annotated[ + str | None, + Query(description="资源主键或业务编码筛选。"), + ] = None, + action: Annotated[ + str | None, + Query(description="动作名称筛选。"), + ] = None, + limit: Annotated[ + int, + Query(ge=1, le=200, description="返回日志上限。"), + ] = 50, ) -> list[AuditLogRead]: return AuditLogService(db).list_logs( resource_type=resource_type, diff --git a/server/src/app/api/v1/endpoints/auth.py b/server/src/app/api/v1/endpoints/auth.py index d3cbbe9..8b7cb3b 100644 --- a/server/src/app/api/v1/endpoints/auth.py +++ b/server/src/app/api/v1/endpoints/auth.py @@ -7,13 +7,25 @@ from sqlalchemy.orm import Session from app.api.deps import get_db from app.schemas.auth import LoginRequest, LoginResponse +from app.schemas.common import ErrorResponse from app.services.auth import AuthService router = APIRouter(prefix="/auth") DbSession = Annotated[Session, Depends(get_db)] -@router.post("/login", response_model=LoginResponse) +@router.post( + "/login", + response_model=LoginResponse, + summary="用户登录", + description="支持管理员账号和员工账号登录,成功后返回前端会话所需的用户信息。", + responses={ + status.HTTP_401_UNAUTHORIZED: { + "model": ErrorResponse, + "description": "账号或密码错误。", + } + }, +) def login(payload: LoginRequest, db: DbSession) -> LoginResponse: try: return AuthService(db).login(payload) diff --git a/server/src/app/api/v1/endpoints/bootstrap.py b/server/src/app/api/v1/endpoints/bootstrap.py index 27feff7..4d8e66b 100644 --- a/server/src/app/api/v1/endpoints/bootstrap.py +++ b/server/src/app/api/v1/endpoints/bootstrap.py @@ -9,11 +9,22 @@ from app.schemas.bootstrap import BootstrapSetupPayload, BootstrapStateRead router = APIRouter(prefix="/bootstrap") -@router.get("", response_model=BootstrapStateRead) +@router.get( + "", + response_model=BootstrapStateRead, + summary="读取初始化状态", + description="返回当前系统是否已完成初始化,以及公司、数据库和缓存配置快照。", +) def get_bootstrap_state() -> BootstrapStateRead: return build_bootstrap_state(get_settings()) -@router.post("", response_model=BootstrapStateRead, status_code=status.HTTP_201_CREATED) +@router.post( + "", + response_model=BootstrapStateRead, + status_code=status.HTTP_201_CREATED, + summary="写入初始化配置", + description="保存系统初始化配置,并刷新运行时数据库连接。", +) def initialize_bootstrap(payload: BootstrapSetupPayload) -> BootstrapStateRead: return persist_bootstrap_config(payload, get_settings()) diff --git a/server/src/app/api/v1/endpoints/employees.py b/server/src/app/api/v1/endpoints/employees.py index fb8a0ca..1f83fd5 100644 --- a/server/src/app/api/v1/endpoints/employees.py +++ b/server/src/app/api/v1/endpoints/employees.py @@ -6,6 +6,7 @@ from fastapi import APIRouter, Depends, HTTPException, Query, status from sqlalchemy.orm import Session from app.api.deps import get_db +from app.schemas.common import ErrorResponse from app.schemas.employee import EmployeeCreate, EmployeeMetaRead, EmployeeRead, EmployeeUpdate from app.services.employee import EmployeeService @@ -13,21 +14,49 @@ router = APIRouter() DbSession = Annotated[Session, Depends(get_db)] -@router.get("/meta", response_model=EmployeeMetaRead) +@router.get( + "/meta", + response_model=EmployeeMetaRead, + summary="读取员工目录元数据", + description="返回员工总数、状态汇总和可选角色列表,供员工管理页面初始化使用。", +) def get_employee_meta(db: DbSession) -> EmployeeMetaRead: return EmployeeService(db).get_employee_meta() -@router.get("", response_model=list[EmployeeRead]) +@router.get( + "", + response_model=list[EmployeeRead], + summary="查询员工列表", + description="按状态和关键字筛选员工目录。", +) def list_employees( db: DbSession, - status_filter: Annotated[str | None, Query(alias="status")] = None, - keyword: str | None = None, + status_filter: Annotated[ + str | None, + Query(alias="status", description="员工状态筛选值。"), + ] = None, + keyword: Annotated[ + str | None, + Query(description="姓名、工号、邮箱等关键字模糊查询。"), + ] = None, ) -> list[EmployeeRead]: return EmployeeService(db).list_employees(status=status_filter, keyword=keyword) -@router.post("", response_model=EmployeeRead, status_code=status.HTTP_201_CREATED) +@router.post( + "", + response_model=EmployeeRead, + status_code=status.HTTP_201_CREATED, + summary="创建员工", + description="创建新的员工目录记录,并初始化基础角色与组织归属。", + responses={ + status.HTTP_400_BAD_REQUEST: { + "model": ErrorResponse, + "description": "员工数据校验失败。", + } + }, +) def create_employee(payload: EmployeeCreate, db: DbSession) -> EmployeeRead: try: return EmployeeService(db).create_employee(payload) @@ -35,7 +64,18 @@ def create_employee(payload: EmployeeCreate, db: DbSession) -> EmployeeRead: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc -@router.get("/{employee_id}", response_model=EmployeeRead) +@router.get( + "/{employee_id}", + response_model=EmployeeRead, + summary="读取员工详情", + description="根据员工主键读取员工完整档案信息。", + responses={ + status.HTTP_404_NOT_FOUND: { + "model": ErrorResponse, + "description": "员工不存在。", + } + }, +) def get_employee(employee_id: str, db: DbSession) -> EmployeeRead: employee = EmployeeService(db).get_employee(employee_id) if employee is None: @@ -43,7 +83,22 @@ def get_employee(employee_id: str, db: DbSession) -> EmployeeRead: return employee -@router.patch("/{employee_id}", response_model=EmployeeRead) +@router.patch( + "/{employee_id}", + response_model=EmployeeRead, + summary="更新员工", + description="更新员工基础信息、角色、密码等可维护字段。", + responses={ + status.HTTP_400_BAD_REQUEST: { + "model": ErrorResponse, + "description": "请求字段不合法。", + }, + status.HTTP_404_NOT_FOUND: { + "model": ErrorResponse, + "description": "员工不存在。", + }, + }, +) def update_employee(employee_id: str, payload: EmployeeUpdate, db: DbSession) -> EmployeeRead: try: return EmployeeService(db).update_employee(employee_id, payload) @@ -53,7 +108,18 @@ def update_employee(employee_id: str, payload: EmployeeUpdate, db: DbSession) -> raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc -@router.post("/{employee_id}/disable", response_model=EmployeeRead) +@router.post( + "/{employee_id}/disable", + response_model=EmployeeRead, + summary="停用员工", + description="将员工状态切换为停用,阻止其继续登录系统。", + responses={ + status.HTTP_404_NOT_FOUND: { + "model": ErrorResponse, + "description": "员工不存在。", + } + }, +) def disable_employee(employee_id: str, db: DbSession) -> EmployeeRead: try: return EmployeeService(db).disable_employee(employee_id) diff --git a/server/src/app/api/v1/endpoints/health.py b/server/src/app/api/v1/endpoints/health.py index 10fbf06..d44cebc 100644 --- a/server/src/app/api/v1/endpoints/health.py +++ b/server/src/app/api/v1/endpoints/health.py @@ -5,12 +5,18 @@ from sqlalchemy import text from app.core.config import get_settings from app.db.session import get_engine +from app.schemas.common import HealthCheckRead router = APIRouter(prefix="/health") -@router.get("") -def health_check() -> dict[str, object]: +@router.get( + "", + response_model=HealthCheckRead, + summary="服务健康检查", + description="检查服务基础状态,并在系统初始化完成后验证数据库连通性。", +) +def health_check() -> HealthCheckRead: settings = get_settings() database_ok = False database_error = None @@ -23,12 +29,12 @@ def health_check() -> dict[str, object]: except Exception as exc: # pragma: no cover - runtime connectivity branch database_error = str(exc) - return { - "status": "ok" if database_ok else "degraded", - "database": { + return HealthCheckRead( + status="ok" if database_ok else "degraded", + database={ "configured": settings.setup_completed, "ok": database_ok, "error": database_error, }, - "redis": {"configured": bool(settings.redis_url), "enabled": bool(settings.redis_url)}, - } + redis={"configured": bool(settings.redis_url), "enabled": bool(settings.redis_url)}, + ) diff --git a/server/src/app/api/v1/endpoints/knowledge.py b/server/src/app/api/v1/endpoints/knowledge.py index 41e4b96..0077f6e 100644 --- a/server/src/app/api/v1/endpoints/knowledge.py +++ b/server/src/app/api/v1/endpoints/knowledge.py @@ -1,124 +1,294 @@ -from __future__ import annotations - -from typing import Annotated - -from fastapi import APIRouter, Depends, HTTPException, Query, Request, status -from fastapi.responses import FileResponse - -from app.api.deps import CurrentUserContext, get_current_user, require_admin_user -from app.schemas.knowledge import ( - KnowledgeActionResponse, - KnowledgeDocumentDetailRead, - KnowledgeLibraryRead, - KnowledgeOnlyOfficeCallbackRead, - KnowledgeOnlyOfficeConfigRead, -) -from app.services.knowledge import KnowledgeService - -router = APIRouter(prefix="/knowledge") - - -@router.get("/library", response_model=KnowledgeLibraryRead) -def get_knowledge_library( - _: Annotated[CurrentUserContext, Depends(get_current_user)], -) -> KnowledgeLibraryRead: - return KnowledgeService().list_library() - - -@router.get("/documents/{document_id}", response_model=KnowledgeDocumentDetailRead) -def get_knowledge_document( - document_id: str, - _: Annotated[CurrentUserContext, Depends(get_current_user)], -) -> KnowledgeDocumentDetailRead: - try: - return KnowledgeService().get_document_detail(document_id) - except FileNotFoundError as exc: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="知识库文件不存在。") from exc - - -@router.get("/documents/{document_id}/onlyoffice-config", response_model=KnowledgeOnlyOfficeConfigRead) -def get_knowledge_document_onlyoffice_config( - document_id: str, - current_user: Annotated[CurrentUserContext, Depends(get_current_user)], -) -> KnowledgeOnlyOfficeConfigRead: - try: - return KnowledgeService().build_onlyoffice_config(document_id, current_user) - except FileNotFoundError as exc: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="知识库文件不存在。") from exc +from __future__ import annotations + +from typing import Annotated + +from fastapi import APIRouter, Body, Depends, HTTPException, Query, status +from fastapi.responses import FileResponse + +from app.api.deps import CurrentUserContext, get_current_user, require_admin_user +from app.schemas.common import ErrorResponse +from app.schemas.knowledge import ( + KnowledgeActionResponse, + KnowledgeDocumentDetailRead, + KnowledgeLibraryRead, + KnowledgeOnlyOfficeCallbackRead, + KnowledgeOnlyOfficeCallbackWrite, + KnowledgeOnlyOfficeConfigRead, +) +from app.services.knowledge import KnowledgeService + +router = APIRouter(prefix="/knowledge") + + +@router.get( + "/library", + response_model=KnowledgeLibraryRead, + summary="查询知识库目录", + description="返回固定知识库目录与当前已上传文档列表。", + responses={ + status.HTTP_401_UNAUTHORIZED: { + "model": ErrorResponse, + "description": "未提供知识库访问用户头。", + } + }, +) +def get_knowledge_library( + _: Annotated[CurrentUserContext, Depends(get_current_user)], +) -> KnowledgeLibraryRead: + return KnowledgeService().list_library() + + +@router.get( + "/documents/{document_id}", + response_model=KnowledgeDocumentDetailRead, + summary="读取知识库文档详情", + description="返回单个知识库文档的元信息、预览类型和预览内容。", + responses={ + status.HTTP_401_UNAUTHORIZED: { + "model": ErrorResponse, + "description": "未提供知识库访问用户头。", + }, + status.HTTP_404_NOT_FOUND: { + "model": ErrorResponse, + "description": "知识库文件不存在。", + }, + }, +) +def get_knowledge_document( + document_id: str, + _: Annotated[CurrentUserContext, Depends(get_current_user)], +) -> KnowledgeDocumentDetailRead: + try: + return KnowledgeService().get_document_detail(document_id) + except FileNotFoundError as exc: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="知识库文件不存在。", + ) from exc + + +@router.get( + "/documents/{document_id}/onlyoffice-config", + response_model=KnowledgeOnlyOfficeConfigRead, + summary="读取 ONLYOFFICE 预览配置", + description="为支持的 Office 文档生成 ONLYOFFICE 前端配置和临时访问令牌。", + responses={ + status.HTTP_400_BAD_REQUEST: { + "model": ErrorResponse, + "description": "ONLYOFFICE 未启用、配置不完整或文件格式不支持。", + }, + status.HTTP_401_UNAUTHORIZED: { + "model": ErrorResponse, + "description": "未提供知识库访问用户头。", + }, + status.HTTP_404_NOT_FOUND: { + "model": ErrorResponse, + "description": "知识库文件不存在。", + }, + }, +) +def get_knowledge_document_onlyoffice_config( + document_id: str, + current_user: Annotated[CurrentUserContext, Depends(get_current_user)], +) -> KnowledgeOnlyOfficeConfigRead: + try: + return KnowledgeService().build_onlyoffice_config(document_id, current_user) + except FileNotFoundError as exc: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="知识库文件不存在。", + ) from exc except ValueError as exc: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc + + +@router.post( + "/documents", + response_model=KnowledgeDocumentDetailRead, + status_code=status.HTTP_201_CREATED, + summary="上传知识库文档", + description="上传原始文件二进制内容到指定知识库目录。已有同名文件会覆盖并提升版本号。", + responses={ + status.HTTP_400_BAD_REQUEST: { + "model": ErrorResponse, + "description": "目录、文件名或文件内容不合法。", + }, + status.HTTP_401_UNAUTHORIZED: { + "model": ErrorResponse, + "description": "未提供知识库访问用户头。", + }, + status.HTTP_403_FORBIDDEN: { + "model": ErrorResponse, + "description": "只有管理员可以上传知识库文件。", + }, + }, +) +def upload_knowledge_document( + content: Annotated[ + bytes, + Body( + media_type="application/octet-stream", + description="待上传的文件二进制内容。", + ), + ], + folder: Annotated[str, Query(min_length=1, description="目标知识库目录名称。")], + filename: Annotated[str, Query(min_length=1, description="原始文件名。")], + current_user: Annotated[CurrentUserContext, Depends(require_admin_user)], +) -> KnowledgeDocumentDetailRead: + try: + return KnowledgeService().upload_document(folder, filename, content, current_user) + except ValueError as exc: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc + + +@router.delete( + "/documents/{document_id}", + response_model=KnowledgeActionResponse, + summary="删除知识库文档", + description="删除知识库文档及其索引记录。", + responses={ + status.HTTP_401_UNAUTHORIZED: { + "model": ErrorResponse, + "description": "未提供知识库访问用户头。", + }, + status.HTTP_403_FORBIDDEN: { + "model": ErrorResponse, + "description": "只有管理员可以删除知识库文件。", + }, + status.HTTP_404_NOT_FOUND: { + "model": ErrorResponse, + "description": "知识库文件不存在。", + }, + }, +) +def delete_knowledge_document( + document_id: str, + _: Annotated[CurrentUserContext, Depends(require_admin_user)], +) -> KnowledgeActionResponse: + try: + KnowledgeService().delete_document(document_id) + except FileNotFoundError as exc: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="知识库文件不存在。", + ) from exc + + return KnowledgeActionResponse(detail="知识库文件已删除。") + + +@router.get( + "/documents/{document_id}/content", + response_class=FileResponse, + summary="下载或预览知识库原文", + description="根据文档 ID 返回原始文件内容,可用于浏览器内联预览或下载。", + responses={ + status.HTTP_200_OK: { + "description": "文件内容。", + "content": {"application/octet-stream": {}}, + }, + status.HTTP_401_UNAUTHORIZED: { + "model": ErrorResponse, + "description": "未提供知识库访问用户头。", + }, + status.HTTP_404_NOT_FOUND: { + "model": ErrorResponse, + "description": "知识库文件不存在。", + }, + }, +) +def get_knowledge_document_content( + document_id: str, + disposition: Annotated[ + str, + Query( + pattern="^(inline|attachment)$", + description="内容展示方式,支持 `inline` 或 `attachment`。", + ), + ] = "inline", + _: Annotated[CurrentUserContext, Depends(get_current_user)] = None, +) -> FileResponse: + try: + file_path, media_type, filename = KnowledgeService().get_document_content(document_id) + except FileNotFoundError as exc: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="知识库文件不存在。", + ) from exc - -@router.post("/documents", response_model=KnowledgeDocumentDetailRead, status_code=status.HTTP_201_CREATED) -async def upload_knowledge_document( - request: Request, - folder: Annotated[str, Query(min_length=1)], - filename: Annotated[str, Query(min_length=1)], - current_user: Annotated[CurrentUserContext, Depends(require_admin_user)], -) -> KnowledgeDocumentDetailRead: - content = await request.body() - try: - return KnowledgeService().upload_document(folder, filename, content, current_user) - except ValueError as exc: - raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc - - -@router.delete("/documents/{document_id}", response_model=KnowledgeActionResponse) -def delete_knowledge_document( - document_id: str, - _: Annotated[CurrentUserContext, Depends(require_admin_user)], -) -> KnowledgeActionResponse: - try: - KnowledgeService().delete_document(document_id) - except FileNotFoundError as exc: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="知识库文件不存在。") from exc - - return KnowledgeActionResponse(detail="知识库文件已删除。") - - -@router.get("/documents/{document_id}/content") -def get_knowledge_document_content( - document_id: str, - disposition: Annotated[str, Query(pattern="^(inline|attachment)$")] = "inline", - _: Annotated[CurrentUserContext, Depends(get_current_user)] = None, -) -> FileResponse: - try: - file_path, media_type, filename = KnowledgeService().get_document_content(document_id) - except FileNotFoundError as exc: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="知识库文件不存在。") from exc - - _ = disposition - return FileResponse(file_path, media_type=media_type, filename=filename) - - -@router.get("/documents/{document_id}/onlyoffice/content") -def get_knowledge_document_onlyoffice_content( - document_id: str, - access_token: Annotated[str, Query(min_length=1)], -) -> FileResponse: - try: - service = KnowledgeService() - service.validate_onlyoffice_access_token(document_id, access_token) - file_path, media_type, filename = service.get_document_content(document_id) - except FileNotFoundError as exc: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="知识库文件不存在。") from exc + _ = disposition + return FileResponse(file_path, media_type=media_type, filename=filename) + + +@router.get( + "/documents/{document_id}/onlyoffice/content", + response_class=FileResponse, + summary="读取 ONLYOFFICE 文档源文件", + description="供 ONLYOFFICE 服务通过短时访问令牌拉取原始文件内容。", + responses={ + status.HTTP_200_OK: { + "description": "文件内容。", + "content": {"application/octet-stream": {}}, + }, + status.HTTP_401_UNAUTHORIZED: { + "model": ErrorResponse, + "description": "ONLYOFFICE 访问令牌无效。", + }, + status.HTTP_404_NOT_FOUND: { + "model": ErrorResponse, + "description": "知识库文件不存在。", + }, + }, +) +def get_knowledge_document_onlyoffice_content( + document_id: str, + access_token: Annotated[ + str, + Query(min_length=1, description="ONLYOFFICE 临时访问令牌。"), + ], +) -> FileResponse: + try: + service = KnowledgeService() + service.validate_onlyoffice_access_token(document_id, access_token) + file_path, media_type, filename = service.get_document_content(document_id) + except FileNotFoundError as exc: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="知识库文件不存在。", + ) from exc except ValueError as exc: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=str(exc)) from exc - - return FileResponse(file_path, media_type=media_type, filename=filename) - - -@router.post("/documents/{document_id}/onlyoffice/callback", response_model=KnowledgeOnlyOfficeCallbackRead) -async def handle_knowledge_document_onlyoffice_callback( - document_id: str, - request: Request, -) -> KnowledgeOnlyOfficeCallbackRead: - payload = await request.json() - try: - KnowledgeService().handle_onlyoffice_callback(document_id, payload) - except FileNotFoundError as exc: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="知识库文件不存在。") from exc - except ValueError as exc: - raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc + + return FileResponse(file_path, media_type=media_type, filename=filename) + + +@router.post( + "/documents/{document_id}/onlyoffice/callback", + response_model=KnowledgeOnlyOfficeCallbackRead, + summary="接收 ONLYOFFICE 回调", + description="接收 ONLYOFFICE 文档回写回调,在状态满足要求时更新知识库文件内容。", + responses={ + status.HTTP_400_BAD_REQUEST: { + "model": ErrorResponse, + "description": "回调载荷不合法。", + }, + status.HTTP_404_NOT_FOUND: { + "model": ErrorResponse, + "description": "知识库文件不存在。", + }, + }, +) +def handle_knowledge_document_onlyoffice_callback( + document_id: str, + payload: KnowledgeOnlyOfficeCallbackWrite, +) -> KnowledgeOnlyOfficeCallbackRead: + try: + KnowledgeService().handle_onlyoffice_callback(document_id, payload.model_dump()) + except FileNotFoundError as exc: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="知识库文件不存在。", + ) from exc + except ValueError as exc: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc return KnowledgeOnlyOfficeCallbackRead() diff --git a/server/src/app/api/v1/endpoints/reimbursements.py b/server/src/app/api/v1/endpoints/reimbursements.py index 6e1c7c7..6259780 100644 --- a/server/src/app/api/v1/endpoints/reimbursements.py +++ b/server/src/app/api/v1/endpoints/reimbursements.py @@ -6,6 +6,7 @@ from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session from app.api.deps import get_db +from app.schemas.common import ErrorResponse from app.schemas.reimbursement import ReimbursementCreate, ReimbursementRead from app.services.reimbursement import ReimbursementService @@ -13,17 +14,39 @@ router = APIRouter() DbSession = Annotated[Session, Depends(get_db)] -@router.get("", response_model=list[ReimbursementRead]) +@router.get( + "", + response_model=list[ReimbursementRead], + summary="查询报销申请列表", + description="返回当前系统中的报销申请列表。", +) def list_reimbursements(db: DbSession) -> list[ReimbursementRead]: return ReimbursementService(db).list_reimbursements() -@router.post("", response_model=ReimbursementRead, status_code=status.HTTP_201_CREATED) +@router.post( + "", + response_model=ReimbursementRead, + status_code=status.HTTP_201_CREATED, + summary="创建报销申请", + description="创建一条新的报销申请记录,初始状态为 `draft`。", +) def create_reimbursement(payload: ReimbursementCreate, db: DbSession) -> ReimbursementRead: return ReimbursementService(db).create_reimbursement(payload) -@router.get("/{request_id}", response_model=ReimbursementRead) +@router.get( + "/{request_id}", + response_model=ReimbursementRead, + summary="读取报销申请详情", + description="根据报销申请主键读取单据详情。", + responses={ + status.HTTP_404_NOT_FOUND: { + "model": ErrorResponse, + "description": "报销申请不存在。", + } + }, +) def get_reimbursement(request_id: str, db: DbSession) -> ReimbursementRead: request = ReimbursementService(db).get_reimbursement(request_id) if request is None: diff --git a/server/src/app/api/v1/endpoints/settings.py b/server/src/app/api/v1/endpoints/settings.py index 3faeb8b..062ad80 100644 --- a/server/src/app/api/v1/endpoints/settings.py +++ b/server/src/app/api/v1/endpoints/settings.py @@ -6,7 +6,8 @@ from fastapi import APIRouter, Depends, Header, HTTPException, status from sqlalchemy.orm import Session from app.api.deps import get_db -from app.core.config import get_settings +from app.core.config import get_settings as get_runtime_settings +from app.schemas.common import ErrorResponse from app.schemas.settings import ( ModelConnectivityTestRead, ModelConnectivityTestRequest, @@ -22,9 +23,12 @@ DbSession = Annotated[Session, Depends(get_db)] def require_hermes_agent_token( - authorization: Annotated[str | None, Header()] = None, + authorization: Annotated[ + str | None, + Header(description="Hermes 读取运行时模型配置时使用的 Bearer Token。"), + ] = None, ) -> None: - configured_token = str(get_settings().hermes_agent_shared_token or "").strip() + configured_token = str(get_runtime_settings().hermes_agent_shared_token or "").strip() if not configured_token: raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, @@ -40,12 +44,28 @@ def require_hermes_agent_token( ) -@router.get("", response_model=SettingsRead) +@router.get( + "", + response_model=SettingsRead, + summary="读取系统设置", + description="返回公司、管理员、模型、日志、邮件和 ONLYOFFICE 的设置快照。", +) def get_settings(db: DbSession) -> SettingsRead: return SettingsService(db).get_settings_snapshot() -@router.put("", response_model=SettingsRead) +@router.put( + "", + response_model=SettingsRead, + summary="保存系统设置", + description="保存系统设置,并同步运行时模型配置与 Hermes 使用的模型路由。", + responses={ + status.HTTP_400_BAD_REQUEST: { + "model": ErrorResponse, + "description": "设置字段校验失败。", + } + }, +) def update_settings(payload: SettingsWrite, db: DbSession) -> SettingsRead: try: return SettingsService(db).save_settings_snapshot(payload) @@ -53,8 +73,16 @@ def update_settings(payload: SettingsWrite, db: DbSession) -> SettingsRead: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc -@router.post("/model-connectivity", response_model=ModelConnectivityTestRead) -def test_model_connectivity(payload: ModelConnectivityTestRequest, db: DbSession) -> ModelConnectivityTestRead: +@router.post( + "/model-connectivity", + response_model=ModelConnectivityTestRead, + summary="测试模型连通性", + description="验证指定模型服务端点是否可用;当未传 API Key 且提供 slot 时会尝试复用已保存密钥。", +) +def test_model_connectivity( + payload: ModelConnectivityTestRequest, + db: DbSession, +) -> ModelConnectivityTestRead: resolved_payload = payload if not payload.api_key and payload.slot: @@ -69,6 +97,22 @@ def test_model_connectivity(payload: ModelConnectivityTestRequest, db: DbSession "/runtime-models/{slot}", response_model=RuntimeModelConfigRead, dependencies=[Depends(require_hermes_agent_token)], + summary="读取 Hermes 运行时模型配置", + description="供 Hermes 进程读取主模型、备用模型、VLM 或 Embedding 模型的运行时配置。", + responses={ + status.HTTP_401_UNAUTHORIZED: { + "model": ErrorResponse, + "description": "Hermes 令牌校验失败。", + }, + status.HTTP_404_NOT_FOUND: { + "model": ErrorResponse, + "description": "指定模型槽位不存在。", + }, + status.HTTP_503_SERVICE_UNAVAILABLE: { + "model": ErrorResponse, + "description": "Hermes 集成令牌尚未配置。", + }, + }, ) def get_runtime_model_config( slot: str, diff --git a/server/src/app/core/openapi.py b/server/src/app/core/openapi.py new file mode 100644 index 0000000..f5971c5 --- /dev/null +++ b/server/src/app/core/openapi.py @@ -0,0 +1,80 @@ +API_DESCRIPTION = """ +X-Financial 后端 OpenAPI 文档。 + +## 基本信息 + +- 所有业务接口都挂在 `/api/v1` 前缀下。 +- 交互式 Swagger 页面默认位于 `/docs`,ReDoc 页面位于 `/redoc`。 +- 仓库内导出的静态规范文件位于 `document/development/backend_api/openapi.json`。 + +## 鉴权约定 + +- 知识库接口依赖以下请求头模拟当前用户: + - `X-Auth-Username` + - `X-Auth-Name` + - `X-Auth-Role-Codes` + - `X-Auth-Is-Admin` +- Agent 资产写接口支持以下审计头: + - `X-Actor` + - `X-Request-Id` +- Hermes 运行时模型配置接口需要: + - `Authorization: Bearer ` + +## 当前模块范围 + +- 系统初始化与健康检查 +- 登录认证 +- 员工目录 +- 报销单 +- 知识库与 ONLYOFFICE +- 系统设置与模型连通性 +- Agent 资产、运行日志、审计日志 +""".strip() + + +OPENAPI_TAGS = [ + { + "name": "health", + "description": "服务健康检查与数据库 / Redis 连通性状态。", + }, + { + "name": "bootstrap", + "description": "系统初始化配置,包括公司信息、数据库和缓存配置。", + }, + { + "name": "auth", + "description": "后台登录认证接口,支持管理员和员工账号登录。", + }, + { + "name": "employees", + "description": "员工目录管理,包括员工列表、详情、创建、更新和停用。", + }, + { + "name": "reimbursements", + "description": "报销申请基础接口,包含列表、创建和详情查询。", + }, + { + "name": "knowledge", + "description": "知识库文件管理、内容访问与 ONLYOFFICE 集成接口。", + }, + { + "name": "settings", + "description": "系统设置、模型配置、模型连通性探测和 Hermes 运行时模型配置。", + }, + { + "name": "agent-assets", + "description": "Agent 资产中心,覆盖规则、技能、MCP、任务及其版本、审核和上线流程。", + }, + { + "name": "agent-runs", + "description": "Agent 运行日志查询,包括工具调用和语义解析结果。", + }, + { + "name": "audit-logs", + "description": "系统审计日志查询接口,用于追踪资产和任务写操作。", + }, + { + "name": "root", + "description": "服务根入口,用于确认应用已启动。", + }, +] diff --git a/server/src/app/main.py b/server/src/app/main.py index 63c7e1c..80d24eb 100644 --- a/server/src/app/main.py +++ b/server/src/app/main.py @@ -1,19 +1,41 @@ -from __future__ import annotations - -from fastapi import FastAPI -from fastapi.middleware.cors import CORSMiddleware - +from __future__ import annotations + +from collections.abc import AsyncIterator +from contextlib import asynccontextmanager + +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + from app.api.router import api_router from app.core.config import get_settings from app.core.logging import get_logger, setup_logging +from app.core.openapi import API_DESCRIPTION, OPENAPI_TAGS from app.middleware.logging import AccessLogMiddleware +from app.schemas.common import RootStatusRead from app.services.agent_foundation import prepare_agent_foundation from app.services.employee import prepare_employee_directory from app.services.knowledge import prepare_knowledge_library - - -def create_app() -> FastAPI: - settings = get_settings() + + +@asynccontextmanager +async def lifespan(_: FastAPI) -> AsyncIterator[None]: + settings = get_settings() + logger = get_logger("app.main") + + prepare_employee_directory() + prepare_agent_foundation() + prepare_knowledge_library() + logger.info( + "Server ready - host=%s port=%s prefix=%s", + settings.app_host, + settings.app_port, + settings.api_v1_prefix, + ) + yield + + +def create_app() -> FastAPI: + settings = get_settings() setup_logging( level=settings.log_level, @@ -26,11 +48,14 @@ def create_app() -> FastAPI: "Starting %s (env=%s, debug=%s)", settings.app_name, settings.app_env, settings.app_debug ) - app = FastAPI( - title=settings.app_name, - debug=settings.app_debug, - version="0.1.0", - ) + app = FastAPI( + title=settings.app_name, + debug=settings.app_debug, + version="0.1.0", + description=API_DESCRIPTION, + openapi_tags=OPENAPI_TAGS, + lifespan=lifespan, + ) app.add_middleware(AccessLogMiddleware) @@ -45,23 +70,17 @@ def create_app() -> FastAPI: app.include_router(api_router, prefix=settings.api_v1_prefix) - @app.get("/", tags=["root"]) - def root() -> dict[str, str]: - return {"message": f"{settings.app_name} is running"} - - @app.on_event("startup") - def _on_startup() -> None: - prepare_employee_directory() - prepare_agent_foundation() - prepare_knowledge_library() - logger.info( - "Server ready - host=%s port=%s prefix=%s", - settings.app_host, - settings.app_port, - settings.api_v1_prefix, - ) - - return app + @app.get( + "/", + tags=["root"], + response_model=RootStatusRead, + summary="服务根检查", + description="用于快速确认后端服务进程已经启动。", + ) + def root() -> RootStatusRead: + return RootStatusRead(message=f"{settings.app_name} is running") + + return app app = create_app() diff --git a/server/src/app/schemas/common.py b/server/src/app/schemas/common.py new file mode 100644 index 0000000..965fc77 --- /dev/null +++ b/server/src/app/schemas/common.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +from pydantic import BaseModel + + +class ErrorResponse(BaseModel): + detail: str + + +class RootStatusRead(BaseModel): + message: str + + +class HealthDatabaseStatusRead(BaseModel): + configured: bool + ok: bool + error: str | None = None + + +class HealthRedisStatusRead(BaseModel): + configured: bool + enabled: bool + + +class HealthCheckRead(BaseModel): + status: str + database: HealthDatabaseStatusRead + redis: HealthRedisStatusRead diff --git a/server/src/app/schemas/knowledge.py b/server/src/app/schemas/knowledge.py index d92bbe3..6839a4a 100644 --- a/server/src/app/schemas/knowledge.py +++ b/server/src/app/schemas/knowledge.py @@ -1,8 +1,8 @@ from __future__ import annotations -from typing import Any - -from pydantic import BaseModel, Field +from typing import Any + +from pydantic import BaseModel, ConfigDict, Field class KnowledgeFolderRead(BaseModel): @@ -58,13 +58,21 @@ class KnowledgeOnlyOfficeConfigRead(BaseModel): config: dict[str, Any] = Field(default_factory=dict) -class KnowledgeOnlyOfficeCallbackRead(BaseModel): - error: int = 0 - - -class KnowledgeLibraryRead(BaseModel): - folders: list[KnowledgeFolderRead] = Field(default_factory=list) - documents: list[KnowledgeDocumentRead] = Field(default_factory=list) +class KnowledgeOnlyOfficeCallbackRead(BaseModel): + error: int = 0 + + +class KnowledgeOnlyOfficeCallbackWrite(BaseModel): + model_config = ConfigDict(extra="allow") + + status: int = Field(description="ONLYOFFICE 回调状态码。") + url: str | None = Field(default=None, description="文档下载地址,状态为 2 或 6 时使用。") + users: list[str] = Field(default_factory=list, description="当前编辑用户列表。") + + +class KnowledgeLibraryRead(BaseModel): + folders: list[KnowledgeFolderRead] = Field(default_factory=list) + documents: list[KnowledgeDocumentRead] = Field(default_factory=list) class KnowledgeActionResponse(BaseModel): diff --git a/server/tests/test_openapi_schema.py b/server/tests/test_openapi_schema.py new file mode 100644 index 0000000..21eabb8 --- /dev/null +++ b/server/tests/test_openapi_schema.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +from app.core.config import get_settings +from app.main import create_app + + +def test_openapi_schema_includes_documented_backend_routes() -> None: + schema = create_app().openapi() + + assert schema["info"]["title"] == get_settings().app_name + assert any(tag["name"] == "agent-assets" for tag in schema["tags"]) + assert any(tag["name"] == "knowledge" for tag in schema["tags"]) + + agent_assets_post = schema["paths"]["/api/v1/agent-assets"]["post"] + assert agent_assets_post["summary"] == "创建 Agent 资产" + assert any(param["name"] == "x-actor" for param in agent_assets_post["parameters"]) + + knowledge_upload_post = schema["paths"]["/api/v1/knowledge/documents"]["post"] + assert knowledge_upload_post["summary"] == "上传知识库文档" + assert "application/octet-stream" in knowledge_upload_post["requestBody"]["content"] + + knowledge_callback_post = schema["paths"][ + "/api/v1/knowledge/documents/{document_id}/onlyoffice/callback" + ]["post"] + assert knowledge_callback_post["summary"] == "接收 ONLYOFFICE 回调" + assert "application/json" in knowledge_callback_post["requestBody"]["content"] + + root_get = schema["paths"]["/"]["get"] + assert root_get["summary"] == "服务根检查"