# Plan: Admin Console — Enterprise Department-Scoped Management **Date**: 2026-06-21 **Status**: completed **Type**: feat **Origin**: `docs/brainstorms/2026-06-21-admin-console-requirements.md` **Branch**: `feat/admin-console` --- ## Summary 为 Fischer AgentKit 构建统一企业管理端,嵌入主应用 `/admin` 路由组。核心模型:单企业部署 + 部门级权限隔离 + 能力按部门绑定 + 用户多部门归属(权限并集)。MVP 覆盖 4 个领域:部门与用户管理、LLM 配置管理、Skill 与 KB 管理、用量仪表盘与配额。Web UI + CLI 双通道共享 service 层。 ## Problem Frame 当前 AgentKit 没有统一管理端:admin 端点只有 4 个(session 管理),无 user CRUD;LLM 配置只在 `agentkit.yaml`;KB/Skill 无部门隔离;用量不分用户。管理工作流是"零散脚本不成体系"。 实际场景:单企业部署,按部门(人事/研发/财务)划分权限,部门绑定专属 skill/KB/工具,用户可多部门(权限并集)。 ## Requirements (from origin doc) - **R1**: 部门 CRUD + 部门能力绑定(skill/KB/工具权限/LLM 配额) - **R2**: 用户 CRUD + 多部门归属(`user_departments` 多对多)+ 密码重置 + 禁用/启用 - **R3**: LLM 配置运行时管理(Provider/Model/API Key CRUD + fallback 链 + 按部门配额) - **R4**: Skill 启停/编辑/导入 + 按部门绑定 - **R5**: KB 文档 CRUD + 按部门隔离 - **R6**: 用量仪表盘(按部门/用户/时间)+ 配额硬拒绝(429) - **R7**: CLI `agentkit admin ` 镜像 Web UI - **R8**: 部门隔离安全测试(人事部用户无法访问研发部资源,除非同时属于两个部门) - **SC1**: 超管可在 Web UI 或 CLI 完成全部管理操作,无需手动编辑 YAML 或操作数据库 - **SC2**: 部门隔离通过安全测试 - **SC3**: LLM 配置运行时修改立即生效 - **SC4**: 用量仪表盘数据延迟 < 1 分钟 - **SC5**: 配额超限返回 429 - **SC6**: CLI 与 Web UI 操作结果一致 ## Key Technical Decisions ### KTD1: 角色模型简化为两级(MVP) **决策**:MVP 只有超级管理员(admin)和普通用户(member)两种角色,不实现部门管理员。 **理由**:用户明确选择"MVP 只有超管和普通用户"。部门管理员涉及跨部门权限委托、本部门资源管理边界等复杂逻辑,放到第二期。 **影响**:现有 `permissions.py` 的 3 级 RBAC(member/operator/admin)保持不变,`operator` 角色保留但 MVP 不主动使用。所有 admin 端点用现有 `_require_admin`(`USER_MANAGE` 权限)守卫。 ### KTD2: 部门隔离用 `department_id` 列 + `user_departments` 多对多表 **决策**: - 新增 `departments` 表(id, name, description, is_active, created_at) - 新增 `user_departments` 多对多表(user_id, department_id, created_at) - 资源表(skills 绑定、kb_documents、llm_usage)加 `department_id` 列(NULL 表示全局共享) - `DepartmentContextMiddleware` 从 JWT 读取 user_id,查询 `user_departments`,注入 `request.state.department_ids`(列表) - Repository 层强制 `WHERE department_id IN (?, ?, NULL)` 过滤(NULL 表示全局资源,所有部门可见) **理由**:用户明确选择"共享数据库 + department_id 列"。多对多表支持用户多部门归属。NULL `department_id` 表示全局共享资源(如默认 LLM 配置),避免每个部门都重复配置。 **风险**:middleware 完整性是关键——任何遗漏 `department_id` 过滤的查询都是数据泄露。通过 repository 层强制参数缓解,但需要安全测试覆盖(R8)。 ### KTD3: LLM 配置保持 YAML + 写回文件(MVP) **决策**:LLM 配置继续以 `agentkit.yaml` 为权威源。运行时修改通过 `PUT /settings/llm` 写回 YAML 文件,依赖现有 `ServerConfig.watch_config()` 文件监听触发热重载。 **理由**:用户明确选择"保持 YAML + 写回文件"。完全迁移到数据库工作量大,且现有文件监听热重载机制已可用。 **影响**: - 不新增 `llm_providers` / `llm_models` / `llm_api_keys` 数据库表 - 按部门配额存储在数据库(`department_quotas` 表),不存 YAML - API Key 在 YAML 中存储(现有方式),按部门配额在 DB 中 **风险**:并发修改 YAML 文件可能冲突。MVP 用文件锁(`fcntl.flock`)缓解,第二期考虑迁移到 DB。 ### KTD4: 用量跟踪加 `user_id` + `department_id` 字段 **决策**: - 修改 `UsageRecord` dataclass,新增 `user_id` 和 `department_id` 字段 - 修改 `InMemoryUsageStore` 和 `RedisUsageStore`,存储和查询时包含新字段 - LLM Gateway 调用时,从请求上下文获取 `user_id` 和 `department_id`,传入 `UsageTracker` - 新增 `GET /admin/usage` 端点,按部门/用户/时间维度聚合查询 **理由**:当前 `UsageRecord` 只有 `agent_name`,无法按用户/部门统计。必须加字段才能满足 R6。 **影响**:`UsageRecord` 是 dataclass,加字段向后兼容(旧记录的 `user_id`/`department_id` 为 None)。Redis Hash 结构需要扩展 key 包含 `user_id` 和 `department_id`。 ### KTD5: CLI/Web 共享 service 层 **决策**:所有管理操作封装在 `src/agentkit/server/admin/` 下的 service 模块(如 `user_service.py`、`department_service.py`、`llm_config_service.py`)。Web UI 路由和 CLI 命令都调用这些 service。 **理由**:用户明确要求"CLI/Web 一致性"。共享 service 层避免双份业务逻辑。 **影响**:CLI 需要通过 HTTP 调用 server(`agentkit admin` 命令实际是调用 `/api/v1/admin/*` 端点),而不是直接操作数据库。这保证了一致性,但要求 CLI 有 server URL 配置。 ### KTD6: 前端 `AdminLayout` 独立路由树 **决策**:前端新增 `AdminLayout.vue`(左侧导航 + 内容区),所有 admin 页面作为子路由。路由结构: ``` /admin → AdminLayout /admin/dashboard → AdminDashboard(概览) /admin/departments → DepartmentsView(部门管理) /admin/users → UsersView(用户管理,扩展现有) /admin/llm → LlmConfigView(LLM 配置) /admin/skills → SkillsView(Skill 管理) /admin/kb → KbManagementView(KB 管理) /admin/usage → UsageDashboardView(用量仪表盘) ``` **理由**:独立路由树便于未来拆分到独立前端应用。左侧导航统一入口,解决"散落各处"问题。 ## High-Level Technical Design ### 数据模型 ERD ```mermaid erDiagram departments ||--o{ user_departments : has users ||--o{ user_departments : belongs_to departments ||--o{ department_skill_bindings : binds skills ||--o{ department_skill_bindings : bound_to departments ||--o{ department_kb_bindings : binds kb_sources ||--o{ department_kb_bindings : bound_to departments ||--o{ department_quotas : has users ||--o{ llm_usage : generates departments ||--o{ llm_usage : belongs_to departments { string id PK string name string description bool is_active datetime created_at } user_departments { string user_id FK string department_id FK datetime created_at } department_skill_bindings { string id PK string department_id FK string skill_name datetime created_at } department_kb_bindings { string id PK string department_id FK string kb_source_id datetime created_at } department_quotas { string id PK string department_id FK string quota_type string limit_value string period datetime updated_at } llm_usage { string id PK string user_id FK string department_id FK string model int prompt_tokens int completion_tokens int total_tokens float cost datetime timestamp } ``` ### 请求隔离流程 ```mermaid sequenceDiagram participant Client participant AuthMiddleware participant DepartmentMiddleware participant Route participant Service participant Repository Client->>AuthMiddleware: Request + JWT AuthMiddleware->>AuthMiddleware: Verify JWT, extract user_id AuthMiddleware->>DepartmentMiddleware: request.user_id DepartmentMiddleware->>DepartmentMiddleware: Query user_departments DepartmentMiddleware->>DepartmentMiddleware: Set request.state.department_ids DepartmentMiddleware->>Route: Forward with department context Route->>Service: Call service method Service->>Repository: Pass department_ids Repository->>Repository: WHERE department_id IN (?, ?, NULL) Repository-->>Service: Filtered results Service-->>Route: Response Route-->>Client: Response ``` ## Implementation Units ### U1. 数据库 schema 扩展——部门表 + 多对多 + 资源表 department_id **Goal**: 新增 `departments`、`user_departments`、`department_skill_bindings`、`department_kb_bindings`、`department_quotas` 表,并为 `llm_usage` 加 `user_id`/`department_id` 字段。 **Requirements**: R1, R2, R4, R5, R6 **Dependencies**: 无(基础单元) **Files**: - `src/agentkit/server/auth/models.py` — 新增表 DDL + ORM 模型,bump `_SCHEMA_VERSION` 到 3 - `src/agentkit/server/admin/__init__.py` — 新建 admin 模块 - `src/agentkit/server/admin/models.py` — admin 相关 Pydantic 模型(Department, UserDepartment, etc.) - `tests/unit/admin/test_models.py` — 表创建 + CRUD 测试 **Approach**: - 在 `models.py` 的 `_SCHEMA_SQL` 中追加 5 个新表的 CREATE TABLE IF NOT EXISTS - 新增 `_migrate_v2_to_v3()` 迁移函数,gated on `auth_meta` marker `schema_v3_departments` - `departments` 表:id (UUID), name (UNIQUE), description, is_active, created_at - `user_departments` 表:user_id, department_id, created_at, PRIMARY KEY (user_id, department_id) - `department_skill_bindings` 表:id, department_id, skill_name, created_at, UNIQUE (department_id, skill_name) - `department_kb_bindings` 表:id, department_id, kb_source_id, created_at, UNIQUE (department_id, kb_source_id) - `department_quotas` 表:id, department_id, quota_type (token_limit/cost_limit/model_whitelist), limit_value (JSON), period (daily/monthly), updated_at - `llm_usage` 不是 SQL 表(是 Redis),所以 `user_id`/`department_id` 加在 `UsageRecord` dataclass 上(见 U8) **Patterns to follow**: 现有 `models.py` 的 `_SCHEMA_SQL` + `_migrate_v2_to_v3` 模式(参考 `_backfill_user_sessions`) **Test scenarios**: - Happy path: `init_auth_db()` 创建新表,`auth_meta` 记录 schema_v3_departments - Edge case: 重复调用 `init_auth_db()` 不报错(幂等) - Edge case: 已有 v2 数据库升级到 v3,新表创建成功,现有数据不丢失 - Integration: `departments` 表插入 + 查询,`user_departments` 多对多关系正确 **Verification**: `pytest tests/unit/admin/test_models.py -v` 通过;手动检查 `data/auth.db` 新表存在 --- ### U2. 部门 CRUD service + API 端点 **Goal**: 实现部门的创建、列表、编辑、禁用/启用、删除,以及部门能力绑定(skill/KB)管理。 **Requirements**: R1, R4, R5 **Dependencies**: U1 **Files**: - `src/agentkit/server/admin/department_service.py` — DepartmentService 类(CRUD + 能力绑定) - `src/agentkit/server/routes/admin.py` — 新建独立 admin_router 模块(从 auth.py 迁出 session 端点 + 新增 department 端点) - `src/agentkit/server/app.py` — 挂载新 admin_router - `tests/unit/admin/test_department_service.py` — service 单元测试 - `tests/integration/admin/test_department_routes.py` — API 集成测试 **Approach**: - `DepartmentService` 方法:`create_department`, `list_departments`, `get_department`, `update_department`, `disable_department`, `delete_department`, `bind_skill`, `unbind_skill`, `list_department_skills`, `bind_kb`, `unbind_kb`, `list_department_kbs` - 删除部门时检查是否有用户归属,有则拒绝(或强制 cascade 删除 user_departments) - 禁用部门时(`is_active=0`),该部门用户仍可登录但无法访问部门资源 - API 端点: - `POST /api/v1/admin/departments` — 创建 - `GET /api/v1/admin/departments` — 列表 - `GET /api/v1/admin/departments/{id}` — 详情 - `PATCH /api/v1/admin/departments/{id}` — 编辑 - `DELETE /api/v1/admin/departments/{id}` — 删除 - `POST /api/v1/admin/departments/{id}/skills/{name}` — 绑定 skill - `DELETE /api/v1/admin/departments/{id}/skills/{name}` — 解绑 skill - `POST /api/v1/admin/departments/{id}/kb/{source_id}` — 绑定 KB - `DELETE /api/v1/admin/departments/{id}/kb/{source_id}` — 解绑 KB - 所有端点用 `_require_admin` 守卫 **Patterns to follow**: `kb_management.py` 的 APIRouter + Pydantic 模型 + Depends 模式 **Test scenarios**: - Happy path: 创建部门 → 列表返回 → 编辑名称 → 禁用 → 启用 → 删除 - Happy path: 绑定 skill → 列表返回绑定的 skill → 解绑 - Edge case: 创建重名部门 → 409 Conflict - Edge case: 删除有用户归属的部门 → 400 Bad Request - Error path: 非管理员访问 → 403 - Error path: 不存在的部门 ID → 404 - Integration: 部门禁用后,该部门用户访问部门资源 → 403 **Verification**: `pytest tests/unit/admin/test_department_service.py tests/integration/admin/test_department_routes.py -v` 通过 --- ### U3. 用户 CRUD service + API 端点 + 密码重置 **Goal**: 实现用户创建、列表、编辑、禁用/启用、删除、密码重置、多部门归属管理。 **Requirements**: R2 **Dependencies**: U1, U2 **Files**: - `src/agentkit/server/admin/user_service.py` — UserService 类 - `src/agentkit/server/routes/admin.py` — 新增 user 端点(扩展 U2 的 admin_router) - `src/agentkit/server/auth/providers/local.py` — 新增 `create_user` 方法 - `tests/unit/admin/test_user_service.py` - `tests/integration/admin/test_user_routes.py` **Approach**: - `LocalAuthProvider.create_user(username, email, password, role)` — bcrypt hash + INSERT INTO users - `UserService` 方法:`create_user`, `list_users`, `get_user`, `update_user`, `disable_user`, `enable_user`, `delete_user`, `reset_password`, `assign_department`, `remove_department`, `list_user_departments` - API 端点: - `POST /api/v1/admin/users` — 创建用户 - `GET /api/v1/admin/users` — 列表(支持 department_id 过滤) - `GET /api/v1/admin/users/{id}` — 详情(含部门归属) - `PATCH /api/v1/admin/users/{id}` — 编辑(role, is_active, is_terminal_authorized) - `DELETE /api/v1/admin/users/{id}` — 删除(软删除:is_active=0) - `POST /api/v1/admin/users/{id}/reset-password` — 重置密码 - `POST /api/v1/admin/users/{id}/departments/{dept_id}` — 分配部门 - `DELETE /api/v1/admin/users/{id}/departments/{dept_id}` — 移除部门归属 - 创建用户时可选指定部门列表 - 密码重置用 bcrypt hash 新密码,更新 `password_hash`,并 revoke 该用户所有会话(`revoke_all_for_user`) **Patterns to follow**: 现有 `auth.py` 的 `_resolve_db_path` + `aiosqlite` 模式 **Test scenarios**: - Happy path: 创建用户 → 列表返回 → 分配部门 → 用户详情含部门 → 重置密码 → 旧会话失效 - Happy path: 用户多部门归属 → 两个部门都返回该用户 - Edge case: 创建重名用户 → 409 - Edge case: 删除自己 → 400 - Edge case: 移除用户最后一个部门 → 允许(用户变为无部门全局用户) - Error path: 非管理员访问 → 403 - Error path: 重置密码后旧 token 仍可用 → 失败(会话已 revoke) - Integration: 创建用户后用新用户登录 → 成功 **Verification**: `pytest tests/unit/admin/test_user_service.py tests/integration/admin/test_user_routes.py -v` 通过 --- ### U4. DepartmentContextMiddleware + repository 层隔离 **Goal**: 实现请求级别的部门上下文注入,确保所有资源查询按部门过滤。 **Requirements**: R8, SC2 **Dependencies**: U1, U2, U3 **Files**: - `src/agentkit/server/admin/middleware.py` — DepartmentContextMiddleware - `src/agentkit/server/admin/context.py` — DepartmentContext dataclass + get_department_context() 依赖 - `src/agentkit/server/app.py` — 注册 middleware(在 AuthMiddleware 之后) - `src/agentkit/server/routes/skills.py` — 修改 skill 查询,按 department_ids 过滤 - `src/agentkit/server/routes/kb_management.py` — 修改 KB 查询,按 department_ids 过滤 - `tests/integration/admin/test_department_isolation.py` — 隔离安全测试 **Approach**: - `DepartmentContextMiddleware`: 1. 从 `request.state.user` 获取 user_id(AuthMiddleware 已注入) 2. 查询 `user_departments` 获取 department_ids 3. 注入 `request.state.department_ids`(列表,可能为空表示全局用户) 4. 白名单路径(/auth/*, /docs, /health)跳过 - `get_department_context()` 依赖:从 request.state 读取 department_ids,返回 DepartmentContext - Skill 查询修改:`GET /skills` 返回全局 skill + 用户部门绑定的 skill - KB 查询修改:`GET /kb-management/sources` 返回全局 KB + 用户部门绑定的 KB - Admin 端点(`/admin/*`)跳过部门过滤(超管可看所有) **Patterns to follow**: 现有 `AuthMiddleware` 的 BaseHTTPMiddleware 模式 **Test scenarios**: - Happy path: 用户属于部门 A → GET /skills 返回全局 skill + 部门 A 绑定的 skill - Happy path: 用户属于部门 A 和 B → GET /skills 返回全局 + A + B 的 skill - Happy path: 用户无部门 → GET /skills 只返回全局 skill - Security: 用户 A(仅部门 A)访问部门 B 绑定的 skill → 404 或不在列表中 - Security: 用户 A 尝试通过 API 直接访问部门 B 的 KB 文档 → 403/404 - Security: Admin 用户访问任意部门资源 → 成功(跳过过滤) - Integration: 用户从部门 A 移除后 → 立即无法访问部门 A 的 skill **Verification**: `pytest tests/integration/admin/test_department_isolation.py -v` 通过;安全测试覆盖所有资源类型 --- ### U5. LLM 配置管理端点(YAML 写回 + 按部门配额) **Goal**: 实现 LLM Provider/Model/API Key 的运行时 CRUD,写回 `agentkit.yaml`,支持按部门配额。 **Requirements**: R3, SC3 **Dependencies**: U1, U2 **Files**: - `src/agentkit/server/admin/llm_config_service.py` — LlmConfigService 类 - `src/agentkit/server/routes/admin.py` — 新增 LLM 配置端点 - `src/agentkit/server/routes/settings.py` — 复用/扩展现有 `GET/PUT /settings/llm` - `tests/unit/admin/test_llm_config_service.py` - `tests/integration/admin/test_llm_config_routes.py` **Approach**: - `LlmConfigService` 方法: - `list_providers()`, `get_provider(name)`, `create_provider(name, config)`, `update_provider(name, config)`, `delete_provider(name)` - `list_models(provider)`, `add_model(provider, model, config)`, `update_model()`, `delete_model()` - `list_api_keys()` — 返回 provider 名 + key 前缀(不返回完整 key) - `set_api_key(provider, key)` — 写入 YAML(`${ENV_VAR}` 替换保持) - `get_fallbacks()`, `set_fallbacks(model, chain)` - `set_department_quota(dept_id, quota_type, limit, period)`, `get_department_quota(dept_id)` - 写回 YAML 用 `yaml.dump` + `fcntl.flock` 文件锁 - 现有 `watch_config()` 监听文件变化触发热重载,无需额外通知 - API 端点: - `GET /api/v1/admin/llm/providers` — 列表 - `POST /api/v1/admin/llm/providers` — 创建 - `PATCH /api/v1/admin/llm/providers/{name}` — 编辑 - `DELETE /api/v1/admin/llm/providers/{name}` — 删除 - `POST /api/v1/admin/llm/providers/{name}/api-key` — 设置 API Key - `GET /api/v1/admin/llm/fallbacks` — fallback 链 - `PUT /api/v1/admin/llm/fallbacks/{model}` — 设置 fallback - `GET /api/v1/admin/departments/{id}/quotas` — 部门配额 - `PUT /api/v1/admin/departments/{id}/quotas` — 设置配额 **Patterns to follow**: 现有 `settings.py` 的 `GET/PUT /settings/llm` 模式 **Test scenarios**: - Happy path: 添加 provider → YAML 文件更新 → 热重载触发 → 新 provider 可用 - Happy path: 修改 API Key → YAML 更新 → 旧 key 失效 - Happy path: 设置 fallback 链 → model A 失败时自动切换到 model B - Happy path: 设置部门配额 → 部门用户超限 → 429 - Edge case: YAML 文件被外部修改 → watch_config 触发重载 → 配置同步 - Edge case: 并发修改 → 文件锁保护 → 后写者覆盖 - Error path: 无效 provider 配置 → 400 - Error path: 删除正在使用的 provider → 400 **Verification**: `pytest tests/unit/admin/test_llm_config_service.py tests/integration/admin/test_llm_config_routes.py -v` 通过;手动验证 YAML 修改后热重载 --- ### U6. Skill 与 KB 管理端点(启停 + 部门绑定) **Goal**: 实现 Skill 启停/编辑/导入的 admin 端点,KB 文档管理 admin 端点,按部门绑定。 **Requirements**: R4, R5 **Dependencies**: U1, U2, U4 **Files**: - `src/agentkit/server/admin/skill_service.py` — SkillService 类(启停/编辑/导入) - `src/agentkit/server/admin/kb_service.py` — KbService 类(文档管理) - `src/agentkit/server/routes/admin.py` — 新增 skill/kb 端点 - `src/agentkit/server/routes/skill_management.py` — 实现 reload 端点(当前是 stub) - `tests/unit/admin/test_skill_service.py` - `tests/integration/admin/test_skill_kb_routes.py` **Approach**: - `SkillService` 方法:`enable_skill(name)`, `disable_skill(name)`, `update_skill_config(name, config)`, `import_skill(yaml_content)`, `reload_skill(name)` - 启停通过在 skill registry 中标记 `enabled=False`,查询时过滤 - `KbService` 方法:`list_documents(department_id)`, `upload_document(file, department_id)`, `delete_document(id)`, `sync_source(id)`, `rebuild_index(id)` - KB 文档加 `department_id` 列(在 KB store 中,当前是内存存储,需要持久化或加 metadata) - API 端点: - `POST /api/v1/admin/skills/{name}/enable` — 启用 - `POST /api/v1/admin/skills/{name}/disable` — 禁用 - `PATCH /api/v1/admin/skills/{name}` — 编辑配置 - `POST /api/v1/admin/skills/import` — YAML 导入 - `POST /api/v1/admin/skills/{name}/reload` — 重载 - `GET /api/v1/admin/kb/documents` — 列表(支持 department_id 过滤) - `POST /api/v1/admin/kb/documents` — 上传(指定 department_id) - `DELETE /api/v1/admin/kb/documents/{id}` — 删除 - `POST /api/v1/admin/kb/sources/{id}/sync` — 同步 - `POST /api/v1/admin/kb/sources/{id}/rebuild` — 重建索引 **Patterns to follow**: 现有 `skills.py` 的 install/unregister 模式 **Test scenarios**: - Happy path: 禁用 skill → GET /skills 不返回该 skill → 用户无法使用 - Happy path: 启用 skill → 恢复可用 - Happy path: 导入 skill YAML → 注册成功 → 可用 - Happy path: 上传 KB 文档到部门 A → 部门 A 用户可搜索 → 部门 B 用户不可见 - Edge case: 禁用不存在的 skill → 404 - Edge case: 导入无效 YAML → 400 - Security: 部门 A 用户尝试访问部门 B 的 KB 文档 → 404 - Integration: 禁用 skill 后,正在使用该 skill 的会话 → 优雅降级或错误提示 **Verification**: `pytest tests/unit/admin/test_skill_service.py tests/integration/admin/test_skill_kb_routes.py -v` 通过 --- ### U7. 用量仪表盘 + 配额执行 **Goal**: 实现按部门/用户/时间的用量查询,配额检查在 LLM 调用时执行(超限返回 429)。 **Requirements**: R6, SC4, SC5 **Dependencies**: U1, U2, U5 **Files**: - `src/agentkit/llm/providers/usage_store.py` — 修改 `UsageRecord` + store 实现 - `src/agentkit/llm/gateway.py` — 调用时传入 user_id/department_id,调用前检查配额 - `src/agentkit/server/admin/usage_service.py` — UsageService 类(聚合查询) - `src/agentkit/server/admin/quota_service.py` — QuotaService 类(配额检查) - `src/agentkit/server/routes/admin.py` — 新增 usage 端点 - `tests/unit/admin/test_usage_service.py` - `tests/unit/admin/test_quota_service.py` - `tests/integration/admin/test_usage_routes.py` **Approach**: - `UsageRecord` 加 `user_id: str | None`, `department_id: str | None` - `RedisUsageStore` Hash key 扩展:`agentkit:usage:{date}:{user_id}:{department_id}` - `LLMGateway.complete()` 调用前: 1. 获取当前 user_id + department_ids 2. 对每个 department_id 检查配额(token/cost,daily/monthly) 3. 超限 → raise `QuotaExceededError` → 路由层返回 429 4. 调用后记录 usage(含 user_id + department_id) - `UsageService` 方法:`get_usage_summary(department_id, user_id, start, end)`, `get_usage_timeseries(department_id, user_id, start, end, interval)`, `get_usage_by_model(department_id, user_id, start, end)`, `get_top_users(department_id, limit)`, `export_usage(department_id, format)` - API 端点: - `GET /api/v1/admin/usage/summary?department_id=&user_id=&start=&end=` — 汇总 - `GET /api/v1/admin/usage/timeseries?...&interval=hour/day` — 时间序列 - `GET /api/v1/admin/usage/by-model?...` — 按 model 分桶 - `GET /api/v1/admin/usage/top-users?department_id=&limit=` — 用户排行 - `GET /api/v1/admin/usage/export?format=csv/json` — 导出 **Patterns to follow**: 现有 `llm.py` 的 `GET /llm/usage` 模式 **Test scenarios**: - Happy path: 用户调用 LLM → usage 记录含 user_id + department_id - Happy path: 查询部门 A 用量 → 返回部门 A 的聚合数据 - Happy path: 查询用户 X 用量 → 返回用户 X 的数据 - Happy path: 导出 CSV → 格式正确 - Quota: 部门 A token 配额 1000 → 第 1001 token → 429 - Quota: 用户月成本上限 $10 → 第 $11 → 429 - Quota: 多部门用户,部门 A 超限但部门 B 未超 → 拒绝(取最严约束) - Edge case: 无 usage 数据 → 返回空结果 - Edge case: 跨天查询 → 按天聚合 - Error path: 非管理员访问 → 403 **Verification**: `pytest tests/unit/admin/test_usage_service.py tests/unit/admin/test_quota_service.py tests/integration/admin/test_usage_routes.py -v` 通过 --- ### U8. CLI admin 命令组 **Goal**: 实现 `agentkit admin ` 命令组,通过 HTTP 调用 server API。 **Requirements**: R7, SC6 **Dependencies**: U2, U3, U5, U6, U7 **Files**: - `src/agentkit/cli/admin.py` — admin Typer sub-app - `src/agentkit/cli/main.py` — 注册 admin sub-app - `src/agentkit/cli/admin_client.py` — AdminHttpClient(封装 HTTP 调用) - `tests/unit/cli/test_admin_commands.py` **Approach**: - `AdminHttpClient`:从 `agentkit.yaml` 或环境变量读取 server URL + admin API key - 命令结构: ``` agentkit admin department list/create/update/delete/bind-skill/unbind-skill/bind-kb/unbind-kb agentkit admin user list/create/update/delete/reset-password/assign-department/remove-department agentkit admin llm list-providers/add-provider/update-provider/delete-provider/set-api-key/set-fallback/set-quota agentkit admin skill list/enable/disable/import/reload agentkit admin kb list-documents/upload/delete/sync/rebuild agentkit admin usage summary/timeseries/by-model/top-users/export ``` - 所有命令输出用 Rich 表格/JSON 格式 - `--json` 标志输出原始 JSON(便于脚本处理) - 认证:用 `agentkit pair` 生成的 API key(已有机制),或 admin 用户名密码登录获取 JWT **Patterns to follow**: 现有 `cli/task.py` 的 Typer sub-app + Rich 输出模式 **Test scenarios**: - Happy path: `agentkit admin department list` → 返回部门列表(Rich 表格) - Happy path: `agentkit admin user create --username alice --email alice@corp.com` → 创建用户 - Happy path: `agentkit admin llm add-provider --name openai --api-key $KEY` → 添加 provider - Happy path: `agentkit admin usage summary --department hr --start 2026-06-01 --end 2026-06-30` → 用量汇总 - Happy path: `--json` 标志 → 输出有效 JSON - Edge case: server 不可达 → 友好错误提示 - Edge case: API key 无效 → 401 提示 - Edge case: 创建重名用户 → 显示 409 错误 **Verification**: `pytest tests/unit/cli/test_admin_commands.py -v` 通过;手动验证 CLI 与 Web UI 操作结果一致 --- ### U9. 前端 AdminLayout + 管理页面 **Goal**: 实现前端管理界面,包括 AdminLayout、7 个管理页面、API 客户端扩展。 **Requirements**: SC1, SC6 **Dependencies**: U2, U3, U5, U6, U7 **Files**: - `src/agentkit/server/frontend/src/layouts/AdminLayout.vue` — 左侧导航 + 内容区 - `src/agentkit/server/frontend/src/views/admin/DashboardView.vue` — 概览 - `src/agentkit/server/frontend/src/views/admin/DepartmentsView.vue` — 部门管理 - `src/agentkit/server/frontend/src/views/admin/UsersView.vue` — 扩展现有(加 CRUD) - `src/agentkit/server/frontend/src/views/admin/LlmConfigView.vue` — LLM 配置 - `src/agentkit/server/frontend/src/views/admin/SkillsView.vue` — Skill 管理 - `src/agentkit/server/frontend/src/views/admin/KbManagementView.vue` — KB 管理 - `src/agentkit/server/frontend/src/views/admin/UsageDashboardView.vue` — 用量仪表盘 - `src/agentkit/server/frontend/src/api/admin.ts` — 扩展 AdminApiClient - `src/agentkit/server/frontend/src/router/index.ts` — 更新路由结构 - `src/agentkit/server/frontend/src/components/layout/TopNav.vue` — 更新 admin 入口 **Approach**: - `AdminLayout`:左侧导航(7 个菜单项)+ 顶部用户信息 + 内容区 `` - 路由结构改为嵌套: ```ts { path: '/admin', component: AdminLayout, meta: { requiresAdmin: true }, children: [ { path: '', redirect: '/admin/dashboard' }, { path: 'dashboard', component: DashboardView }, { path: 'departments', component: DepartmentsView }, { path: 'users', component: UsersView }, { path: 'llm', component: LlmConfigView }, { path: 'skills', component: SkillsView }, { path: 'kb', component: KbManagementView }, { path: 'usage', component: UsageDashboardView }, ] } ``` - `AdminApiClient` 扩展:department/user/llm/skill/kb/usage 方法 - 各页面用 Ant Design Vue 组件(Table, Form, Modal, Card, Chart) - 用量仪表盘用 ECharts 或 Ant Design Charts **Patterns to follow**: 现有 `UsersView.vue` 的双标签页 + `UserSessionsPanel` 模式 **Test scenarios**: - Happy path: admin 用户访问 /admin → 重定向到 /admin/dashboard - Happy path: 部门管理页 → 创建/编辑/删除部门 → 列表更新 - Happy path: 用户管理页 → 创建用户 → 分配部门 → 列表更新 - Happy path: LLM 配置页 → 添加 provider → 列表更新 → 热重载提示 - Happy path: 用量仪表盘 → 选择部门和时间范围 → 图表显示 - Edge case: 非管理员访问 /admin → 重定向到 /agent/chat - Edge case: 网络错误 → 友好错误提示 - Edge case: 表单验证错误 → 字段级错误提示 **Verification**: `npm run typecheck` 通过;手动验证各页面功能 --- ### U10. 集成测试 + 安全测试 **Goal**: 端到端集成测试 + 部门隔离安全测试,验证 SC1-SC6。 **Requirements**: R8, SC1-SC6 **Dependencies**: U1-U9 **Files**: - `tests/integration/admin/test_e2e_admin_flow.py` — 端到端流程测试 - `tests/integration/admin/test_security_isolation.py` — 部门隔离安全测试 - `tests/integration/admin/test_quota_enforcement.py` — 配额执行测试 **Approach**: - E2E 流程测试: 1. 创建部门(人事、研发) 2. 创建用户并分配部门 3. 绑定 skill/KB 到部门 4. 配置 LLM provider + 部门配额 5. 用普通用户登录 → 验证只能访问所属部门资源 6. 查看用量仪表盘 → 数据正确 7. CLI 执行相同操作 → 结果一致 - 安全测试: 1. 人事部用户尝试访问研发部 skill → 404 2. 人事部用户尝试访问研发部 KB 文档 → 404 3. 人事部用户尝试访问研发部用量 → 403 4. 用户从部门移除后 → 立即无法访问该部门资源 5. 部门禁用后 → 该部门用户无法访问部门资源 6. JWT 篡改 department_ids → middleware 重新查询,不信任 JWT - 配额测试: 1. 设置部门 token 配额 1000 2. 用户调用 LLM 直到 1000 token 3. 第 1001 token → 429 4. 重置配额 → 可继续调用 **Test scenarios**: 见上述 Approach **Verification**: `pytest tests/integration/admin/ -v` 通过;安全测试 100% 覆盖 --- ## Scope Boundaries ### In Scope - 部门 CRUD + 能力绑定 - 用户 CRUD + 多部门归属 + 密码重置 - LLM 配置 YAML 写回 + 按部门配额 - Skill 启停/编辑/导入 + 部门绑定 - KB 文档管理 + 部门隔离 - 用量仪表盘 + 配额硬拒绝 - CLI admin 命令组 - 前端 AdminLayout + 7 个管理页面 - 部门隔离安全测试 ### Deferred to Follow-Up Work - 部门管理员角色(第二期) - LLM 配置迁移到数据库(第二期) - 审计日志(第二期) - SLA 监控(第二期) - SSO/SAML 集成(第二期) - 独立管理应用部署(架构已预留,第二期实现) - 配额软降级(第二期) - 用量告警 Webhook/邮件(第二期,MVP 只做日志) ### Outside This Product's Identity - SaaS 多租户(外部多客户、租户计费) - 物理隔离的独立数据库 - 计费系统集成(实际扣款) ## Risks & Dependencies ### Risks 1. **部门隔离 middleware 完整性**(高)—— 任何遗漏 `department_id` 过滤的查询都是数据泄露。缓解:repository 层强制参数 + 安全测试覆盖(U10)。 2. **LLM 配置 YAML 并发修改**(中)—— 多人同时修改 YAML 可能冲突。缓解:`fcntl.flock` 文件锁。第二期迁移到 DB。 3. **用量跟踪 Redis Hash key 扩展**(中)—— 修改 key 结构可能影响现有数据。缓解:版本化 key(`agentkit:usage:v2:{date}:{user_id}:{dept_id}`),旧 key 保留。 4. **KB 内存存储持久化**(中)—— 当前 KB 是内存存储,加 `department_id` 需要持久化或 metadata 扩展。缓解:MVP 用 metadata dict 存储 department_id,第二期迁移到 DB。 5. **CLI 认证机制**(低)—— CLI 需要 server URL + API key 配置。缓解:复用 `agentkit pair` 机制。 ### Dependencies - 现有 JWT 认证 + RBAC 权限模型 - 现有 `agentkit.yaml` 文件监听热重载机制 - 现有 `agentkit` CLI 框架(Typer) - 现有 Ant Design Vue 组件库 ## Phased Delivery | 批次 | 实施单元 | 交付物 | |------|----------|--------| | 批次 1 | U1, U2, U3, U4 | 部门与用户管理 + 隔离 middleware | | 批次 2 | U5 | LLM 配置管理 | | 批次 3 | U6 | Skill 与 KB 管理 | | 批次 4 | U7 | 用量仪表盘 + 配额 | | 批次 5 | U8, U9, U10 | CLI + 前端 + 集成测试 | 每批次独立可交付、可测试、可合并。 ## Open Questions (Resolved) - ~~KB 内存存储如何加 `department_id`?~~ → **已明确**:MVP 用 metadata dict 存储 department_id,不持久化(重启丢失绑定关系,可接受)。第二期迁移到 SQLite。 - ~~CLI 认证用 API key 还是用户名密码?~~ → **已明确**:API key,复用 `agentkit pair` 机制。CLI 配置文件存储 server_url + api_key。 - ~~用量仪表盘图表库选 ECharts 还是 Ant Design Charts?~~ → **已明确**:Ant Design Charts,与现有 UI 一致。 - ~~部门删除策略:拒绝删除有用户的部门,还是 cascade 删除 user_departments?~~ → **已明确**:拒绝删除,强制管理员先移除用户。