35 KiB
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 <domain> <action>镜像 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 字段
决策:
- 修改
UsageRecorddataclass,新增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
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
}
请求隔离流程
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到 3src/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 onauth_metamarkerschema_v3_departments departments表:id (UUID), name (UNIQUE), description, is_active, created_atuser_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_atllm_usage不是 SQL 表(是 Redis),所以user_id/department_id加在UsageRecorddataclass 上(见 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_routertests/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}— 绑定 skillDELETE /api/v1/admin/departments/{id}/skills/{name}— 解绑 skillPOST /api/v1/admin/departments/{id}/kb/{source_id}— 绑定 KBDELETE /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.pytests/integration/admin/test_user_routes.py
Approach:
LocalAuthProvider.create_user(username, email, password, role)— bcrypt hash + INSERT INTO usersUserService方法: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— DepartmentContextMiddlewaresrc/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:- 从
request.state.user获取 user_id(AuthMiddleware 已注入) - 查询
user_departments获取 department_ids - 注入
request.state.department_ids(列表,可能为空表示全局用户) - 白名单路径(/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/llmtests/unit/admin/test_llm_config_service.pytests/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 KeyGET /api/v1/admin/llm/fallbacks— fallback 链PUT /api/v1/admin/llm/fallbacks/{model}— 设置 fallbackGET /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.pytests/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.pytests/unit/admin/test_quota_service.pytests/integration/admin/test_usage_routes.py
Approach:
UsageRecord加user_id: str | None,department_id: str | NoneRedisUsageStoreHash key 扩展:agentkit:usage:{date}:{user_id}:{department_id}LLMGateway.complete()调用前:- 获取当前 user_id + department_ids
- 对每个 department_id 检查配额(token/cost,daily/monthly)
- 超限 → raise
QuotaExceededError→ 路由层返回 429 - 调用后记录 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 <domain> <action> 命令组,通过 HTTP 调用 server API。
Requirements: R7, SC6
Dependencies: U2, U3, U5, U6, U7
Files:
src/agentkit/cli/admin.py— admin Typer sub-appsrc/agentkit/cli/main.py— 注册 admin sub-appsrc/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— 扩展 AdminApiClientsrc/agentkit/server/frontend/src/router/index.ts— 更新路由结构src/agentkit/server/frontend/src/components/layout/TopNav.vue— 更新 admin 入口
Approach:
AdminLayout:左侧导航(7 个菜单项)+ 顶部用户信息 + 内容区<router-view />- 路由结构改为嵌套:
{ 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 流程测试:
- 创建部门(人事、研发)
- 创建用户并分配部门
- 绑定 skill/KB 到部门
- 配置 LLM provider + 部门配额
- 用普通用户登录 → 验证只能访问所属部门资源
- 查看用量仪表盘 → 数据正确
- CLI 执行相同操作 → 结果一致
- 安全测试:
- 人事部用户尝试访问研发部 skill → 404
- 人事部用户尝试访问研发部 KB 文档 → 404
- 人事部用户尝试访问研发部用量 → 403
- 用户从部门移除后 → 立即无法访问该部门资源
- 部门禁用后 → 该部门用户无法访问部门资源
- JWT 篡改 department_ids → middleware 重新查询,不信任 JWT
- 配额测试:
- 设置部门 token 配额 1000
- 用户调用 LLM 直到 1000 token
- 第 1001 token → 429
- 重置配额 → 可继续调用
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
- 部门隔离 middleware 完整性(高)—— 任何遗漏
department_id过滤的查询都是数据泄露。缓解:repository 层强制参数 + 安全测试覆盖(U10)。 - LLM 配置 YAML 并发修改(中)—— 多人同时修改 YAML 可能冲突。缓解:
fcntl.flock文件锁。第二期迁移到 DB。 - 用量跟踪 Redis Hash key 扩展(中)—— 修改 key 结构可能影响现有数据。缓解:版本化 key(
agentkit:usage:v2:{date}:{user_id}:{dept_id}),旧 key 保留。 - KB 内存存储持久化(中)—— 当前 KB 是内存存储,加
department_id需要持久化或 metadata 扩展。缓解:MVP 用 metadata dict 存储 department_id,第二期迁移到 DB。 - CLI 认证机制(低)—— CLI 需要 server URL + API key 配置。缓解:复用
agentkit pair机制。
Dependencies
- 现有 JWT 认证 + RBAC 权限模型
- 现有
agentkit.yaml文件监听热重载机制 - 现有
agentkitCLI 框架(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 内存存储如何加→ 已明确:MVP 用 metadata dict 存储 department_id,不持久化(重启丢失绑定关系,可接受)。第二期迁移到 SQLite。department_id?CLI 认证用 API key 还是用户名密码?→ 已明确:API key,复用agentkit pair机制。CLI 配置文件存储 server_url + api_key。用量仪表盘图表库选 ECharts 还是 Ant Design Charts?→ 已明确:Ant Design Charts,与现有 UI 一致。部门删除策略:拒绝删除有用户的部门,还是 cascade 删除 user_departments?→ 已明确:拒绝删除,强制管理员先移除用户。