feat: deliver agent foundation day 1

This commit is contained in:
caoxiaozhu
2026-05-11 03:51:24 +00:00
parent f738b6cdd4
commit b2beeaa136
54 changed files with 6747 additions and 1724 deletions

View File

@@ -11,6 +11,25 @@
- 建设 Agent Orchestrator统一负责路由、权限、工具调用、审计和失败处理。
- 让规则中心、MCP、知识库、数据库查询和任务系统使用同一套语义协议。
## 与一周计划的关系
`document/development/agent week plan` 是一周开发路线图,只描述每天要完成的大方向和交付结果。
本目录是具体架构和执行细则,包含:
- 架构设计。
- 数据协议。
- Agent 职责。
- Orchestrator 流程。
- OCR、知识库、规则生命周期。
- 一周开发中每天对应的详细 TODO。
执行时按这个顺序阅读:
1. 先看 `document/development/agent week plan/MASTER_TODO.md`,确认今天做什么。
2. 再看本目录的架构文档,理解为什么这样做。
3. 最后进入 [weekly_execution_details](./weekly_execution_details/README.md),按具体 TODO 开发。
推荐阅读顺序:
1. [01_overall_architecture.md](./01_overall_architecture.md)
@@ -28,6 +47,7 @@
13. [13_rule_formation_lifecycle.md](./13_rule_formation_lifecycle.md)
14. [14_financial_document_canonical_model.md](./14_financial_document_canonical_model.md)
15. [15_feedback_learning_loop.md](./15_feedback_learning_loop.md)
16. [weekly_execution_details/README.md](./weekly_execution_details/README.md)
开发原则:

View File

@@ -11,6 +11,13 @@ OCR、MCP、用户填写、业务数据库可能都描述同一张发票
- Hermes 难以批量统计。
- MCP 返回结果难以合并。
这里要区分两层:
- 标准模型:定义 Agent、规则、MCP、OCR、数据库之间统一交换的数据结构。
- 业务数据库表:定义 MVP 阶段真正落库存储、查询和统计所依赖的最小业务表。
如果只有标准模型没有最小业务表User Agent 和 Hermes 仍然无法完成报销、应收、应付的查询、解释、巡检和统计。
## 2. 标准对象
第一版建议定义这些对象:
@@ -20,6 +27,8 @@ Invoice
Receipt
ReimbursementRequest
PaymentRequest
AccountsReceivableRecord
AccountsPayableRecord
BankTransaction
Contract
Customer
@@ -57,11 +66,17 @@ CostCenter
"request_id": "",
"request_no": "",
"employee_id": "",
"employee_name": "",
"department_id": "",
"department_name": "",
"project_code": "",
"expense_type": "",
"reason": "",
"location": "",
"amount": 0,
"currency": "CNY",
"status": "",
"occurred_at": "",
"submitted_at": "",
"approval_stage": "",
"invoices": [],
@@ -70,7 +85,55 @@ CostCenter
}
```
## 5. BankTransaction 标准模型
说明:
- `reason``location``occurred_at` 是报销查询、规则解释、风险识别的最小必要字段。
- 如果一张报销单包含多条费用明细,应在数据库层拆到明细表,但对外仍可聚合为一个 `ReimbursementRequest`
## 5. AccountsReceivableRecord 标准模型
```json
{
"ar_id": "",
"document_no": "",
"customer_id": "",
"customer_name": "",
"contract_no": "",
"invoice_no": "",
"amount_receivable": 0,
"amount_received": 0,
"amount_outstanding": 0,
"currency": "CNY",
"due_date": "",
"posting_date": "",
"status": "",
"aging_days": 0,
"risk_flags": []
}
```
## 6. AccountsPayableRecord 标准模型
```json
{
"ap_id": "",
"document_no": "",
"vendor_id": "",
"vendor_name": "",
"invoice_no": "",
"amount_payable": 0,
"amount_paid": 0,
"amount_outstanding": 0,
"currency": "CNY",
"due_date": "",
"posting_date": "",
"status": "",
"aging_days": 0,
"risk_flags": []
}
```
## 7. BankTransaction 标准模型
```json
{
@@ -87,7 +150,133 @@ CostCenter
}
```
## 6. 字段来源优先级
## 8. MVP 最小业务表设计建议
标准模型不等于数据库表,但 MVP 至少要有以下业务表,才能支撑 Day 5 和 Day 6。
### 8.1 报销主表 `expense_claims`
建议字段:
```text
id
claim_no
employee_id
employee_name
department_id
department_name
project_code
expense_type
reason
location
amount
currency
invoice_count
occurred_at
submitted_at
status
approval_stage
risk_flags_json
created_at
updated_at
```
适用场景:
- 查询员工报销金额、状态、进度。
- 解释报销为什么被拦截。
- 识别超标、重复、异常等风险。
### 8.2 报销明细表 `expense_claim_items`
建议字段:
```text
id
claim_id
item_date
item_type
item_reason
item_location
item_amount
invoice_id
created_at
updated_at
```
适用场景:
- 一单多明细。
- 重复报销匹配。
- 与发票 OCR 结果逐条比对。
### 8.3 应收主表 `accounts_receivable`
建议字段:
```text
id
receivable_no
customer_id
customer_name
contract_no
invoice_no
amount_receivable
amount_received
amount_outstanding
currency
posting_date
due_date
aging_days
status
risk_flags_json
created_at
updated_at
```
适用场景:
- 客户应收查询。
- 账龄分析。
- 逾期风险巡检。
### 8.4 应付主表 `accounts_payable`
建议字段:
```text
id
payable_no
vendor_id
vendor_name
invoice_no
amount_payable
amount_paid
amount_outstanding
currency
posting_date
due_date
aging_days
status
risk_flags_json
created_at
updated_at
```
适用场景:
- 供应商待付款查询。
- 付款状态查询。
- 逾期应付和异常付款巡检。
### 8.5 MVP 设计边界
- 不要求一次建完整 ERP 总账、分录、核销、凭证体系。
- 第一周只要求支撑查询、解释、统计、风险识别的最小字段。
- 如果现有业务系统已有对应表或 API优先复用不重复造表。
- 如果当前环境没有真实业务数据源,可先建立 Mock 表,但字段命名应尽量贴近最终标准模型。
## 9. 字段来源优先级
建议优先级:
@@ -101,7 +290,7 @@ CostCenter
LLM 推断字段必须标记来源和置信度。
## 7. 与语义本体关系
## 10. 与语义本体关系
语义本体识别的是用户意图和对象。
@@ -112,15 +301,28 @@ ontology.entities[].type = invoice
-> 映射到 Invoice 标准模型
```
## 8. 开发阶段建议
补充映射建议:
```text
ontology.scenario = expense
-> 查询 expense_claims / expense_claim_items
ontology.scenario = accounts_receivable
-> 查询 accounts_receivable
ontology.scenario = accounts_payable
-> 查询 accounts_payable
```
## 11. 开发阶段建议
```text
Step 1: 定义 Invoice 标准模型
Step 2: 定义 ReimbursementRequest 标准模型
Step 3: OCR 输出映射到 Invoice
Step 4: MCP 输出映射到 Invoice
Step 5: 规则中心基于标准模型执行
Step 6: 扩展 AR/AP 标准模型
Step 7: 建立字段血缘和置信度
Step 3: 定义 AccountsReceivableRecord / AccountsPayableRecord 标准模型
Step 4: 设计 MVP 最小业务表 expense_claims / expense_claim_items / accounts_receivable / accounts_payable
Step 5: OCR 输出映射到 Invoice
Step 6: MCP 输出映射到 Invoice 或 AR/AP 标准模型
Step 7: 规则中心基于标准模型执行
Step 8: 建立字段血缘和置信度
```

View File

@@ -0,0 +1,155 @@
# Weekly Execution Details 总执行清单
本文件是 `agent plan` 下的执行索引,承接 `agent week plan` 的 7 天路线图。
这里不重新解释为什么这样排期,只负责把每天的“大开发点”映射到可执行 TODO 文档。
## 执行方式
- [ ] 先读 `document/development/agent week plan/MASTER_TODO.md`,确认当天主题。
- [ ] 再读当天 daily 文档,确认交付物和验收门槛。
- [ ] 最后进入本目录对应的详细 TODO 文档。
- [ ] 每完成一个最小 TODO就改成 `[x] ~~...~~`
- [ ] 每天结束时回到 daily 文档,确认当天是否达到验收门槛。
## Day 1基础模型与工程骨架
路线图:
- `document/development/agent week plan/day_1_foundation_models.md`
执行细则:
- [day_1_foundation_models.md](./day_1_foundation_models.md)
核心完成物:
- [x] ~~统一资产模型。~~
- [x] ~~版本模型。~~
- [x] ~~审核模型。~~
- [x] ~~AgentRun。~~
- [x] ~~ToolCall。~~
- [x] ~~SemanticParseLog。~~
- [x] ~~AuditLog。~~
- [x] ~~最小财务业务数据来源。~~
## Day 2任务规则中心联调
路线图:
- `document/development/agent week plan/day_2_rule_center_integration.md`
执行细则:
- [day_2_rule_center_integration.md](./day_2_rule_center_integration.md)
核心完成物:
- [ ] 规则、技能、MCP、任务列表。
- [ ] 资产详情。
- [ ] 规则 Markdown 编辑。
- [ ] 最近 5 个版本。
- [ ] 版本切换弹窗。
- [ ] 审核者信息。
- [ ] 未审核不能上线。
## Day 3语义本体 MVP
路线图:
- `document/development/agent week plan/day_3_semantic_ontology_mvp.md`
执行细则:
- [day_3_semantic_ontology_mvp.md](./day_3_semantic_ontology_mvp.md)
核心完成物:
- [ ] 8 字段语义结构。
- [ ] 语义解析 API。
- [ ] 解析日志。
- [ ] 权限级别判断。
- [ ] 最小评测集。
## Day 4Orchestrator 运行时
路线图:
- `document/development/agent week plan/day_4_orchestrator_runtime.md`
执行细则:
- [day_4_orchestrator_runtime.md](./day_4_orchestrator_runtime.md)
核心完成物:
- [ ] Orchestrator 入口。
- [ ] Agent 路由。
- [ ] 权限拦截。
- [ ] 工具调用封装。
- [ ] Trace 查询。
- [ ] 降级返回。
## Day 5User Agent MVP
路线图:
- `document/development/agent week plan/day_5_user_agent_mvp.md`
执行细则:
- [day_5_user_agent_mvp.md](./day_5_user_agent_mvp.md)
核心完成物:
- [ ] 用户自然语言入口。
- [ ] 报销查询解释。
- [ ] 应收查询解释。
- [ ] 应付查询解释。
- [ ] 规则引用解释。
- [ ] 草稿生成。
## Day 6Hermes MVP
路线图:
- `document/development/agent week plan/day_6_hermes_mvp.md`
执行细则:
- [day_6_hermes_mvp.md](./day_6_hermes_mvp.md)
核心完成物:
- [ ] 任务触发入口。
- [ ] 风险巡检。
- [ ] 每日统计。
- [ ] OCR Mock 接入。
- [ ] 知识候选生成。
- [ ] 规则草稿生成。
## Day 7加固、演示和验收
路线图:
- `document/development/agent week plan/day_7_hardening_demo_acceptance.md`
执行细则:
- [day_7_hardening_demo_acceptance.md](./day_7_hardening_demo_acceptance.md)
核心完成物:
- [ ] 核心链路回归。
- [ ] 权限边界复查。
- [ ] 审计和 Trace 补齐。
- [ ] 测试记录。
- [ ] 演示脚本。
- [ ] 下一阶段交接。
## 最终完成标准
- [ ] 周计划每一天都有清晰大开发点。
- [ ] 每个大开发点都能跳转到具体执行细则。
- [ ] 执行细则覆盖模型、接口、服务、前端、测试、验收。
- [ ] Codex 可以从任意一天开始,根据 TODO 独立推进开发。

View File

@@ -0,0 +1,45 @@
# Weekly Execution Details 执行细则
本目录承接 `document/development/agent week plan` 的 7 天路线图。
分工方式:
- `agent week plan`:说明每天的大方向、交付物、验收门槛。
- `agent plan/weekly_execution_details`:说明每天具体怎么做,拆到模型、字段、接口、服务、前端、测试和验收证据。
执行时先看周计划,再进入本目录对应日期的详细 TODO。
## 对应关系
| 周计划 Day | 开发主题 | 执行细则 |
| --- | --- | --- |
| Day 1 | 基础模型与工程骨架 | [day_1_foundation_models.md](./day_1_foundation_models.md) |
| Day 2 | 任务规则中心联调 | [day_2_rule_center_integration.md](./day_2_rule_center_integration.md) |
| Day 3 | 语义本体 MVP | [day_3_semantic_ontology_mvp.md](./day_3_semantic_ontology_mvp.md) |
| Day 4 | Orchestrator 运行时 | [day_4_orchestrator_runtime.md](./day_4_orchestrator_runtime.md) |
| Day 5 | User Agent MVP | [day_5_user_agent_mvp.md](./day_5_user_agent_mvp.md) |
| Day 6 | Hermes MVP | [day_6_hermes_mvp.md](./day_6_hermes_mvp.md) |
| Day 7 | 加固、演示和验收 | [day_7_hardening_demo_acceptance.md](./day_7_hardening_demo_acceptance.md) |
## 完成标记规则
未完成:
```md
- [ ] 建立 AgentAsset 数据模型
```
完成后:
```md
- [x] ~~建立 AgentAsset 数据模型~~
```
执行要求:
- [ ] 每次只处理一个最小 TODO。
- [ ] 完成后先自测,再改成 `[x]`
- [ ] 改成 `[x]` 时,同时用 `~~` 画线。
- [ ] 不能因为代码写完就标完成,必须满足该 TODO 的验收证据。
- [ ] 遇到阻塞时,在当天文档的“阻塞记录”下新增一条说明。
- [ ] 每天收尾时更新当天文档的“日终交接”。

View File

@@ -0,0 +1,158 @@
# Day 1基础模型与工程骨架 TODO
本文件是周计划 Day 1 的具体执行细则。路线图见 `document/development/agent week plan/day_1_foundation_models.md`
状态Day 1 已于 `2026-05-11` 完成,以下 TODO 已按完成态回填。
## 完成摘要
- [x] ~~完成 Agent 资产、版本、审核、运行日志、工具调用日志、语义解析日志、审计日志基础模型。~~
- [x] ~~完成报销、应收、应付最小业务数据源,后续 User Agent 和 Hermes 有明确查询来源。~~
- [x] ~~完成基础 API、服务层、种子数据和测试Day 2 可直接进入前后端联调。~~
## 0. 开始前检查
- [x] ~~确认后端目录为 `/app/server`,模型、路由、启动入口和测试目录已定位。~~
- [x] ~~确认本次改动以增量方式落到现有 FastAPI + SQLAlchemy 工程,不回退无关文件。~~
验收证据:
- [x] ~~模型注册位于 `server/src/app/db/base.py`,路由注册位于 `server/src/app/api/v1/router.py`,启动入口位于 `server/src/app/main.py`,测试位于 `server/tests`。~~
## 1. 统一命名和边界
- [x] ~~统一枚举:`rule | skill | mcp | task`、`draft | review | active | disabled`、`pending | approved | rejected`、`orchestrator | user_agent | hermes`。~~
- [x] ~~统一运行来源、权限级别、内容类型、运行状态和工具类型命名,避免出现第二套并行语义。~~
验收证据:
- [x] ~~`server/src/app/core/agent_enums.py` 已成为模型、Schema 和服务层的统一枚举入口。~~
## 2. 设计最小财务业务数据模型
- [x] ~~建立 `expense_claims`、`expense_claim_items`、`accounts_receivable`、`accounts_payable`。~~
- [x] ~~字段覆盖时间、地点、理由、金额、员工、部门、状态,以及应收 / 应付的金额、到期日、账龄、风险标记。~~
验收证据:
- [x] ~~`server/src/app/models/financial_record.py` 与 `document/development/agent plan/14_financial_document_canonical_model.md` 形成直接映射。~~
## 3. 建立 AgentAsset 模型
- [x] ~~建立 `AgentAsset`,包含 `asset_type`、`code`、`name`、`description`、`domain`、`scenario_json`、`owner`、`reviewer`、`status`、`current_version`、`config_json` 等核心字段。~~
- [x] ~~对 `code`、`asset_type`、`status`、`domain` 建立唯一约束或索引。~~
验收证据:
- [x] ~~资产列表可按 `rule`、`skill`、`mcp`、`task` 四类过滤返回。~~
## 4. 建立 AgentAssetVersion 模型
- [x] ~~建立 `AgentAssetVersion`,规则版本保存 Markdown其余资产版本保存 JSON 快照。~~
- [x] ~~对 `asset_id + version` 建立唯一约束,并支持按资产读取最近版本列表。~~
验收证据:
- [x] ~~规则详情接口可返回 `current_version_content` 和 `recent_versions`。~~
## 5. 建立 AgentAssetReview 模型
- [x] ~~建立 `AgentAssetReview`,保存版本、审核人、审核状态、审核备注和审核时间。~~
- [x] ~~服务层实现规则版本未 `approved` 时禁止上线。~~
验收证据:
- [x] ~~`POST /api/v1/agent-assets/{asset_id}/activate` 对待审规则返回 400 拦截。~~
## 6. 建立 AgentRun 模型
- [x] ~~建立 `AgentRun`,包含 `run_id`、`agent`、`source`、`ontology_json`、`route_json`、`permission_level`、`status`、`result_summary`、`error_message` 等字段。~~
- [x] ~~所有运行记录统一生成 `run_id`,并允许失败态保存错误信息。~~
验收证据:
- [x] ~~`AgentRunService.create_run()` 会自动生成 `run_` 前缀标识,并可回读失败摘要。~~
## 7. 建立 AgentToolCall 模型
- [x] ~~建立 `AgentToolCall`,可记录工具类型、工具名、请求 / 响应 JSON、耗时和错误信息。~~
- [x] ~~同一个 `run_id` 下支持多次工具调用追踪。~~
验收证据:
- [x] ~~种子运行数据已覆盖数据库查询、MCP 调用和权限规则引擎调用。~~
## 8. 建立 SemanticParseLog 模型
- [x] ~~建立 `SemanticParseLog`,覆盖场景、意图、实体、时间范围、指标、约束、风险、权限和置信度。~~
- [x] ~~支持按 `run_id` 回放 Day 3 语义结果。~~
验收证据:
- [x] ~~`GET /api/v1/agent-runs/{run_id}` 已能携带 `semantic_parse` 返回。~~
## 9. 建立 AuditLog 模型
- [x] ~~建立 `AuditLog` 和统一 `AuditLogService`。~~
- [x] ~~资产创建、版本保存、审核、上线等写操作都会留下审计记录。~~
验收证据:
- [x] ~~`GET /api/v1/audit-logs` 可返回种子审计日志,服务层新建资产也会落审计。~~
## 10. 建立 Schema / DTO
- [x] ~~建立 `AgentAssetCreate / Update / Read / ListItem`、`AgentAssetVersionRead`、`AgentAssetReviewRead`、`RuleMarkdownUpdate`、`AgentRunRead`、`AgentToolCallRead`、`SemanticParseRead`。~~
- [x] ~~所有 JSON 字段以结构化对象返回,不回传字符串化 JSON。~~
验收证据:
- [x] ~~列表 DTO 不返回大块 Markdown详情 DTO 返回当前版本正文和最近版本。~~
## 11. 建立 API 骨架
- [x] ~~建立 `GET/POST/PATCH /api/v1/agent-assets`、`GET /api/v1/agent-assets/{asset_id}`、`GET/POST /api/v1/agent-assets/{asset_id}/versions`、`POST /api/v1/agent-assets/{asset_id}/reviews`、`POST /api/v1/agent-assets/{asset_id}/activate`。~~
- [x] ~~建立 `GET /api/v1/agent-runs`、`GET /api/v1/agent-runs/{run_id}`、`GET /api/v1/audit-logs`。~~
验收证据:
- [x] ~~所有接口已挂到 `server/src/app/api/v1/router.py`,并通过 `create_app()` 自动暴露。~~
## 12. 建立种子数据
- [x] ~~种子资产补齐到 3 条规则、2 条技能、2 条 MCP、3 条任务。~~
- [x] ~~三条规则都具备至少 2 个版本,并覆盖 `approved / pending / rejected` 三种审核样本。~~
- [x] ~~旧开发数据库启动时会自动增量补齐新增资产和版本,不要求手动清库。~~
验收证据:
- [x] ~~Smoke`GET /api/v1/agent-assets` 返回 10 条资产,`GET /api/v1/agent-runs` 返回 3 条运行日志,`GET /api/v1/audit-logs` 返回 4 条审计日志。~~
## 13. 最小测试
- [x] ~~新增 Day 1 服务层与接口层测试,覆盖种子完整性、版本历史、未审核不能上线、运行日志生成和审计日志写入。~~
- [x] ~~Ruff 校验通过Day 1 新增文件保持可检查状态。~~
验收证据:
- [x] ~~`/app/server/.venv/bin/pytest -q /app/server/tests/test_agent_asset_service.py /app/server/tests/test_agent_foundation_endpoints.py` -> `11 passed`。~~
- [x] ~~`/app/server/.venv/bin/pytest -q tests` 已通过全量后端测试。~~
## 14. Day 1 验收
- [x] ~~数据库能创建所有新增表或等价结构。~~
- [x] ~~API 服务能启动OpenAPI 能看到新增接口。~~
- [x] ~~资产列表接口返回规则、技能、MCP、任务规则详情带 Markdown 当前版本和最近版本列表。~~
- [x] ~~未审核规则不能上线AgentRun 和 AuditLog 均可保存记录。~~
- [x] ~~所有 Day 1 TODO 已改为完成态。~~
## 阻塞记录
- [x] ~~暂无阻塞。~~
## 日终交接
- [x] ~~已完成模型:资产、版本、审核、运行日志、工具调用、语义解析、审计、报销、应收、应付。~~
- [x] ~~已完成 API`/api/v1/agent-assets`、`/api/v1/agent-runs`、`/api/v1/audit-logs`。~~
- [x] ~~Day 2 前端联调应优先使用 `GET /api/v1/agent-assets`、`GET /api/v1/agent-assets/{asset_id}`、`GET /api/v1/agent-assets/{asset_id}/versions?limit=5`、`POST /api/v1/agent-assets/{asset_id}/reviews`、`POST /api/v1/agent-assets/{asset_id}/activate`。~~
- [x] ~~后续 Day 4 及以后运行时方向按用户要求转向 `LangChain + LangGraph`Hermes 继续作为内部数字员工入口Day 1 保留为数据与治理底座。~~

View File

@@ -0,0 +1,222 @@
# Day 2任务规则中心联调 TODO
本文件是周计划 Day 2 的具体执行细则。路线图见 `document/development/agent week plan/day_2_rule_center_integration.md`
目标:把任务规则中心从静态 UI 改成可对接后端的生产形态覆盖规则、技能、MCP、任务四类资产。重点是规则 Markdown 编辑、版本切换、审核者信息、上线约束。
参考文档:
- `document/development/agent plan/07_capability_registry.md`
- `document/development/agent plan/13_rule_formation_lifecycle.md`
- `document/development/agent plan/06_data_contracts_and_governance.md`
## 0. 开始前检查
- [ ] 确认 Day 1 API 已可访问。
- [ ] 确认前端任务规则中心文件位置。
- [ ] 确认现有路由名称和导航名称。
- [ ] 确认现有 UI 风格,不重新做大改版。
- [ ] 确认当前页面已有页签规则、技能、MCP、任务。
- [ ] 确认详情页隐藏顶部 title bar 的逻辑仍然有效。
- [ ] 确认返回列表栏高度没有被重新拉高。
## 1. API Client
- [ ] 新增或扩展资产列表请求函数。
- [ ] 新增资产详情请求函数。
- [ ] 新增版本列表请求函数。
- [ ] 新增规则 Markdown 保存请求函数。
- [ ] 新增审核请求函数。
- [ ] 新增上线请求函数。
- [ ] 新增运行日志请求函数。
- [ ] 给所有请求增加加载态。
- [ ] 给所有请求增加错误态。
- [ ] 给所有写请求增加成功提示。
验收证据:
- [ ] 前端不再只依赖本地硬编码资产数据。
- [ ] 后端不可用时页面有明确错误提示。
## 2. 列表页数据接入
- [ ] 规则页签请求 `asset_type=rule`
- [ ] 技能页签请求 `asset_type=skill`
- [ ] MCP 页签请求 `asset_type=mcp`
- [ ] 任务页签请求 `asset_type=task`
- [ ] 搜索框传递关键词或本地过滤。
- [ ] 类型下拉和搜索框可以同时生效。
- [ ] 状态筛选可以过滤 `draft | review | active | disabled`
- [ ] 列表卡片展示名称。
- [ ] 列表卡片展示摘要。
- [ ] 列表卡片展示状态。
- [ ] 列表卡片展示负责人。
- [ ] 列表卡片展示最近更新时间。
- [ ] 空数据时展示空态。
- [ ] 加载中时展示骨架或加载状态。
验收证据:
- [ ] 四个页签都能切换。
- [ ] 四个页签都有数据或空态。
- [ ] 搜索和筛选不会互相覆盖。
## 3. 规则详情页主信息
- [ ] 打开规则资产时请求详情 API。
- [ ] Hero title 展示规则名称。
- [ ] Hero title 下方展示审核者。
- [ ] Hero title 下方展示审核状态。
- [ ] Hero title 下方展示上线条件。
- [ ] Hero title 高度保持紧凑。
- [ ] 详情页不显示外层顶部 title bar。
- [ ] 返回列表栏高度保持原有紧凑高度。
验收证据:
- [ ] 用户能一眼看到该规则是否已审核。
- [ ] 用户不会看到两层 title。
## 4. Markdown 编辑器
- [ ] 从当前版本读取 Markdown 内容。
- [ ] Markdown 编辑框高度和右侧版本卡片底部对齐。
- [ ] Markdown 编辑框支持长内容滚动。
- [ ] Markdown 编辑框保存时调用 API。
- [ ] 保存后创建新版本或更新草稿版本,按后端约定执行。
- [ ] 保存成功后刷新版本列表。
- [ ] 保存失败时保留用户输入。
- [ ] 编辑器禁用态覆盖 `active` 且无编辑权限的情况。
- [ ] 编辑器底部展示最后保存时间。
验收证据:
- [ ] 编辑 Markdown 后刷新页面内容仍存在。
- [ ] 保存失败不会丢内容。
- [ ] 左右卡片底部视觉对齐。
## 5. 版本卡片
- [ ] 右侧只保留版本信息卡片。
- [ ] 版本卡片宽度足够展示版本号、日期、状态。
- [ ] 展示最近 5 个版本。
- [ ] 当前版本有明显但不突兀的标识。
- [ ] 当前版本标识居中显示。
- [ ] 选中状态只变色,不改变内容对齐。
- [ ] 日期列和其他版本日期对齐。
- [ ] 点击非当前版本时弹出确认弹窗。
- [ ] 弹窗展示目标版本号。
- [ ] 弹窗展示切换风险提示。
- [ ] 确认后切换当前展示内容。
- [ ] 取消后不改变当前版本。
验收证据:
- [ ] 版本切换不会造成列表文字位移。
- [ ] 当前版本背景能完全覆盖内容区域。
- [ ] 版本卡片不贴右侧边界。
## 6. 审核与上线
- [ ] 详情中展示审核者姓名。
- [ ] 详情中展示审核时间。
- [ ] 详情中展示审核意见。
- [ ] 未审核规则显示不能上线原因。
- [ ] 点击上线时调用后端上线接口。
- [ ] 后端拒绝时展示拒绝原因。
- [ ] 审核通过后上线按钮可用。
- [ ] 审核动作写入审计日志。
- [ ] 上线动作写入审计日志。
验收证据:
- [ ] pending 规则无法上线。
- [ ] approved 规则可以上线。
- [ ] rejected 规则无法上线。
## 7. 技能详情
- [ ] 技能页签列表展示能力名称。
- [ ] 技能详情展示能力说明。
- [ ] 技能详情展示输入参数。
- [ ] 技能详情展示输出参数。
- [ ] 技能详情展示依赖能力。
- [ ] 技能详情展示适用场景。
- [ ] 技能详情展示负责人。
- [ ] 技能详情展示版本。
- [ ] 技能详情不使用规则 Markdown 编辑器。
验收证据:
- [ ] 技能和规则详情不会混用 UI。
## 8. MCP 详情
- [ ] MCP 页签列表展示外部服务名称。
- [ ] MCP 详情展示服务类型。
- [ ] MCP 详情展示调用地址或能力名。
- [ ] MCP 详情展示鉴权方式。
- [ ] MCP 详情展示超时配置。
- [ ] MCP 详情展示降级策略。
- [ ] MCP 详情展示最近调用状态。
- [ ] MCP 详情展示负责人。
验收证据:
- [ ] MCP 被定义为外部服务,而不是技能规则。
## 9. 任务详情
- [ ] 任务页签展示定时任务名称。
- [ ] 任务详情展示 cron 或调度周期。
- [ ] 任务详情展示执行 Agent默认 Hermes。
- [ ] 任务详情展示任务目标。
- [ ] 任务详情展示风险等级。
- [ ] 任务详情展示最近执行时间。
- [ ] 任务详情展示最近执行结果。
- [ ] 任务详情展示启停状态。
验收证据:
- [ ] 定时任务用户可见名称为“任务”。
- [ ] 技术字段可保留 `schedule`,但 UI 不显示“定时任务”。
## 10. 前端质量
- [ ] 页面在 1366 宽度下无横向滚动。
- [ ] 页面在 1920 宽度下右侧卡片不过宽。
- [ ] 页面在窄屏下详情区域可滚动。
- [ ] 所有按钮有禁用态。
- [ ] 所有弹窗有取消按钮。
- [ ] 所有表单错误有提示。
- [ ] 所有日期格式统一。
- [ ] 状态颜色和现有系统一致。
验收证据:
- [ ] `npm run build` 通过。
- [ ] 任务规则中心手动走查通过。
## 11. Day 2 验收
- [ ] 规则、技能、MCP、任务四个页签可用。
- [ ] 搜索框和筛选下拉可用。
- [ ] 规则详情展示 Markdown。
- [ ] 规则 Markdown 可保存。
- [ ] 右侧只保留版本信息。
- [ ] 版本可切换且有弹窗确认。
- [ ] 审核者信息在标题下方。
- [ ] 未审核规则不能上线。
- [ ] 前端构建通过。
- [ ] 所有完成项已用 `[x] ~~...~~` 标记。
## 阻塞记录
- [ ] 暂无。
## 日终交接
- [ ] 写明已接入的 API。
- [ ] 写明仍然使用 Mock 的字段。
- [ ] 写明 UI 未完成项。
- [ ] 写明 Day 3 语义本体需要复用的资产数据。

View File

@@ -0,0 +1,238 @@
# Day 3语义本体 MVP TODO
本文件是周计划 Day 3 的具体执行细则。路线图见 `document/development/agent week plan/day_3_semantic_ontology_mvp.md`
目标:建立用户问题的语义解析层,输出稳定的 8 个核心字段,让 User Agent、Hermes 和 Orchestrator 都能使用同一套语义结构。
参考文档:
- `document/development/agent plan/02_semantic_ontology.md`
- `document/development/agent plan/14_financial_document_canonical_model.md`
- `document/development/agent plan/06_data_contracts_and_governance.md`
## 0. 开始前检查
- [ ] 确认 Day 1 的 `SemanticParseLog` 可用。
- [ ] 确认 Day 1 的 `AgentRun` 可用。
- [ ] 确认 Day 2 的资产 API 可用。
- [ ] 找到后端服务层目录。
- [ ] 找到现有 LLM 调用或 Mock 调用方式。
- [ ] 确认当前是否允许真实调用 LLM。
- [ ] 如果不能调用真实 LLM准备规则解析加 Mock 解析。
## 1. 定义 8 个核心字段
- [ ] 定义字段 `scenario`,表示业务场景。
- [ ] 定义字段 `intent`,表示用户意图。
- [ ] 定义字段 `entities`,表示业务对象。
- [ ] 定义字段 `time_range`,表示时间范围。
- [ ] 定义字段 `metrics`,表示指标或金额口径。
- [ ] 定义字段 `constraints`,表示过滤条件。
- [ ] 定义字段 `risk_flags`,表示风险信号。
- [ ] 定义字段 `permission`,表示动作权限。
- [ ] 为每个字段写清楚类型。
- [ ] 为每个字段写清楚是否必填。
- [ ] 为每个字段写清楚默认值。
- [ ] 为每个字段写清楚示例。
验收证据:
- [ ] 8 个字段在 Schema、服务层、日志中名字一致。
## 2. 设计字段枚举
- [ ] `scenario` 支持 `expense`
- [ ] `scenario` 支持 `accounts_receivable`
- [ ] `scenario` 支持 `accounts_payable`
- [ ] `scenario` 支持 `knowledge`
- [ ] `scenario` 支持 `unknown`
- [ ] `intent` 支持 `query`
- [ ] `intent` 支持 `explain`
- [ ] `intent` 支持 `compare`
- [ ] `intent` 支持 `risk_check`
- [ ] `intent` 支持 `draft`
- [ ] `intent` 支持 `operate`
- [ ] `permission.level` 支持 `read`
- [ ] `permission.level` 支持 `draft_write`
- [ ] `permission.level` 支持 `approval_required`
- [ ] `permission.level` 支持 `forbidden`
验收证据:
- [ ] 未识别的问题不会抛异常,返回 `unknown`
## 3. 建立 Schema
- [ ] 定义 `OntologyParseRequest`
- [ ] `OntologyParseRequest` 包含 `query`
- [ ] `OntologyParseRequest` 包含 `user_id`
- [ ] `OntologyParseRequest` 包含 `context_json`
- [ ] 定义 `OntologyParseResult`
- [ ] `OntologyParseResult` 包含 8 个核心字段。
- [ ] `OntologyParseResult` 包含 `confidence`
- [ ] `OntologyParseResult` 包含 `clarification_required`
- [ ] `OntologyParseResult` 包含 `clarification_question`
- [ ] `OntologyParseResult` 包含 `run_id`
- [ ] 定义字段级错误结构。
验收证据:
- [ ] OpenAPI 中可以看到语义解析请求和响应。
## 4. 实现解析服务
- [ ] 新增 `SemanticOntologyService` 或同等服务。
- [ ] 实现 `parse(query, user_context)` 主函数。
- [ ] 先做关键词规则解析。
- [ ] 报销关键词映射到 `expense`
- [ ] 应收、回款、客户欠款映射到 `accounts_receivable`
- [ ] 应付、供应商、付款映射到 `accounts_payable`
- [ ] 风险、异常、重复、超标映射到 `risk_check`
- [ ] 为什么、依据、规则映射到 `explain`
- [ ] 统计、汇总、多少映射到 `query`
- [ ] 生成、创建、发起映射到 `draft``operate`
- [ ] 无法识别时返回低置信度和澄清问题。
验收证据:
- [ ] “查一下本周报销超标风险”能识别为 expense + risk_check。
- [ ] “客户 A 这个月还有多少应收”能识别为 accounts_receivable + query。
- [ ] “供应商 B 明天要付多少钱”能识别为 accounts_payable + query。
## 5. 解析业务对象
- [ ] 从问题中提取员工姓名。
- [ ] 从问题中提取部门。
- [ ] 从问题中提取客户。
- [ ] 从问题中提取供应商。
- [ ] 从问题中提取项目。
- [ ] 从问题中提取单据号。
- [ ] 从问题中提取金额。
- [ ] 从问题中提取费用类型。
- [ ] 无法提取时返回空数组,不返回 null。
验收证据:
- [ ] “张三 4 月差旅报销”能提取员工、月份、费用类型。
## 6. 解析时间范围
- [ ] 支持今天。
- [ ] 支持昨天。
- [ ] 支持本周。
- [ ] 支持上周。
- [ ] 支持本月。
- [ ] 支持上月。
- [ ] 支持本季度。
- [ ] 支持今年。
- [ ] 支持明确日期。
- [ ] 支持日期区间。
- [ ] 解析结果包含 `start_date``end_date`
- [ ] 日期使用 ISO 格式。
验收证据:
- [ ] “本周”能解析为当前周起止日期。
- [ ] “2026 年 4 月”能解析为 `2026-04-01``2026-04-30`
## 7. 解析指标与约束
- [ ] 识别金额指标。
- [ ] 识别数量指标。
- [ ] 识别超标指标。
- [ ] 识别逾期指标。
- [ ] 识别重复报销指标。
- [ ] 识别部门过滤条件。
- [ ] 识别状态过滤条件。
- [ ] 识别金额阈值过滤条件。
- [ ] 识别排序要求。
- [ ] 识别 Top N 要求。
验收证据:
- [ ] “列出金额最高的 10 笔报销”能识别排序和 Top 10。
## 8. 解析风险与权限
- [ ] 重复报销映射到 `duplicate_expense`
- [ ] 发票异常映射到 `invoice_anomaly`
- [ ] 金额超标映射到 `amount_over_limit`
- [ ] 逾期应收映射到 `ar_overdue`
- [ ] 逾期应付映射到 `ap_overdue`
- [ ] 查询类问题权限为 `read`
- [ ] 生成草稿权限为 `draft_write`
- [ ] 审批、上线、付款类动作权限为 `approval_required`
- [ ] 越权动作权限为 `forbidden`
验收证据:
- [ ] “帮我直接付款”不能被标为可直接执行。
## 9. API 接口
- [ ] 新增 `POST /api/ontology/parse`
- [ ] 请求参数包含用户问题。
- [ ] 请求参数包含用户上下文。
- [ ] 响应包含 8 个字段。
- [ ] 响应包含 `run_id`
- [ ] 响应包含置信度。
- [ ] 响应包含澄清问题。
- [ ] 每次调用写入 `SemanticParseLog`
- [ ] 每次调用写入 `AgentRun` 或关联已有 `AgentRun`
验收证据:
- [ ] 连续调用 5 次都能在日志中查到。
## 10. 前端调试入口
- [ ] 在合适页面增加语义解析调试入口。
- [ ] 输入框支持自然语言问题。
- [ ] 点击解析后调用 API。
- [ ] 展示 8 个字段。
- [ ] 展示 JSON 原始结果。
- [ ] 展示置信度。
- [ ] 展示澄清问题。
- [ ] 展示 `run_id`
- [ ] 错误时展示错误信息。
验收证据:
- [ ] 产品和开发可以直接在页面验证解析结果。
## 11. 评测集
- [ ] 创建至少 5 条报销问题。
- [ ] 创建至少 5 条应收问题。
- [ ] 创建至少 5 条应付问题。
- [ ] 创建至少 3 条知识库问题。
- [ ] 创建至少 3 条越权操作问题。
- [ ] 为每条问题写期望 `scenario`
- [ ] 为每条问题写期望 `intent`
- [ ] 为每条问题写期望权限级别。
- [ ] 编写评测脚本或测试。
验收证据:
- [ ] 核心场景识别准确率达到当天设定阈值,例如 80%。
## 12. Day 3 验收
- [ ] 语义解析 API 可用。
- [ ] 8 个核心字段完整返回。
- [ ] 解析日志可查询。
- [ ] 低置信度问题有澄清问题。
- [ ] 越权动作不会被标为可执行。
- [ ] 前端调试入口可用。
- [ ] 评测集可运行。
- [ ] 所有完成项已用 `[x] ~~...~~` 标记。
## 阻塞记录
- [ ] 暂无。
## 日终交接
- [ ] 写明已支持的关键词。
- [ ] 写明识别不准的样例。
- [ ] 写明 Day 4 Orchestrator 可以直接复用的响应结构。

View File

@@ -0,0 +1,185 @@
# Day 4Orchestrator 运行时 TODO
本文件是周计划 Day 4 的具体执行细则。路线图见 `document/development/agent week plan/day_4_orchestrator_runtime.md`
目标:建立统一调度层,让用户请求和系统任务都先进入 Orchestrator再根据语义本体、权限、能力注册路由到 User Agent、Hermes、MCP 或规则引擎。
参考文档:
- `document/development/agent plan/04_orchestrator_and_runtime_flow.md`
- `document/development/agent plan/07_capability_registry.md`
- `document/development/agent plan/08_permission_confirmation.md`
- `document/development/agent plan/09_observability_and_trace.md`
## 0. 开始前检查
- [ ] 确认 Day 3 `POST /api/ontology/parse` 可用。
- [ ] 确认 `AgentRun` 可创建。
- [ ] 确认 `AgentToolCall` 可创建。
- [ ] 确认资产列表能查询技能、MCP、任务。
- [ ] 确认权限级别枚举已稳定。
- [ ] 找到后端服务层适合放 Orchestrator 的位置。
## 1. Orchestrator 输入输出
- [ ] 定义 `OrchestratorRequest`
- [ ] 请求包含 `source`
- [ ] 请求包含 `user_id`
- [ ] 请求包含 `message`
- [ ] 请求包含 `task_id`
- [ ] 请求包含 `context_json`
- [ ] 定义 `OrchestratorResponse`
- [ ] 响应包含 `run_id`
- [ ] 响应包含 `selected_agent`
- [ ] 响应包含 `route_reason`
- [ ] 响应包含 `permission_level`
- [ ] 响应包含 `status`
- [ ] 响应包含 `result`
- [ ] 响应包含 `requires_confirmation`
- [ ] 响应包含 `trace_summary`
验收证据:
- [ ] Orchestrator 响应能直接被前端展示。
## 2. 建立 Orchestrator 服务
- [ ] 新增 `OrchestratorService`
- [ ] 实现 `run(request)` 主入口。
- [ ] 主入口第一步创建 `AgentRun`
- [ ] 主入口第二步调用语义解析。
- [ ] 主入口第三步执行权限判断。
- [ ] 主入口第四步选择 Agent。
- [ ] 主入口第五步调用目标 Agent 或返回阻断结果。
- [ ] 主入口第六步更新 `AgentRun` 状态。
- [ ] 所有异常都写入 `AgentRun.error_message`
验收证据:
- [ ] 正常请求状态为 `succeeded`
- [ ] 被权限拦截请求状态为 `blocked`
- [ ] 异常请求状态为 `failed`
## 3. 路由规则
- [ ] `source=user_message` 默认路由到 User Agent。
- [ ] `source=schedule` 默认路由到 Hermes。
- [ ] `intent=risk_check` 且来源为 schedule 时路由到 Hermes。
- [ ] `intent=query` 且来源为 user_message 时路由到 User Agent。
- [ ] `intent=explain` 路由到 User Agent。
- [ ] `intent=draft` 路由到 User Agent但只允许生成草稿。
- [ ] `permission.level=approval_required` 时设置 `requires_confirmation=true`
- [ ] `permission.level=forbidden` 时不调用下游 Agent。
- [ ] 无法识别时返回澄清问题。
验收证据:
- [ ] 同一句风险检查,在用户入口和任务入口有不同路由结果。
## 4. 权限判断
- [ ] 新增权限判断服务或函数。
- [ ] 查询类请求返回 `read`
- [ ] 草稿类请求返回 `draft_write`
- [ ] 审批、上线、付款类请求返回 `approval_required`
- [ ] 用户无权限时返回 `forbidden`
- [ ] 高风险动作不允许自动执行。
- [ ] 需要确认的动作返回确认提示。
- [ ] 权限判断结果写入 `AgentRun.permission_level`
验收证据:
- [ ] “直接上线规则”不会被自动执行。
- [ ] “直接付款”不会被自动执行。
## 5. 能力注册查询
- [ ]`AgentAsset` 查询 active 技能。
- [ ]`AgentAsset` 查询 active MCP。
- [ ]`AgentAsset` 查询 active 任务。
- [ ] 过滤 disabled 能力。
- [ ] 过滤未审核 active 条件不满足的规则。
- [ ] 为每次能力选择记录 `route_json`
- [ ] 找不到能力时返回降级说明。
验收证据:
- [ ] 禁用 MCP 不会被 Orchestrator 调用。
## 6. 工具调用封装
- [ ] 定义统一工具调用接口。
- [ ] 工具请求前写入 `AgentToolCall` running 或准备记录。
- [ ] 工具成功后写入响应和耗时。
- [ ] 工具失败后写入错误。
- [ ] 外部 MCP 调用失败时返回降级结果。
- [ ] 数据库查询失败时返回明确错误。
- [ ] LLM 调用失败时返回可读提示。
验收证据:
- [ ] 每次 Orchestrator 运行至少可以看到 0 到多条工具调用记录。
## 7. API 接口
- [ ] 新增 `POST /api/orchestrator/run`
- [ ] 请求支持用户消息。
- [ ] 请求支持任务触发。
- [ ] 响应返回 `run_id`
- [ ] 响应返回路由结果。
- [ ] 响应返回权限结果。
- [ ] 新增 `GET /api/orchestrator/runs/{run_id}/trace` 或复用 AgentRun 详情接口。
- [ ] Trace 接口返回语义解析、路由、工具调用、最终结果。
验收证据:
- [ ] 前端或 curl 可以完整看到一次运行链路。
## 8. 前端最小 Trace 查看
- [ ] 在合适位置展示最近运行记录。
- [ ] 点击运行记录能查看 `run_id`
- [ ] 展示 selected_agent。
- [ ] 展示 route_reason。
- [ ] 展示 permission_level。
- [ ] 展示工具调用列表。
- [ ] 展示错误信息。
- [ ] 展示耗时。
验收证据:
- [ ] 开发调试时不需要直接查数据库才能理解路由结果。
## 9. 测试
- [ ] 测试用户查询路由到 User Agent。
- [ ] 测试定时任务路由到 Hermes。
- [ ] 测试 forbidden 不调用下游 Agent。
- [ ] 测试 approval_required 返回确认。
- [ ] 测试工具失败写入 ToolCall。
- [ ] 测试 Orchestrator 异常写入 AgentRun。
验收证据:
- [ ] Orchestrator 核心测试通过。
## 10. Day 4 验收
- [ ] Orchestrator API 可用。
- [ ] 用户请求能路由到 User Agent 占位实现。
- [ ] 定时任务能路由到 Hermes 占位实现。
- [ ] 权限阻断有效。
- [ ] 运行 Trace 可查询。
- [ ] 工具调用日志可查询。
- [ ] 降级结果可读。
- [ ] 所有完成项已用 `[x] ~~...~~` 标记。
## 阻塞记录
- [ ] 暂无。
## 日终交接
- [ ] 写明路由规则现状。
- [ ] 写明权限判断现状。
- [ ] 写明 Day 5 User Agent 需要实现的接口契约。

View File

@@ -0,0 +1,185 @@
# Day 5User Agent MVP TODO
本文件是周计划 Day 5 的具体执行细则。路线图见 `document/development/agent week plan/day_5_user_agent_mvp.md`
目标:实现面向用户的自建 Agent。它负责用户提问、流程辅助、规则解释、查询结果解释和草稿生成不做自动审批、自动付款、自动上线等高风险动作。
参考文档:
- `document/development/agent plan/03_agent_responsibilities.md`
- `document/development/agent plan/04_orchestrator_and_runtime_flow.md`
- `document/development/agent plan/12_llm_wiki_knowledge_architecture.md`
- `document/development/agent plan/13_rule_formation_lifecycle.md`
## 0. 开始前检查
- [ ] 确认 Orchestrator 能把用户请求路由到 User Agent。
- [ ] 确认语义本体 8 字段可用。
- [ ] 确认规则资产可查询。
- [ ] 确认 AgentRun 和 ToolCall 可记录。
- [ ] 确认是否有现成对话 UI。
- [ ] 确认财务业务数据是否真实可查。
- [ ] 如果业务数据不可查,准备最小 Mock 数据服务。
## 1. User Agent 输入输出
- [ ] 定义 `UserAgentRequest`
- [ ] 请求包含 `run_id`
- [ ] 请求包含 `user_id`
- [ ] 请求包含 `message`
- [ ] 请求包含 `ontology`
- [ ] 请求包含 `context_json`
- [ ] 定义 `UserAgentResponse`
- [ ] 响应包含 `answer`
- [ ] 响应包含 `citations`
- [ ] 响应包含 `suggested_actions`
- [ ] 响应包含 `draft_payload`
- [ ] 响应包含 `risk_flags`
- [ ] 响应包含 `requires_confirmation`
验收证据:
- [ ] User Agent 响应结构能被 Orchestrator 直接包装返回。
## 2. 查询处理
- [ ] 实现报销查询处理器。
- [ ] 实现应收查询处理器。
- [ ] 实现应付查询处理器。
- [ ] 查询前检查权限级别。
- [ ] 查询时记录 ToolCall。
- [ ] 查询失败时返回可读错误。
- [ ] 查询为空时返回空态解释。
- [ ] 查询结果限制返回条数,避免一次返回过大。
验收证据:
- [ ] “查本周报销金额”有可读回答。
- [ ] “客户 A 本月应收多少”有可读回答。
- [ ] “供应商 B 待付款多少”有可读回答。
## 3. 规则解释
- [ ] 根据语义场景查询相关规则资产。
- [ ] 只引用 active 规则。
- [ ] 读取规则当前版本 Markdown。
- [ ] 从 Markdown 中提取规则摘要。
- [ ] 回答中说明使用了哪些规则。
- [ ] 回答中包含规则版本号。
- [ ] 回答中包含规则更新时间。
- [ ] 没有相关规则时说明缺失。
验收证据:
- [ ] “为什么这笔报销有风险”能引用规则。
## 4. 风险解释
- [ ] 识别重复报销风险。
- [ ] 识别金额超标风险。
- [ ] 识别发票异常风险。
- [ ] 识别逾期应收风险。
- [ ] 识别逾期应付风险。
- [ ] 风险回答包含风险类型。
- [ ] 风险回答包含触发原因。
- [ ] 风险回答包含建议处理动作。
- [ ] 高风险建议不能变成自动执行。
验收证据:
- [ ] 风险解释结果不是单纯“有风险”,而是有依据。
## 5. 草稿生成
- [ ] 支持生成报销处理意见草稿。
- [ ] 支持生成应收催收建议草稿。
- [ ] 支持生成应付付款建议草稿。
- [ ] 草稿中标明“待人工确认”。
- [ ] 草稿不直接提交业务系统。
- [ ] 草稿生成写入审计日志。
- [ ] 草稿生成写入 AgentRun 结果。
验收证据:
- [ ] “帮我生成处理意见”只返回草稿,不执行审批。
## 6. 知识库读取骨架
- [ ] 建立知识条目查询接口或服务。
- [ ] 支持按关键词查询知识条目。
- [ ] 支持按业务场景查询知识条目。
- [ ] User Agent 回答可以引用知识条目。
- [ ] 引用中包含知识标题。
- [ ] 引用中包含更新时间。
- [ ] 知识库不可用时返回降级说明。
验收证据:
- [ ] 知识库失败不会导致整个回答失败。
## 7. 对话或操作入口
- [ ] 前端增加用户问题输入框。
- [ ] 输入框支持回车或按钮提交。
- [ ] 提交时调用 Orchestrator而不是绕过 Orchestrator。
- [ ] 展示 Agent 回答。
- [ ] 展示引用规则或知识。
- [ ] 展示建议动作。
- [ ] 展示需要人工确认的提示。
- [ ] 展示 `run_id`
- [ ] 展示加载态。
- [ ] 展示错误态。
验收证据:
- [ ] 用户可在页面完成一次问答闭环。
## 8. 安全边界
- [ ] User Agent 不直接修改规则状态。
- [ ] User Agent 不直接上线规则。
- [ ] User Agent 不直接审批报销。
- [ ] User Agent 不直接付款。
- [ ] User Agent 不直接删除知识。
- [ ] 所有高风险动作只返回建议或草稿。
- [ ] 所有草稿动作标记 `requires_confirmation=true`
验收证据:
- [ ] 提示词要求“直接付款”时仍被阻断。
## 9. 测试
- [ ] 测试报销查询。
- [ ] 测试应收查询。
- [ ] 测试应付查询。
- [ ] 测试规则解释。
- [ ] 测试风险解释。
- [ ] 测试草稿生成。
- [ ] 测试越权动作阻断。
- [ ] 测试知识库降级。
验收证据:
- [ ] User Agent 核心测试通过。
## 10. Day 5 验收
- [ ] User Agent 服务可被 Orchestrator 调用。
- [ ] 用户入口可提交自然语言问题。
- [ ] 至少 3 个财务场景有回答。
- [ ] 回答能引用规则或知识。
- [ ] 高风险动作不会自动执行。
- [ ] AgentRun Trace 能看到 User Agent 步骤。
- [ ] 前端构建通过。
- [ ] 所有完成项已用 `[x] ~~...~~` 标记。
## 阻塞记录
- [ ] 暂无。
## 日终交接
- [ ] 写明已支持的问题类型。
- [ ] 写明仍使用 Mock 的数据。
- [ ] 写明 Day 6 Hermes 可以复用的规则、风险、知识接口。

View File

@@ -0,0 +1,193 @@
# Day 6Hermes MVP TODO
本文件是周计划 Day 6 的具体执行细则。路线图见 `document/development/agent week plan/day_6_hermes_mvp.md`
目标:实现 Hermes 数字员工的最小闭环。Hermes 不面向用户即时对话,而是负责定时巡检、统计、风险预警、知识维护和规则草稿形成。
参考文档:
- `document/development/agent plan/03_agent_responsibilities.md`
- `document/development/agent plan/11_ocr_invoice_architecture.md`
- `document/development/agent plan/12_llm_wiki_knowledge_architecture.md`
- `document/development/agent plan/15_feedback_learning_loop.md`
## 0. 开始前检查
- [ ] 确认任务资产 `asset_type=task` 可查询。
- [ ] 确认 Orchestrator 能处理 `source=schedule`
- [ ] 确认 Hermes 占位服务可被调用。
- [ ] 确认 AgentRun 和 ToolCall 可记录。
- [ ] 确认是否已有后台任务框架。
- [ ] 如果没有后台任务框架,先用手动触发 API 模拟定时执行。
## 1. Hermes 输入输出
- [ ] 定义 `HermesTaskRequest`
- [ ] 请求包含 `run_id`
- [ ] 请求包含 `task_asset_id`
- [ ] 请求包含 `task_type`
- [ ] 请求包含 `schedule_time`
- [ ] 请求包含 `context_json`
- [ ] 定义 `HermesTaskResult`
- [ ] 响应包含 `summary`
- [ ] 响应包含 `risk_items`
- [ ] 响应包含 `statistics`
- [ ] 响应包含 `knowledge_updates`
- [ ] 响应包含 `draft_rules`
- [ ] 响应包含 `next_actions`
验收证据:
- [ ] Hermes 响应能被任务详情或运行日志展示。
## 2. 任务调度入口
- [ ] 新增手动触发任务 API。
- [ ] API 参数支持任务资产 ID。
- [ ] API 调用 Orchestratorsource 为 `schedule`
- [ ] Orchestrator 路由到 Hermes。
- [ ] Hermes 执行结果写入 AgentRun。
- [ ] 任务执行失败时写入错误。
- [ ] 任务执行结束后更新任务最近执行时间。
- [ ] 任务执行结束后更新任务最近执行状态。
验收证据:
- [ ] 可以手动触发一次 Hermes 任务并看到运行结果。
## 3. 每日风险巡检
- [ ] 实现重复报销巡检。
- [ ] 实现金额超标巡检。
- [ ] 实现发票异常巡检占位。
- [ ] 实现应收逾期巡检。
- [ ] 实现应付异常付款巡检。
- [ ] 每个风险项包含风险类型。
- [ ] 每个风险项包含业务对象。
- [ ] 每个风险项包含触发规则。
- [ ] 每个风险项包含建议动作。
- [ ] 每个风险项包含风险等级。
验收证据:
- [ ] 风险巡检结果可以被用户理解和追溯。
## 4. 每日统计
- [ ] 统计当日报销单数量。
- [ ] 统计当日报销金额。
- [ ] 统计当日报账数量。
- [ ] 统计当日报账金额。
- [ ] 统计应收新增金额。
- [ ] 统计应收逾期金额。
- [ ] 统计应付待付金额。
- [ ] 统计应付逾期金额。
- [ ] 输出日报摘要。
验收证据:
- [ ] Hermes 能生成一份每日财务摘要。
## 5. OCR 接入点
- [ ] 建立 OCR 识别服务接口。
- [ ] 定义发票识别输入结构。
- [ ] 定义发票识别输出结构。
- [ ] 输出结构包含发票号。
- [ ] 输出结构包含开票日期。
- [ ] 输出结构包含金额。
- [ ] 输出结构包含税额。
- [ ] 输出结构包含销售方。
- [ ] 输出结构包含购买方。
- [ ] 输出结构包含置信度。
- [ ] 当前阶段允许使用 Mock 结果。
- [ ] OCR 调用写入 ToolCall。
验收证据:
- [ ] Hermes 风险巡检中可以调用 OCR Mock。
## 6. 知识库维护
- [ ] 建立知识条目写入服务。
- [ ] Hermes 可以生成知识候选条目。
- [ ] 候选条目包含标题。
- [ ] 候选条目包含正文。
- [ ] 候选条目包含来源。
- [ ] 候选条目包含适用场景。
- [ ] 候选条目默认状态为 `draft`
- [ ] 知识条目不能自动发布。
- [ ] 知识条目写入审计日志。
验收证据:
- [ ] Hermes 可以生成待审核知识条目。
## 7. 规则草稿形成
- [ ] Hermes 可以根据风险巡检结果生成规则草稿。
- [ ] 规则草稿保存为 `asset_type=rule`
- [ ] 规则草稿状态为 `draft`
- [ ] 规则草稿包含 Markdown 内容。
- [ ] 规则草稿包含生成原因。
- [ ] 规则草稿包含关联风险样例。
- [ ] 规则草稿不能自动上线。
- [ ] 规则草稿需要审核人。
- [ ] 规则草稿写入审计日志。
验收证据:
- [ ] Hermes 生成的新规则出现在规则列表中,但不是 active。
## 8. Hermes 页面或日志展示
- [ ] 任务详情能看到最近执行结果。
- [ ] 任务详情能手动触发执行。
- [ ] 任务详情能看到风险项数量。
- [ ] 任务详情能看到日报摘要。
- [ ] 任务详情能看到知识候选数量。
- [ ] 任务详情能看到规则草稿数量。
- [ ] 运行 Trace 能看到 Hermes 步骤。
- [ ] 错误时展示错误原因。
验收证据:
- [ ] 不查数据库也能判断 Hermes 是否执行成功。
## 9. 测试
- [ ] 测试手动触发任务。
- [ ] 测试 Orchestrator 路由到 Hermes。
- [ ] 测试风险巡检输出。
- [ ] 测试日报统计输出。
- [ ] 测试 OCR Mock 调用。
- [ ] 测试知识候选写入。
- [ ] 测试规则草稿生成。
- [ ] 测试 Hermes 异常写入 AgentRun。
验收证据:
- [ ] Hermes 核心测试通过。
## 10. Day 6 验收
- [ ] Hermes 可被 Orchestrator 调用。
- [ ] 至少一个任务可以手动触发。
- [ ] 风险巡检有结构化结果。
- [ ] 每日统计有结构化结果。
- [ ] OCR Mock 接入点可用。
- [ ] 知识候选可生成。
- [ ] 规则草稿可生成且不能自动上线。
- [ ] 任务详情或运行日志能展示结果。
- [ ] 所有完成项已用 `[x] ~~...~~` 标记。
## 阻塞记录
- [ ] 暂无。
## 日终交接
- [ ] 写明 Hermes 已支持任务类型。
- [ ] 写明 OCR 当前是真实还是 Mock。
- [ ] 写明生成的知识和规则草稿状态。
- [ ] 写明 Day 7 需要重点回归的路径。

View File

@@ -0,0 +1,225 @@
# Day 7加固、演示和验收 TODO
本文件是周计划 Day 7 的具体执行细则。路线图见 `document/development/agent week plan/day_7_hardening_demo_acceptance.md`
目标:把前 6 天做出的功能整理成可演示、可验收、可继续迭代的基础平台。Day 7 不再大规模扩功能,重点是修缺口、补测试、补日志、补文档、完成演示链路。
参考文档:
- `document/development/agent plan/00_README.md`
- `document/development/agent plan/05_development_roadmap.md`
- `document/development/agent plan/09_observability_and_trace.md`
- `document/development/agent plan/10_evaluation_and_testset.md`
## 0. 开始前检查
- [ ] 汇总 Day 1 未完成项。
- [ ] 汇总 Day 2 未完成项。
- [ ] 汇总 Day 3 未完成项。
- [ ] 汇总 Day 4 未完成项。
- [ ] 汇总 Day 5 未完成项。
- [ ] 汇总 Day 6 未完成项。
- [ ] 标记必须今天修复的问题。
- [ ] 标记可以进入下一阶段的问题。
- [ ] 冻结新增需求,只处理验收相关问题。
## 1. 核心链路回归
- [ ] 回归资产列表接口。
- [ ] 回归规则详情接口。
- [ ] 回归 Markdown 保存。
- [ ] 回归版本列表。
- [ ] 回归版本切换。
- [ ] 回归审核接口。
- [ ] 回归上线拦截。
- [ ] 回归语义解析接口。
- [ ] 回归 Orchestrator 路由。
- [ ] 回归 User Agent 问答。
- [ ] 回归 Hermes 任务执行。
- [ ] 回归 AgentRun Trace。
- [ ] 回归 ToolCall 日志。
- [ ] 回归 AuditLog 日志。
验收证据:
- [ ] 从前端能完成至少一条端到端演示路径。
## 2. 权限和风险边界
- [ ] 未审核规则不能上线。
- [ ] rejected 规则不能上线。
- [ ] disabled 能力不能被调用。
- [ ] 用户请求付款必须拦截。
- [ ] 用户请求审批必须需要确认。
- [ ] Hermes 生成规则只能是 draft。
- [ ] Hermes 生成知识只能是 draft。
- [ ] User Agent 生成处理意见只能是草稿。
- [ ] 所有高风险动作响应中包含 `requires_confirmation`
验收证据:
- [ ] 不存在 MVP 期间绕过人工审核的路径。
## 3. 审计和 Trace 补齐
- [ ] 规则保存写 AuditLog。
- [ ] 规则审核写 AuditLog。
- [ ] 规则上线写 AuditLog。
- [ ] Hermes 生成规则草稿写 AuditLog。
- [ ] Hermes 生成知识候选写 AuditLog。
- [ ] User Agent 草稿生成写 AuditLog。
- [ ] Orchestrator 每次运行有 AgentRun。
- [ ] 每次工具调用有 ToolCall。
- [ ] Trace 页面或接口能串起 run_id。
- [ ] 错误 Trace 包含 error_message。
验收证据:
- [ ] 任意一条演示链路都能追溯到 run_id。
## 4. 前端体验修补
- [ ] 任务规则中心列表无明显错位。
- [ ] 详情页无双 title。
- [ ] Hero title 高度紧凑。
- [ ] 返回列表栏高度正常。
- [ ] Markdown 编辑器和版本卡片底部对齐。
- [ ] 版本卡片不贴右侧。
- [ ] 当前版本标识不突兀。
- [ ] 日期列对齐。
- [ ] 弹窗文案清楚。
- [ ] 加载态可见。
- [ ] 错误态可见。
- [ ] 空态可见。
- [ ] 按钮禁用态可见。
- [ ] 窄屏不出现内容重叠。
验收证据:
- [ ] 任务规则中心可以给业务用户演示,不需要解释 UI 异常。
## 5. 测试补齐
- [ ] 运行后端现有测试。
- [ ] 运行新增模型测试。
- [ ] 运行新增 API 测试。
- [ ] 运行语义解析测试。
- [ ] 运行 Orchestrator 测试。
- [ ] 运行 User Agent 测试。
- [ ] 运行 Hermes 测试。
- [ ] 运行前端构建。
- [ ] 如果有前端测试,运行前端测试。
- [ ] 记录未能运行的测试和原因。
验收证据:
- [ ] 测试结果写入本文件“测试记录”。
## 6. 评测集
- [ ] 准备 5 条报销问题。
- [ ] 准备 5 条应收问题。
- [ ] 准备 5 条应付问题。
- [ ] 准备 3 条规则解释问题。
- [ ] 准备 3 条越权动作问题。
- [ ] 执行语义解析评测。
- [ ] 执行 User Agent 回答评测。
- [ ] 执行权限拦截评测。
- [ ] 记录失败样例。
- [ ] 为失败样例写下一阶段优化建议。
验收证据:
- [ ] 可以说明 MVP 当前能力边界和准确率风险。
## 7. 演示数据
- [ ] 准备 active 规则。
- [ ] 准备 pending 规则。
- [ ] 准备 rejected 规则。
- [ ] 准备至少一条报销数据。
- [ ] 准备至少一条应收数据。
- [ ] 准备至少一条应付数据。
- [ ] 准备至少一个 Hermes 任务。
- [ ] 准备至少一个 MCP Mock。
- [ ] 准备至少一个知识条目。
- [ ] 准备至少一个风险样例。
验收证据:
- [ ] 演示不会因为没有数据而中断。
## 8. 演示脚本
- [ ] 编写演示步骤 1打开任务规则中心。
- [ ] 编写演示步骤 2查看规则详情。
- [ ] 编写演示步骤 3编辑 Markdown 并保存。
- [ ] 编写演示步骤 4切换版本。
- [ ] 编写演示步骤 5尝试上线未审核规则并被拦截。
- [ ] 编写演示步骤 6输入用户问题。
- [ ] 编写演示步骤 7查看语义本体结果。
- [ ] 编写演示步骤 8查看 User Agent 回答。
- [ ] 编写演示步骤 9手动触发 Hermes 任务。
- [ ] 编写演示步骤 10查看 AgentRun Trace。
- [ ] 编写演示步骤 11查看审计日志。
验收证据:
- [ ] 新开发者按脚本可以复现演示。
## 9. 文档收尾
- [ ] 更新一周计划完成情况。
- [ ] 更新剩余风险。
- [ ] 更新下一阶段开发建议。
- [ ] 更新接口清单。
- [ ] 更新数据模型清单。
- [ ] 更新前端页面清单。
- [ ] 更新评测结果。
- [ ] 更新演示脚本。
- [ ] 更新部署或启动说明。
验收证据:
- [ ] 文档能指导下一周继续开发。
## 10. 最终验收清单
- [ ] 任务规则中心可查看规则、技能、MCP、任务。
- [ ] 规则详情可编辑 Markdown。
- [ ] 规则详情可查看最近 5 个版本。
- [ ] 版本切换有确认弹窗。
- [ ] 审核者信息可见。
- [ ] 未审核规则不能上线。
- [ ] 语义本体 8 字段可返回。
- [ ] Orchestrator 能路由用户请求。
- [ ] Orchestrator 能路由定时任务。
- [ ] User Agent 能回答至少 3 类财务问题。
- [ ] Hermes 能执行至少 1 个任务。
- [ ] OCR Mock 接入点可用。
- [ ] 知识候选可生成。
- [ ] 规则草稿可生成。
- [ ] AgentRun Trace 可查。
- [ ] AuditLog 可查。
- [ ] 前端构建通过。
- [ ] 后端核心测试通过。
- [ ] 演示脚本可执行。
- [ ] 所有完成项已用 `[x] ~~...~~` 标记。
## 测试记录
- [ ] 后端测试:未运行。
- [ ] 前端构建:未运行。
- [ ] 语义评测:未运行。
- [ ] 手动验收:未运行。
## 阻塞记录
- [ ] 暂无。
## 日终交接
- [ ] 写明本周最终完成内容。
- [ ] 写明未完成内容。
- [ ] 写明生产化前必须补齐内容。
- [ ] 写明下一周建议优先级。

View File

@@ -1,105 +1,51 @@
# Agent Week Plan 一周开发计划
# Agent Week Plan 一周开发路线图
本目录是 `document/development/agent plan` 架构文档基础上拆出的 7 天生产标准开发计划
本目录是 `agent_weekly_plan` 的路线图层,负责回答“这一周每天大概做什么、为什么这样排、每天交付什么”
这版文档不是概念说明,而是给 Codex 或开发人员逐项执行的 TODO 手册。执行时必须按顺序推进,每完成一项就在对应文档中标记。
具体到字段、接口、服务、前端、测试和验收步骤的执行清单,不放在这里,而是放在:
## 执行标记规则
[../agent plan/weekly_execution_details/README.md](<../agent plan/weekly_execution_details/README.md>)
未完成:
## 两层文档分工
```md
- [ ] 建立 AgentAsset 数据模型
```
| 目录 | 职责 | 读者 |
| --- | --- | --- |
| `agent week plan` | 7 天路线图,每天只讲大开发点、交付物、验收门槛 | 产品、架构、排期、开发启动前 |
| `agent plan` | 架构设计、协议、流程、每天具体执行 TODO | Codex、开发人员、评审人员 |
完成后:
## 使用方式
```md
- [x] ~~建立 AgentAsset 数据模型~~
```
1. 先读 [MASTER_TODO.md](./MASTER_TODO.md),确认 7 天节奏。
2. 进入当天 daily 文档,看当天主题、交付结果和验收门槛。
3. 点击 daily 中的“对应执行细则”,进入 `agent plan/weekly_execution_details`
4. 在执行细则中按 `- [ ]` 一项一项开发。
5. 完成细则后回到 daily 文档,确认当天是否达到交付门槛。
执行要求:
## 一周总体目标
- [ ] 每次只处理一个最小 TODO
- [ ] 完成后先自测,再改成 `[x]`
- [ ] 改成 `[x]` 时,同时用 `~~` 画线
- [ ] 不能因为代码写完就标完成,必须满足该 TODO 的验收证据
- [ ] 遇到阻塞时,在当天文档的“阻塞记录”下新增一条说明
- [ ] 每天收尾时更新当天文档的“日终交接”
- Day 1先把资产、版本、审核、运行日志、审计日志等基础地基建好
- Day 2把任务规则中心和后端资产体系打通
- Day 3建立语义本体 MVP让用户问题能变成稳定结构
- Day 4建立 Orchestrator让请求能被统一路由、审计、降级
- Day 5建立 User Agent MVP处理用户查询、解释和草稿生成
- Day 6建立 Hermes MVP处理定时巡检、统计、知识和规则草稿
- Day 7做加固、测试、演示、验收和下一阶段交接。
## 文档顺序
## 一周暂不完成
先看总控清单,再进入每天的执行文档:
1. [MASTER_TODO.md](./MASTER_TODO.md)
2. [day_1_foundation_models.md](./day_1_foundation_models.md)
3. [day_2_rule_center_integration.md](./day_2_rule_center_integration.md)
4. [day_3_semantic_ontology_mvp.md](./day_3_semantic_ontology_mvp.md)
5. [day_4_orchestrator_runtime.md](./day_4_orchestrator_runtime.md)
6. [day_5_user_agent_mvp.md](./day_5_user_agent_mvp.md)
7. [day_6_hermes_mvp.md](./day_6_hermes_mvp.md)
8. [day_7_hardening_demo_acceptance.md](./day_7_hardening_demo_acceptance.md)
## 一周总目标
- [ ] 建立规则、技能、MCP、任务的统一资产模型。
- [ ] 建立规则 Markdown 内容、版本、审核、上线状态的闭环。
- [ ] 建立语义本体 8 字段解析接口。
- [ ] 建立 Orchestrator 路由和 Agent Run Trace。
- [ ] 建立 User Agent 的查询、解释、流程辅助 MVP。
- [ ] 建立 Hermes 的定时风险巡检和日报 MVP。
- [ ] 建立基础权限分级、人工确认、审计日志。
- [ ] 建立最小评测集、手动验收脚本和演示流程。
## 一周暂不做
- [ ] 不做完整 OCR 生产识别引擎,只预留标准接口和 Mock 结果。
- [ ] 不做完整发票验真 MCP 深度接入,只做能力注册和 Mock 调用。
- [ ] 不做完整 LLM Wiki 向量检索,只做知识条目写入和读取骨架。
- [ ] 不做所有财务域数据全量打通,只覆盖报销、应收、应付的最小字段。
- [ ] 不做规则自动上线,规则只能生成草稿,必须人工审核。
- [ ] 不做完整 CI/CD只做本地构建、核心测试和验收脚本。
- 完整 OCR 生产识别引擎。
- 完整发票验真 MCP 深度接入。
- 完整 LLM Wiki 向量检索。
- 全量财务域数据打通。
- 规则自动上线。
- 完整 CI/CD 质量门禁。
## 生产底线
以下底线不得被 MVP 名义绕过:
- [ ] 所有写操作必须记录审计日志
- [ ] 所有 Agent 执行必须生成 `run_id`
- [ ] 所有规则必须有版本
- [ ] 未审核规则不能上线
- [ ] 高风险动作只能生成草稿或建议,不能自动提交。
- [ ] 外部服务失败必须有降级结果。
- [ ] 语义解析结果必须落库或落日志,便于回放。
- [ ] 前端不能只写静态 UI必须至少对接 Mock 或真实 API。
## 每日固定流程
上午:
- [ ] 读取当天文档。
- [ ] 检查前一天遗留阻塞。
- [ ] 确认数据库模型、API、服务边界。
- [ ] 完成后端主路径。
下午:
- [ ] 完成前端联调。
- [ ] 接入 Agent 或 Orchestrator 流程。
- [ ] 完成权限、审计、错误态。
傍晚:
- [ ] 运行测试和构建。
- [ ] 按当天验收清单逐项验收。
- [ ] 更新 TODO 完成状态。
- [ ] 填写日终交接。
## Codex 执行约束
- [ ] 修改代码前先读相关文件,不凭空创建重复模块。
- [ ] 优先复用现有 FastAPI、SQLAlchemy、Vue、PrimeVue 写法。
- [ ] API 命名必须稳定,不能一天一个风格。
- [ ] 数据模型新增字段必须写清楚用途。
- [ ] 前端状态、空态、错误态、加载态都要覆盖。
- [ ] 每天结束必须能给出可运行证据。
- 所有写操作必须有审计日志。
- 所有 Agent 执行必须生成 `run_id`
- 所有规则必须有版本
- 未审核规则不能上线
- 高风险动作只能生成草稿或建议,不能自动提交
- 外部能力失败必须有降级结果
- 语义解析结果必须可回放。

View File

@@ -1,119 +1,46 @@
# Agent Week Plan 总控 TODO
# Agent Week Plan 总
本文件用于控制 7 天开发顺序。每个大项完成后,再进入对应天的详细文档
本文件只放一周路线图,不放细节 TODO。具体执行步骤在 `agent plan/weekly_execution_details`
完成标记规则:
## 快速浏览
```md
- [x] ~~已完成的任务~~
```
- HTML 总览:[agent_week_plan_html/index.html](<../agent_week_plan_html/index.html>)
- Day 1 HTML[agent_week_plan_html/day-1.html](<../agent_week_plan_html/day-1.html>)
## Day 1基础模型与工程骨架
## 一周节奏
- [ ] 阅读 `document/development/agent plan/01_overall_architecture.md`
- [ ] 阅读 `document/development/agent plan/02_semantic_ontology.md`
- [ ] 阅读 `document/development/agent plan/06_data_contracts_and_governance.md`
- [ ] 阅读 `document/development/agent plan/07_capability_registry.md`
- [ ] 阅读 `document/development/agent plan/08_permission_confirmation.md`
- [ ] 阅读 `document/development/agent plan/09_observability_and_trace.md`
- [ ] 完成统一资产模型 `AgentAsset`
- [ ] 完成资产版本模型 `AgentAssetVersion`
- [ ] 完成资产审核模型 `AgentAssetReview`
- [ ] 完成 Agent 运行日志模型 `AgentRun`
- [ ] 完成工具调用日志模型 `AgentToolCall`
- [ ] 完成语义解析日志模型 `SemanticParseLog`
- [ ] 完成审计日志模型 `AuditLog`
- [ ] 完成基础 API 路由骨架。
- [ ] 完成种子数据。
- [ ] 完成 Day 1 验收。
| Day | 状态 | 主题 | 主要交付 | 快速视图 | 对应执行细则 |
| --- | --- | --- | --- | --- | --- |
| Day 1 | 已完成2026-05-11 | 基础模型与工程骨架 | 资产、版本、审核、运行日志、审计日志、基础 API、最小财务数据源 | [HTML](<../agent_week_plan_html/day-1.html>) | [细则](<../agent plan/weekly_execution_details/day_1_foundation_models.md>) |
| Day 2 | 未开始 | 任务规则中心联调 | 规则/技能/MCP/任务列表与详情、Markdown、版本、审核 | [HTML](<../agent_week_plan_html/day-2.html>) | [细则](<../agent plan/weekly_execution_details/day_2_rule_center_integration.md>) |
| Day 3 | 未开始 | 语义本体 MVP | 8 字段语义解析、日志、评测入口 | [HTML](<../agent_week_plan_html/day-3.html>) | [细则](<../agent plan/weekly_execution_details/day_3_semantic_ontology_mvp.md>) |
| Day 4 | 未开始 | Orchestrator 运行时 | 统一入口、路由、权限、工具调用、Trace | [HTML](<../agent_week_plan_html/day-4.html>) | [细则](<../agent plan/weekly_execution_details/day_4_orchestrator_runtime.md>) |
| Day 5 | 未开始 | User Agent MVP | 用户问答、财务查询、规则解释、草稿生成 | [HTML](<../agent_week_plan_html/day-5.html>) | [细则](<../agent plan/weekly_execution_details/day_5_user_agent_mvp.md>) |
| Day 6 | 未开始 | Hermes MVP | 定时任务、风险巡检、日报、知识候选、规则草稿 | [HTML](<../agent_week_plan_html/day-6.html>) | [细则](<../agent plan/weekly_execution_details/day_6_hermes_mvp.md>) |
| Day 7 | 未开始 | 加固、演示和验收 | 回归、测试、演示脚本、交付说明 | [HTML](<../agent_week_plan_html/day-7.html>) | [细则](<../agent plan/weekly_execution_details/day_7_hardening_demo_acceptance.md>) |
## Day 2任务规则中心联调
## 当前完成情况
- [ ] 阅读 `document/development/agent plan/13_rule_formation_lifecycle.md`
- [ ] 阅读 `document/development/agent plan/07_capability_registry.md`
- [ ] 对接规则、技能、MCP、任务资产列表 API。
- [ ] 对接资产详情 API。
- [ ] 对接规则 Markdown 读取和保存 API。
- [ ] 对接版本列表和版本切换 API。
- [ ] 对接审核者信息和审核状态。
- [ ] 对接规则上线前审核拦截。
- [ ] 完成前端筛选、搜索、详情、弹窗状态。
- [ ] 完成 Day 2 验收。
- Day 1 已完成,后端基础模型、审计和最小财务数据源已可供 Day 2 前端联调使用
- Day 2 到 Day 7 保持原排期,下一步直接进入规则中心联调
## Day 3语义本体 MVP
## 关键依赖顺序
- [ ] 阅读 `document/development/agent plan/02_semantic_ontology.md`
- [ ] 阅读 `document/development/agent plan/14_financial_document_canonical_model.md`
- [ ] 定义 8 个核心字段的数据结构
- [ ] 实现语义解析服务
- [ ] 实现语义解析 API
- [ ] 实现解析日志保存
- [ ] 实现场景、意图、对象、时间、指标、约束、风险、权限字段。
- [ ] 接入 User Agent 查询入口。
- [ ] 完成最小评测集。
- [ ] 完成 Day 3 验收。
1. Day 1 必须先完成,因为后面所有能力都依赖资产、版本、审核、日志
2. Day 2 必须在 Day 3 前完成,因为语义和 Agent 需要读取规则、技能、MCP、任务资产
3. Day 3 必须在 Day 4 前完成,因为 Orchestrator 依赖语义本体做路由
4. Day 4 必须在 Day 5 / Day 6 前完成,因为 User Agent 和 Hermes 都应该由 Orchestrator 调用
5. Day 5 和 Day 6 可以部分并行但都必须遵守权限、审计、Trace
6. Day 7 不新增大功能,只做加固、验收和交接
## Day 4Orchestrator 运行时
## 最终验收
- [ ] 阅读 `document/development/agent plan/04_orchestrator_and_runtime_flow.md`
- [ ] 阅读 `document/development/agent plan/08_permission_confirmation.md`
- [ ] 阅读 `document/development/agent plan/09_observability_and_trace.md`
- [ ] 实现 Orchestrator 入口服务
- [ ] 实现语义本体到 Agent 路由
- [ ] 实现权限级别判断
- [ ] 实现工具调用封装
- [ ] 实现运行 Trace
- [ ] 实现降级和错误返回。
- [ ] 完成 Day 4 验收。
## Day 5User Agent MVP
- [ ] 阅读 `document/development/agent plan/03_agent_responsibilities.md`
- [ ] 阅读 `document/development/agent plan/12_llm_wiki_knowledge_architecture.md`
- [ ] 实现用户自然语言入口。
- [ ] 实现报销查询解释流程。
- [ ] 实现应收账款查询解释流程。
- [ ] 实现应付账款查询解释流程。
- [ ] 实现规则引用解释。
- [ ] 实现建议草稿输出。
- [ ] 完成前端对话或操作入口。
- [ ] 完成 Day 5 验收。
## Day 6Hermes MVP
- [ ] 阅读 `document/development/agent plan/03_agent_responsibilities.md`
- [ ] 阅读 `document/development/agent plan/11_ocr_invoice_architecture.md`
- [ ] 阅读 `document/development/agent plan/15_feedback_learning_loop.md`
- [ ] 实现任务资产调度入口。
- [ ] 实现每日风险巡检任务。
- [ ] 实现每日报销/报账/账款统计任务。
- [ ] 实现知识库维护任务。
- [ ] 实现 OCR Mock 接入点。
- [ ] 实现 Hermes 运行结果面板或 API。
- [ ] 完成 Day 6 验收。
## Day 7加固、演示和验收
- [ ] 回归 Day 1 到 Day 6 所有核心路径。
- [ ] 补齐权限拦截。
- [ ] 补齐审计日志。
- [ ] 补齐错误态和空态。
- [ ] 补齐评测用例。
- [ ] 补齐演示数据。
- [ ] 完成构建。
- [ ] 完成一周交付说明。
- [ ] 完成 Day 7 验收。
## 一周最终验收
- [ ] 能从任务规则中心看到规则、技能、MCP、任务。
- [ ] 能打开规则详情,编辑 Markdown查看版本切换版本。
- [ ] 未审核规则不能上线。
- [ ] 能输入一句自然语言问题并得到语义本体 8 字段结果。
- [ ] 能由 Orchestrator 路由到 User Agent。
- [ ] 能由 Hermes 执行一次模拟定时任务。
- [ ] 能查看 Agent Run Trace。
- [ ] 能查看工具调用日志。
- [ ] 能查看审计日志。
- [ ] 能运行核心测试。
- [ ] 能完成演示脚本。
- 任务规则中心能看到规则、技能、MCP、任务
- 规则详情能编辑 Markdown、查看最近 5 个版本、切换版本
- 未审核规则不能上线
- 用户问题能解析出语义本体 8 字段
- Orchestrator 能路由到 User Agent 和 Hermes
- User Agent 能完成查询、解释、草稿生成
- Hermes 能执行一次风险巡检或日报任务
- AgentRun、ToolCall、AuditLog 都能追溯
- 有演示脚本和下一阶段交接文档。

View File

@@ -1,316 +1,74 @@
# Day 1基础模型与工程骨架 TODO
# Day 1基础模型与工程骨架
目标:建立后续 6 天开发所需的后端地基。Day 1 不做复杂业务逻辑只做稳定模型、API 骨架、种子数据、基础审计和可运行验证。
## 当前状态
参考文档:
- [x] ~~Day 1 已完成2026-05-11。~~
- [x] ~~后端基础模型、API 骨架、种子数据、审计能力和 Day 2 联调入口均已落地。~~
- `document/development/agent plan/01_overall_architecture.md`
- `document/development/agent plan/02_semantic_ontology.md`
- `document/development/agent plan/06_data_contracts_and_governance.md`
- `document/development/agent plan/07_capability_registry.md`
- `document/development/agent plan/08_permission_confirmation.md`
- `document/development/agent plan/09_observability_and_trace.md`
## 今天的大开发点
## 0. 开始前检查
Day 1 只做地基,不做复杂 Agent 智能。
- [ ] 确认当前分支和工作区状态
- [ ] 确认后端目录位置,例如 `/app/server`
- [ ] 确认前端目录位置,例如 `/app/web`
- [ ] 确认后端使用的框架、ORM、迁移方式。
- [ ] 找到现有数据库模型目录。
- [ ] 找到现有 API 路由目录。
- [ ] 找到现有启动入口。
- [ ] 找到现有测试目录。
- [ ] 找到现有种子数据或初始化脚本。
- [ ] 记录不应修改的无关文件。
核心是把后面 6 天都会用到的基础对象建出来:资产、版本、审核、运行日志、工具调用日志、语义解析日志、审计日志,以及最小财务业务数据来源
## 1. 统一命名和边界
## 为什么第一天做这个
- [ ] 确认统一模块名使用 `agent_assets`
- [ ] 确认资产类型枚举为 `rule``skill``mcp``task`
- [ ] 确认资产状态枚举为 `draft``review``active``disabled`
- [ ] 确认审核状态枚举为 `pending``approved``rejected`
- [ ] 确认 Agent 枚举为 `orchestrator``user_agent``hermes`
- [ ] 确认运行来源枚举为 `user_message``schedule``system_event`
- [ ] 确认权限级别枚举为 `read``draft_write``approval_required``forbidden`
- [ ] 确认所有主键、外键、时间字段命名符合现有代码风格。
如果没有稳定的数据模型后面的任务规则中心、语义本体、Orchestrator、User Agent、Hermes 都会各自临时造结构,后期会很难合并
验收证据:
## 今天主要交付
- [ ] 枚举命名在模型、Schema、服务层保持一致。
- [ ] 没有同时出现 `schedule``task` 两套用户可见命名。
- [x] ~~统一资产模型规则、技能、MCP、任务。~~
- [x] ~~版本模型:规则 Markdown 和其他资产配置快照。~~
- [x] ~~审核模型:未审核不能上线。~~
- [x] ~~Agent 运行日志:所有 Agent 执行都有 `run_id`。~~
- [x] ~~工具调用日志MCP、数据库、LLM、OCR、规则引擎调用都可追踪。~~
- [x] ~~语义解析日志:后续语义本体结果可回放。~~
- [x] ~~审计日志:所有写操作可追责。~~
- [x] ~~最小财务业务数据来源:报销、应收、应付。~~
## 2. 建立 AgentAsset 模型
## 实际落地结果
- [ ] 新增或扩展模型文件,定义 `AgentAsset`
- [ ] 增加字段 `id`
- [ ] 增加字段 `asset_type`,取值 `rule | skill | mcp | task`
- [ ] 增加字段 `code`,作为业务编码。
- [ ] 增加字段 `name`
- [ ] 增加字段 `description`
- [ ] 增加字段 `domain`,例如 `expense | ar | ap | knowledge | system`
- [ ] 增加字段 `scenario_json`,保存适用场景。
- [ ] 增加字段 `owner`
- [ ] 增加字段 `reviewer`
- [ ] 增加字段 `status`
- [ ] 增加字段 `current_version`
- [ ] 增加字段 `config_json`
- [ ] 增加字段 `created_at`
- [ ] 增加字段 `updated_at`
- [ ]`code` 增加唯一约束。
- [ ]`asset_type` 增加索引。
- [ ]`status` 增加索引。
- [ ]`domain` 增加索引。
- [x] ~~新增 `AgentAsset`、`AgentAssetVersion`、`AgentAssetReview`、`AgentRun`、`AgentToolCall`、`SemanticParseLog`、`AuditLog`、`ExpenseClaim`、`ExpenseClaimItem`、`AccountsReceivableRecord`、`AccountsPayableRecord`。~~
- [x] ~~新增 `/api/v1/agent-assets`、`/api/v1/agent-runs`、`/api/v1/audit-logs` 相关接口。~~
- [x] ~~种子数据已覆盖 3 条规则、2 条技能、2 条 MCP、3 条任务,以及报销 / 应收 / 应付示例数据。~~
- [x] ~~旧开发库启动时会自动补齐新增资产和版本,不需要手动清库。~~
验收证据:
## 对应执行细则
- [ ] 能创建一条规则资产。
- [ ] 能创建一条技能资产。
- [ ] 能创建一条 MCP 资产。
- [ ] 能创建一条任务资产。
- [Day 1 执行细则](<../agent plan/weekly_execution_details/day_1_foundation_models.md>)
## 3. 建立 AgentAssetVersion 模型
相关架构文档:
- [ ] 定义 `AgentAssetVersion`
- [ ] 增加字段 `id`
- [ ] 增加字段 `asset_id`
- [ ] 增加字段 `version`,例如 `v1.0.0`
- [ ] 增加字段 `content`
- [ ] 增加字段 `content_type`,取值 `markdown | json`
- [ ] 增加字段 `change_note`
- [ ] 增加字段 `created_by`
- [ ] 增加字段 `created_at`
- [ ] 增加 `asset_id + version` 唯一约束。
- [ ] 建立 `AgentAsset``AgentAssetVersion` 的关系。
- [ ] 约定规则资产的 `content` 保存 Markdown。
- [ ] 约定技能、MCP、任务资产的 `content` 保存 JSON 快照。
- [整体架构](<../agent plan/01_overall_architecture.md>)
- [语义本体](<../agent plan/02_semantic_ontology.md>)
- [数据契约与治理](<../agent plan/06_data_contracts_and_governance.md>)
- [能力注册](<../agent plan/07_capability_registry.md>)
- [权限与确认](<../agent plan/08_permission_confirmation.md>)
- [观测与 Trace](<../agent plan/09_observability_and_trace.md>)
- [财务单据标准模型](<../agent plan/14_financial_document_canonical_model.md>)
验收证据:
## 当天验收门槛
- [ ] 同一个资产不能重复创建同一个版本号。
- [ ] 资产详情能拿到最近 5 个版本。
- [x] ~~数据库或等价存储能创建基础对象。~~
- [x] ~~API 服务能启动。~~
- [x] ~~资产列表能返回规则、技能、MCP、任务。~~
- [x] ~~规则资产能关联 Markdown 当前版本。~~
- [x] ~~未审核规则不能上线。~~
- [x] ~~AgentRun 能保存一条运行记录。~~
- [x] ~~AuditLog 能保存一条写操作记录。~~
## 4. 建立 AgentAssetReview 模型
## Day 2 联调入口
- [ ] 定义 `AgentAssetReview`
- [ ] 增加字段 `id`
- [ ] 增加字段 `asset_id`
- [ ] 增加字段 `version`
- [ ] 增加字段 `reviewer`
- [ ] 增加字段 `review_status`
- [ ] 增加字段 `review_note`
- [ ] 增加字段 `reviewed_at`
- [ ] 增加字段 `created_at`
- [ ] 建立资产、版本、审核之间的查询关系。
- [ ] 增加服务层校验:没有 `approved` 审核时不能把规则置为 `active`
- `GET /api/v1/agent-assets`
- `GET /api/v1/agent-assets/{asset_id}`
- `GET /api/v1/agent-assets/{asset_id}/versions?limit=5`
- `POST /api/v1/agent-assets/{asset_id}/reviews`
- `POST /api/v1/agent-assets/{asset_id}/activate`
- `GET /api/v1/audit-logs`
验收证据:
## 今天不做
- [ ] `pending` 规则上线会被拒绝
- [ ] `rejected` 规则上线会被拒绝
- [ ] `approved` 规则可以上线
## 5. 建立 AgentRun 模型
- [ ] 定义 `AgentRun`
- [ ] 增加字段 `id`
- [ ] 增加字段 `run_id`
- [ ] 增加字段 `agent`
- [ ] 增加字段 `source`
- [ ] 增加字段 `user_id`
- [ ] 增加字段 `task_id`
- [ ] 增加字段 `ontology_json`
- [ ] 增加字段 `route_json`
- [ ] 增加字段 `permission_level`
- [ ] 增加字段 `status`,取值 `running | succeeded | failed | blocked`
- [ ] 增加字段 `result_summary`
- [ ] 增加字段 `error_message`
- [ ] 增加字段 `started_at`
- [ ] 增加字段 `finished_at`
- [ ]`run_id` 增加唯一约束。
- [ ]`agent``status``started_at` 增加索引。
验收证据:
- [ ] 创建任意 Agent 执行记录时必须生成 `run_id`
- [ ] 失败执行能保存错误信息。
## 6. 建立 AgentToolCall 模型
- [ ] 定义 `AgentToolCall`
- [ ] 增加字段 `id`
- [ ] 增加字段 `run_id`
- [ ] 增加字段 `tool_type`,例如 `mcp | database | llm | ocr | rule_engine`
- [ ] 增加字段 `tool_name`
- [ ] 增加字段 `request_json`
- [ ] 增加字段 `response_json`
- [ ] 增加字段 `status`
- [ ] 增加字段 `duration_ms`
- [ ] 增加字段 `error_message`
- [ ] 增加字段 `created_at`
- [ ] 建立 `AgentRun``AgentToolCall` 的关系。
验收证据:
- [ ] 一个 `run_id` 下可以记录多个工具调用。
- [ ] 工具调用失败时不影响主运行日志保存。
## 7. 建立 SemanticParseLog 模型
- [ ] 定义 `SemanticParseLog`
- [ ] 增加字段 `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`
验收证据:
- [ ] 能保存一条完整 8 字段语义解析日志。
- [ ] 能按 `run_id` 查询语义解析结果。
## 8. 建立 AuditLog 模型
- [ ] 定义 `AuditLog`
- [ ] 增加字段 `id`
- [ ] 增加字段 `actor`
- [ ] 增加字段 `action`
- [ ] 增加字段 `resource_type`
- [ ] 增加字段 `resource_id`
- [ ] 增加字段 `before_json`
- [ ] 增加字段 `after_json`
- [ ] 增加字段 `request_id`
- [ ] 增加字段 `created_at`
- [ ] 为规则保存、审核、上线、任务执行创建审计记录接口。
验收证据:
- [ ] 保存规则 Markdown 时有审计日志。
- [ ] 审核规则时有审计日志。
- [ ] 修改任务状态时有审计日志。
## 9. 建立 Schema / DTO
- [ ] 定义 `AgentAssetCreate`
- [ ] 定义 `AgentAssetUpdate`
- [ ] 定义 `AgentAssetRead`
- [ ] 定义 `AgentAssetListItem`
- [ ] 定义 `AgentAssetVersionRead`
- [ ] 定义 `AgentAssetReviewRead`
- [ ] 定义 `RuleMarkdownUpdate`
- [ ] 定义 `AgentRunRead`
- [ ] 定义 `AgentToolCallRead`
- [ ] 定义 `SemanticParseRead`
- [ ] 所有 JSON 字段在 DTO 中保持结构化,不返回字符串化 JSON。
验收证据:
- [ ] OpenAPI 文档能展示新增 Schema。
- [ ] 列表 DTO 不返回大块 Markdown 内容。
- [ ] 详情 DTO 返回当前版本内容。
## 10. 建立 API 骨架
- [ ] 新增 `GET /api/agent-assets`
- [ ] 新增 `GET /api/agent-assets/{asset_id}`
- [ ] 新增 `POST /api/agent-assets`
- [ ] 新增 `PATCH /api/agent-assets/{asset_id}`
- [ ] 新增 `GET /api/agent-assets/{asset_id}/versions`
- [ ] 新增 `POST /api/agent-assets/{asset_id}/versions`
- [ ] 新增 `POST /api/agent-assets/{asset_id}/reviews`
- [ ] 新增 `POST /api/agent-assets/{asset_id}/activate`
- [ ] 新增 `GET /api/agent-runs`
- [ ] 新增 `GET /api/agent-runs/{run_id}`
- [ ] 新增 `GET /api/audit-logs`
- [ ] 所有接口先返回真实数据库结果,不使用前端硬编码数据作为最终结果。
验收证据:
- [ ] 能调用资产列表接口。
- [ ] 能调用资产详情接口。
- [ ] 能调用版本接口。
- [ ] 能调用运行日志接口。
## 11. 建立服务层
- [ ] 新增资产查询服务。
- [ ] 新增资产保存服务。
- [ ] 新增版本创建服务。
- [ ] 新增审核服务。
- [ ] 新增上线校验服务。
- [ ] 新增 Agent Run 创建服务。
- [ ] 新增 Tool Call 记录服务。
- [ ] 新增审计日志服务。
- [ ] 所有服务函数返回明确错误,不直接把数据库异常暴露给前端。
验收证据:
- [ ] API 路由中不堆业务判断。
- [ ] 上线校验逻辑在服务层。
- [ ] 审计日志通过统一服务写入。
## 12. 建立种子数据
- [ ] 创建至少 3 条规则资产。
- [ ] 每条规则资产至少有 2 个版本。
- [ ] 至少 1 条规则为 `active`
- [ ] 至少 1 条规则为 `review`
- [ ] 至少 1 条规则为 `draft`
- [ ] 创建至少 2 条技能资产。
- [ ] 创建至少 2 条 MCP 资产。
- [ ] 创建至少 3 条任务资产。
- [ ] 为 active 规则创建 approved 审核记录。
- [ ] 为 review 规则创建 pending 审核记录。
验收证据:
- [ ] 资产列表按类型筛选时四类都有数据。
- [ ] 规则详情能看到版本和审核者。
## 13. 最小测试
- [ ] 编写资产模型创建测试。
- [ ] 编写版本唯一约束测试。
- [ ] 编写未审核不能上线测试。
- [ ] 编写资产列表接口测试。
- [ ] 编写资产详情接口测试。
- [ ] 编写 AgentRun 创建测试。
- [ ] 编写 AuditLog 写入测试。
验收证据:
- [ ] 后端核心测试通过。
- [ ] 测试失败时能定位到具体服务。
## 14. Day 1 验收
- [ ] 数据库能创建所有新增表或等价结构。
- [ ] API 服务能启动。
- [ ] OpenAPI 能看到新增接口。
- [ ] 资产列表接口返回规则、技能、MCP、任务。
- [ ] 规则资产有 Markdown 当前版本。
- [ ] 规则资产有最近版本列表。
- [ ] 未审核规则不能上线。
- [ ] AgentRun 能保存一条运行记录。
- [ ] AuditLog 能保存一条写操作记录。
- [ ] 所有完成项已用 `[x] ~~...~~` 标记。
## 阻塞记录
- [ ] 暂无。
## 日终交接
- [ ] 写明已完成模型。
- [ ] 写明已完成 API。
- [ ] 写明未完成问题。
- [ ] 写明 Day 2 前端联调需要使用的接口地址。
- 不做完整 Agent 对话
- 不做完整 Hermes 调度
- 不做真实 OCR
- 不做复杂规则推理。

View File

@@ -1,220 +1,49 @@
# Day 2任务规则中心联调 TODO
# Day 2任务规则中心联调
目标:把任务规则中心从静态 UI 改成可对接后端的生产形态覆盖规则、技能、MCP、任务四类资产。重点是规则 Markdown 编辑、版本切换、审核者信息、上线约束。
## 今天的大开发点
参考文档:
把任务规则中心从静态页面改成可和后端资产体系联动的生产形态。
- `document/development/agent plan/07_capability_registry.md`
- `document/development/agent plan/13_rule_formation_lifecycle.md`
- `document/development/agent plan/06_data_contracts_and_governance.md`
重点是规则、技能、MCP、任务四类资产的列表和详情以及规则 Markdown、版本、审核、上线约束。
## 0. 开始前检查
## 为什么第二天做这个
- [ ] 确认 Day 1 API 已可访问
- [ ] 确认前端任务规则中心文件位置。
- [ ] 确认现有路由名称和导航名称。
- [ ] 确认现有 UI 风格,不重新做大改版。
- [ ] 确认当前页面已有页签规则、技能、MCP、任务。
- [ ] 确认详情页隐藏顶部 title bar 的逻辑仍然有效。
- [ ] 确认返回列表栏高度没有被重新拉高。
任务规则中心是业务人员管理 Agent 能力的入口。后续语义本体、Orchestrator、User Agent、Hermes 都要读取这里注册的规则、技能、MCP 和任务
## 1. API Client
## 今天主要交付
- [ ] 新增或扩展资产列表请求函数
- [ ] 新增资产详情请求函数
- [ ] 新增版本列表请求函数
- [ ] 新增规则 Markdown 保存请求函数
- [ ] 新增审核请求函数
- [ ] 新增上线请求函数
- [ ] 新增运行日志请求函数
- [ ] 给所有请求增加加载态
- [ ] 给所有请求增加错误态
- [ ] 给所有写请求增加成功提示。
- 规则、技能、MCP、任务四个页签对接资产 API
- 列表支持搜索、筛选、状态展示
- 规则详情展示 Markdown 内容
- 管理员可编辑规则 Markdown。
- 规则版本展示最近 5 个版本
- 版本切换需要弹窗确认
- 审核者信息放在标题区域
- 右侧只保留版本信息
- 未审核规则上线时被后端拦截
验收证据:
## 对应执行细则
- [ ] 前端不再只依赖本地硬编码资产数据。
- [ ] 后端不可用时页面有明确错误提示。
- [Day 2 执行细则](<../agent plan/weekly_execution_details/day_2_rule_center_integration.md>)
## 2. 列表页数据接入
相关架构文档:
- [ ] 规则页签请求 `asset_type=rule`
- [ ] 技能页签请求 `asset_type=skill`
- [ ] MCP 页签请求 `asset_type=mcp`
- [ ] 任务页签请求 `asset_type=task`
- [ ] 搜索框传递关键词或本地过滤。
- [ ] 类型下拉和搜索框可以同时生效。
- [ ] 状态筛选可以过滤 `draft | review | active | disabled`
- [ ] 列表卡片展示名称。
- [ ] 列表卡片展示摘要。
- [ ] 列表卡片展示状态。
- [ ] 列表卡片展示负责人。
- [ ] 列表卡片展示最近更新时间。
- [ ] 空数据时展示空态。
- [ ] 加载中时展示骨架或加载状态。
- [能力注册](<../agent plan/07_capability_registry.md>)
- [规则形成生命周期](<../agent plan/13_rule_formation_lifecycle.md>)
- [数据契约与治理](<../agent plan/06_data_contracts_and_governance.md>)
验收证据:
## 当天验收门槛
- [ ] 四个页签都能切换
- [ ] 四个页签都有数据或空态
- [ ] 搜索和筛选不会互相覆盖
- 四个页签可切换并有真实 API 或 Mock API 数据
- 规则详情可编辑 Markdown
- Markdown 保存后刷新不丢失
- 版本卡片可切换版本。
- 未审核规则不能上线。
- 前端构建通过。
## 3. 规则详情页主信息
## 今天不做
- [ ] 打开规则资产时请求详情 API
- [ ] Hero title 展示规则名称
- [ ] Hero title 下方展示审核者
- [ ] Hero title 下方展示审核状态。
- [ ] Hero title 下方展示上线条件。
- [ ] Hero title 高度保持紧凑。
- [ ] 详情页不显示外层顶部 title bar。
- [ ] 返回列表栏高度保持原有紧凑高度。
验收证据:
- [ ] 用户能一眼看到该规则是否已审核。
- [ ] 用户不会看到两层 title。
## 4. Markdown 编辑器
- [ ] 从当前版本读取 Markdown 内容。
- [ ] Markdown 编辑框高度和右侧版本卡片底部对齐。
- [ ] Markdown 编辑框支持长内容滚动。
- [ ] Markdown 编辑框保存时调用 API。
- [ ] 保存后创建新版本或更新草稿版本,按后端约定执行。
- [ ] 保存成功后刷新版本列表。
- [ ] 保存失败时保留用户输入。
- [ ] 编辑器禁用态覆盖 `active` 且无编辑权限的情况。
- [ ] 编辑器底部展示最后保存时间。
验收证据:
- [ ] 编辑 Markdown 后刷新页面内容仍存在。
- [ ] 保存失败不会丢内容。
- [ ] 左右卡片底部视觉对齐。
## 5. 版本卡片
- [ ] 右侧只保留版本信息卡片。
- [ ] 版本卡片宽度足够展示版本号、日期、状态。
- [ ] 展示最近 5 个版本。
- [ ] 当前版本有明显但不突兀的标识。
- [ ] 当前版本标识居中显示。
- [ ] 选中状态只变色,不改变内容对齐。
- [ ] 日期列和其他版本日期对齐。
- [ ] 点击非当前版本时弹出确认弹窗。
- [ ] 弹窗展示目标版本号。
- [ ] 弹窗展示切换风险提示。
- [ ] 确认后切换当前展示内容。
- [ ] 取消后不改变当前版本。
验收证据:
- [ ] 版本切换不会造成列表文字位移。
- [ ] 当前版本背景能完全覆盖内容区域。
- [ ] 版本卡片不贴右侧边界。
## 6. 审核与上线
- [ ] 详情中展示审核者姓名。
- [ ] 详情中展示审核时间。
- [ ] 详情中展示审核意见。
- [ ] 未审核规则显示不能上线原因。
- [ ] 点击上线时调用后端上线接口。
- [ ] 后端拒绝时展示拒绝原因。
- [ ] 审核通过后上线按钮可用。
- [ ] 审核动作写入审计日志。
- [ ] 上线动作写入审计日志。
验收证据:
- [ ] pending 规则无法上线。
- [ ] approved 规则可以上线。
- [ ] rejected 规则无法上线。
## 7. 技能详情
- [ ] 技能页签列表展示能力名称。
- [ ] 技能详情展示能力说明。
- [ ] 技能详情展示输入参数。
- [ ] 技能详情展示输出参数。
- [ ] 技能详情展示依赖能力。
- [ ] 技能详情展示适用场景。
- [ ] 技能详情展示负责人。
- [ ] 技能详情展示版本。
- [ ] 技能详情不使用规则 Markdown 编辑器。
验收证据:
- [ ] 技能和规则详情不会混用 UI。
## 8. MCP 详情
- [ ] MCP 页签列表展示外部服务名称。
- [ ] MCP 详情展示服务类型。
- [ ] MCP 详情展示调用地址或能力名。
- [ ] MCP 详情展示鉴权方式。
- [ ] MCP 详情展示超时配置。
- [ ] MCP 详情展示降级策略。
- [ ] MCP 详情展示最近调用状态。
- [ ] MCP 详情展示负责人。
验收证据:
- [ ] MCP 被定义为外部服务,而不是技能规则。
## 9. 任务详情
- [ ] 任务页签展示定时任务名称。
- [ ] 任务详情展示 cron 或调度周期。
- [ ] 任务详情展示执行 Agent默认 Hermes。
- [ ] 任务详情展示任务目标。
- [ ] 任务详情展示风险等级。
- [ ] 任务详情展示最近执行时间。
- [ ] 任务详情展示最近执行结果。
- [ ] 任务详情展示启停状态。
验收证据:
- [ ] 定时任务用户可见名称为“任务”。
- [ ] 技术字段可保留 `schedule`,但 UI 不显示“定时任务”。
## 10. 前端质量
- [ ] 页面在 1366 宽度下无横向滚动。
- [ ] 页面在 1920 宽度下右侧卡片不过宽。
- [ ] 页面在窄屏下详情区域可滚动。
- [ ] 所有按钮有禁用态。
- [ ] 所有弹窗有取消按钮。
- [ ] 所有表单错误有提示。
- [ ] 所有日期格式统一。
- [ ] 状态颜色和现有系统一致。
验收证据:
- [ ] `npm run build` 通过。
- [ ] 任务规则中心手动走查通过。
## 11. Day 2 验收
- [ ] 规则、技能、MCP、任务四个页签可用。
- [ ] 搜索框和筛选下拉可用。
- [ ] 规则详情展示 Markdown。
- [ ] 规则 Markdown 可保存。
- [ ] 右侧只保留版本信息。
- [ ] 版本可切换且有弹窗确认。
- [ ] 审核者信息在标题下方。
- [ ] 未审核规则不能上线。
- [ ] 前端构建通过。
- [ ] 所有完成项已用 `[x] ~~...~~` 标记。
## 阻塞记录
- [ ] 暂无。
## 日终交接
- [ ] 写明已接入的 API。
- [ ] 写明仍然使用 Mock 的字段。
- [ ] 写明 UI 未完成项。
- [ ] 写明 Day 3 语义本体需要复用的资产数据。
- 不做规则自动生成
- 不做完整 MCP 真实调用
- 不做复杂权限矩阵
- 不重做 UI 风格,只在现有风格上微调。

View File

@@ -1,236 +1,47 @@
# Day 3语义本体 MVP TODO
# Day 3语义本体 MVP
目标:建立用户问题的语义解析层,输出稳定的 8 个核心字段,让 User Agent、Hermes 和 Orchestrator 都能使用同一套语义结构。
## 今天的大开发点
参考文档:
建立用户问题的语义解析层,把自然语言问题转换成统一的 8 个核心字段。
- `document/development/agent plan/02_semantic_ontology.md`
- `document/development/agent plan/14_financial_document_canonical_model.md`
- `document/development/agent plan/06_data_contracts_and_governance.md`
这一天的目标不是做到 LLM 全能理解,而是先让报销、应收、应付、知识和风险相关问题能进入稳定结构。
## 0. 开始前检查
## 为什么第三天做这个
- [ ] 确认 Day 1 的 `SemanticParseLog` 可用
- [ ] 确认 Day 1 的 `AgentRun` 可用。
- [ ] 确认 Day 2 的资产 API 可用。
- [ ] 找到后端服务层目录。
- [ ] 找到现有 LLM 调用或 Mock 调用方式。
- [ ] 确认当前是否允许真实调用 LLM。
- [ ] 如果不能调用真实 LLM准备规则解析加 Mock 解析。
Orchestrator 不能直接根据原始文本做可靠路由。它需要先拿到结构化语义,再决定调用 User Agent、Hermes、规则、MCP 或知识库
## 1. 定义 8 个核心字段
## 今天主要交付
- [ ] 定义字段 `scenario`,表示业务场景
- [ ] 定义字段 `intent`,表示用户意图
- [ ] 定义字段 `entities`,表示业务对象
- [ ] 定义字段 `time_range`,表示时间范围
- [ ] 定义字段 `metrics`,表示指标或金额口径
- [ ] 定义字段 `constraints`,表示过滤条件
- [ ] 定义字段 `risk_flags`,表示风险信号
- [ ] 定义字段 `permission`,表示动作权限
- [ ] 为每个字段写清楚类型
- [ ] 为每个字段写清楚是否必填。
- [ ] 为每个字段写清楚默认值。
- [ ] 为每个字段写清楚示例。
- 语义本体 8 字段结构
- 场景识别:报销、应收、应付、知识、未知
- 意图识别:查询、解释、对比、风险检查、草稿、操作
- 业务对象提取:员工、客户、供应商、部门、项目、单据、金额
- 时间范围解析
- 指标和约束解析
- 风险信号和权限级别判断
- 语义解析 API
- 解析日志和最小评测集
验收证据:
## 对应执行细则
- [ ] 8 个字段在 Schema、服务层、日志中名字一致。
- [Day 3 执行细则](<../agent plan/weekly_execution_details/day_3_semantic_ontology_mvp.md>)
## 2. 设计字段枚举
相关架构文档:
- [ ] `scenario` 支持 `expense`
- [ ] `scenario` 支持 `accounts_receivable`
- [ ] `scenario` 支持 `accounts_payable`
- [ ] `scenario` 支持 `knowledge`
- [ ] `scenario` 支持 `unknown`
- [ ] `intent` 支持 `query`
- [ ] `intent` 支持 `explain`
- [ ] `intent` 支持 `compare`
- [ ] `intent` 支持 `risk_check`
- [ ] `intent` 支持 `draft`
- [ ] `intent` 支持 `operate`
- [ ] `permission.level` 支持 `read`
- [ ] `permission.level` 支持 `draft_write`
- [ ] `permission.level` 支持 `approval_required`
- [ ] `permission.level` 支持 `forbidden`
- [语义本体](<../agent plan/02_semantic_ontology.md>)
- [财务单据标准模型](<../agent plan/14_financial_document_canonical_model.md>)
- [数据契约与治理](<../agent plan/06_data_contracts_and_governance.md>)
验收证据:
## 当天验收门槛
- [ ] 未识别的问题不会抛异常,返回 `unknown`
- 输入自然语言问题能返回 8 个字段
- 低置信度问题能返回澄清问题。
- 越权动作不会被标记为可直接执行。
- 解析结果能写入日志。
- 至少覆盖报销、应收、应付三个场景。
## 3. 建立 Schema
## 今天不做
- [ ] 定义 `OntologyParseRequest`
- [ ] `OntologyParseRequest` 包含 `query`
- [ ] `OntologyParseRequest` 包含 `user_id`
- [ ] `OntologyParseRequest` 包含 `context_json`
- [ ] 定义 `OntologyParseResult`
- [ ] `OntologyParseResult` 包含 8 个核心字段。
- [ ] `OntologyParseResult` 包含 `confidence`
- [ ] `OntologyParseResult` 包含 `clarification_required`
- [ ] `OntologyParseResult` 包含 `clarification_question`
- [ ] `OntologyParseResult` 包含 `run_id`
- [ ] 定义字段级错误结构。
验收证据:
- [ ] OpenAPI 中可以看到语义解析请求和响应。
## 4. 实现解析服务
- [ ] 新增 `SemanticOntologyService` 或同等服务。
- [ ] 实现 `parse(query, user_context)` 主函数。
- [ ] 先做关键词规则解析。
- [ ] 报销关键词映射到 `expense`
- [ ] 应收、回款、客户欠款映射到 `accounts_receivable`
- [ ] 应付、供应商、付款映射到 `accounts_payable`
- [ ] 风险、异常、重复、超标映射到 `risk_check`
- [ ] 为什么、依据、规则映射到 `explain`
- [ ] 统计、汇总、多少映射到 `query`
- [ ] 生成、创建、发起映射到 `draft``operate`
- [ ] 无法识别时返回低置信度和澄清问题。
验收证据:
- [ ] “查一下本周报销超标风险”能识别为 expense + risk_check。
- [ ] “客户 A 这个月还有多少应收”能识别为 accounts_receivable + query。
- [ ] “供应商 B 明天要付多少钱”能识别为 accounts_payable + query。
## 5. 解析业务对象
- [ ] 从问题中提取员工姓名。
- [ ] 从问题中提取部门。
- [ ] 从问题中提取客户。
- [ ] 从问题中提取供应商。
- [ ] 从问题中提取项目。
- [ ] 从问题中提取单据号。
- [ ] 从问题中提取金额。
- [ ] 从问题中提取费用类型。
- [ ] 无法提取时返回空数组,不返回 null。
验收证据:
- [ ] “张三 4 月差旅报销”能提取员工、月份、费用类型。
## 6. 解析时间范围
- [ ] 支持今天。
- [ ] 支持昨天。
- [ ] 支持本周。
- [ ] 支持上周。
- [ ] 支持本月。
- [ ] 支持上月。
- [ ] 支持本季度。
- [ ] 支持今年。
- [ ] 支持明确日期。
- [ ] 支持日期区间。
- [ ] 解析结果包含 `start_date``end_date`
- [ ] 日期使用 ISO 格式。
验收证据:
- [ ] “本周”能解析为当前周起止日期。
- [ ] “2026 年 4 月”能解析为 `2026-04-01``2026-04-30`
## 7. 解析指标与约束
- [ ] 识别金额指标。
- [ ] 识别数量指标。
- [ ] 识别超标指标。
- [ ] 识别逾期指标。
- [ ] 识别重复报销指标。
- [ ] 识别部门过滤条件。
- [ ] 识别状态过滤条件。
- [ ] 识别金额阈值过滤条件。
- [ ] 识别排序要求。
- [ ] 识别 Top N 要求。
验收证据:
- [ ] “列出金额最高的 10 笔报销”能识别排序和 Top 10。
## 8. 解析风险与权限
- [ ] 重复报销映射到 `duplicate_expense`
- [ ] 发票异常映射到 `invoice_anomaly`
- [ ] 金额超标映射到 `amount_over_limit`
- [ ] 逾期应收映射到 `ar_overdue`
- [ ] 逾期应付映射到 `ap_overdue`
- [ ] 查询类问题权限为 `read`
- [ ] 生成草稿权限为 `draft_write`
- [ ] 审批、上线、付款类动作权限为 `approval_required`
- [ ] 越权动作权限为 `forbidden`
验收证据:
- [ ] “帮我直接付款”不能被标为可直接执行。
## 9. API 接口
- [ ] 新增 `POST /api/ontology/parse`
- [ ] 请求参数包含用户问题。
- [ ] 请求参数包含用户上下文。
- [ ] 响应包含 8 个字段。
- [ ] 响应包含 `run_id`
- [ ] 响应包含置信度。
- [ ] 响应包含澄清问题。
- [ ] 每次调用写入 `SemanticParseLog`
- [ ] 每次调用写入 `AgentRun` 或关联已有 `AgentRun`
验收证据:
- [ ] 连续调用 5 次都能在日志中查到。
## 10. 前端调试入口
- [ ] 在合适页面增加语义解析调试入口。
- [ ] 输入框支持自然语言问题。
- [ ] 点击解析后调用 API。
- [ ] 展示 8 个字段。
- [ ] 展示 JSON 原始结果。
- [ ] 展示置信度。
- [ ] 展示澄清问题。
- [ ] 展示 `run_id`
- [ ] 错误时展示错误信息。
验收证据:
- [ ] 产品和开发可以直接在页面验证解析结果。
## 11. 评测集
- [ ] 创建至少 5 条报销问题。
- [ ] 创建至少 5 条应收问题。
- [ ] 创建至少 5 条应付问题。
- [ ] 创建至少 3 条知识库问题。
- [ ] 创建至少 3 条越权操作问题。
- [ ] 为每条问题写期望 `scenario`
- [ ] 为每条问题写期望 `intent`
- [ ] 为每条问题写期望权限级别。
- [ ] 编写评测脚本或测试。
验收证据:
- [ ] 核心场景识别准确率达到当天设定阈值,例如 80%。
## 12. Day 3 验收
- [ ] 语义解析 API 可用。
- [ ] 8 个核心字段完整返回。
- [ ] 解析日志可查询。
- [ ] 低置信度问题有澄清问题。
- [ ] 越权动作不会被标为可执行。
- [ ] 前端调试入口可用。
- [ ] 评测集可运行。
- [ ] 所有完成项已用 `[x] ~~...~~` 标记。
## 阻塞记录
- [ ] 暂无。
## 日终交接
- [ ] 写明已支持的关键词。
- [ ] 写明识别不准的样例。
- [ ] 写明 Day 4 Orchestrator 可以直接复用的响应结构。
- 不做复杂多轮对话记忆
- 不做完整 LLM 提示词优化
- 不做自动执行业务流程。

View File

@@ -1,183 +1,47 @@
# Day 4Orchestrator 运行时 TODO
# Day 4Orchestrator 运行时
目标:建立统一调度层,让用户请求和系统任务都先进入 Orchestrator再根据语义本体、权限、能力注册路由到 User Agent、Hermes、MCP 或规则引擎。
## 今天的大开发点
参考文档:
建立统一调度层。用户请求和系统任务都先进入 Orchestrator由它完成语义解析、权限判断、能力选择、Agent 路由、工具调用记录和失败降级。
- `document/development/agent plan/04_orchestrator_and_runtime_flow.md`
- `document/development/agent plan/07_capability_registry.md`
- `document/development/agent plan/08_permission_confirmation.md`
- `document/development/agent plan/09_observability_and_trace.md`
## 为什么第四天做这个
## 0. 开始前检查
没有 OrchestratorUser Agent 和 Hermes 会各自直接调用能力权限、审计、降级、Trace 都会分散。生产系统必须有统一入口。
- [ ] 确认 Day 3 `POST /api/ontology/parse` 可用。
- [ ] 确认 `AgentRun` 可创建。
- [ ] 确认 `AgentToolCall` 可创建。
- [ ] 确认资产列表能查询技能、MCP、任务。
- [ ] 确认权限级别枚举已稳定。
- [ ] 找到后端服务层适合放 Orchestrator 的位置。
## 今天主要交付
## 1. Orchestrator 输入输出
- Orchestrator 请求和响应结构。
- 用户请求路由到 User Agent。
- 定时任务路由到 Hermes。
- 权限级别判断。
- 高风险动作确认机制。
- 能力注册查询。
- 工具调用封装。
- AgentRun Trace 查询。
- 失败降级返回。
- [ ] 定义 `OrchestratorRequest`
- [ ] 请求包含 `source`
- [ ] 请求包含 `user_id`
- [ ] 请求包含 `message`
- [ ] 请求包含 `task_id`
- [ ] 请求包含 `context_json`
- [ ] 定义 `OrchestratorResponse`
- [ ] 响应包含 `run_id`
- [ ] 响应包含 `selected_agent`
- [ ] 响应包含 `route_reason`
- [ ] 响应包含 `permission_level`
- [ ] 响应包含 `status`
- [ ] 响应包含 `result`
- [ ] 响应包含 `requires_confirmation`
- [ ] 响应包含 `trace_summary`
## 对应执行细则
验收证据:
- [Day 4 执行细则](<../agent plan/weekly_execution_details/day_4_orchestrator_runtime.md>)
- [ ] Orchestrator 响应能直接被前端展示。
相关架构文档:
## 2. 建立 Orchestrator 服务
- [Orchestrator 与运行流程](<../agent plan/04_orchestrator_and_runtime_flow.md>)
- [能力注册](<../agent plan/07_capability_registry.md>)
- [权限与确认](<../agent plan/08_permission_confirmation.md>)
- [观测与 Trace](<../agent plan/09_observability_and_trace.md>)
- [ ] 新增 `OrchestratorService`
- [ ] 实现 `run(request)` 主入口。
- [ ] 主入口第一步创建 `AgentRun`
- [ ] 主入口第二步调用语义解析。
- [ ] 主入口第三步执行权限判断。
- [ ] 主入口第四步选择 Agent。
- [ ] 主入口第五步调用目标 Agent 或返回阻断结果。
- [ ] 主入口第六步更新 `AgentRun` 状态。
- [ ] 所有异常都写入 `AgentRun.error_message`
## 当天验收门槛
验收证据:
- Orchestrator API 可用。
- 用户消息能路由到 User Agent 占位实现。
- 定时任务能路由到 Hermes 占位实现。
- forbidden 请求不会调用下游 Agent。
- 每次运行都有 `run_id` 和 Trace。
- 工具调用失败能记录并返回降级结果。
- [ ] 正常请求状态为 `succeeded`
- [ ] 被权限拦截请求状态为 `blocked`
- [ ] 异常请求状态为 `failed`
## 今天不做
## 3. 路由规则
- [ ] `source=user_message` 默认路由到 User Agent。
- [ ] `source=schedule` 默认路由到 Hermes。
- [ ] `intent=risk_check` 且来源为 schedule 时路由到 Hermes。
- [ ] `intent=query` 且来源为 user_message 时路由到 User Agent。
- [ ] `intent=explain` 路由到 User Agent。
- [ ] `intent=draft` 路由到 User Agent但只允许生成草稿。
- [ ] `permission.level=approval_required` 时设置 `requires_confirmation=true`
- [ ] `permission.level=forbidden` 时不调用下游 Agent。
- [ ] 无法识别时返回澄清问题。
验收证据:
- [ ] 同一句风险检查,在用户入口和任务入口有不同路由结果。
## 4. 权限判断
- [ ] 新增权限判断服务或函数。
- [ ] 查询类请求返回 `read`
- [ ] 草稿类请求返回 `draft_write`
- [ ] 审批、上线、付款类请求返回 `approval_required`
- [ ] 用户无权限时返回 `forbidden`
- [ ] 高风险动作不允许自动执行。
- [ ] 需要确认的动作返回确认提示。
- [ ] 权限判断结果写入 `AgentRun.permission_level`
验收证据:
- [ ] “直接上线规则”不会被自动执行。
- [ ] “直接付款”不会被自动执行。
## 5. 能力注册查询
- [ ]`AgentAsset` 查询 active 技能。
- [ ]`AgentAsset` 查询 active MCP。
- [ ]`AgentAsset` 查询 active 任务。
- [ ] 过滤 disabled 能力。
- [ ] 过滤未审核 active 条件不满足的规则。
- [ ] 为每次能力选择记录 `route_json`
- [ ] 找不到能力时返回降级说明。
验收证据:
- [ ] 禁用 MCP 不会被 Orchestrator 调用。
## 6. 工具调用封装
- [ ] 定义统一工具调用接口。
- [ ] 工具请求前写入 `AgentToolCall` running 或准备记录。
- [ ] 工具成功后写入响应和耗时。
- [ ] 工具失败后写入错误。
- [ ] 外部 MCP 调用失败时返回降级结果。
- [ ] 数据库查询失败时返回明确错误。
- [ ] LLM 调用失败时返回可读提示。
验收证据:
- [ ] 每次 Orchestrator 运行至少可以看到 0 到多条工具调用记录。
## 7. API 接口
- [ ] 新增 `POST /api/orchestrator/run`
- [ ] 请求支持用户消息。
- [ ] 请求支持任务触发。
- [ ] 响应返回 `run_id`
- [ ] 响应返回路由结果。
- [ ] 响应返回权限结果。
- [ ] 新增 `GET /api/orchestrator/runs/{run_id}/trace` 或复用 AgentRun 详情接口。
- [ ] Trace 接口返回语义解析、路由、工具调用、最终结果。
验收证据:
- [ ] 前端或 curl 可以完整看到一次运行链路。
## 8. 前端最小 Trace 查看
- [ ] 在合适位置展示最近运行记录。
- [ ] 点击运行记录能查看 `run_id`
- [ ] 展示 selected_agent。
- [ ] 展示 route_reason。
- [ ] 展示 permission_level。
- [ ] 展示工具调用列表。
- [ ] 展示错误信息。
- [ ] 展示耗时。
验收证据:
- [ ] 开发调试时不需要直接查数据库才能理解路由结果。
## 9. 测试
- [ ] 测试用户查询路由到 User Agent。
- [ ] 测试定时任务路由到 Hermes。
- [ ] 测试 forbidden 不调用下游 Agent。
- [ ] 测试 approval_required 返回确认。
- [ ] 测试工具失败写入 ToolCall。
- [ ] 测试 Orchestrator 异常写入 AgentRun。
验收证据:
- [ ] Orchestrator 核心测试通过。
## 10. Day 4 验收
- [ ] Orchestrator API 可用。
- [ ] 用户请求能路由到 User Agent 占位实现。
- [ ] 定时任务能路由到 Hermes 占位实现。
- [ ] 权限阻断有效。
- [ ] 运行 Trace 可查询。
- [ ] 工具调用日志可查询。
- [ ] 降级结果可读。
- [ ] 所有完成项已用 `[x] ~~...~~` 标记。
## 阻塞记录
- [ ] 暂无。
## 日终交接
- [ ] 写明路由规则现状。
- [ ] 写明权限判断现状。
- [ ] 写明 Day 5 User Agent 需要实现的接口契约。
- 不做复杂任务编排 DAG。
- 不做多 Agent 协商。
- 不做自动高风险动作。

View File

@@ -1,183 +1,50 @@
# Day 5User Agent MVP TODO
# Day 5User Agent MVP
目标:实现面向用户的自建 Agent。它负责用户提问、流程辅助、规则解释、查询结果解释和草稿生成不做自动审批、自动付款、自动上线等高风险动作。
## 今天的大开发点
参考文档:
实现面向用户的自建 Agent。它负责用户提问、流程辅助、规则解释、查询结果解释和草稿生成。
- `document/development/agent plan/03_agent_responsibilities.md`
- `document/development/agent plan/04_orchestrator_and_runtime_flow.md`
- `document/development/agent plan/12_llm_wiki_knowledge_architecture.md`
- `document/development/agent plan/13_rule_formation_lifecycle.md`
User Agent 只能处理用户侧交互,不负责后台定时内循环,也不能自动执行高风险动作。
## 0. 开始前检查
## 为什么第五天做这个
- [ ] 确认 Orchestrator 能把用户请求路由到 User Agent
- [ ] 确认语义本体 8 字段可用。
- [ ] 确认规则资产可查询。
- [ ] 确认 AgentRun 和 ToolCall 可记录。
- [ ] 确认是否有现成对话 UI。
- [ ] 确认财务业务数据是否真实可查。
- [ ] 如果业务数据不可查,准备最小 Mock 数据服务。
Day 1 到 Day 4 已经具备资产、语义、路由和日志基础,此时可以把用户自然语言入口接到真实流程上
## 1. User Agent 输入输出
## 今天主要交付
- [ ] 定义 `UserAgentRequest`
- [ ] 请求包含 `run_id`
- [ ] 请求包含 `user_id`
- [ ] 请求包含 `message`
- [ ] 请求包含 `ontology`
- [ ] 请求包含 `context_json`
- [ ] 定义 `UserAgentResponse`
- [ ] 响应包含 `answer`
- [ ] 响应包含 `citations`
- [ ] 响应包含 `suggested_actions`
- [ ] 响应包含 `draft_payload`
- [ ] 响应包含 `risk_flags`
- [ ] 响应包含 `requires_confirmation`
- 用户自然语言入口
- 报销查询和解释
- 应收查询和解释
- 应付查询和解释
- 规则引用解释
- 风险原因说明
- 处理意见草稿
- 知识库读取骨架
- 前端问答或操作入口
验收证据:
## 对应执行细则
- [ ] User Agent 响应结构能被 Orchestrator 直接包装返回。
- [Day 5 执行细则](<../agent plan/weekly_execution_details/day_5_user_agent_mvp.md>)
## 2. 查询处理
相关架构文档:
- [ ] 实现报销查询处理器。
- [ ] 实现应收查询处理器。
- [ ] 实现应付查询处理器。
- [ ] 查询前检查权限级别。
- [ ] 查询时记录 ToolCall。
- [ ] 查询失败时返回可读错误。
- [ ] 查询为空时返回空态解释。
- [ ] 查询结果限制返回条数,避免一次返回过大。
- [Agent 职责边界](<../agent plan/03_agent_responsibilities.md>)
- [Orchestrator 与运行流程](<../agent plan/04_orchestrator_and_runtime_flow.md>)
- [LLM Wiki 知识库架构](<../agent plan/12_llm_wiki_knowledge_architecture.md>)
- [规则形成生命周期](<../agent plan/13_rule_formation_lifecycle.md>)
验收证据:
## 当天验收门槛
- [ ] “查本周报销金额”有可读回答
- [ ] “客户 A 本月应收多少”有可读回答
- [ ] “供应商 B 待付款多少”有可读回答。
- 用户能输入自然语言问题
- 请求必须经过 Orchestrator
- 至少 3 类财务问题有可读回答。
- 回答能引用规则或知识。
- 高风险动作只生成草稿或建议。
- AgentRun Trace 能看到 User Agent 步骤。
## 3. 规则解释
## 今天不做
- [ ] 根据语义场景查询相关规则资产
- [ ] 只引用 active 规则
- [ ] 读取规则当前版本 Markdown
- [ ] 从 Markdown 中提取规则摘要。
- [ ] 回答中说明使用了哪些规则。
- [ ] 回答中包含规则版本号。
- [ ] 回答中包含规则更新时间。
- [ ] 没有相关规则时说明缺失。
验收证据:
- [ ] “为什么这笔报销有风险”能引用规则。
## 4. 风险解释
- [ ] 识别重复报销风险。
- [ ] 识别金额超标风险。
- [ ] 识别发票异常风险。
- [ ] 识别逾期应收风险。
- [ ] 识别逾期应付风险。
- [ ] 风险回答包含风险类型。
- [ ] 风险回答包含触发原因。
- [ ] 风险回答包含建议处理动作。
- [ ] 高风险建议不能变成自动执行。
验收证据:
- [ ] 风险解释结果不是单纯“有风险”,而是有依据。
## 5. 草稿生成
- [ ] 支持生成报销处理意见草稿。
- [ ] 支持生成应收催收建议草稿。
- [ ] 支持生成应付付款建议草稿。
- [ ] 草稿中标明“待人工确认”。
- [ ] 草稿不直接提交业务系统。
- [ ] 草稿生成写入审计日志。
- [ ] 草稿生成写入 AgentRun 结果。
验收证据:
- [ ] “帮我生成处理意见”只返回草稿,不执行审批。
## 6. 知识库读取骨架
- [ ] 建立知识条目查询接口或服务。
- [ ] 支持按关键词查询知识条目。
- [ ] 支持按业务场景查询知识条目。
- [ ] User Agent 回答可以引用知识条目。
- [ ] 引用中包含知识标题。
- [ ] 引用中包含更新时间。
- [ ] 知识库不可用时返回降级说明。
验收证据:
- [ ] 知识库失败不会导致整个回答失败。
## 7. 对话或操作入口
- [ ] 前端增加用户问题输入框。
- [ ] 输入框支持回车或按钮提交。
- [ ] 提交时调用 Orchestrator而不是绕过 Orchestrator。
- [ ] 展示 Agent 回答。
- [ ] 展示引用规则或知识。
- [ ] 展示建议动作。
- [ ] 展示需要人工确认的提示。
- [ ] 展示 `run_id`
- [ ] 展示加载态。
- [ ] 展示错误态。
验收证据:
- [ ] 用户可在页面完成一次问答闭环。
## 8. 安全边界
- [ ] User Agent 不直接修改规则状态。
- [ ] User Agent 不直接上线规则。
- [ ] User Agent 不直接审批报销。
- [ ] User Agent 不直接付款。
- [ ] User Agent 不直接删除知识。
- [ ] 所有高风险动作只返回建议或草稿。
- [ ] 所有草稿动作标记 `requires_confirmation=true`
验收证据:
- [ ] 提示词要求“直接付款”时仍被阻断。
## 9. 测试
- [ ] 测试报销查询。
- [ ] 测试应收查询。
- [ ] 测试应付查询。
- [ ] 测试规则解释。
- [ ] 测试风险解释。
- [ ] 测试草稿生成。
- [ ] 测试越权动作阻断。
- [ ] 测试知识库降级。
验收证据:
- [ ] User Agent 核心测试通过。
## 10. Day 5 验收
- [ ] User Agent 服务可被 Orchestrator 调用。
- [ ] 用户入口可提交自然语言问题。
- [ ] 至少 3 个财务场景有回答。
- [ ] 回答能引用规则或知识。
- [ ] 高风险动作不会自动执行。
- [ ] AgentRun Trace 能看到 User Agent 步骤。
- [ ] 前端构建通过。
- [ ] 所有完成项已用 `[x] ~~...~~` 标记。
## 阻塞记录
- [ ] 暂无。
## 日终交接
- [ ] 写明已支持的问题类型。
- [ ] 写明仍使用 Mock 的数据。
- [ ] 写明 Day 6 Hermes 可以复用的规则、风险、知识接口。
- 不做自动审批
- 不做自动付款
- 不做自动上线规则
- 不做完整知识库检索优化。

View File

@@ -1,191 +1,47 @@
# Day 6Hermes MVP TODO
# Day 6Hermes MVP
目标:实现 Hermes 数字员工的最小闭环。Hermes 不面向用户即时对话,而是负责定时巡检、统计、风险预警、知识维护和规则草稿形成。
## 今天的大开发点
参考文档:
实现 Hermes 数字员工的最小闭环。Hermes 负责后台内循环:定时巡检、统计日报、风险预警、知识维护、规则草稿形成。
- `document/development/agent plan/03_agent_responsibilities.md`
- `document/development/agent plan/11_ocr_invoice_architecture.md`
- `document/development/agent plan/12_llm_wiki_knowledge_architecture.md`
- `document/development/agent plan/15_feedback_learning_loop.md`
## 为什么第六天做这个
## 0. 开始前检查
Hermes 依赖前几天已经建立的资产、规则、语义、Orchestrator、Trace 和权限体系。放在第六天做,可以避免它变成孤立脚本。
- [ ] 确认任务资产 `asset_type=task` 可查询。
- [ ] 确认 Orchestrator 能处理 `source=schedule`
- [ ] 确认 Hermes 占位服务可被调用。
- [ ] 确认 AgentRun 和 ToolCall 可记录。
- [ ] 确认是否已有后台任务框架。
- [ ] 如果没有后台任务框架,先用手动触发 API 模拟定时执行。
## 今天主要交付
## 1. Hermes 输入输出
- 任务资产调度入口。
- 手动触发任务 API。
- 每日风险巡检。
- 每日报销、报账、账款统计。
- OCR Mock 接入点。
- 知识候选条目生成。
- 规则草稿生成。
- Hermes 运行结果展示。
- [ ] 定义 `HermesTaskRequest`
- [ ] 请求包含 `run_id`
- [ ] 请求包含 `task_asset_id`
- [ ] 请求包含 `task_type`
- [ ] 请求包含 `schedule_time`
- [ ] 请求包含 `context_json`
- [ ] 定义 `HermesTaskResult`
- [ ] 响应包含 `summary`
- [ ] 响应包含 `risk_items`
- [ ] 响应包含 `statistics`
- [ ] 响应包含 `knowledge_updates`
- [ ] 响应包含 `draft_rules`
- [ ] 响应包含 `next_actions`
## 对应执行细则
验收证据:
- [Day 6 执行细则](<../agent plan/weekly_execution_details/day_6_hermes_mvp.md>)
- [ ] Hermes 响应能被任务详情或运行日志展示。
相关架构文档:
## 2. 任务调度入口
- [Agent 职责边界](<../agent plan/03_agent_responsibilities.md>)
- [OCR 票据识别架构](<../agent plan/11_ocr_invoice_architecture.md>)
- [LLM Wiki 知识库架构](<../agent plan/12_llm_wiki_knowledge_architecture.md>)
- [反馈学习闭环](<../agent plan/15_feedback_learning_loop.md>)
- [ ] 新增手动触发任务 API。
- [ ] API 参数支持任务资产 ID。
- [ ] API 调用 Orchestratorsource 为 `schedule`
- [ ] Orchestrator 路由到 Hermes。
- [ ] Hermes 执行结果写入 AgentRun。
- [ ] 任务执行失败时写入错误。
- [ ] 任务执行结束后更新任务最近执行时间。
- [ ] 任务执行结束后更新任务最近执行状态。
## 当天验收门槛
验收证据:
- 至少一个 Hermes 任务可以手动触发。
- 风险巡检有结构化结果。
- 每日统计有结构化结果。
- OCR Mock 调用能记录 ToolCall。
- 知识候选只能是草稿。
- 规则草稿只能是 draft不能自动上线。
- [ ] 可以手动触发一次 Hermes 任务并看到运行结果。
## 今天不做
## 3. 每日风险巡检
- [ ] 实现重复报销巡检
- [ ] 实现金额超标巡检。
- [ ] 实现发票异常巡检占位。
- [ ] 实现应收逾期巡检。
- [ ] 实现应付异常付款巡检。
- [ ] 每个风险项包含风险类型。
- [ ] 每个风险项包含业务对象。
- [ ] 每个风险项包含触发规则。
- [ ] 每个风险项包含建议动作。
- [ ] 每个风险项包含风险等级。
验收证据:
- [ ] 风险巡检结果可以被用户理解和追溯。
## 4. 每日统计
- [ ] 统计当日报销单数量。
- [ ] 统计当日报销金额。
- [ ] 统计当日报账数量。
- [ ] 统计当日报账金额。
- [ ] 统计应收新增金额。
- [ ] 统计应收逾期金额。
- [ ] 统计应付待付金额。
- [ ] 统计应付逾期金额。
- [ ] 输出日报摘要。
验收证据:
- [ ] Hermes 能生成一份每日财务摘要。
## 5. OCR 接入点
- [ ] 建立 OCR 识别服务接口。
- [ ] 定义发票识别输入结构。
- [ ] 定义发票识别输出结构。
- [ ] 输出结构包含发票号。
- [ ] 输出结构包含开票日期。
- [ ] 输出结构包含金额。
- [ ] 输出结构包含税额。
- [ ] 输出结构包含销售方。
- [ ] 输出结构包含购买方。
- [ ] 输出结构包含置信度。
- [ ] 当前阶段允许使用 Mock 结果。
- [ ] OCR 调用写入 ToolCall。
验收证据:
- [ ] Hermes 风险巡检中可以调用 OCR Mock。
## 6. 知识库维护
- [ ] 建立知识条目写入服务。
- [ ] Hermes 可以生成知识候选条目。
- [ ] 候选条目包含标题。
- [ ] 候选条目包含正文。
- [ ] 候选条目包含来源。
- [ ] 候选条目包含适用场景。
- [ ] 候选条目默认状态为 `draft`
- [ ] 知识条目不能自动发布。
- [ ] 知识条目写入审计日志。
验收证据:
- [ ] Hermes 可以生成待审核知识条目。
## 7. 规则草稿形成
- [ ] Hermes 可以根据风险巡检结果生成规则草稿。
- [ ] 规则草稿保存为 `asset_type=rule`
- [ ] 规则草稿状态为 `draft`
- [ ] 规则草稿包含 Markdown 内容。
- [ ] 规则草稿包含生成原因。
- [ ] 规则草稿包含关联风险样例。
- [ ] 规则草稿不能自动上线。
- [ ] 规则草稿需要审核人。
- [ ] 规则草稿写入审计日志。
验收证据:
- [ ] Hermes 生成的新规则出现在规则列表中,但不是 active。
## 8. Hermes 页面或日志展示
- [ ] 任务详情能看到最近执行结果。
- [ ] 任务详情能手动触发执行。
- [ ] 任务详情能看到风险项数量。
- [ ] 任务详情能看到日报摘要。
- [ ] 任务详情能看到知识候选数量。
- [ ] 任务详情能看到规则草稿数量。
- [ ] 运行 Trace 能看到 Hermes 步骤。
- [ ] 错误时展示错误原因。
验收证据:
- [ ] 不查数据库也能判断 Hermes 是否执行成功。
## 9. 测试
- [ ] 测试手动触发任务。
- [ ] 测试 Orchestrator 路由到 Hermes。
- [ ] 测试风险巡检输出。
- [ ] 测试日报统计输出。
- [ ] 测试 OCR Mock 调用。
- [ ] 测试知识候选写入。
- [ ] 测试规则草稿生成。
- [ ] 测试 Hermes 异常写入 AgentRun。
验收证据:
- [ ] Hermes 核心测试通过。
## 10. Day 6 验收
- [ ] Hermes 可被 Orchestrator 调用。
- [ ] 至少一个任务可以手动触发。
- [ ] 风险巡检有结构化结果。
- [ ] 每日统计有结构化结果。
- [ ] OCR Mock 接入点可用。
- [ ] 知识候选可生成。
- [ ] 规则草稿可生成且不能自动上线。
- [ ] 任务详情或运行日志能展示结果。
- [ ] 所有完成项已用 `[x] ~~...~~` 标记。
## 阻塞记录
- [ ] 暂无。
## 日终交接
- [ ] 写明 Hermes 已支持任务类型。
- [ ] 写明 OCR 当前是真实还是 Mock。
- [ ] 写明生成的知识和规则草稿状态。
- [ ] 写明 Day 7 需要重点回归的路径。
- 不做完整生产调度集群。
- 不做真实 OCR 深度集成。
- 不做自动发布知识
- 不做自动上线规则。

View File

@@ -1,223 +1,47 @@
# Day 7加固、演示和验收 TODO
# Day 7加固、演示和验收
目标:把前 6 天做出的功能整理成可演示、可验收、可继续迭代的基础平台。Day 7 不再大规模扩功能,重点是修缺口、补测试、补日志、补文档、完成演示链路。
## 今天的大开发点
参考文档:
不再大规模扩功能,集中做回归、加固、测试、演示脚本、文档收尾和下一阶段交接。
- `document/development/agent plan/00_README.md`
- `document/development/agent plan/05_development_roadmap.md`
- `document/development/agent plan/09_observability_and_trace.md`
- `document/development/agent plan/10_evaluation_and_testset.md`
## 为什么第七天做这个
## 0. 开始前检查
一周开发不能只停留在“代码写了”。必须能演示、能追溯、能说清楚边界、能交给下一阶段继续开发。
- [ ] 汇总 Day 1 未完成项。
- [ ] 汇总 Day 2 未完成项。
- [ ] 汇总 Day 3 未完成项。
- [ ] 汇总 Day 4 未完成项。
- [ ] 汇总 Day 5 未完成项。
- [ ] 汇总 Day 6 未完成项。
- [ ] 标记必须今天修复的问题。
- [ ] 标记可以进入下一阶段的问题。
- [ ] 冻结新增需求,只处理验收相关问题。
## 今天主要交付
## 1. 核心链路回归
- 核心链路回归
- 权限和风险边界复查。
- 审计日志补齐。
- AgentRun Trace 补齐。
- 前端体验修补。
- 测试和构建记录。
- 评测集执行记录。
- 演示数据准备。
- 演示脚本。
- 下一阶段开发建议。
- [ ] 回归资产列表接口。
- [ ] 回归规则详情接口。
- [ ] 回归 Markdown 保存。
- [ ] 回归版本列表。
- [ ] 回归版本切换。
- [ ] 回归审核接口。
- [ ] 回归上线拦截。
- [ ] 回归语义解析接口。
- [ ] 回归 Orchestrator 路由。
- [ ] 回归 User Agent 问答。
- [ ] 回归 Hermes 任务执行。
- [ ] 回归 AgentRun Trace。
- [ ] 回归 ToolCall 日志。
- [ ] 回归 AuditLog 日志。
## 对应执行细则
验收证据:
- [Day 7 执行细则](<../agent plan/weekly_execution_details/day_7_hardening_demo_acceptance.md>)
- [ ] 从前端能完成至少一条端到端演示路径。
相关架构文档:
## 2. 权限和风险边界
- [Agent Plan 总览](<../agent plan/00_README.md>)
- [开发路线图](<../agent plan/05_development_roadmap.md>)
- [观测与 Trace](<../agent plan/09_observability_and_trace.md>)
- [评测与测试集](<../agent plan/10_evaluation_and_testset.md>)
- [ ] 未审核规则不能上线。
- [ ] rejected 规则不能上线。
- [ ] disabled 能力不能被调用。
- [ ] 用户请求付款必须拦截。
- [ ] 用户请求审批必须需要确认。
- [ ] Hermes 生成规则只能是 draft。
- [ ] Hermes 生成知识只能是 draft。
- [ ] User Agent 生成处理意见只能是草稿。
- [ ] 所有高风险动作响应中包含 `requires_confirmation`
## 当天验收门槛
验收证据:
- 任务规则中心核心路径可演示。
- 语义本体、Orchestrator、User Agent、Hermes 都能跑通最小链路。
- 未审核规则、高风险动作、自动付款等边界都被拦截。
- AgentRun、ToolCall、AuditLog 可追溯。
- 有测试记录、演示脚本和交接说明。
- [ ] 不存在 MVP 期间绕过人工审核的路径。
## 今天不做
## 3. 审计和 Trace 补齐
- [ ] 规则保存写 AuditLog。
- [ ] 规则审核写 AuditLog。
- [ ] 规则上线写 AuditLog。
- [ ] Hermes 生成规则草稿写 AuditLog。
- [ ] Hermes 生成知识候选写 AuditLog。
- [ ] User Agent 草稿生成写 AuditLog。
- [ ] Orchestrator 每次运行有 AgentRun。
- [ ] 每次工具调用有 ToolCall。
- [ ] Trace 页面或接口能串起 run_id。
- [ ] 错误 Trace 包含 error_message。
验收证据:
- [ ] 任意一条演示链路都能追溯到 run_id。
## 4. 前端体验修补
- [ ] 任务规则中心列表无明显错位。
- [ ] 详情页无双 title。
- [ ] Hero title 高度紧凑。
- [ ] 返回列表栏高度正常。
- [ ] Markdown 编辑器和版本卡片底部对齐。
- [ ] 版本卡片不贴右侧。
- [ ] 当前版本标识不突兀。
- [ ] 日期列对齐。
- [ ] 弹窗文案清楚。
- [ ] 加载态可见。
- [ ] 错误态可见。
- [ ] 空态可见。
- [ ] 按钮禁用态可见。
- [ ] 窄屏不出现内容重叠。
验收证据:
- [ ] 任务规则中心可以给业务用户演示,不需要解释 UI 异常。
## 5. 测试补齐
- [ ] 运行后端现有测试。
- [ ] 运行新增模型测试。
- [ ] 运行新增 API 测试。
- [ ] 运行语义解析测试。
- [ ] 运行 Orchestrator 测试。
- [ ] 运行 User Agent 测试。
- [ ] 运行 Hermes 测试。
- [ ] 运行前端构建。
- [ ] 如果有前端测试,运行前端测试。
- [ ] 记录未能运行的测试和原因。
验收证据:
- [ ] 测试结果写入本文件“测试记录”。
## 6. 评测集
- [ ] 准备 5 条报销问题。
- [ ] 准备 5 条应收问题。
- [ ] 准备 5 条应付问题。
- [ ] 准备 3 条规则解释问题。
- [ ] 准备 3 条越权动作问题。
- [ ] 执行语义解析评测。
- [ ] 执行 User Agent 回答评测。
- [ ] 执行权限拦截评测。
- [ ] 记录失败样例。
- [ ] 为失败样例写下一阶段优化建议。
验收证据:
- [ ] 可以说明 MVP 当前能力边界和准确率风险。
## 7. 演示数据
- [ ] 准备 active 规则。
- [ ] 准备 pending 规则。
- [ ] 准备 rejected 规则。
- [ ] 准备至少一条报销数据。
- [ ] 准备至少一条应收数据。
- [ ] 准备至少一条应付数据。
- [ ] 准备至少一个 Hermes 任务。
- [ ] 准备至少一个 MCP Mock。
- [ ] 准备至少一个知识条目。
- [ ] 准备至少一个风险样例。
验收证据:
- [ ] 演示不会因为没有数据而中断。
## 8. 演示脚本
- [ ] 编写演示步骤 1打开任务规则中心。
- [ ] 编写演示步骤 2查看规则详情。
- [ ] 编写演示步骤 3编辑 Markdown 并保存。
- [ ] 编写演示步骤 4切换版本。
- [ ] 编写演示步骤 5尝试上线未审核规则并被拦截。
- [ ] 编写演示步骤 6输入用户问题。
- [ ] 编写演示步骤 7查看语义本体结果。
- [ ] 编写演示步骤 8查看 User Agent 回答。
- [ ] 编写演示步骤 9手动触发 Hermes 任务。
- [ ] 编写演示步骤 10查看 AgentRun Trace。
- [ ] 编写演示步骤 11查看审计日志。
验收证据:
- [ ] 新开发者按脚本可以复现演示。
## 9. 文档收尾
- [ ] 更新一周计划完成情况。
- [ ] 更新剩余风险。
- [ ] 更新下一阶段开发建议。
- [ ] 更新接口清单。
- [ ] 更新数据模型清单。
- [ ] 更新前端页面清单。
- [ ] 更新评测结果。
- [ ] 更新演示脚本。
- [ ] 更新部署或启动说明。
验收证据:
- [ ] 文档能指导下一周继续开发。
## 10. 最终验收清单
- [ ] 任务规则中心可查看规则、技能、MCP、任务。
- [ ] 规则详情可编辑 Markdown。
- [ ] 规则详情可查看最近 5 个版本。
- [ ] 版本切换有确认弹窗。
- [ ] 审核者信息可见。
- [ ] 未审核规则不能上线。
- [ ] 语义本体 8 字段可返回。
- [ ] Orchestrator 能路由用户请求。
- [ ] Orchestrator 能路由定时任务。
- [ ] User Agent 能回答至少 3 类财务问题。
- [ ] Hermes 能执行至少 1 个任务。
- [ ] OCR Mock 接入点可用。
- [ ] 知识候选可生成。
- [ ] 规则草稿可生成。
- [ ] AgentRun Trace 可查。
- [ ] AuditLog 可查。
- [ ] 前端构建通过。
- [ ] 后端核心测试通过。
- [ ] 演示脚本可执行。
- [ ] 所有完成项已用 `[x] ~~...~~` 标记。
## 测试记录
- [ ] 后端测试:未运行。
- [ ] 前端构建:未运行。
- [ ] 语义评测:未运行。
- [ ] 手动验收:未运行。
## 阻塞记录
- [ ] 暂无。
## 日终交接
- [ ] 写明本周最终完成内容。
- [ ] 写明未完成内容。
- [ ] 写明生产化前必须补齐内容。
- [ ] 写明下一周建议优先级。
- 不做新大功能。
- 不临时扩大范围。
- 不绕过测试和验收。

View File

@@ -0,0 +1,137 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Day 1 - 基础模型与工程骨架</title>
<link rel="stylesheet" href="./styles.css">
</head>
<body>
<div class="shell">
<div class="topbar">
<a class="brand" href="./index.html"><span class="brand-mark">D1</span><span>Day 1 View</span></a>
<div class="quick-links">
<a class="pill" href="./index.html">返回总览</a>
<a class="pill" href="../agent%20week%20plan/day_1_foundation_models.md">周计划原文</a>
<a class="pill" href="../agent%20plan/weekly_execution_details/day_1_foundation_models.md">执行细则原文</a>
</div>
</div>
<div class="day-nav">
<a class="pill active" href="./day-1.html">Day 1</a>
<a class="pill" href="./day-2.html">Day 2</a>
<a class="pill" href="./day-3.html">Day 3</a>
<a class="pill" href="./day-4.html">Day 4</a>
<a class="pill" href="./day-5.html">Day 5</a>
<a class="pill" href="./day-6.html">Day 6</a>
<a class="pill" href="./day-7.html">Day 7</a>
</div>
<section class="hero">
<div class="hero-badge">Foundation Completed</div>
<h1>Day 1 基础模型与工程骨架</h1>
<p>这一天的任务不是做炫目的业务能力,而是把后面 6 天要反复依赖的模型、版本、审核、run trace、审计日志和最小业务数据源一次定稳。Day 1 做虚了Day 4 到 Day 6 会全部返工。</p>
<div class="hero-meta">
<div class="meta-card"><div class="meta-label">当前状态</div><div class="meta-value">已完成2026-05-11可直接进入 Day 2 联调。</div></div>
<div class="meta-card"><div class="meta-label">上游依赖</div><div class="meta-value">Day 1 是全周底座。</div></div>
<div class="meta-card"><div class="meta-label">下游交接</div><div class="meta-value">Day 2 资产 APIDay 3 解析日志Day 4 run traceDay 5/6 业务数据查询。</div></div>
<div class="meta-card"><div class="meta-label">当天关键</div><div class="meta-value">先确定统一模型,再接 API 骨架和种子数据。</div></div>
</div>
</section>
<div class="section-kicker">Three-Layer Mapping</div>
<h2 class="section-title">三层文档映射</h2>
<div class="grid three">
<section class="card tone-warm">
<h3>路线图</h3>
<p>周计划里定义这一天要完成“工程地基”强调只做稳定模型、API 骨架、种子数据、基础审计和可运行验证。</p>
<div class="card-links"><a class="link-chip" href="../agent%20week%20plan/day_1_foundation_models.md">day_1_foundation_models.md</a></div>
</section>
<section class="card tone-teal">
<h3>执行细则</h3>
<p>执行层把 Day 1 拆成命名边界、最小财务业务数据模型、Agent 资产模型、版本、审核、Run、ToolCall、SemanticParseLog、AuditLog、Schema、API、服务层。</p>
<div class="card-links"><a class="link-chip" href="../agent%20plan/weekly_execution_details/day_1_foundation_models.md">weekly_execution_details/day_1</a></div>
</section>
<section class="card tone-olive">
<h3>架构依据</h3>
<p>主要受总体架构、语义本体、数据契约、能力注册、权限确认、可观测性和财务标准模型约束。</p>
<div class="card-links">
<a class="link-chip" href="../agent%20plan/01_overall_architecture.md">01</a>
<a class="link-chip" href="../agent%20plan/02_semantic_ontology.md">02</a>
<a class="link-chip" href="../agent%20plan/06_data_contracts_and_governance.md">06</a>
<a class="link-chip" href="../agent%20plan/07_capability_registry.md">07</a>
<a class="link-chip" href="../agent%20plan/08_permission_confirmation.md">08</a>
<a class="link-chip" href="../agent%20plan/09_observability_and_trace.md">09</a>
<a class="link-chip" href="../agent%20plan/14_financial_document_canonical_model.md">14</a>
</div>
</section>
</div>
<div class="section-kicker">Build Order</div>
<h2 class="section-title">推荐开发顺序</h2>
<div class="timeline">
<div class="timeline-step"><strong>Step 1</strong>先确认后端目录、ORM、迁移方式、测试目录和不该碰的文件。</div>
<div class="timeline-step"><strong>Step 2</strong>统一命名资产类型、状态、审核状态、Agent、权限级别。</div>
<div class="timeline-step"><strong>Step 3</strong>补最小财务业务数据模型:<code>expense_claims</code><code>accounts_receivable</code><code>accounts_payable</code></div>
<div class="timeline-step"><strong>Step 4</strong>完成 AgentAsset、Version、Review、Run、ToolCall、ParseLog、AuditLog。</div>
<div class="timeline-step"><strong>Step 5</strong>把 Schema、API 骨架、服务层、种子数据接起来。</div>
</div>
<div class="section-kicker">Must Deliver</div>
<h2 class="section-title">今天必须产出的东西</h2>
<div class="grid two">
<section class="card">
<h3>平台底座表</h3>
<ul class="list">
<li><code>AgentAsset</code><code>AgentAssetVersion</code><code>AgentAssetReview</code></li>
<li><code>AgentRun</code><code>AgentToolCall</code><code>SemanticParseLog</code></li>
<li><code>AuditLog</code></li>
</ul>
</section>
<section class="card">
<h3>最小业务数据来源</h3>
<ul class="list">
<li>报销至少有时间、地点、理由、金额、员工、部门、状态。</li>
<li>应收至少有客户、金额、未收金额、到期日、账龄、状态。</li>
<li>应付至少有供应商、金额、未付金额、到期日、账龄、状态。</li>
</ul>
</section>
<section class="card">
<h3>API 骨架</h3>
<ul class="list">
<li>资产列表 / 详情 / 版本 / 审核 / 上线。</li>
<li>运行日志与审计日志查询。</li>
<li>返回真实数据库结果,不用前端硬编码收尾。</li>
</ul>
</section>
<section class="card">
<h3>统一服务边界</h3>
<ul class="list">
<li>上线拦截逻辑在服务层,不堆到路由。</li>
<li>所有写操作要留审计接口。</li>
<li>任何 Agent 执行记录都必须生成 <code>run_id</code></li>
</ul>
</section>
</div>
<div class="section-kicker">Acceptance Snapshot</div>
<h2 class="section-title">验收快照</h2>
<div class="table-like">
<div class="row"><div class="row-label">资产模型</div><div class="row-value">已落地 3 条规则、2 条技能、2 条 MCP、3 条任务,并可通过资产接口返回。</div></div>
<div class="row"><div class="row-label">版本与审核</div><div class="row-value">三条规则都具备版本历史;同一资产版本号不可重复,未审核规则不能上线。</div></div>
<div class="row"><div class="row-label">运行与错误</div><div class="row-value">`GET /api/v1/agent-runs` 可返回 3 条运行日志,任意新建 Run 自动生成 <code>run_id</code></div></div>
<div class="row"><div class="row-label">最小业务表</div><div class="row-value">报销、应收、应付种子数据已就位,后续查询和风险巡检都有明确数据来源。</div></div>
</div>
<div class="section-kicker">Common Misses</div>
<h2 class="section-title">这一天最容易漏掉的点</h2>
<ul class="list">
<li>只建 Agent 表,不建最小财务业务表,导致 User Agent 和 Hermes 后面无数据可查。</li>
<li>把审核拦截塞在 API 路由里,后面很难复用到 Orchestrator 和别的入口。</li>
<li>没有统一 <code>run_id</code> 和审计接口Day 4 到 Day 7 的 Trace 会断链。</li>
</ul>
<div class="footer">Day 1 的判断标准很简单:不是“代码写了多少”,而是“后面 6 天会不会反复回头补地基”。</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,132 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Day 2 - 任务规则中心联调</title>
<link rel="stylesheet" href="./styles.css">
</head>
<body>
<div class="shell">
<div class="topbar">
<a class="brand" href="./index.html"><span class="brand-mark">D2</span><span>Day 2 View</span></a>
<div class="quick-links">
<a class="pill" href="./index.html">返回总览</a>
<a class="pill" href="../agent%20week%20plan/day_2_rule_center_integration.md">周计划原文</a>
<a class="pill" href="../agent%20plan/weekly_execution_details/day_2_rule_center_integration.md">执行细则原文</a>
</div>
</div>
<div class="day-nav">
<a class="pill" href="./day-1.html">Day 1</a>
<a class="pill active" href="./day-2.html">Day 2</a>
<a class="pill" href="./day-3.html">Day 3</a>
<a class="pill" href="./day-4.html">Day 4</a>
<a class="pill" href="./day-5.html">Day 5</a>
<a class="pill" href="./day-6.html">Day 6</a>
<a class="pill" href="./day-7.html">Day 7</a>
</div>
<section class="hero">
<div class="hero-badge">Integration</div>
<h1>Day 2 任务规则中心联调</h1>
<p>Day 2 的核心不是“把页面做漂亮”而是让规则、技能、MCP、任务这四类资产第一次脱离本地假数据真正连到 Day 1 的数据库和 API。最关键的能力是 Markdown、版本、审核和上线约束闭环。</p>
<div class="hero-meta">
<div class="meta-card"><div class="meta-label">上游依赖</div><div class="meta-value">Day 1 的资产模型、版本模型、审核模型、资产 API。</div></div>
<div class="meta-card"><div class="meta-label">下游交接</div><div class="meta-value">Day 3 要复用资产数据Day 4 要查询 active 技能 / MCP / 任务。</div></div>
<div class="meta-card"><div class="meta-label">当天关键</div><div class="meta-value">前端联调不是硬编码演示,而是可对接真实后端。</div></div>
</div>
</section>
<div class="section-kicker">Three-Layer Mapping</div>
<h2 class="section-title">三层文档映射</h2>
<div class="grid three">
<section class="card tone-warm">
<h3>路线图</h3>
<p>周计划要求把任务规则中心从静态 UI 升级到真实数据对接覆盖规则、技能、MCP、任务四类资产。</p>
<div class="card-links"><a class="link-chip" href="../agent%20week%20plan/day_2_rule_center_integration.md">day_2_rule_center_integration.md</a></div>
</section>
<section class="card tone-teal">
<h3>执行细则</h3>
<p>执行层拆成 API Client、四类列表、规则详情、Markdown 编辑、版本卡片、审核与上线、技能详情、MCP 详情、任务详情、前端质量和当天验收。</p>
<div class="card-links"><a class="link-chip" href="../agent%20plan/weekly_execution_details/day_2_rule_center_integration.md">weekly_execution_details/day_2</a></div>
</section>
<section class="card tone-olive">
<h3>架构依据</h3>
<p>这一天主要受能力注册、规则形成生命周期和数据治理约束,重点在四类资产的统一展示方式和规则上线前审核拦截。</p>
<div class="card-links">
<a class="link-chip" href="../agent%20plan/07_capability_registry.md">07</a>
<a class="link-chip" href="../agent%20plan/13_rule_formation_lifecycle.md">13</a>
<a class="link-chip" href="../agent%20plan/06_data_contracts_and_governance.md">06</a>
</div>
</section>
</div>
<div class="section-kicker">Build Order</div>
<h2 class="section-title">推荐开发顺序</h2>
<div class="timeline">
<div class="timeline-step"><strong>Step 1</strong>先补 API Client列表、详情、版本、保存、审核、上线、运行日志。</div>
<div class="timeline-step"><strong>Step 2</strong>把四个页签的真实数据接起来,覆盖筛选、搜索、状态、空态和加载态。</div>
<div class="timeline-step"><strong>Step 3</strong>把规则详情的 Hero 区、Markdown 编辑器、版本卡片和审核信息拉通。</div>
<div class="timeline-step"><strong>Step 4</strong>补技能 / MCP / 任务的差异化详情,不复用规则编辑器。</div>
<div class="timeline-step"><strong>Step 5</strong>最后收 UI 细节、错误态、禁用态、确认弹窗和构建验证。</div>
</div>
<div class="section-kicker">Must Deliver</div>
<h2 class="section-title">今天必须产出的东西</h2>
<div class="grid two">
<section class="card">
<h3>规则中心四页签</h3>
<ul class="list">
<li>规则、技能、MCP、任务都能切换。</li>
<li>每个页签都来自真实接口,不再只读本地常量。</li>
<li>搜索和状态筛选同时生效。</li>
</ul>
</section>
<section class="card">
<h3>规则详情闭环</h3>
<ul class="list">
<li>能读取当前 Markdown。</li>
<li>能保存并刷新版本列表。</li>
<li>能展示审核者、审核状态、上线条件。</li>
</ul>
</section>
<section class="card">
<h3>版本与上线约束</h3>
<ul class="list">
<li>最近 5 个版本可见。</li>
<li>切换旧版本必须弹确认框。</li>
<li>未审核规则不能上线,拒绝原因要可见。</li>
</ul>
</section>
<section class="card">
<h3>详情差异化</h3>
<ul class="list">
<li>技能详情展示输入输出与依赖。</li>
<li>MCP 详情展示服务地址、鉴权、降级策略。</li>
<li>任务详情展示 cron、执行 Agent、最近执行结果。</li>
</ul>
</section>
</div>
<div class="section-kicker">Acceptance Snapshot</div>
<h2 class="section-title">验收快照</h2>
<div class="table-like">
<div class="row"><div class="row-label">真实数据</div><div class="row-value">四个页签都能用真实后端数据渲染,后端不可用时有明确错误提示。</div></div>
<div class="row"><div class="row-label">规则编辑</div><div class="row-value">Markdown 保存后刷新页面仍在,保存失败不丢输入。</div></div>
<div class="row"><div class="row-label">版本卡片</div><div class="row-value">最近 5 个版本可切换,当前版本标识清楚但不造成布局位移。</div></div>
<div class="row"><div class="row-label">审核上线</div><div class="row-value"><code>pending</code> / <code>rejected</code> 规则都无法上线,<code>approved</code> 才能放行。</div></div>
</div>
<div class="section-kicker">Common Misses</div>
<h2 class="section-title">这一天最容易漏掉的点</h2>
<ul class="list">
<li>只把规则页签接成真实数据技能、MCP、任务仍然靠假数据撑场面。</li>
<li>只做版本列表展示,不做确认弹窗和拒绝风险提示。</li>
<li>把任务写成“定时任务”暴露给用户,违背文档里 UI 名称统一成“任务”的约束。</li>
</ul>
<div class="footer">Day 2 的完成标准不是“页面能打开”,而是“规则中心第一次成为真实的资产入口”。</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,132 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Day 3 - 语义本体 MVP</title>
<link rel="stylesheet" href="./styles.css">
</head>
<body>
<div class="shell">
<div class="topbar">
<a class="brand" href="./index.html"><span class="brand-mark">D3</span><span>Day 3 View</span></a>
<div class="quick-links">
<a class="pill" href="./index.html">返回总览</a>
<a class="pill" href="../agent%20week%20plan/day_3_semantic_ontology_mvp.md">周计划原文</a>
<a class="pill" href="../agent%20plan/weekly_execution_details/day_3_semantic_ontology_mvp.md">执行细则原文</a>
</div>
</div>
<div class="day-nav">
<a class="pill" href="./day-1.html">Day 1</a>
<a class="pill" href="./day-2.html">Day 2</a>
<a class="pill active" href="./day-3.html">Day 3</a>
<a class="pill" href="./day-4.html">Day 4</a>
<a class="pill" href="./day-5.html">Day 5</a>
<a class="pill" href="./day-6.html">Day 6</a>
<a class="pill" href="./day-7.html">Day 7</a>
</div>
<section class="hero">
<div class="hero-badge">Ontology</div>
<h1>Day 3 语义本体 MVP</h1>
<p>这一天把自然语言问题统一切成 8 个核心字段。Day 3 不是追求大模型多聪明,而是先让结构稳定、可落日志、可被 Orchestrator、User Agent 和 Hermes 共用。</p>
<div class="hero-meta">
<div class="meta-card"><div class="meta-label">上游依赖</div><div class="meta-value">Day 1 的 <code>SemanticParseLog</code> / <code>AgentRun</code>Day 2 的资产 API。</div></div>
<div class="meta-card"><div class="meta-label">下游交接</div><div class="meta-value">Day 4 路由、Day 5 查询解释、Day 6 风险巡检都直接消费这 8 字段。</div></div>
<div class="meta-card"><div class="meta-label">当天关键</div><div class="meta-value">名字统一、类型统一、日志统一、低置信度有澄清问题。</div></div>
</div>
</section>
<div class="section-kicker">Three-Layer Mapping</div>
<h2 class="section-title">三层文档映射</h2>
<div class="grid three">
<section class="card tone-warm">
<h3>路线图</h3>
<p>周计划要求建立用户问题的统一语义解析层,覆盖场景、意图、对象、时间、指标、约束、风险、权限 8 字段。</p>
<div class="card-links"><a class="link-chip" href="../agent%20week%20plan/day_3_semantic_ontology_mvp.md">day_3_semantic_ontology_mvp.md</a></div>
</section>
<section class="card tone-teal">
<h3>执行细则</h3>
<p>执行层拆成 8 字段定义、字段枚举、Schema、解析服务、对象提取、时间范围、指标约束、风险权限、API、前端调试入口和评测集。</p>
<div class="card-links"><a class="link-chip" href="../agent%20plan/weekly_execution_details/day_3_semantic_ontology_mvp.md">weekly_execution_details/day_3</a></div>
</section>
<section class="card tone-olive">
<h3>架构依据</h3>
<p>主要受语义本体、财务标准模型和数据治理约束。应收、应付、报销的对象语义必须能回到最小业务表和标准对象。</p>
<div class="card-links">
<a class="link-chip" href="../agent%20plan/02_semantic_ontology.md">02</a>
<a class="link-chip" href="../agent%20plan/14_financial_document_canonical_model.md">14</a>
<a class="link-chip" href="../agent%20plan/06_data_contracts_and_governance.md">06</a>
</div>
</section>
</div>
<div class="section-kicker">Build Order</div>
<h2 class="section-title">推荐开发顺序</h2>
<div class="timeline">
<div class="timeline-step"><strong>Step 1</strong>先固定 8 个字段名字、类型、默认值和示例。</div>
<div class="timeline-step"><strong>Step 2</strong><code>scenario</code><code>intent</code><code>permission.level</code> 的枚举定死。</div>
<div class="timeline-step"><strong>Step 3</strong>做请求/响应 Schema再写解析服务。</div>
<div class="timeline-step"><strong>Step 4</strong>补对象提取、时间范围、指标约束、风险和权限映射。</div>
<div class="timeline-step"><strong>Step 5</strong>接 API、日志、调试入口和最小评测集。</div>
</div>
<div class="section-kicker">Must Deliver</div>
<h2 class="section-title">今天必须产出的东西</h2>
<div class="grid two">
<section class="card">
<h3>8 字段统一结构</h3>
<ul class="list">
<li><code>scenario</code><code>intent</code><code>entities</code><code>time_range</code></li>
<li><code>metrics</code><code>constraints</code><code>risk_flags</code><code>permission</code></li>
<li>附带 <code>confidence</code><code>clarification_required</code><code>run_id</code></li>
</ul>
</section>
<section class="card">
<h3>规则解析优先版</h3>
<ul class="list">
<li>先用关键词和规则解析打底。</li>
<li>报销 / 应收 / 应付 / 知识 / unknown 场景都能落到结构。</li>
<li>越权动作能识别为 <code>approval_required</code><code>forbidden</code></li>
</ul>
</section>
<section class="card">
<h3>日志和调试入口</h3>
<ul class="list">
<li>每次解析都要落 <code>SemanticParseLog</code></li>
<li>前端可直接输入一句话看 8 字段结果。</li>
<li>低置信度问题必须给澄清问题。</li>
</ul>
</section>
<section class="card">
<h3>最小评测集</h3>
<ul class="list">
<li>至少覆盖报销、应收、应付、知识、越权动作。</li>
<li>每条样例要写期望 <code>scenario</code><code>intent</code> 和权限级别。</li>
<li>当天目标是可评测,而不是追求完美准确率。</li>
</ul>
</section>
</div>
<div class="section-kicker">Acceptance Snapshot</div>
<h2 class="section-title">验收快照</h2>
<div class="table-like">
<div class="row"><div class="row-label">语义结构</div><div class="row-value">8 字段在 Schema、服务层、日志里名字完全一致。</div></div>
<div class="row"><div class="row-label">关键识别</div><div class="row-value">“本周报销超标风险”“客户 A 本月应收”“供应商 B 明天要付多少钱”都能落到正确场景和意图。</div></div>
<div class="row"><div class="row-label">权限结果</div><div class="row-value">“帮我直接付款”不能被识别成可直接执行动作。</div></div>
<div class="row"><div class="row-label">日志与前端</div><div class="row-value">连续调用多次都能在日志中查到,并能通过调试入口观察结果。</div></div>
</div>
<div class="section-kicker">Common Misses</div>
<h2 class="section-title">这一天最容易漏掉的点</h2>
<ul class="list">
<li>字段结构和日志结构各写一套名字,后面 Trace 很难串。</li>
<li>只做 <code>scenario</code><code>intent</code>,不做 <code>permission</code>Day 4 会直接失去拦截依据。</li>
<li>只在服务里返回结果,不把解析过程落库或落日志,后续无法复盘误判样例。</li>
</ul>
<div class="footer">Day 3 的价值在于把“语义理解”从模糊文本变成稳定协议。后面所有智能能力都站在这层协议上。</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,133 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Day 4 - Orchestrator 运行时</title>
<link rel="stylesheet" href="./styles.css">
</head>
<body>
<div class="shell">
<div class="topbar">
<a class="brand" href="./index.html"><span class="brand-mark">D4</span><span>Day 4 View</span></a>
<div class="quick-links">
<a class="pill" href="./index.html">返回总览</a>
<a class="pill" href="../agent%20week%20plan/day_4_orchestrator_runtime.md">周计划原文</a>
<a class="pill" href="../agent%20plan/weekly_execution_details/day_4_orchestrator_runtime.md">执行细则原文</a>
</div>
</div>
<div class="day-nav">
<a class="pill" href="./day-1.html">Day 1</a>
<a class="pill" href="./day-2.html">Day 2</a>
<a class="pill" href="./day-3.html">Day 3</a>
<a class="pill active" href="./day-4.html">Day 4</a>
<a class="pill" href="./day-5.html">Day 5</a>
<a class="pill" href="./day-6.html">Day 6</a>
<a class="pill" href="./day-7.html">Day 7</a>
</div>
<section class="hero">
<div class="hero-badge">Runtime</div>
<h1>Day 4 Orchestrator 运行时</h1>
<p>Day 4 把整个系统第一次串成“能跑的链”。用户消息和定时任务都先走 Orchestrator由它创建 run、调用语义解析、做权限判断、选择 Agent、记录 ToolCall 和 Trace然后再给下游执行。</p>
<div class="hero-meta">
<div class="meta-card"><div class="meta-label">上游依赖</div><div class="meta-value">Day 3 的语义解析结果Day 1 的 Run / ToolCallDay 2 的 active 资产。</div></div>
<div class="meta-card"><div class="meta-label">下游交接</div><div class="meta-value">Day 5 User Agent 和 Day 6 Hermes 都通过它被调度。</div></div>
<div class="meta-card"><div class="meta-label">当天关键</div><div class="meta-value">权限拦截和 Trace 必须在 Orchestrator 层,而不是散落在各 Agent。</div></div>
</div>
</section>
<div class="section-kicker">Three-Layer Mapping</div>
<h2 class="section-title">三层文档映射</h2>
<div class="grid three">
<section class="card tone-warm">
<h3>路线图</h3>
<p>周计划要求建立统一调度层,让用户请求和系统任务都先进入 Orchestrator再根据语义、权限、能力注册路由到 User Agent、Hermes、MCP 或规则引擎。</p>
<div class="card-links"><a class="link-chip" href="../agent%20week%20plan/day_4_orchestrator_runtime.md">day_4_orchestrator_runtime.md</a></div>
</section>
<section class="card tone-teal">
<h3>执行细则</h3>
<p>执行层拆成输入输出、Orchestrator 服务、路由规则、权限判断、能力查询、工具调用封装、API、最小 Trace 查看和测试。</p>
<div class="card-links"><a class="link-chip" href="../agent%20plan/weekly_execution_details/day_4_orchestrator_runtime.md">weekly_execution_details/day_4</a></div>
</section>
<section class="card tone-olive">
<h3>架构依据</h3>
<p>主要受运行时流程、能力注册、权限确认和可观测性约束。Day 4 的输出要能直接给前端展示,并支持 Day 5/6 的占位实现接入。</p>
<div class="card-links">
<a class="link-chip" href="../agent%20plan/04_orchestrator_and_runtime_flow.md">04</a>
<a class="link-chip" href="../agent%20plan/07_capability_registry.md">07</a>
<a class="link-chip" href="../agent%20plan/08_permission_confirmation.md">08</a>
<a class="link-chip" href="../agent%20plan/09_observability_and_trace.md">09</a>
</div>
</section>
</div>
<div class="section-kicker">Build Order</div>
<h2 class="section-title">推荐开发顺序</h2>
<div class="timeline">
<div class="timeline-step"><strong>Step 1</strong>先定 <code>OrchestratorRequest</code><code>OrchestratorResponse</code></div>
<div class="timeline-step"><strong>Step 2</strong><code>run(request)</code> 主流程:创建 Run、解析语义、判权限、选 Agent、更新状态。</div>
<div class="timeline-step"><strong>Step 3</strong>把用户入口 / 任务入口的路由规则固化下来。</div>
<div class="timeline-step"><strong>Step 4</strong>封装工具调用记录和降级策略。</div>
<div class="timeline-step"><strong>Step 5</strong>暴露 API 和最小 Trace 页面或接口。</div>
</div>
<div class="section-kicker">Must Deliver</div>
<h2 class="section-title">今天必须产出的东西</h2>
<div class="grid two">
<section class="card">
<h3>统一入口</h3>
<ul class="list">
<li><code>source=user_message</code><code>source=schedule</code> 都能进同一入口。</li>
<li>请求返回 <code>run_id</code><code>selected_agent</code><code>route_reason</code><code>permission_level</code></li>
<li>返回结果要能被前端直接展示。</li>
</ul>
</section>
<section class="card">
<h3>权限与路由</h3>
<ul class="list">
<li>查询类走 User Agent定时风险类走 Hermes。</li>
<li><code>approval_required</code> 只返回确认,不直接执行。</li>
<li><code>forbidden</code> 直接阻断,不调下游 Agent。</li>
</ul>
</section>
<section class="card">
<h3>能力与工具调用</h3>
<ul class="list">
<li>只查询 active 技能 / MCP / 任务。</li>
<li>禁用能力不允许被调用。</li>
<li>每次工具调用都能落 <code>AgentToolCall</code></li>
</ul>
</section>
<section class="card">
<h3>Trace 与降级</h3>
<ul class="list">
<li>Trace 能串起语义解析、路由、工具调用和最终结果。</li>
<li>外部 MCP 失败要返回降级说明,不让前端拿到不可读错误。</li>
<li>异常都要写进 <code>AgentRun.error_message</code></li>
</ul>
</section>
</div>
<div class="section-kicker">Acceptance Snapshot</div>
<h2 class="section-title">验收快照</h2>
<div class="table-like">
<div class="row"><div class="row-label">路由结果</div><div class="row-value">同一句风险检查,在用户入口和任务入口会有不同路由结果。</div></div>
<div class="row"><div class="row-label">权限边界</div><div class="row-value">“直接上线规则”和“直接付款”都不会被自动执行。</div></div>
<div class="row"><div class="row-label">日志完整度</div><div class="row-value">每次运行至少有一条 <code>AgentRun</code>,工具调用有 0 到多条 <code>AgentToolCall</code></div></div>
<div class="row"><div class="row-label">可观察性</div><div class="row-value">前端或 curl 可以完整看到一次运行链路,不需要直接查数据库猜过程。</div></div>
</div>
<div class="section-kicker">Common Misses</div>
<h2 class="section-title">这一天最容易漏掉的点</h2>
<ul class="list">
<li>把权限判断放到 User Agent / Hermes 内部,导致系统没有统一边界。</li>
<li>只记录成功 ToolCall不记录失败 ToolCall后面降级和排错会缺证据。</li>
<li>路由能跑,但没有统一 Trace 输出Day 7 演示时会非常难讲清链路。</li>
</ul>
<div class="footer">Day 4 的价值是把系统从“有很多零件”变成“有一条统一运行链”。</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,133 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Day 5 - User Agent MVP</title>
<link rel="stylesheet" href="./styles.css">
</head>
<body>
<div class="shell">
<div class="topbar">
<a class="brand" href="./index.html"><span class="brand-mark">D5</span><span>Day 5 View</span></a>
<div class="quick-links">
<a class="pill" href="./index.html">返回总览</a>
<a class="pill" href="../agent%20week%20plan/day_5_user_agent_mvp.md">周计划原文</a>
<a class="pill" href="../agent%20plan/weekly_execution_details/day_5_user_agent_mvp.md">执行细则原文</a>
</div>
</div>
<div class="day-nav">
<a class="pill" href="./day-1.html">Day 1</a>
<a class="pill" href="./day-2.html">Day 2</a>
<a class="pill" href="./day-3.html">Day 3</a>
<a class="pill" href="./day-4.html">Day 4</a>
<a class="pill active" href="./day-5.html">Day 5</a>
<a class="pill" href="./day-6.html">Day 6</a>
<a class="pill" href="./day-7.html">Day 7</a>
</div>
<section class="hero">
<div class="hero-badge">User Agent</div>
<h1>Day 5 User Agent MVP</h1>
<p>这一天开始让“用户真的能问问题”。但 User Agent 只负责查询、解释、规则引用和草稿生成,绝不绕过权限做审批、付款、上线这类高风险动作。</p>
<div class="hero-meta">
<div class="meta-card"><div class="meta-label">上游依赖</div><div class="meta-value">Day 4 Orchestrator、Day 3 语义结构、Day 1 业务数据与日志模型、Day 2 规则资产。</div></div>
<div class="meta-card"><div class="meta-label">下游交接</div><div class="meta-value">Day 7 要拿它做问答演示、规则解释演示和草稿生成演示。</div></div>
<div class="meta-card"><div class="meta-label">当天关键</div><div class="meta-value">回答可读、引用可追溯、草稿可确认、高风险不自动执行。</div></div>
</div>
</section>
<div class="section-kicker">Three-Layer Mapping</div>
<h2 class="section-title">三层文档映射</h2>
<div class="grid three">
<section class="card tone-warm">
<h3>路线图</h3>
<p>周计划要求做用户自然语言入口、报销 / 应收 / 应付查询解释、规则引用解释、建议草稿和前端入口。</p>
<div class="card-links"><a class="link-chip" href="../agent%20week%20plan/day_5_user_agent_mvp.md">day_5_user_agent_mvp.md</a></div>
</section>
<section class="card tone-teal">
<h3>执行细则</h3>
<p>执行层拆成输入输出、查询处理、规则解释、风险解释、草稿生成、知识库读取骨架、对话入口、安全边界和测试。</p>
<div class="card-links"><a class="link-chip" href="../agent%20plan/weekly_execution_details/day_5_user_agent_mvp.md">weekly_execution_details/day_5</a></div>
</section>
<section class="card tone-olive">
<h3>架构依据</h3>
<p>主要受 Agent 职责划分、运行时流程、知识架构和规则形成生命周期约束。所有高风险动作只能停留在建议或草稿层。</p>
<div class="card-links">
<a class="link-chip" href="../agent%20plan/03_agent_responsibilities.md">03</a>
<a class="link-chip" href="../agent%20plan/04_orchestrator_and_runtime_flow.md">04</a>
<a class="link-chip" href="../agent%20plan/12_llm_wiki_knowledge_architecture.md">12</a>
<a class="link-chip" href="../agent%20plan/13_rule_formation_lifecycle.md">13</a>
</div>
</section>
</div>
<div class="section-kicker">Build Order</div>
<h2 class="section-title">推荐开发顺序</h2>
<div class="timeline">
<div class="timeline-step"><strong>Step 1</strong>先定 <code>UserAgentRequest</code> / <code>UserAgentResponse</code> 协议。</div>
<div class="timeline-step"><strong>Step 2</strong>优先实现报销、应收、应付查询处理器。</div>
<div class="timeline-step"><strong>Step 3</strong>补规则解释和风险解释,让回答有依据而不是只给一句话。</div>
<div class="timeline-step"><strong>Step 4</strong>补草稿生成与知识读取骨架。</div>
<div class="timeline-step"><strong>Step 5</strong>最后接前端问答入口、加载态、错误态和确认提示。</div>
</div>
<div class="section-kicker">Must Deliver</div>
<h2 class="section-title">今天必须产出的东西</h2>
<div class="grid two">
<section class="card">
<h3>三类财务查询</h3>
<ul class="list">
<li>报销查询可读,能查金额、状态或进度。</li>
<li>应收查询可读,能查客户未收金额或账龄。</li>
<li>应付查询可读,能查供应商待付款或付款状态。</li>
</ul>
</section>
<section class="card">
<h3>解释能力</h3>
<ul class="list">
<li>规则解释能引用 active 规则、版本号和更新时间。</li>
<li>风险解释能说明风险类型、原因和建议动作。</li>
<li>知识库不可用时要优雅降级。</li>
</ul>
</section>
<section class="card">
<h3>草稿而非执行</h3>
<ul class="list">
<li>可生成报销处理意见草稿、应收催收建议草稿、应付付款建议草稿。</li>
<li>草稿必须写明“待人工确认”。</li>
<li>草稿行为写入审计日志和 AgentRun 结果。</li>
</ul>
</section>
<section class="card">
<h3>用户入口</h3>
<ul class="list">
<li>前端输入框走 Orchestrator不绕行。</li>
<li>显示回答、引用、建议动作、确认提示和 <code>run_id</code></li>
<li>有加载态和错误态。</li>
</ul>
</section>
</div>
<div class="section-kicker">Acceptance Snapshot</div>
<h2 class="section-title">验收快照</h2>
<div class="table-like">
<div class="row"><div class="row-label">问答闭环</div><div class="row-value">用户在页面上能完成一次自然语言提问、拿到回答、看到引用和 run_id。</div></div>
<div class="row"><div class="row-label">三类场景</div><div class="row-value">至少报销、应收、应付三类财务问题都有结构化回答。</div></div>
<div class="row"><div class="row-label">引用能力</div><div class="row-value">“为什么这笔报销有风险”这类问题能引用规则,而不是只给模糊判断。</div></div>
<div class="row"><div class="row-label">安全边界</div><div class="row-value">“直接付款”“直接审批”类提示不会自动执行,只能变成建议或草稿。</div></div>
</div>
<div class="section-kicker">Common Misses</div>
<h2 class="section-title">这一天最容易漏掉的点</h2>
<ul class="list">
<li>只返回原始查询数据,不把结果翻译成用户可读回答。</li>
<li>只做草稿内容,不做 <code>requires_confirmation</code> 和审计日志。</li>
<li>绕过 Orchestrator 直接从前端打 User Agent导致 Day 4 的统一链路失效。</li>
</ul>
<div class="footer">Day 5 的判断标准是:用户能问、系统能答、回答有依据、动作不越权。</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,133 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Day 6 - Hermes MVP</title>
<link rel="stylesheet" href="./styles.css">
</head>
<body>
<div class="shell">
<div class="topbar">
<a class="brand" href="./index.html"><span class="brand-mark">D6</span><span>Day 6 View</span></a>
<div class="quick-links">
<a class="pill" href="./index.html">返回总览</a>
<a class="pill" href="../agent%20week%20plan/day_6_hermes_mvp.md">周计划原文</a>
<a class="pill" href="../agent%20plan/weekly_execution_details/day_6_hermes_mvp.md">执行细则原文</a>
</div>
</div>
<div class="day-nav">
<a class="pill" href="./day-1.html">Day 1</a>
<a class="pill" href="./day-2.html">Day 2</a>
<a class="pill" href="./day-3.html">Day 3</a>
<a class="pill" href="./day-4.html">Day 4</a>
<a class="pill" href="./day-5.html">Day 5</a>
<a class="pill active" href="./day-6.html">Day 6</a>
<a class="pill" href="./day-7.html">Day 7</a>
</div>
<section class="hero">
<div class="hero-badge">Hermes</div>
<h1>Day 6 Hermes MVP</h1>
<p>Hermes 是后台数字员工,不做即时对话,而是负责定时巡检、风险预警、日报统计、知识候选和规则草稿。它的关键不是“会不会说”,而是“任务能不能跑、结果能不能追”。</p>
<div class="hero-meta">
<div class="meta-card"><div class="meta-label">上游依赖</div><div class="meta-value">Day 4 的 Orchestrator 路由Day 1 的任务与日志表Day 3 的语义结构Day 5 可复用的风险/规则/知识接口。</div></div>
<div class="meta-card"><div class="meta-label">下游交接</div><div class="meta-value">Day 7 要用它做手动触发任务、查看结果、展示规则草稿和知识候选。</div></div>
<div class="meta-card"><div class="meta-label">当天关键</div><div class="meta-value">任务入口、风险项结构、OCR Mock、知识候选和规则草稿都必须可追溯。</div></div>
</div>
</section>
<div class="section-kicker">Three-Layer Mapping</div>
<h2 class="section-title">三层文档映射</h2>
<div class="grid three">
<section class="card tone-warm">
<h3>路线图</h3>
<p>周计划要求实现 Hermes 调度入口、每日风险巡检、统计任务、知识库维护、OCR Mock 和运行结果面板或 API。</p>
<div class="card-links"><a class="link-chip" href="../agent%20week%20plan/day_6_hermes_mvp.md">day_6_hermes_mvp.md</a></div>
</section>
<section class="card tone-teal">
<h3>执行细则</h3>
<p>执行层拆成输入输出、任务调度入口、风险巡检、每日统计、OCR 接入点、知识库维护、规则草稿形成、结果展示和测试。</p>
<div class="card-links"><a class="link-chip" href="../agent%20plan/weekly_execution_details/day_6_hermes_mvp.md">weekly_execution_details/day_6</a></div>
</section>
<section class="card tone-olive">
<h3>架构依据</h3>
<p>主要受 Agent 职责、OCR 架构、知识库架构和反馈学习闭环约束。Hermes 能生成候选和草稿,但不能自动发布正式结果。</p>
<div class="card-links">
<a class="link-chip" href="../agent%20plan/03_agent_responsibilities.md">03</a>
<a class="link-chip" href="../agent%20plan/11_ocr_invoice_architecture.md">11</a>
<a class="link-chip" href="../agent%20plan/12_llm_wiki_knowledge_architecture.md">12</a>
<a class="link-chip" href="../agent%20plan/15_feedback_learning_loop.md">15</a>
</div>
</section>
</div>
<div class="section-kicker">Build Order</div>
<h2 class="section-title">推荐开发顺序</h2>
<div class="timeline">
<div class="timeline-step"><strong>Step 1</strong>先定 <code>HermesTaskRequest</code> / <code>HermesTaskResult</code></div>
<div class="timeline-step"><strong>Step 2</strong>建立手动触发任务 API经 Orchestrator 路由到 Hermes。</div>
<div class="timeline-step"><strong>Step 3</strong>补风险巡检和每日统计的结构化输出。</div>
<div class="timeline-step"><strong>Step 4</strong>接入 OCR Mock、知识候选生成、规则草稿生成。</div>
<div class="timeline-step"><strong>Step 5</strong>补任务详情展示、错误信息和测试。</div>
</div>
<div class="section-kicker">Must Deliver</div>
<h2 class="section-title">今天必须产出的东西</h2>
<div class="grid two">
<section class="card">
<h3>任务调度入口</h3>
<ul class="list">
<li>可手动触发至少一个任务资产。</li>
<li>任务经 Orchestrator 进入 Hermes。</li>
<li>结束后能更新最近执行时间和状态。</li>
</ul>
</section>
<section class="card">
<h3>风险与统计</h3>
<ul class="list">
<li>重复报销、金额超标、应收逾期、应付异常付款等风险有结构化输出。</li>
<li>日报包含报销、报账、应收、应付的关键统计口径。</li>
<li>每个风险项都要能被业务人员理解和追溯。</li>
</ul>
</section>
<section class="card">
<h3>知识候选与规则草稿</h3>
<ul class="list">
<li>知识候选默认是 <code>draft</code>,不能自动发布。</li>
<li>规则草稿保存为 <code>asset_type=rule</code>,状态为 <code>draft</code></li>
<li>两类生成都要写审计日志。</li>
</ul>
</section>
<section class="card">
<h3>OCR Mock 与结果展示</h3>
<ul class="list">
<li>OCR 服务接口和输入输出结构定下来。</li>
<li>当前阶段允许完全使用 Mock 结果。</li>
<li>任务详情或运行日志中能直接看到 Hermes 的执行结果。</li>
</ul>
</section>
</div>
<div class="section-kicker">Acceptance Snapshot</div>
<h2 class="section-title">验收快照</h2>
<div class="table-like">
<div class="row"><div class="row-label">任务可触发</div><div class="row-value">至少一个任务可以手动触发,并能查到结构化结果。</div></div>
<div class="row"><div class="row-label">风险巡检</div><div class="row-value">输出里能看到风险类型、业务对象、触发规则、建议动作和风险等级。</div></div>
<div class="row"><div class="row-label">候选与草稿</div><div class="row-value">知识候选和规则草稿都能生成,但都不是 active / published 正式状态。</div></div>
<div class="row"><div class="row-label">可观察性</div><div class="row-value">不用查数据库,也能从任务详情或运行日志判断 Hermes 是否执行成功。</div></div>
</div>
<div class="section-kicker">Common Misses</div>
<h2 class="section-title">这一天最容易漏掉的点</h2>
<ul class="list">
<li>只做 Hermes 服务逻辑,不做任务入口和结果展示,最后无法演示。</li>
<li>能生成知识或规则,但没把状态锁在 <code>draft</code>,会直接越过人工审核边界。</li>
<li>OCR Mock 只返回一段自由文本,不定义结构字段,后面无法和规则或风险逻辑对接。</li>
</ul>
<div class="footer">Day 6 的价值是让“后台数字员工”第一次具备可触发、可解释、可留痕的闭环。</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,132 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Day 7 - 加固、演示和验收</title>
<link rel="stylesheet" href="./styles.css">
</head>
<body>
<div class="shell">
<div class="topbar">
<a class="brand" href="./index.html"><span class="brand-mark">D7</span><span>Day 7 View</span></a>
<div class="quick-links">
<a class="pill" href="./index.html">返回总览</a>
<a class="pill" href="../agent%20week%20plan/day_7_hardening_demo_acceptance.md">周计划原文</a>
<a class="pill" href="../agent%20plan/weekly_execution_details/day_7_hardening_demo_acceptance.md">执行细则原文</a>
</div>
</div>
<div class="day-nav">
<a class="pill" href="./day-1.html">Day 1</a>
<a class="pill" href="./day-2.html">Day 2</a>
<a class="pill" href="./day-3.html">Day 3</a>
<a class="pill" href="./day-4.html">Day 4</a>
<a class="pill" href="./day-5.html">Day 5</a>
<a class="pill" href="./day-6.html">Day 6</a>
<a class="pill active" href="./day-7.html">Day 7</a>
</div>
<section class="hero">
<div class="hero-badge">Hardening</div>
<h1>Day 7 加固、演示和验收</h1>
<p>Day 7 不再追求新增大功能,而是把 Day 1 到 Day 6 的链路整理成“可演示、可验收、可继续接手”的状态。没有这一层收口,前面做出来的东西很容易停在“只有作者自己懂”的阶段。</p>
<div class="hero-meta">
<div class="meta-card"><div class="meta-label">上游依赖</div><div class="meta-value">Day 1 到 Day 6 的全部核心路径。</div></div>
<div class="meta-card"><div class="meta-label">当天输出</div><div class="meta-value">回归记录、权限边界、审计和 Trace 补齐、测试记录、演示脚本、交接说明。</div></div>
<div class="meta-card"><div class="meta-label">当天关键</div><div class="meta-value">冻结新增需求,只收验收相关缺口。</div></div>
</div>
</section>
<div class="section-kicker">Three-Layer Mapping</div>
<h2 class="section-title">三层文档映射</h2>
<div class="grid three">
<section class="card tone-warm">
<h3>路线图</h3>
<p>周计划要求完成回归、权限补齐、审计补齐、错误态和空态、评测、演示数据、构建和交付说明。</p>
<div class="card-links"><a class="link-chip" href="../agent%20week%20plan/day_7_hardening_demo_acceptance.md">day_7_hardening_demo_acceptance.md</a></div>
</section>
<section class="card tone-teal">
<h3>执行细则</h3>
<p>执行层拆成核心链路回归、权限和风险边界、审计和 Trace、前端体验修补、测试补齐、评测集、演示数据、演示脚本和文档收尾。</p>
<div class="card-links"><a class="link-chip" href="../agent%20plan/weekly_execution_details/day_7_hardening_demo_acceptance.md">weekly_execution_details/day_7</a></div>
</section>
<section class="card tone-olive">
<h3>架构依据</h3>
<p>主要受整体 README、开发路线图、可观测性和评测集约束。Day 7 的本质是把所有边界和证据讲清楚。</p>
<div class="card-links">
<a class="link-chip" href="../agent%20plan/00_README.md">00</a>
<a class="link-chip" href="../agent%20plan/05_development_roadmap.md">05</a>
<a class="link-chip" href="../agent%20plan/09_observability_and_trace.md">09</a>
<a class="link-chip" href="../agent%20plan/10_evaluation_and_testset.md">10</a>
</div>
</section>
</div>
<div class="section-kicker">Build Order</div>
<h2 class="section-title">推荐收口顺序</h2>
<div class="timeline">
<div class="timeline-step"><strong>Step 1</strong>先汇总 Day 1 到 Day 6 未完成项,冻结新增需求。</div>
<div class="timeline-step"><strong>Step 2</strong>回归核心链路资产、规则、语义解析、Orchestrator、User Agent、Hermes、Trace、AuditLog。</div>
<div class="timeline-step"><strong>Step 3</strong>补权限边界与高风险动作拦截。</div>
<div class="timeline-step"><strong>Step 4</strong>补测试、评测、演示数据和前端体验问题。</div>
<div class="timeline-step"><strong>Step 5</strong>写演示脚本和交接说明,形成最终交付。</div>
</div>
<div class="section-kicker">Must Deliver</div>
<h2 class="section-title">今天必须产出的东西</h2>
<div class="grid two">
<section class="card">
<h3>回归与边界</h3>
<ul class="list">
<li>未审核规则不能上线。</li>
<li>付款、审批、上线等高风险动作都不能绕过确认。</li>
<li>disabled 能力不能被调用。</li>
</ul>
</section>
<section class="card">
<h3>审计与 Trace</h3>
<ul class="list">
<li>规则保存、审核、上线都能看到 AuditLog。</li>
<li>Hermes 生成知识候选 / 规则草稿有审计。</li>
<li>任意演示路径都能追到 <code>run_id</code></li>
</ul>
</section>
<section class="card">
<h3>测试、评测、演示数据</h3>
<ul class="list">
<li>后端测试、前端构建、语义评测至少有执行记录。</li>
<li>报销 / 应收 / 应付 / 风险 / 知识都准备好演示数据。</li>
<li>失败样例和已知边界要明确写出。</li>
</ul>
</section>
<section class="card">
<h3>演示脚本与交接</h3>
<ul class="list">
<li>从任务规则中心、规则详情、版本切换、上线拦截,到 User Agent 问答、Hermes 任务、Trace 和审计,都有明确步骤。</li>
<li>新开发者按脚本能走通一遍。</li>
</ul>
</section>
</div>
<div class="section-kicker">Acceptance Snapshot</div>
<h2 class="section-title">最终验收快照</h2>
<div class="table-like">
<div class="row"><div class="row-label">端到端链路</div><div class="row-value">从规则中心到 User Agent再到 Hermes 和 Trace至少有一条完整演示路径可复现。</div></div>
<div class="row"><div class="row-label">证据完整</div><div class="row-value">AgentRun、ToolCall、AuditLog、测试记录、评测结果和演示脚本都存在。</div></div>
<div class="row"><div class="row-label">风险边界</div><div class="row-value">MVP 期间不存在绕过人工审核、自动付款、自动上线的暗门路径。</div></div>
<div class="row"><div class="row-label">可交接性</div><div class="row-value">下一位开发或 Codex 打开文档就能知道已完成、未完成和生产化前必补项。</div></div>
</div>
<div class="section-kicker">Common Misses</div>
<h2 class="section-title">这一天最容易漏掉的点</h2>
<ul class="list">
<li>只验证 Happy Path不回归错误态、空态、禁用态和被权限拦截路径。</li>
<li>能讲演示,但没有测试记录和已知风险说明,交接质量会很差。</li>
<li>前 6 天的 TODO 没回写完成状态,导致页面和 Markdown 脱节。</li>
</ul>
<div class="footer">Day 7 的目标不是继续堆功能,而是把一周产出变成别人也能运行、理解和接手的系统。</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,181 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Agent Week Plan HTML</title>
<link rel="stylesheet" href="./styles.css">
</head>
<body>
<div class="shell">
<div class="topbar">
<a class="brand" href="./index.html">
<span class="brand-mark">A7</span>
<span>Agent Week HTML</span>
</a>
<div class="quick-links">
<a class="pill" href="../agent%20week%20plan/MASTER_TODO.md">周计划总控</a>
<a class="pill" href="../agent%20plan/weekly_execution_details/00_execution_index.md">执行细则索引</a>
<a class="pill" href="../agent%20plan/00_README.md">架构目录</a>
</div>
</div>
<section class="hero">
<div class="hero-badge">Static Map</div>
<h1>把 7 天周计划变成可直接浏览的开发视图</h1>
<p>这一套 HTML 页面不是替代 Markdown而是把 <code>agent week plan</code><code>weekly_execution_details</code><code>agent plan</code> 的对应关系收成一个稳定入口。Codex 可以按日推进,开发人员也能按目标、依赖、验收和风险快速定位。</p>
<div class="hero-meta">
<div class="meta-card">
<div class="meta-label">阅读顺序</div>
<div class="meta-value">先总览,再选 Day再跳转到具体 Markdown 落地执行。</div>
</div>
<div class="meta-card">
<div class="meta-label">核心视图</div>
<div class="meta-value">路线图、执行细则、架构依据三层同时可见。</div>
</div>
<div class="meta-card">
<div class="meta-label">适用对象</div>
<div class="meta-value">Codex 开发、后端开发、前端开发、项目 owner、验收人员。</div>
</div>
</div>
</section>
<div class="section-kicker">How To Use</div>
<h2 class="section-title">怎么用这套页面</h2>
<div class="grid two">
<section class="card tone-teal">
<h3>Codex 开发视角</h3>
<ol class="list">
<li>先看今天在哪一天,确认上游依赖和下游交接。</li>
<li>用“三层映射”定位:周计划看目标,执行细则看步骤,架构文档看约束。</li>
<li>按“推荐开发顺序”推进,不跳天,不跨层乱做。</li>
<li>完成后回到原始 Markdown把 TODO、阻塞、交接更新回文档。</li>
</ol>
</section>
<section class="card tone-warm">
<h3>人工开发与验收视角</h3>
<ol class="list">
<li>先看每一天的“今日定位”,知道这一天到底产出什么。</li>
<li>再看“今天必须产出的东西”和“验收快照”,确认完成标准。</li>
<li>最后跳转到对应 Markdown逐条执行或验收。</li>
<li>如果发现跨天阻塞,优先回前一天补地基,而不是在当前天临时兜底。</li>
</ol>
</section>
</div>
<div class="section-kicker">Three Layers</div>
<h2 class="section-title">文档结构一眼看清</h2>
<div class="grid three">
<section class="card">
<h3>1. 周计划路线图</h3>
<p>定义每天的大方向、交付物和验收门槛。用于排期、对齐和验收。核心入口是 <code>MASTER_TODO.md</code> 和 Day 1 到 Day 7 daily 文档。</p>
<div class="card-links">
<a class="link-chip" href="../agent%20week%20plan/00_README.md">00_README</a>
<a class="link-chip" href="../agent%20week%20plan/MASTER_TODO.md">MASTER_TODO</a>
</div>
</section>
<section class="card">
<h3>2. 执行细则</h3>
<p>把每天的开发目标拆到模型、字段、接口、服务、前端、测试和验收证据。这里是 Codex 和研发的直接执行层。</p>
<div class="card-links">
<a class="link-chip" href="../agent%20plan/weekly_execution_details/README.md">README</a>
<a class="link-chip" href="../agent%20plan/weekly_execution_details/00_execution_index.md">执行索引</a>
</div>
</section>
<section class="card">
<h3>3. 架构依据</h3>
<p>提供为什么要这么做、协议怎么定、权限和审计边界是什么。它不直接当 TODO但所有实现都要受它约束。</p>
<div class="card-links">
<a class="link-chip" href="../agent%20plan/01_overall_architecture.md">总体架构</a>
<a class="link-chip" href="../agent%20plan/02_semantic_ontology.md">语义本体</a>
<a class="link-chip" href="../agent%20plan/09_observability_and_trace.md">观测与 Trace</a>
</div>
</section>
</div>
<div class="section-kicker">Seven Days</div>
<h2 class="section-title">7 天总览</h2>
<div class="grid two">
<section class="card tone-olive">
<h3>Day 1 基础模型与工程骨架</h3>
<p><strong>当前状态:</strong>已完成2026-05-11。先把 Agent 资产、版本、审核、运行日志、审计日志,以及报销 / 应收 / 应付的最小业务数据来源定下来。后面所有能力都站在这一天的模型上。</p>
<div class="card-links">
<a class="link-chip" href="./day-1.html">打开日视图</a>
<a class="link-chip" href="../agent%20week%20plan/day_1_foundation_models.md">周计划</a>
<a class="link-chip" href="../agent%20plan/weekly_execution_details/day_1_foundation_models.md">执行细则</a>
</div>
</section>
<section class="card tone-teal">
<h3>Day 2 任务规则中心联调</h3>
<p>把规则、技能、MCP、任务从静态 UI 拉到真实后端数据。重点是规则 Markdown、版本切换、审核和上线拦截。</p>
<div class="card-links">
<a class="link-chip" href="./day-2.html">打开日视图</a>
<a class="link-chip" href="../agent%20week%20plan/day_2_rule_center_integration.md">周计划</a>
<a class="link-chip" href="../agent%20plan/weekly_execution_details/day_2_rule_center_integration.md">执行细则</a>
</div>
</section>
<section class="card tone-warm">
<h3>Day 3 语义本体 MVP</h3>
<p>建立 8 字段语义解析协议,让报销、应收、应付、知识查询进入同一结构,给 Orchestrator、User Agent、Hermes 统一消费。</p>
<div class="card-links">
<a class="link-chip" href="./day-3.html">打开日视图</a>
<a class="link-chip" href="../agent%20week%20plan/day_3_semantic_ontology_mvp.md">周计划</a>
<a class="link-chip" href="../agent%20plan/weekly_execution_details/day_3_semantic_ontology_mvp.md">执行细则</a>
</div>
</section>
<section class="card">
<h3>Day 4 Orchestrator 运行时</h3>
<p>把用户消息和定时任务统一接到 Orchestrator完成 run_id、权限拦截、Agent 路由、ToolCall 和 Trace。</p>
<div class="card-links">
<a class="link-chip" href="./day-4.html">打开日视图</a>
<a class="link-chip" href="../agent%20week%20plan/day_4_orchestrator_runtime.md">周计划</a>
<a class="link-chip" href="../agent%20plan/weekly_execution_details/day_4_orchestrator_runtime.md">执行细则</a>
</div>
</section>
<section class="card tone-teal">
<h3>Day 5 User Agent MVP</h3>
<p>面向用户的问答和流程辅助层。做查询、解释、规则引用、草稿生成,但严格不碰自动审批、自动付款和自动上线。</p>
<div class="card-links">
<a class="link-chip" href="./day-5.html">打开日视图</a>
<a class="link-chip" href="../agent%20week%20plan/day_5_user_agent_mvp.md">周计划</a>
<a class="link-chip" href="../agent%20plan/weekly_execution_details/day_5_user_agent_mvp.md">执行细则</a>
</div>
</section>
<section class="card tone-olive">
<h3>Day 6 Hermes MVP</h3>
<p>后台数字员工层。做任务触发、风险巡检、日报统计、OCR Mock、知识候选、规则草稿结果都必须可追溯。</p>
<div class="card-links">
<a class="link-chip" href="./day-6.html">打开日视图</a>
<a class="link-chip" href="../agent%20week%20plan/day_6_hermes_mvp.md">周计划</a>
<a class="link-chip" href="../agent%20plan/weekly_execution_details/day_6_hermes_mvp.md">执行细则</a>
</div>
</section>
<section class="card tone-accent">
<h3>Day 7 加固、演示和验收</h3>
<p>不再大扩功能只做回归、权限边界、审计、Trace、测试、演示脚本和交接收口让整周产出可跑、可演示、可继续接手。</p>
<div class="card-links">
<a class="link-chip" href="./day-7.html">打开日视图</a>
<a class="link-chip" href="../agent%20week%20plan/day_7_hardening_demo_acceptance.md">周计划</a>
<a class="link-chip" href="../agent%20plan/weekly_execution_details/day_7_hardening_demo_acceptance.md">执行细则</a>
</div>
</section>
</div>
<div class="section-kicker">Dependency Chain</div>
<h2 class="section-title">跨天依赖链</h2>
<div class="timeline">
<div class="timeline-step"><strong>Day 1</strong>模型、审计、运行日志、最小业务数据源</div>
<div class="timeline-step"><strong>Day 2</strong>把 Day 1 的资产 API 接进规则中心 UI</div>
<div class="timeline-step"><strong>Day 3</strong>在 Day 1/2 基础上产出统一语义结构</div>
<div class="timeline-step"><strong>Day 4</strong>用 Day 3 的语义结果完成路由与权限</div>
<div class="timeline-step"><strong>Day 5</strong>接入 User Agent 问答、解释和草稿</div>
<div class="timeline-step"><strong>Day 6</strong>接入 Hermes 任务、巡检和知识/规则候选</div>
<div class="timeline-step"><strong>Day 7</strong>统一回归、补日志、做演示和交接</div>
</div>
<div class="footer">
打开顺序建议:<a href="./day-1.html">Day 1</a><a href="./day-7.html">Day 7</a>。真正执行时,仍以原始 Markdown 为准,这套 HTML 负责加速定位和浏览。
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,426 @@
:root {
--bg: #f3ead9;
--bg-deep: #e7d8bc;
--panel: rgba(255, 250, 241, 0.9);
--panel-strong: #fff8ee;
--ink: #1f2a24;
--muted: #64655d;
--line: #dbc8a9;
--accent: #bb5b2c;
--accent-strong: #8d3d1b;
--accent-soft: #f4d9bf;
--teal: #20656d;
--teal-soft: #d8ecee;
--olive: #5f6b3a;
--olive-soft: #e6ecd7;
--shadow: 0 24px 60px rgba(84, 59, 30, 0.12);
--radius-xl: 28px;
--radius-lg: 20px;
--radius-md: 14px;
--max: 1240px;
}
* {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
margin: 0;
font-family: "Trebuchet MS", "Gill Sans", "Lucida Grande", sans-serif;
color: var(--ink);
background:
radial-gradient(circle at top left, rgba(32, 101, 109, 0.14), transparent 26%),
radial-gradient(circle at top right, rgba(187, 91, 44, 0.15), transparent 30%),
linear-gradient(180deg, #f8f0e2 0%, var(--bg) 40%, #efe2cb 100%);
}
a {
color: inherit;
}
.shell {
width: min(100% - 40px, var(--max));
margin: 0 auto;
padding: 28px 0 56px;
}
.topbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
flex-wrap: wrap;
margin-bottom: 18px;
}
.brand {
display: inline-flex;
align-items: center;
gap: 12px;
text-decoration: none;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--accent-strong);
}
.brand-mark {
display: inline-flex;
align-items: center;
justify-content: center;
width: 42px;
height: 42px;
border-radius: 50%;
background: linear-gradient(135deg, var(--accent), #df9a44);
color: #fff7ef;
box-shadow: 0 14px 30px rgba(187, 91, 44, 0.28);
}
.quick-links,
.day-nav {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.pill {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 38px;
padding: 10px 14px;
border-radius: 999px;
border: 1px solid rgba(143, 114, 74, 0.22);
background: rgba(255, 248, 238, 0.75);
text-decoration: none;
color: var(--muted);
font-size: 14px;
transition: transform 180ms ease, border-color 180ms ease, background 180ms ease;
}
.pill:hover,
.pill:focus-visible {
transform: translateY(-1px);
border-color: rgba(187, 91, 44, 0.4);
background: rgba(255, 251, 245, 0.96);
outline: none;
}
.pill.active {
color: #fff6ef;
border-color: transparent;
background: linear-gradient(135deg, var(--accent-strong), var(--accent));
box-shadow: 0 14px 24px rgba(141, 61, 27, 0.24);
}
.hero {
position: relative;
overflow: hidden;
margin-bottom: 22px;
padding: 30px;
border: 1px solid rgba(128, 109, 82, 0.18);
border-radius: var(--radius-xl);
background:
linear-gradient(135deg, rgba(255, 248, 238, 0.95), rgba(247, 236, 216, 0.88)),
var(--panel);
box-shadow: var(--shadow);
}
.hero::after {
content: "";
position: absolute;
right: -50px;
top: -50px;
width: 220px;
height: 220px;
border-radius: 50%;
background: radial-gradient(circle, rgba(32, 101, 109, 0.16), transparent 68%);
}
.hero-badge {
display: inline-flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
padding: 7px 12px;
border-radius: 999px;
background: var(--accent-soft);
color: var(--accent-strong);
font-size: 13px;
font-weight: 700;
letter-spacing: 0.05em;
text-transform: uppercase;
}
.hero h1 {
margin: 0;
font-family: "Iowan Old Style", "Palatino Linotype", "Book Antiqua", serif;
font-size: clamp(34px, 5vw, 62px);
line-height: 1.03;
}
.hero p {
max-width: 880px;
margin: 14px 0 0;
color: var(--muted);
font-size: 18px;
line-height: 1.65;
}
.hero-meta {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 14px;
margin-top: 20px;
}
.meta-card {
padding: 14px 16px;
border-radius: var(--radius-md);
background: rgba(255, 255, 255, 0.55);
border: 1px solid rgba(132, 109, 83, 0.16);
}
.meta-label {
margin-bottom: 6px;
color: var(--muted);
font-size: 12px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.meta-value {
font-size: 16px;
line-height: 1.45;
}
.grid {
display: grid;
gap: 18px;
}
.grid.two {
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
.grid.three {
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}
.card {
padding: 22px;
border: 1px solid rgba(132, 109, 83, 0.15);
border-radius: var(--radius-lg);
background: var(--panel);
box-shadow: 0 16px 36px rgba(78, 58, 32, 0.08);
animation: rise 420ms ease both;
}
.card:nth-child(2) { animation-delay: 60ms; }
.card:nth-child(3) { animation-delay: 120ms; }
.card:nth-child(4) { animation-delay: 180ms; }
.card:nth-child(5) { animation-delay: 240ms; }
.card h2,
.card h3 {
margin: 0 0 10px;
font-family: "Iowan Old Style", "Palatino Linotype", "Book Antiqua", serif;
}
.card h2 {
font-size: 28px;
}
.card h3 {
font-size: 22px;
}
.card p {
margin: 0;
color: var(--muted);
line-height: 1.7;
}
.section-title {
margin: 28px 0 14px;
font-family: "Iowan Old Style", "Palatino Linotype", "Book Antiqua", serif;
font-size: 28px;
}
.section-kicker {
margin: 30px 0 8px;
color: var(--accent-strong);
font-size: 13px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.list,
.compact-list {
margin: 12px 0 0;
padding-left: 18px;
color: var(--ink);
line-height: 1.72;
}
.compact-list {
font-size: 15px;
}
.list li + li,
.compact-list li + li {
margin-top: 8px;
}
.card-links {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 16px;
}
.link-chip {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 13px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.76);
border: 1px solid rgba(132, 109, 83, 0.18);
text-decoration: none;
font-size: 14px;
}
.tone-warm {
background: linear-gradient(180deg, rgba(244, 217, 191, 0.55), rgba(255, 250, 241, 0.9));
}
.tone-teal {
background: linear-gradient(180deg, rgba(216, 236, 238, 0.76), rgba(255, 250, 241, 0.92));
}
.tone-olive {
background: linear-gradient(180deg, rgba(230, 236, 215, 0.82), rgba(255, 250, 241, 0.92));
}
.tone-accent {
background: linear-gradient(160deg, rgba(141, 61, 27, 0.94), rgba(187, 91, 44, 0.92));
color: #fff8f1;
}
.tone-accent p,
.tone-accent .meta-label,
.tone-accent .meta-value,
.tone-accent li {
color: rgba(255, 248, 241, 0.92);
}
.tone-accent .link-chip,
.tone-accent .pill {
background: rgba(255, 255, 255, 0.14);
border-color: rgba(255, 255, 255, 0.18);
color: #fff8f1;
}
.timeline {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 12px;
}
.timeline-step {
position: relative;
padding: 16px;
border-radius: var(--radius-md);
border: 1px solid rgba(132, 109, 83, 0.16);
background: rgba(255, 252, 247, 0.84);
}
.timeline-step strong {
display: block;
margin-bottom: 8px;
font-size: 15px;
}
.footer {
margin-top: 26px;
padding: 20px 4px 0;
color: var(--muted);
font-size: 14px;
}
.muted {
color: var(--muted);
}
.table-like {
display: grid;
gap: 12px;
}
.row {
display: grid;
grid-template-columns: minmax(120px, 0.9fr) minmax(0, 2.3fr);
gap: 14px;
padding: 14px 16px;
border-radius: var(--radius-md);
border: 1px solid rgba(132, 109, 83, 0.15);
background: rgba(255, 255, 255, 0.56);
}
.row-label {
font-size: 13px;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--accent-strong);
}
.row-value {
line-height: 1.68;
}
code {
padding: 1px 6px;
border-radius: 8px;
background: rgba(32, 101, 109, 0.08);
color: var(--teal);
font-family: "Lucida Console", "Courier New", monospace;
font-size: 0.92em;
}
@keyframes rise {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@media (max-width: 760px) {
.shell {
width: min(100% - 24px, var(--max));
padding-top: 18px;
}
.hero {
padding: 22px;
}
.hero p {
font-size: 16px;
}
.row {
grid-template-columns: 1fr;
}
}

View File

@@ -0,0 +1,163 @@
from __future__ import annotations
from typing import Annotated
from fastapi import APIRouter, Depends, Header, HTTPException, Query, status
from sqlalchemy.orm import Session
from app.api.deps import get_db
from app.schemas.agent_asset import (
AgentAssetCreate,
AgentAssetListItem,
AgentAssetRead,
AgentAssetReviewCreate,
AgentAssetReviewRead,
AgentAssetUpdate,
AgentAssetVersionCreate,
AgentAssetVersionRead,
)
from app.services.agent_assets import AgentAssetService
router = APIRouter(prefix="/agent-assets")
DbSession = Annotated[Session, Depends(get_db)]
def _handle_asset_error(exc: Exception) -> None:
if isinstance(exc, LookupError):
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
if isinstance(exc, PermissionError):
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc
if isinstance(exc, ValueError):
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc
raise exc
@router.get("", response_model=list[AgentAssetListItem])
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),
) -> list[AgentAssetListItem]:
return AgentAssetService(db).list_assets(
asset_type=asset_type,
status=status_value,
domain=domain,
keyword=keyword,
)
@router.get("/{asset_id}", response_model=AgentAssetRead)
def get_agent_asset(asset_id: str, db: DbSession) -> AgentAssetRead:
asset = AgentAssetService(db).get_asset(asset_id)
if asset is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Asset not found")
return asset
@router.post("", response_model=AgentAssetRead, status_code=status.HTTP_201_CREATED)
def create_agent_asset(
payload: AgentAssetCreate,
db: DbSession,
x_actor: Annotated[str | None, Header()] = None,
x_request_id: Annotated[str | None, Header()] = None,
) -> AgentAssetRead:
try:
return AgentAssetService(db).create_asset(
payload,
actor=(x_actor or payload.owner).strip() or "system",
request_id=x_request_id,
)
except Exception as exc:
_handle_asset_error(exc)
@router.patch("/{asset_id}", response_model=AgentAssetRead)
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,
) -> AgentAssetRead:
try:
return AgentAssetService(db).update_asset(
asset_id,
payload,
actor=(x_actor or "system").strip() or "system",
request_id=x_request_id,
)
except Exception as exc:
_handle_asset_error(exc)
@router.get("/{asset_id}/versions", response_model=list[AgentAssetVersionRead])
def list_agent_asset_versions(
asset_id: str, db: DbSession, limit: int = Query(default=20, ge=1, le=100)
) -> list[AgentAssetVersionRead]:
try:
return AgentAssetService(db).list_versions(asset_id, limit=limit)
except Exception as exc:
_handle_asset_error(exc)
@router.post(
"/{asset_id}/versions",
response_model=AgentAssetVersionRead,
status_code=status.HTTP_201_CREATED,
)
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,
) -> AgentAssetVersionRead:
try:
return AgentAssetService(db).create_version(
asset_id,
payload,
actor=(x_actor or payload.created_by).strip() or "system",
request_id=x_request_id,
)
except Exception as exc:
_handle_asset_error(exc)
@router.post(
"/{asset_id}/reviews", response_model=AgentAssetReviewRead, status_code=status.HTTP_201_CREATED
)
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,
) -> AgentAssetReviewRead:
try:
return AgentAssetService(db).create_review(
asset_id,
payload,
actor=(x_actor or payload.reviewer).strip() or "system",
request_id=x_request_id,
)
except Exception as exc:
_handle_asset_error(exc)
@router.post("/{asset_id}/activate", response_model=AgentAssetRead)
def activate_agent_asset(
asset_id: str,
db: DbSession,
x_actor: Annotated[str | None, Header()] = None,
x_request_id: Annotated[str | None, Header()] = None,
) -> AgentAssetRead:
try:
return AgentAssetService(db).activate_asset(
asset_id,
actor=(x_actor or "system").strip() or "system",
request_id=x_request_id,
)
except Exception as exc:
_handle_asset_error(exc)

View File

@@ -0,0 +1,34 @@
from __future__ import annotations
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy.orm import Session
from app.api.deps import get_db
from app.schemas.agent_run import AgentRunRead
from app.services.agent_runs import AgentRunService
router = APIRouter(prefix="/agent-runs")
DbSession = Annotated[Session, Depends(get_db)]
@router.get("", response_model=list[AgentRunRead])
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),
) -> list[AgentRunRead]:
return AgentRunService(db).list_runs(
agent=agent, status=status_value, source=source, limit=limit
)
@router.get("/{run_id}", response_model=AgentRunRead)
def get_agent_run(run_id: str, db: DbSession) -> AgentRunRead:
run = AgentRunService(db).get_run(run_id)
if run is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Run not found")
return run

View File

@@ -0,0 +1,29 @@
from __future__ import annotations
from typing import Annotated
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
from app.api.deps import get_db
from app.schemas.audit_log import AuditLogRead
from app.services.audit import AuditLogService
router = APIRouter(prefix="/audit-logs")
DbSession = Annotated[Session, Depends(get_db)]
@router.get("", response_model=list[AuditLogRead])
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),
) -> list[AuditLogRead]:
return AuditLogService(db).list_logs(
resource_type=resource_type,
resource_id=resource_id,
action=action,
limit=limit,
)

View File

@@ -1,18 +1,24 @@
from fastapi import APIRouter
from app.api.v1.endpoints.auth import router as auth_router
from app.api.v1.endpoints.bootstrap import router as bootstrap_router
from app.api.v1.endpoints.employees import router as employees_router
from app.api.v1.endpoints.health import router as health_router
from app.api.v1.endpoints.knowledge import router as knowledge_router
from fastapi import APIRouter
from app.api.v1.endpoints.agent_assets import router as agent_assets_router
from app.api.v1.endpoints.agent_runs import router as agent_runs_router
from app.api.v1.endpoints.audit_logs import router as audit_logs_router
from app.api.v1.endpoints.auth import router as auth_router
from app.api.v1.endpoints.bootstrap import router as bootstrap_router
from app.api.v1.endpoints.employees import router as employees_router
from app.api.v1.endpoints.health import router as health_router
from app.api.v1.endpoints.knowledge import router as knowledge_router
from app.api.v1.endpoints.reimbursements import router as reimbursements_router
from app.api.v1.endpoints.settings import router as settings_router
router = APIRouter()
router.include_router(health_router, tags=["health"])
router.include_router(bootstrap_router, tags=["bootstrap"])
router.include_router(auth_router, tags=["auth"])
router.include_router(knowledge_router, tags=["knowledge"])
router.include_router(employees_router, prefix="/employees", tags=["employees"])
router.include_router(reimbursements_router, prefix="/reimbursements", tags=["reimbursements"])
router.include_router(settings_router, tags=["settings"])
router = APIRouter()
router.include_router(health_router, tags=["health"])
router.include_router(bootstrap_router, tags=["bootstrap"])
router.include_router(auth_router, tags=["auth"])
router.include_router(agent_assets_router, tags=["agent-assets"])
router.include_router(agent_runs_router, tags=["agent-runs"])
router.include_router(audit_logs_router, tags=["audit-logs"])
router.include_router(knowledge_router, tags=["knowledge"])
router.include_router(employees_router, prefix="/employees", tags=["employees"])
router.include_router(reimbursements_router, prefix="/reimbursements", tags=["reimbursements"])
router.include_router(settings_router, tags=["settings"])

View File

@@ -0,0 +1,70 @@
from __future__ import annotations
from enum import StrEnum
class AgentAssetType(StrEnum):
RULE = "rule"
SKILL = "skill"
MCP = "mcp"
TASK = "task"
class AgentAssetStatus(StrEnum):
DRAFT = "draft"
REVIEW = "review"
ACTIVE = "active"
DISABLED = "disabled"
class AgentReviewStatus(StrEnum):
PENDING = "pending"
APPROVED = "approved"
REJECTED = "rejected"
class AgentName(StrEnum):
ORCHESTRATOR = "orchestrator"
USER_AGENT = "user_agent"
HERMES = "hermes"
class AgentRunSource(StrEnum):
USER_MESSAGE = "user_message"
SCHEDULE = "schedule"
SYSTEM_EVENT = "system_event"
class AgentPermissionLevel(StrEnum):
READ = "read"
DRAFT_WRITE = "draft_write"
APPROVAL_REQUIRED = "approval_required"
FORBIDDEN = "forbidden"
class AgentAssetContentType(StrEnum):
MARKDOWN = "markdown"
JSON = "json"
class AgentRunStatus(StrEnum):
RUNNING = "running"
SUCCEEDED = "succeeded"
FAILED = "failed"
BLOCKED = "blocked"
class AgentToolType(StrEnum):
MCP = "mcp"
DATABASE = "database"
LLM = "llm"
OCR = "ocr"
RULE_ENGINE = "rule_engine"
class AgentAssetDomain(StrEnum):
EXPENSE = "expense"
AR = "ar"
AP = "ap"
KNOWLEDGE = "knowledge"
SYSTEM = "system"

View File

@@ -1,23 +1,43 @@
from app.db.base_class import Base
from app.models.approval import ApprovalRecord
from app.models.employee_change_log import EmployeeChangeLog
from app.models.employee import Employee
from app.models.organization import OrganizationUnit
from app.models.reimbursement import ReimbursementRequest
from app.models.role import Role
from app.models.system_model_setting import SystemModelSetting
from app.models.system_setting import SystemSetting
from app.models.system_setting_secret import SystemSettingSecret
__all__ = [
"Base",
"ApprovalRecord",
"Employee",
"EmployeeChangeLog",
"OrganizationUnit",
"ReimbursementRequest",
"Role",
"SystemModelSetting",
"SystemSetting",
"SystemSettingSecret",
]
from app.db.base_class import Base
from app.models.agent_asset import AgentAsset, AgentAssetReview, AgentAssetVersion
from app.models.agent_run import AgentRun, AgentToolCall, SemanticParseLog
from app.models.approval import ApprovalRecord
from app.models.audit_log import AuditLog
from app.models.employee_change_log import EmployeeChangeLog
from app.models.employee import Employee
from app.models.financial_record import (
AccountsPayableRecord,
AccountsReceivableRecord,
ExpenseClaim,
ExpenseClaimItem,
)
from app.models.organization import OrganizationUnit
from app.models.reimbursement import ReimbursementRequest
from app.models.role import Role
from app.models.system_model_setting import SystemModelSetting
from app.models.system_setting import SystemSetting
from app.models.system_setting_secret import SystemSettingSecret
__all__ = [
"Base",
"AccountsPayableRecord",
"AccountsReceivableRecord",
"AgentAsset",
"AgentAssetReview",
"AgentAssetVersion",
"AgentRun",
"AgentToolCall",
"ApprovalRecord",
"AuditLog",
"Employee",
"EmployeeChangeLog",
"ExpenseClaim",
"ExpenseClaimItem",
"OrganizationUnit",
"ReimbursementRequest",
"Role",
"SemanticParseLog",
"SystemModelSetting",
"SystemSetting",
"SystemSettingSecret",
]

View File

@@ -3,12 +3,13 @@ from __future__ import annotations
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.middleware.logging import AccessLogMiddleware
from app.services.employee import prepare_employee_directory
from app.services.knowledge import prepare_knowledge_library
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.middleware.logging import AccessLogMiddleware
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:
@@ -49,11 +50,12 @@ def create_app() -> FastAPI:
return {"message": f"{settings.app_name} is running"}
@app.on_event("startup")
def _on_startup() -> None:
prepare_employee_directory()
prepare_knowledge_library()
logger.info(
"Server ready - host=%s port=%s prefix=%s",
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,

View File

@@ -1,21 +1,41 @@
from app.models.approval import ApprovalRecord
from app.models.employee_change_log import EmployeeChangeLog
from app.models.employee import Employee
from app.models.organization import OrganizationUnit
from app.models.reimbursement import ReimbursementRequest
from app.models.role import Role
from app.models.system_model_setting import SystemModelSetting
from app.models.system_setting import SystemSetting
from app.models.system_setting_secret import SystemSettingSecret
__all__ = [
"ApprovalRecord",
"Employee",
"EmployeeChangeLog",
"OrganizationUnit",
"ReimbursementRequest",
"Role",
"SystemModelSetting",
"SystemSetting",
"SystemSettingSecret",
]
from app.models.agent_asset import AgentAsset, AgentAssetReview, AgentAssetVersion
from app.models.agent_run import AgentRun, AgentToolCall, SemanticParseLog
from app.models.approval import ApprovalRecord
from app.models.audit_log import AuditLog
from app.models.employee_change_log import EmployeeChangeLog
from app.models.employee import Employee
from app.models.financial_record import (
AccountsPayableRecord,
AccountsReceivableRecord,
ExpenseClaim,
ExpenseClaimItem,
)
from app.models.organization import OrganizationUnit
from app.models.reimbursement import ReimbursementRequest
from app.models.role import Role
from app.models.system_model_setting import SystemModelSetting
from app.models.system_setting import SystemSetting
from app.models.system_setting_secret import SystemSettingSecret
__all__ = [
"AccountsPayableRecord",
"AccountsReceivableRecord",
"AgentAsset",
"AgentAssetReview",
"AgentAssetVersion",
"AgentRun",
"AgentToolCall",
"ApprovalRecord",
"AuditLog",
"Employee",
"EmployeeChangeLog",
"ExpenseClaim",
"ExpenseClaimItem",
"OrganizationUnit",
"ReimbursementRequest",
"Role",
"SemanticParseLog",
"SystemModelSetting",
"SystemSetting",
"SystemSettingSecret",
]

View File

@@ -0,0 +1,79 @@
from __future__ import annotations
import uuid
from datetime import datetime
from typing import Any
from sqlalchemy import DateTime, ForeignKey, String, Text, UniqueConstraint, func
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.types import JSON
from app.db.base_class import Base
class AgentAsset(Base):
__tablename__ = "agent_assets"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
asset_type: Mapped[str] = mapped_column(String(20), index=True)
code: Mapped[str] = mapped_column(String(100), unique=True, index=True)
name: Mapped[str] = mapped_column(String(200))
description: Mapped[str] = mapped_column(Text(), default="")
domain: Mapped[str] = mapped_column(String(50), index=True)
scenario_json: Mapped[list[Any]] = mapped_column(JSON, default=list)
owner: Mapped[str] = mapped_column(String(100))
reviewer: Mapped[str | None] = mapped_column(String(100), nullable=True)
status: Mapped[str] = mapped_column(String(20), index=True, default="draft")
current_version: Mapped[str | None] = mapped_column(String(30), nullable=True)
config_json: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
)
versions = relationship(
"AgentAssetVersion",
back_populates="asset",
cascade="all, delete-orphan",
order_by="desc(AgentAssetVersion.created_at)",
)
reviews = relationship(
"AgentAssetReview",
back_populates="asset",
cascade="all, delete-orphan",
order_by="desc(AgentAssetReview.created_at)",
)
scheduled_runs = relationship("AgentRun", back_populates="task_asset")
class AgentAssetVersion(Base):
__tablename__ = "agent_asset_versions"
__table_args__ = (
UniqueConstraint("asset_id", "version", name="uq_agent_asset_versions_asset_version"),
)
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
asset_id: Mapped[str] = mapped_column(ForeignKey("agent_assets.id"), index=True)
version: Mapped[str] = mapped_column(String(30))
content: Mapped[str] = mapped_column(Text())
content_type: Mapped[str] = mapped_column(String(20))
change_note: Mapped[str | None] = mapped_column(Text(), nullable=True)
created_by: Mapped[str] = mapped_column(String(100))
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
asset = relationship("AgentAsset", back_populates="versions")
class AgentAssetReview(Base):
__tablename__ = "agent_asset_reviews"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
asset_id: Mapped[str] = mapped_column(ForeignKey("agent_assets.id"), index=True)
version: Mapped[str] = mapped_column(String(30))
reviewer: Mapped[str] = mapped_column(String(100))
review_status: Mapped[str] = mapped_column(String(20), index=True)
review_note: Mapped[str | None] = mapped_column(Text(), nullable=True)
reviewed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
asset = relationship("AgentAsset", back_populates="reviews")

View File

@@ -0,0 +1,86 @@
from __future__ import annotations
import uuid
from datetime import datetime
from typing import Any
from sqlalchemy import DateTime, Float, ForeignKey, Integer, String, Text, func
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.types import JSON
from app.db.base_class import Base
class AgentRun(Base):
__tablename__ = "agent_runs"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
run_id: Mapped[str] = mapped_column(String(50), unique=True, index=True)
agent: Mapped[str] = mapped_column(String(30), index=True)
source: Mapped[str] = mapped_column(String(30))
user_id: Mapped[str | None] = mapped_column(String(100), nullable=True)
task_id: Mapped[str | None] = mapped_column(
ForeignKey("agent_assets.id"), nullable=True, index=True
)
ontology_json: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict)
route_json: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict)
permission_level: Mapped[str] = mapped_column(String(30), default="read")
status: Mapped[str] = mapped_column(String(20), index=True)
result_summary: Mapped[str | None] = mapped_column(Text(), nullable=True)
error_message: Mapped[str | None] = mapped_column(Text(), nullable=True)
started_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), index=True
)
finished_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
task_asset = relationship("AgentAsset", back_populates="scheduled_runs")
tool_calls = relationship(
"AgentToolCall",
back_populates="run",
cascade="all, delete-orphan",
order_by="asc(AgentToolCall.created_at)",
)
semantic_parse_logs = relationship(
"SemanticParseLog",
back_populates="run",
cascade="all, delete-orphan",
order_by="asc(SemanticParseLog.created_at)",
)
class AgentToolCall(Base):
__tablename__ = "agent_tool_calls"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
run_id: Mapped[str] = mapped_column(ForeignKey("agent_runs.run_id"), index=True)
tool_type: Mapped[str] = mapped_column(String(30))
tool_name: Mapped[str] = mapped_column(String(100))
request_json: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict)
response_json: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict)
status: Mapped[str] = mapped_column(String(20))
duration_ms: Mapped[int] = mapped_column(Integer, default=0)
error_message: Mapped[str | None] = mapped_column(Text(), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
run = relationship("AgentRun", back_populates="tool_calls")
class SemanticParseLog(Base):
__tablename__ = "semantic_parse_logs"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
run_id: Mapped[str] = mapped_column(ForeignKey("agent_runs.run_id"), index=True)
user_id: Mapped[str | None] = mapped_column(String(100), nullable=True)
raw_query: Mapped[str] = mapped_column(Text())
scenario: Mapped[str] = mapped_column(String(50), index=True)
intent: Mapped[str] = mapped_column(String(50), index=True)
entities_json: Mapped[list[Any]] = mapped_column(JSON, default=list)
time_range_json: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict)
metrics_json: Mapped[list[Any]] = mapped_column(JSON, default=list)
constraints_json: Mapped[list[Any]] = mapped_column(JSON, default=list)
risk_flags_json: Mapped[list[Any]] = mapped_column(JSON, default=list)
permission_json: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict)
confidence: Mapped[float] = mapped_column(Float, default=0.0)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
run = relationship("AgentRun", back_populates="semantic_parse_logs")

View File

@@ -0,0 +1,25 @@
from __future__ import annotations
import uuid
from datetime import datetime
from typing import Any
from sqlalchemy import DateTime, String, func
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.types import JSON
from app.db.base_class import Base
class AuditLog(Base):
__tablename__ = "audit_logs"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
actor: Mapped[str] = mapped_column(String(100))
action: Mapped[str] = mapped_column(String(100), index=True)
resource_type: Mapped[str] = mapped_column(String(50), index=True)
resource_id: Mapped[str] = mapped_column(String(100), index=True)
before_json: Mapped[dict[str, Any] | None] = mapped_column(JSON, nullable=True)
after_json: Mapped[dict[str, Any] | None] = mapped_column(JSON, nullable=True)
request_id: Mapped[str] = mapped_column(String(64), index=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())

View File

@@ -0,0 +1,118 @@
from __future__ import annotations
import uuid
from datetime import date, datetime
from decimal import Decimal
from typing import Any
from sqlalchemy import Date, DateTime, ForeignKey, Integer, Numeric, String, Text, func
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.types import JSON
from app.db.base_class import Base
class ExpenseClaim(Base):
__tablename__ = "expense_claims"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
claim_no: Mapped[str] = mapped_column(String(50), unique=True, index=True)
employee_id: Mapped[str | None] = mapped_column(
ForeignKey("employees.id"), nullable=True, index=True
)
employee_name: Mapped[str] = mapped_column(String(100), index=True)
department_id: Mapped[str | None] = mapped_column(
ForeignKey("organization_units.id"), nullable=True, index=True
)
department_name: Mapped[str] = mapped_column(String(100), index=True)
project_code: Mapped[str | None] = mapped_column(String(50), nullable=True)
expense_type: Mapped[str] = mapped_column(String(50), index=True)
reason: Mapped[str] = mapped_column(Text())
location: Mapped[str] = mapped_column(String(100))
amount: Mapped[Decimal] = mapped_column(Numeric(12, 2))
currency: Mapped[str] = mapped_column(String(10), default="CNY")
invoice_count: Mapped[int] = mapped_column(Integer, default=0)
occurred_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), index=True)
submitted_at: Mapped[datetime | None] = mapped_column(
DateTime(timezone=True), nullable=True, index=True
)
status: Mapped[str] = mapped_column(String(30), index=True)
approval_stage: Mapped[str | None] = mapped_column(String(50), nullable=True)
risk_flags_json: Mapped[list[Any]] = mapped_column(JSON, default=list)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
)
items = relationship(
"ExpenseClaimItem",
back_populates="claim",
cascade="all, delete-orphan",
order_by="asc(ExpenseClaimItem.item_date)",
)
class ExpenseClaimItem(Base):
__tablename__ = "expense_claim_items"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
claim_id: Mapped[str] = mapped_column(ForeignKey("expense_claims.id"), index=True)
item_date: Mapped[date] = mapped_column(Date(), index=True)
item_type: Mapped[str] = mapped_column(String(50))
item_reason: Mapped[str] = mapped_column(Text())
item_location: Mapped[str] = mapped_column(String(100))
item_amount: Mapped[Decimal] = mapped_column(Numeric(12, 2))
invoice_id: Mapped[str | None] = mapped_column(String(100), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
)
claim = relationship("ExpenseClaim", back_populates="items")
class AccountsReceivableRecord(Base):
__tablename__ = "accounts_receivable"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
receivable_no: Mapped[str] = mapped_column(String(50), unique=True, index=True)
customer_id: Mapped[str] = mapped_column(String(64), index=True)
customer_name: Mapped[str] = mapped_column(String(120), index=True)
contract_no: Mapped[str | None] = mapped_column(String(100), nullable=True)
invoice_no: Mapped[str | None] = mapped_column(String(100), nullable=True)
amount_receivable: Mapped[Decimal] = mapped_column(Numeric(12, 2))
amount_received: Mapped[Decimal] = mapped_column(Numeric(12, 2))
amount_outstanding: Mapped[Decimal] = mapped_column(Numeric(12, 2))
currency: Mapped[str] = mapped_column(String(10), default="CNY")
posting_date: Mapped[date] = mapped_column(Date(), index=True)
due_date: Mapped[date] = mapped_column(Date(), index=True)
aging_days: Mapped[int] = mapped_column(Integer, default=0)
status: Mapped[str] = mapped_column(String(30), index=True)
risk_flags_json: Mapped[list[Any]] = mapped_column(JSON, default=list)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
)
class AccountsPayableRecord(Base):
__tablename__ = "accounts_payable"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
payable_no: Mapped[str] = mapped_column(String(50), unique=True, index=True)
vendor_id: Mapped[str] = mapped_column(String(64), index=True)
vendor_name: Mapped[str] = mapped_column(String(120), index=True)
invoice_no: Mapped[str | None] = mapped_column(String(100), nullable=True)
amount_payable: Mapped[Decimal] = mapped_column(Numeric(12, 2))
amount_paid: Mapped[Decimal] = mapped_column(Numeric(12, 2))
amount_outstanding: Mapped[Decimal] = mapped_column(Numeric(12, 2))
currency: Mapped[str] = mapped_column(String(10), default="CNY")
posting_date: Mapped[date] = mapped_column(Date(), index=True)
due_date: Mapped[date] = mapped_column(Date(), index=True)
aging_days: Mapped[int] = mapped_column(Integer, default=0)
status: Mapped[str] = mapped_column(String(30), index=True)
risk_flags_json: Mapped[list[Any]] = mapped_column(JSON, default=list)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
)

View File

@@ -0,0 +1,110 @@
from __future__ import annotations
from sqlalchemy import or_, select
from sqlalchemy.orm import Session
from app.models.agent_asset import AgentAsset, AgentAssetReview, AgentAssetVersion
class AgentAssetRepository:
def __init__(self, db: Session) -> None:
self.db = db
def list(
self,
*,
asset_type: str | None = None,
status: str | None = None,
domain: str | None = None,
keyword: str | None = None,
) -> list[AgentAsset]:
stmt = select(AgentAsset)
if asset_type:
stmt = stmt.where(AgentAsset.asset_type == asset_type)
if status:
stmt = stmt.where(AgentAsset.status == status)
if domain:
stmt = stmt.where(AgentAsset.domain == domain)
if keyword:
like_keyword = f"%{keyword.strip()}%"
stmt = stmt.where(
or_(
AgentAsset.name.ilike(like_keyword),
AgentAsset.code.ilike(like_keyword),
AgentAsset.description.ilike(like_keyword),
)
)
stmt = stmt.order_by(AgentAsset.updated_at.desc(), AgentAsset.created_at.desc())
return list(self.db.scalars(stmt).all())
def get(self, asset_id: str) -> AgentAsset | None:
return self.db.get(AgentAsset, asset_id)
def get_by_code(self, code: str) -> AgentAsset | None:
stmt = select(AgentAsset).where(AgentAsset.code == code)
return self.db.scalar(stmt)
def list_versions(self, asset_id: str, *, limit: int | None = None) -> list[AgentAssetVersion]:
stmt = (
select(AgentAssetVersion)
.where(AgentAssetVersion.asset_id == asset_id)
.order_by(AgentAssetVersion.created_at.desc())
)
if limit is not None:
stmt = stmt.limit(limit)
return list(self.db.scalars(stmt).all())
def get_version(self, asset_id: str, version: str) -> AgentAssetVersion | None:
stmt = select(AgentAssetVersion).where(
AgentAssetVersion.asset_id == asset_id,
AgentAssetVersion.version == version,
)
return self.db.scalar(stmt)
def list_reviews(self, asset_id: str, *, limit: int | None = None) -> list[AgentAssetReview]:
stmt = (
select(AgentAssetReview)
.where(AgentAssetReview.asset_id == asset_id)
.order_by(AgentAssetReview.created_at.desc())
)
if limit is not None:
stmt = stmt.limit(limit)
return list(self.db.scalars(stmt).all())
def get_review(
self, asset_id: str, version: str, review_status: str | None = None
) -> AgentAssetReview | None:
stmt = select(AgentAssetReview).where(
AgentAssetReview.asset_id == asset_id,
AgentAssetReview.version == version,
)
if review_status:
stmt = stmt.where(AgentAssetReview.review_status == review_status)
stmt = stmt.order_by(AgentAssetReview.created_at.desc())
return self.db.scalar(stmt)
def create_asset(self, asset: AgentAsset) -> AgentAsset:
self.db.add(asset)
self.db.commit()
self.db.refresh(asset)
return asset
def save_asset(self, asset: AgentAsset) -> AgentAsset:
self.db.add(asset)
self.db.commit()
self.db.refresh(asset)
return asset
def create_version(self, version: AgentAssetVersion) -> AgentAssetVersion:
self.db.add(version)
self.db.commit()
self.db.refresh(version)
return version
def create_review(self, review: AgentAssetReview) -> AgentAssetReview:
self.db.add(review)
self.db.commit()
self.db.refresh(review)
return review

View File

@@ -0,0 +1,57 @@
from __future__ import annotations
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.models.agent_run import AgentRun, AgentToolCall, SemanticParseLog
class AgentRunRepository:
def __init__(self, db: Session) -> None:
self.db = db
def list(
self,
*,
agent: str | None = None,
status: str | None = None,
source: str | None = None,
limit: int = 20,
) -> list[AgentRun]:
stmt = select(AgentRun)
if agent:
stmt = stmt.where(AgentRun.agent == agent)
if status:
stmt = stmt.where(AgentRun.status == status)
if source:
stmt = stmt.where(AgentRun.source == source)
stmt = stmt.order_by(AgentRun.started_at.desc()).limit(limit)
return list(self.db.scalars(stmt).all())
def get_by_run_id(self, run_id: str) -> AgentRun | None:
stmt = select(AgentRun).where(AgentRun.run_id == run_id)
return self.db.scalar(stmt)
def create_run(self, run: AgentRun) -> AgentRun:
self.db.add(run)
self.db.commit()
self.db.refresh(run)
return run
def save_run(self, run: AgentRun) -> AgentRun:
self.db.add(run)
self.db.commit()
self.db.refresh(run)
return run
def create_tool_call(self, tool_call: AgentToolCall) -> AgentToolCall:
self.db.add(tool_call)
self.db.commit()
self.db.refresh(tool_call)
return tool_call
def create_semantic_parse(self, semantic_parse: SemanticParseLog) -> SemanticParseLog:
self.db.add(semantic_parse)
self.db.commit()
self.db.refresh(semantic_parse)
return semantic_parse

View File

@@ -0,0 +1,35 @@
from __future__ import annotations
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.models.audit_log import AuditLog
class AuditLogRepository:
def __init__(self, db: Session) -> None:
self.db = db
def list(
self,
*,
resource_type: str | None = None,
resource_id: str | None = None,
action: str | None = None,
limit: int = 50,
) -> list[AuditLog]:
stmt = select(AuditLog)
if resource_type:
stmt = stmt.where(AuditLog.resource_type == resource_type)
if resource_id:
stmt = stmt.where(AuditLog.resource_id == resource_id)
if action:
stmt = stmt.where(AuditLog.action == action)
stmt = stmt.order_by(AuditLog.created_at.desc()).limit(limit)
return list(self.db.scalars(stmt).all())
def create(self, log: AuditLog) -> AuditLog:
self.db.add(log)
self.db.commit()
self.db.refresh(log)
return log

View File

@@ -0,0 +1,115 @@
from __future__ import annotations
from datetime import datetime
from typing import Any
from pydantic import BaseModel, ConfigDict, Field
from app.core.agent_enums import (
AgentAssetContentType,
AgentAssetDomain,
AgentAssetStatus,
AgentAssetType,
AgentReviewStatus,
)
class AgentAssetCreate(BaseModel):
asset_type: AgentAssetType
code: str = Field(min_length=1, max_length=100)
name: str = Field(min_length=1, max_length=200)
description: str = ""
domain: AgentAssetDomain
scenario_json: list[Any] = Field(default_factory=list)
owner: str = Field(min_length=1, max_length=100)
reviewer: str | None = Field(default=None, max_length=100)
status: AgentAssetStatus = AgentAssetStatus.DRAFT
config_json: dict[str, Any] = Field(default_factory=dict)
class AgentAssetUpdate(BaseModel):
name: str | None = Field(default=None, min_length=1, max_length=200)
description: str | None = None
domain: AgentAssetDomain | None = None
scenario_json: list[Any] | None = None
owner: str | None = Field(default=None, min_length=1, max_length=100)
reviewer: str | None = Field(default=None, max_length=100)
status: AgentAssetStatus | None = None
current_version: str | None = Field(default=None, max_length=30)
config_json: dict[str, Any] | None = None
class AgentAssetVersionCreate(BaseModel):
version: str = Field(min_length=1, max_length=30)
content: Any
content_type: AgentAssetContentType
change_note: str | None = None
created_by: str = Field(min_length=1, max_length=100)
class RuleMarkdownUpdate(BaseModel):
version: str = Field(min_length=1, max_length=30)
content: str
change_note: str | None = None
created_by: str = Field(min_length=1, max_length=100)
class AgentAssetReviewCreate(BaseModel):
version: str = Field(min_length=1, max_length=30)
reviewer: str = Field(min_length=1, max_length=100)
review_status: AgentReviewStatus
review_note: str | None = None
class AgentAssetReviewRead(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
asset_id: str
version: str
reviewer: str
review_status: str
review_note: str | None
reviewed_at: datetime | None
created_at: datetime
class AgentAssetVersionRead(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
asset_id: str
version: str
content: Any
content_type: str
change_note: str | None
created_by: str
created_at: datetime
is_current: bool = False
class AgentAssetListItem(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
asset_type: str
code: str
name: str
description: str
domain: str
scenario_json: list[Any]
owner: str
reviewer: str | None
status: str
current_version: str | None
config_json: dict[str, Any]
created_at: datetime
updated_at: datetime
class AgentAssetRead(AgentAssetListItem):
current_version_content: Any | None = None
current_version_content_type: str | None = None
current_version_change_note: str | None = None
recent_versions: list[AgentAssetVersionRead] = Field(default_factory=list)
latest_review: AgentAssetReviewRead | None = None

View File

@@ -0,0 +1,61 @@
from __future__ import annotations
from datetime import datetime
from typing import Any
from pydantic import BaseModel, ConfigDict, Field
class AgentToolCallRead(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
run_id: str
tool_type: str
tool_name: str
request_json: dict[str, Any]
response_json: dict[str, Any]
status: str
duration_ms: int
error_message: str | None
created_at: datetime
class SemanticParseRead(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
run_id: str
user_id: str | None
raw_query: str
scenario: str
intent: str
entities_json: list[Any]
time_range_json: dict[str, Any]
metrics_json: list[Any]
constraints_json: list[Any]
risk_flags_json: list[Any]
permission_json: dict[str, Any]
confidence: float
created_at: datetime
class AgentRunRead(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
run_id: str
agent: str
source: str
user_id: str | None
task_id: str | None
ontology_json: dict[str, Any]
route_json: dict[str, Any]
permission_level: str
status: str
result_summary: str | None
error_message: str | None
started_at: datetime
finished_at: datetime | None
tool_calls: list[AgentToolCallRead] = Field(default_factory=list)
semantic_parse: SemanticParseRead | None = None

View File

@@ -0,0 +1,20 @@
from __future__ import annotations
from datetime import datetime
from typing import Any
from pydantic import BaseModel, ConfigDict
class AuditLogRead(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
actor: str
action: str
resource_type: str
resource_id: str
before_json: dict[str, Any] | None
after_json: dict[str, Any] | None
request_id: str
created_at: datetime

View File

@@ -0,0 +1,407 @@
from __future__ import annotations
import json
from datetime import UTC, datetime
from typing import Any
from sqlalchemy.orm import Session
from app.core.agent_enums import (
AgentAssetContentType,
AgentAssetStatus,
AgentAssetType,
AgentReviewStatus,
)
from app.core.logging import get_logger
from app.models.agent_asset import AgentAsset, AgentAssetReview, AgentAssetVersion
from app.repositories.agent_asset import AgentAssetRepository
from app.schemas.agent_asset import (
AgentAssetCreate,
AgentAssetListItem,
AgentAssetRead,
AgentAssetReviewCreate,
AgentAssetReviewRead,
AgentAssetUpdate,
AgentAssetVersionCreate,
AgentAssetVersionRead,
)
from app.services.agent_foundation import AgentFoundationService
from app.services.audit import AuditLogService
logger = get_logger("app.services.agent_assets")
class AgentAssetService:
def __init__(self, db: Session) -> None:
self.db = db
self.repository = AgentAssetRepository(db)
self.audit_service = AuditLogService(db)
def list_assets(
self,
*,
asset_type: str | None = None,
status: str | None = None,
domain: str | None = None,
keyword: str | None = None,
) -> list[AgentAssetListItem]:
self._ensure_ready()
items = self.repository.list(
asset_type=asset_type, status=status, domain=domain, keyword=keyword
)
return [AgentAssetListItem.model_validate(item) for item in items]
def get_asset(self, asset_id: str) -> AgentAssetRead | None:
self._ensure_ready()
asset = self.repository.get(asset_id)
if asset is None:
return None
recent_versions = self._sort_versions(
self.repository.list_versions(asset_id, limit=5),
asset.current_version,
)
latest_review = next(iter(self.repository.list_reviews(asset_id, limit=1)), None)
current_version = (
self.repository.get_version(asset_id, asset.current_version)
if asset.current_version
else None
)
return AgentAssetRead(
**AgentAssetListItem.model_validate(asset).model_dump(),
current_version_content=self._deserialize_content(current_version)
if current_version
else None,
current_version_content_type=current_version.content_type if current_version else None,
current_version_change_note=current_version.change_note if current_version else None,
recent_versions=[
self._serialize_version(item, asset.current_version) for item in recent_versions
],
latest_review=AgentAssetReviewRead.model_validate(latest_review)
if latest_review
else None,
)
def create_asset(
self,
payload: AgentAssetCreate,
*,
actor: str,
request_id: str | None = None,
) -> AgentAssetRead:
self._ensure_ready()
if self.repository.get_by_code(payload.code):
raise ValueError(f"资产编码 {payload.code} 已存在")
if payload.status == AgentAssetStatus.ACTIVE:
raise ValueError("请先创建资产并完成审核,再通过上线接口激活。")
asset = AgentAsset(
asset_type=payload.asset_type.value,
code=payload.code,
name=payload.name,
description=payload.description,
domain=payload.domain.value,
scenario_json=payload.scenario_json,
owner=payload.owner,
reviewer=payload.reviewer,
status=payload.status.value,
config_json=payload.config_json,
)
created = self.repository.create_asset(asset)
self.audit_service.log_action(
actor=actor,
action="create_agent_asset",
resource_type=created.asset_type,
resource_id=created.id,
before_json=None,
after_json=self._asset_snapshot(created),
request_id=request_id,
)
logger.info("Created agent asset id=%s code=%s", created.id, created.code)
return self.get_asset(created.id) # type: ignore[return-value]
def update_asset(
self,
asset_id: str,
payload: AgentAssetUpdate,
*,
actor: str,
request_id: str | None = None,
) -> AgentAssetRead:
self._ensure_ready()
asset = self.repository.get(asset_id)
if asset is None:
raise LookupError("Asset not found")
before = self._asset_snapshot(asset)
if payload.status == AgentAssetStatus.ACTIVE:
raise ValueError("请使用上线接口激活资产。")
for field_name in (
"name",
"description",
"owner",
"reviewer",
"current_version",
"config_json",
"scenario_json",
):
value = getattr(payload, field_name)
if value is not None:
setattr(asset, field_name, value)
if payload.domain is not None:
asset.domain = payload.domain.value
if payload.status is not None:
asset.status = payload.status.value
if payload.current_version is not None and not self.repository.get_version(
asset_id, payload.current_version
):
raise LookupError(f"版本 {payload.current_version} 不存在")
updated = self.repository.save_asset(asset)
self.audit_service.log_action(
actor=actor,
action="update_agent_asset",
resource_type=updated.asset_type,
resource_id=updated.id,
before_json=before,
after_json=self._asset_snapshot(updated),
request_id=request_id,
)
logger.info("Updated agent asset id=%s code=%s", updated.id, updated.code)
return self.get_asset(updated.id) # type: ignore[return-value]
def list_versions(self, asset_id: str, *, limit: int = 20) -> list[AgentAssetVersionRead]:
self._ensure_ready()
asset = self.repository.get(asset_id)
if asset is None:
raise LookupError("Asset not found")
versions = self._sort_versions(
self.repository.list_versions(asset_id, limit=limit),
asset.current_version,
)
return [self._serialize_version(item, asset.current_version) for item in versions]
def create_version(
self,
asset_id: str,
payload: AgentAssetVersionCreate,
*,
actor: str,
request_id: str | None = None,
) -> AgentAssetVersionRead:
self._ensure_ready()
asset = self.repository.get(asset_id)
if asset is None:
raise LookupError("Asset not found")
if self.repository.get_version(asset_id, payload.version):
raise ValueError(f"版本号 {payload.version} 已存在")
self._validate_version_payload(asset, payload)
serialized_content = self._serialize_content(payload.content, payload.content_type.value)
version = AgentAssetVersion(
asset_id=asset_id,
version=payload.version,
content=serialized_content,
content_type=payload.content_type.value,
change_note=payload.change_note,
created_by=payload.created_by,
)
created = self.repository.create_version(version)
before = self._asset_snapshot(asset)
asset.current_version = payload.version
if (
asset.asset_type == AgentAssetType.RULE.value
and asset.status == AgentAssetStatus.ACTIVE.value
):
asset.status = AgentAssetStatus.REVIEW.value
updated_asset = self.repository.save_asset(asset)
self.audit_service.log_action(
actor=actor,
action="save_agent_asset_version",
resource_type=updated_asset.asset_type,
resource_id=updated_asset.id,
before_json=before,
after_json={
"current_version": updated_asset.current_version,
"status": updated_asset.status,
},
request_id=request_id,
)
logger.info("Created agent asset version asset_id=%s version=%s", asset_id, payload.version)
return self._serialize_version(created, updated_asset.current_version)
def create_review(
self,
asset_id: str,
payload: AgentAssetReviewCreate,
*,
actor: str,
request_id: str | None = None,
) -> AgentAssetReviewRead:
self._ensure_ready()
asset = self.repository.get(asset_id)
if asset is None:
raise LookupError("Asset not found")
if self.repository.get_version(asset_id, payload.version) is None:
raise LookupError(f"版本 {payload.version} 不存在")
review = AgentAssetReview(
asset_id=asset_id,
version=payload.version,
reviewer=payload.reviewer,
review_status=payload.review_status.value,
review_note=payload.review_note,
reviewed_at=None
if payload.review_status == AgentReviewStatus.PENDING
else datetime.now(UTC),
)
created = self.repository.create_review(review)
before = self._asset_snapshot(asset)
asset.reviewer = payload.reviewer
if payload.review_status == AgentReviewStatus.PENDING:
asset.status = AgentAssetStatus.REVIEW.value
elif payload.review_status == AgentReviewStatus.REJECTED:
asset.status = AgentAssetStatus.DRAFT.value
elif asset.status != AgentAssetStatus.ACTIVE.value:
asset.status = AgentAssetStatus.REVIEW.value
self.repository.save_asset(asset)
self.audit_service.log_action(
actor=actor,
action="review_agent_asset",
resource_type=asset.asset_type,
resource_id=asset.id,
before_json=before,
after_json={
"review_version": payload.version,
"review_status": payload.review_status.value,
"asset_status": asset.status,
},
request_id=request_id,
)
logger.info(
"Created review asset_id=%s version=%s status=%s",
asset_id,
payload.version,
payload.review_status.value,
)
return AgentAssetReviewRead.model_validate(created)
def activate_asset(
self,
asset_id: str,
*,
actor: str,
request_id: str | None = None,
) -> AgentAssetRead:
self._ensure_ready()
asset = self.repository.get(asset_id)
if asset is None:
raise LookupError("Asset not found")
if not asset.current_version:
raise ValueError("资产尚未设置当前版本,无法上线。")
if asset.asset_type == AgentAssetType.RULE.value:
review = self.repository.get_review(
asset.id, asset.current_version, AgentReviewStatus.APPROVED.value
)
if review is None:
raise PermissionError("规则当前版本尚未审核通过,不能上线。")
before = self._asset_snapshot(asset)
asset.status = AgentAssetStatus.ACTIVE.value
updated = self.repository.save_asset(asset)
self.audit_service.log_action(
actor=actor,
action="activate_agent_asset",
resource_type=updated.asset_type,
resource_id=updated.id,
before_json=before,
after_json=self._asset_snapshot(updated),
request_id=request_id,
)
logger.info("Activated agent asset id=%s code=%s", updated.id, updated.code)
return self.get_asset(updated.id) # type: ignore[return-value]
def _ensure_ready(self) -> None:
AgentFoundationService(self.db).ensure_foundation_ready()
def _validate_version_payload(
self, asset: AgentAsset, payload: AgentAssetVersionCreate
) -> None:
if (
asset.asset_type == AgentAssetType.RULE.value
and payload.content_type != AgentAssetContentType.MARKDOWN
):
raise ValueError("规则资产版本内容必须使用 markdown。")
if (
asset.asset_type != AgentAssetType.RULE.value
and payload.content_type != AgentAssetContentType.JSON
):
raise ValueError("技能、MCP、任务资产版本内容必须使用 json。")
if payload.content_type == AgentAssetContentType.MARKDOWN and not isinstance(
payload.content, str
):
raise ValueError("Markdown 内容必须是字符串。")
if payload.content_type == AgentAssetContentType.JSON and not isinstance(
payload.content, (dict, list)
):
raise ValueError("JSON 内容必须是对象或数组。")
def _serialize_version(
self, version: AgentAssetVersion, current_version: str | None
) -> AgentAssetVersionRead:
return AgentAssetVersionRead(
id=version.id,
asset_id=version.asset_id,
version=version.version,
content=self._deserialize_content(version),
content_type=version.content_type,
change_note=version.change_note,
created_by=version.created_by,
created_at=version.created_at,
is_current=version.version == current_version,
)
@staticmethod
def _sort_versions(
versions: list[AgentAssetVersion], current_version: str | None
) -> list[AgentAssetVersion]:
return sorted(
versions,
key=lambda item: (item.version == current_version, item.created_at),
reverse=True,
)
@staticmethod
def _serialize_content(content: Any, content_type: str) -> str:
if content_type == AgentAssetContentType.MARKDOWN.value:
return str(content)
return json.dumps(content, ensure_ascii=False, sort_keys=True, indent=2)
@staticmethod
def _deserialize_content(version: AgentAssetVersion | None) -> Any:
if version is None:
return None
if version.content_type == AgentAssetContentType.MARKDOWN.value:
return version.content
return json.loads(version.content)
@staticmethod
def _asset_snapshot(asset: AgentAsset) -> dict[str, Any]:
return {
"asset_type": asset.asset_type,
"code": asset.code,
"name": asset.name,
"status": asset.status,
"current_version": asset.current_version,
"domain": asset.domain,
"owner": asset.owner,
"reviewer": asset.reviewer,
}

View File

@@ -0,0 +1,977 @@
from __future__ import annotations
import json
from datetime import UTC, date, datetime
from decimal import Decimal
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.core.agent_enums import (
AgentAssetContentType,
AgentAssetDomain,
AgentAssetStatus,
AgentAssetType,
AgentName,
AgentPermissionLevel,
AgentReviewStatus,
AgentRunSource,
AgentRunStatus,
AgentToolType,
)
from app.core.config import get_settings
from app.core.logging import get_logger
from app.db.base import Base
from app.db.session import get_session_factory
from app.models.agent_asset import AgentAsset, AgentAssetReview, AgentAssetVersion
from app.models.agent_run import AgentRun, AgentToolCall, SemanticParseLog
from app.models.audit_log import AuditLog
from app.models.financial_record import (
AccountsPayableRecord,
AccountsReceivableRecord,
ExpenseClaim,
ExpenseClaimItem,
)
logger = get_logger("app.services.agent_foundation")
def prepare_agent_foundation() -> None:
settings = get_settings()
if not settings.setup_completed:
logger.info("Agent foundation bootstrap skipped because setup is incomplete")
return
session_factory = get_session_factory()
with session_factory() as db:
AgentFoundationService(db).ensure_foundation_ready()
class AgentFoundationService:
def __init__(self, db: Session) -> None:
self.db = db
def ensure_foundation_ready(self) -> None:
try:
Base.metadata.create_all(bind=self.db.get_bind())
self._seed_agent_assets()
self._seed_financial_records()
self._seed_runs_and_logs()
self.db.commit()
except Exception:
self.db.rollback()
logger.exception("Failed to prepare agent foundation")
raise
def _seed_agent_assets(self) -> None:
existing_codes = set(self.db.scalars(select(AgentAsset.code)).all())
if existing_codes:
self._top_up_agent_assets(existing_codes)
return
approved_rule = AgentAsset(
asset_type=AgentAssetType.RULE.value,
code="rule.expense.duplicate_expense_check",
name="重复报销识别规则",
description="识别同一员工短时间内同金额、同地点、同理由的重复报销风险。",
domain=AgentAssetDomain.EXPENSE.value,
scenario_json=["expense", "risk_check", "duplicate_expense"],
owner="财务共享中心",
reviewer="张晓晴",
status=AgentAssetStatus.ACTIVE.value,
current_version="v1.1.0",
config_json={"severity": "high", "enabled": True},
)
pending_rule = AgentAsset(
asset_type=AgentAssetType.RULE.value,
code="rule.expense.travel_receipt_requirements",
name="差旅票据完整性规则",
description="检查差旅报销是否附齐发票、行程单和住宿凭证。",
domain=AgentAssetDomain.EXPENSE.value,
scenario_json=["expense", "explain", "invoice_anomaly"],
owner="费用运营组",
reviewer="高嘉禾",
status=AgentAssetStatus.REVIEW.value,
current_version="v1.0.0",
config_json={"severity": "medium", "enabled": False},
)
rejected_rule = AgentAsset(
asset_type=AgentAssetType.RULE.value,
code="rule.ap.payment_dual_review",
name="付款双人复核规则",
description="大额付款必须由两名财务人员复核后再进入付款建议。",
domain=AgentAssetDomain.AP.value,
scenario_json=["accounts_payable", "approval_required"],
owner="付款管理组",
reviewer="孙楠",
status=AgentAssetStatus.DRAFT.value,
current_version="v0.9.0",
config_json={"amount_threshold": 50000},
)
skill_expense_asset = AgentAsset(
asset_type=AgentAssetType.SKILL.value,
code="skill.expense.summary_lookup",
name="报销汇总查询技能",
description="根据时间、员工和部门汇总报销金额与单据数量。",
domain=AgentAssetDomain.EXPENSE.value,
scenario_json=["expense", "query", "summary"],
owner="平台研发组",
reviewer="陈硕",
status=AgentAssetStatus.ACTIVE.value,
current_version="v1.0.0",
config_json={"input_schema": ["time_range", "employee", "department"]},
)
skill_ar_asset = AgentAsset(
asset_type=AgentAssetType.SKILL.value,
code="skill.ar.aging_summary",
name="应收账龄汇总技能",
description="按客户、账龄和逾期状态汇总应收风险分布。",
domain=AgentAssetDomain.AR.value,
scenario_json=["accounts_receivable", "query", "aging_summary"],
owner="平台研发组",
reviewer="陈硕",
status=AgentAssetStatus.ACTIVE.value,
current_version="v1.0.0",
config_json={"input_schema": ["customer", "aging_bucket", "status"]},
)
invoice_mcp_asset = AgentAsset(
asset_type=AgentAssetType.MCP.value,
code="mcp.invoice.verify_mock",
name="发票验真 Mock 服务",
description="模拟发票验真、发票状态查询和异常降级说明。",
domain=AgentAssetDomain.SYSTEM.value,
scenario_json=["expense", "invoice_validation"],
owner="平台研发组",
reviewer="周悦宁",
status=AgentAssetStatus.ACTIVE.value,
current_version="v1.0.0",
config_json={"endpoint": "mock://invoice/verify", "timeout_ms": 1200},
)
ledger_mcp_asset = AgentAsset(
asset_type=AgentAssetType.MCP.value,
code="mcp.ledger.snapshot_mock",
name="总账快照 Mock 服务",
description="模拟返回应收、应付和费用汇总快照,供 Agent 查询和巡检。",
domain=AgentAssetDomain.SYSTEM.value,
scenario_json=["expense", "accounts_receivable", "accounts_payable"],
owner="平台研发组",
reviewer="周悦宁",
status=AgentAssetStatus.ACTIVE.value,
current_version="v1.0.0",
config_json={"endpoint": "mock://ledger/snapshot", "timeout_ms": 1500},
)
task_asset = AgentAsset(
asset_type=AgentAssetType.TASK.value,
code="task.hermes.daily_risk_scan",
name="Hermes 每日风险巡检",
description="每天早上巡检重复报销、金额超标、逾期应收和异常付款。",
domain=AgentAssetDomain.SYSTEM.value,
scenario_json=["schedule", "risk_check"],
owner="风控与审计部",
reviewer="顾承宇",
status=AgentAssetStatus.ACTIVE.value,
current_version="v1.0.0",
config_json={"cron": "0 9 * * *", "agent": AgentName.HERMES.value},
)
ar_summary_task = AgentAsset(
asset_type=AgentAssetType.TASK.value,
code="task.hermes.weekly_ar_summary",
name="Hermes 每周应收账龄汇总",
description="每周汇总逾期应收、账龄分布和客户风险变化。",
domain=AgentAssetDomain.SYSTEM.value,
scenario_json=["schedule", "accounts_receivable", "summary"],
owner="风控与审计部",
reviewer="顾承宇",
status=AgentAssetStatus.ACTIVE.value,
current_version="v1.0.0",
config_json={"cron": "0 10 * * 1", "agent": AgentName.HERMES.value},
)
rule_digest_task = AgentAsset(
asset_type=AgentAssetType.TASK.value,
code="task.hermes.rule_review_digest",
name="Hermes 规则待审摘要",
description="每天汇总待审规则、待补样例和被拒规则修订建议。",
domain=AgentAssetDomain.SYSTEM.value,
scenario_json=["schedule", "rule_center", "review_digest"],
owner="风控与审计部",
reviewer="顾承宇",
status=AgentAssetStatus.ACTIVE.value,
current_version="v1.0.0",
config_json={"cron": "0 18 * * *", "agent": AgentName.HERMES.value},
)
self.db.add_all(
[
approved_rule,
pending_rule,
rejected_rule,
skill_expense_asset,
skill_ar_asset,
invoice_mcp_asset,
ledger_mcp_asset,
task_asset,
ar_summary_task,
rule_digest_task,
]
)
self.db.flush()
self.db.add_all(
[
AgentAssetVersion(
asset=approved_rule,
version="v1.0.0",
content=self._markdown_content(
"# 重复报销识别规则\n\n"
"- 检查员工、金额、地点、发生日期是否高度重复。\n"
"- 命中后输出 `duplicate_expense` 风险标签。"
),
content_type=AgentAssetContentType.MARKDOWN.value,
change_note="初始化生产规则版本。",
created_by="系统初始化",
),
AgentAssetVersion(
asset=approved_rule,
version="v1.1.0",
content=self._markdown_content(
"# 重复报销识别规则\n\n"
"- 检查员工、金额、地点、发生日期是否高度重复。\n"
"- 新增对同项目、同金额、跨单重复提交的识别。\n"
"- 命中后输出 `duplicate_expense` 风险标签。"
),
content_type=AgentAssetContentType.MARKDOWN.value,
change_note="补充跨单重复提交判断。",
created_by="系统初始化",
),
AgentAssetVersion(
asset=pending_rule,
version="v0.9.0",
content=self._markdown_content(
"# 差旅票据完整性规则\n\n"
"- 差旅报销必须具备发票、行程单、住宿凭证。\n"
"- 缺失时输出 `invoice_anomaly`。"
),
content_type=AgentAssetContentType.MARKDOWN.value,
change_note="首版草稿。",
created_by="高嘉禾",
),
AgentAssetVersion(
asset=pending_rule,
version="v1.0.0",
content=self._markdown_content(
"# 差旅票据完整性规则\n\n"
"- 差旅报销必须具备发票、行程单、住宿凭证。\n"
"- 新增高铁改签和住宿分拆票据的补件说明。\n"
"- 缺失时输出 `invoice_anomaly`。"
),
content_type=AgentAssetContentType.MARKDOWN.value,
change_note="补充差旅特殊票据口径,待审核。",
created_by="高嘉禾",
),
AgentAssetVersion(
asset=rejected_rule,
version="v0.8.0",
content=self._markdown_content(
"# 付款双人复核规则\n\n"
"- 单笔付款超过阈值时必须双人复核。\n"
"- 本版本规则口径过宽,待修订。"
),
content_type=AgentAssetContentType.MARKDOWN.value,
change_note="首版方案。",
created_by="孙楠",
),
AgentAssetVersion(
asset=rejected_rule,
version="v0.9.0",
content=self._markdown_content(
"# 付款双人复核规则\n\n"
"- 单笔付款超过阈值时必须双人复核。\n"
"- 新增跨币种付款也进入复核队列。\n"
"- 当前阈值定义仍不清晰,需继续修订。"
),
content_type=AgentAssetContentType.MARKDOWN.value,
change_note="补充跨币种场景,但阈值仍待明确。",
created_by="孙楠",
),
AgentAssetVersion(
asset=skill_expense_asset,
version="v1.0.0",
content=self._json_content(
{
"inputs": ["time_range", "employee", "department"],
"outputs": ["total_amount", "claim_count"],
"dependencies": ["database.expense_claims"],
}
),
content_type=AgentAssetContentType.JSON.value,
change_note="初始化技能快照。",
created_by="系统初始化",
),
AgentAssetVersion(
asset=skill_ar_asset,
version="v1.0.0",
content=self._json_content(
{
"inputs": ["customer", "aging_bucket", "status"],
"outputs": ["receivable_total", "overdue_total", "customer_count"],
"dependencies": ["database.accounts_receivable"],
}
),
content_type=AgentAssetContentType.JSON.value,
change_note="初始化应收账龄技能快照。",
created_by="系统初始化",
),
AgentAssetVersion(
asset=invoice_mcp_asset,
version="v1.0.0",
content=self._json_content(
{
"service_type": "mock",
"auth_mode": "none",
"degrade_strategy": "return_stub_with_warning",
}
),
content_type=AgentAssetContentType.JSON.value,
change_note="初始化 MCP 快照。",
created_by="系统初始化",
),
AgentAssetVersion(
asset=ledger_mcp_asset,
version="v1.0.0",
content=self._json_content(
{
"service_type": "mock",
"auth_mode": "service_account",
"degrade_strategy": "return_cached_snapshot_with_warning",
}
),
content_type=AgentAssetContentType.JSON.value,
change_note="初始化总账快照 MCP。",
created_by="系统初始化",
),
AgentAssetVersion(
asset=task_asset,
version="v1.0.0",
content=self._json_content(
{
"task_type": "daily_risk_scan",
"schedule": "0 9 * * *",
"target_agent": AgentName.HERMES.value,
}
),
content_type=AgentAssetContentType.JSON.value,
change_note="初始化任务快照。",
created_by="系统初始化",
),
AgentAssetVersion(
asset=ar_summary_task,
version="v1.0.0",
content=self._json_content(
{
"task_type": "weekly_ar_summary",
"schedule": "0 10 * * 1",
"target_agent": AgentName.HERMES.value,
}
),
content_type=AgentAssetContentType.JSON.value,
change_note="初始化应收账龄汇总任务。",
created_by="系统初始化",
),
AgentAssetVersion(
asset=rule_digest_task,
version="v1.0.0",
content=self._json_content(
{
"task_type": "rule_review_digest",
"schedule": "0 18 * * *",
"target_agent": AgentName.HERMES.value,
}
),
content_type=AgentAssetContentType.JSON.value,
change_note="初始化规则待审摘要任务。",
created_by="系统初始化",
),
]
)
self.db.add_all(
[
AgentAssetReview(
asset=approved_rule,
version="v1.1.0",
reviewer="张晓晴",
review_status=AgentReviewStatus.APPROVED.value,
review_note="规则口径清晰,可上线。",
reviewed_at=datetime.now(UTC),
),
AgentAssetReview(
asset=pending_rule,
version="v1.0.0",
reviewer="高嘉禾",
review_status=AgentReviewStatus.PENDING.value,
review_note="等待补充票据异常样例。",
reviewed_at=None,
),
AgentAssetReview(
asset=rejected_rule,
version="v0.9.0",
reviewer="孙楠",
review_status=AgentReviewStatus.REJECTED.value,
review_note="阈值定义不清,暂不通过。",
reviewed_at=datetime.now(UTC),
),
]
)
def _seed_financial_records(self) -> None:
if self.db.scalar(select(ExpenseClaim.id).limit(1)) is not None:
return
claim_1 = ExpenseClaim(
claim_no="EXP-202605-001",
employee_name="张三",
department_name="财务共享中心",
project_code="PRJ-EXP-01",
expense_type="travel",
reason="华南客户拜访差旅报销",
location="深圳",
amount=Decimal("3280.00"),
currency="CNY",
invoice_count=3,
occurred_at=datetime(2026, 5, 6, 9, 0, tzinfo=UTC),
submitted_at=datetime(2026, 5, 7, 10, 20, tzinfo=UTC),
status="submitted",
approval_stage="finance_review",
risk_flags_json=["amount_over_limit"],
)
claim_1.items = [
ExpenseClaimItem(
item_date=date(2026, 5, 5),
item_type="hotel",
item_reason="客户拜访住宿",
item_location="深圳",
item_amount=Decimal("1880.00"),
invoice_id="INV-HOTEL-001",
),
ExpenseClaimItem(
item_date=date(2026, 5, 6),
item_type="transport",
item_reason="往返交通",
item_location="深圳",
item_amount=Decimal("1400.00"),
invoice_id="INV-TRANS-009",
),
]
claim_2 = ExpenseClaim(
claim_no="EXP-202605-002",
employee_name="李四",
department_name="华东销售部",
project_code="PRJ-SALES-02",
expense_type="meal",
reason="客户路演餐费",
location="上海",
amount=Decimal("860.00"),
currency="CNY",
invoice_count=1,
occurred_at=datetime(2026, 5, 8, 12, 0, tzinfo=UTC),
submitted_at=datetime(2026, 5, 8, 18, 30, tzinfo=UTC),
status="approved",
approval_stage="completed",
risk_flags_json=[],
)
claim_3 = ExpenseClaim(
claim_no="EXP-202605-003",
employee_name="王五",
department_name="市场品牌部",
project_code="PRJ-MKT-08",
expense_type="travel",
reason="市场活动会务差旅",
location="北京",
amount=Decimal("3280.00"),
currency="CNY",
invoice_count=2,
occurred_at=datetime(2026, 5, 6, 11, 30, tzinfo=UTC),
submitted_at=datetime(2026, 5, 8, 9, 10, tzinfo=UTC),
status="review",
approval_stage="risk_check",
risk_flags_json=["duplicate_expense"],
)
ar_records = [
AccountsReceivableRecord(
receivable_no="AR-202605-001",
customer_id="CUS-A",
customer_name="客户A",
contract_no="CTR-AR-1001",
invoice_no="INV-AR-9001",
amount_receivable=Decimal("120000.00"),
amount_received=Decimal("70000.00"),
amount_outstanding=Decimal("50000.00"),
currency="CNY",
posting_date=date(2026, 4, 1),
due_date=date(2026, 4, 30),
aging_days=11,
status="partial",
risk_flags_json=[],
),
AccountsReceivableRecord(
receivable_no="AR-202605-002",
customer_id="CUS-B",
customer_name="客户B",
contract_no="CTR-AR-1002",
invoice_no="INV-AR-9002",
amount_receivable=Decimal("88000.00"),
amount_received=Decimal("10000.00"),
amount_outstanding=Decimal("78000.00"),
currency="CNY",
posting_date=date(2026, 3, 15),
due_date=date(2026, 4, 15),
aging_days=26,
status="overdue",
risk_flags_json=["ar_overdue"],
),
]
ap_records = [
AccountsPayableRecord(
payable_no="AP-202605-001",
vendor_id="VEN-A",
vendor_name="供应商A",
invoice_no="INV-AP-5001",
amount_payable=Decimal("43000.00"),
amount_paid=Decimal("10000.00"),
amount_outstanding=Decimal("33000.00"),
currency="CNY",
posting_date=date(2026, 4, 20),
due_date=date(2026, 5, 12),
aging_days=0,
status="scheduled",
risk_flags_json=[],
),
AccountsPayableRecord(
payable_no="AP-202605-002",
vendor_id="VEN-B",
vendor_name="供应商B",
invoice_no="INV-AP-5002",
amount_payable=Decimal("96000.00"),
amount_paid=Decimal("0.00"),
amount_outstanding=Decimal("96000.00"),
currency="CNY",
posting_date=date(2026, 4, 10),
due_date=date(2026, 5, 5),
aging_days=6,
status="overdue",
risk_flags_json=["ap_overdue"],
),
]
self.db.add_all([claim_1, claim_2, claim_3, *ar_records, *ap_records])
def _seed_runs_and_logs(self) -> None:
if self.db.scalar(select(AgentRun.id).limit(1)) is not None:
return
task_asset = self.db.scalar(
select(AgentAsset).where(AgentAsset.code == "task.hermes.daily_risk_scan")
)
user_run = AgentRun(
run_id="run_user_20260511_001",
agent=AgentName.USER_AGENT.value,
source=AgentRunSource.USER_MESSAGE.value,
user_id="emp_001",
task_id=None,
ontology_json={"scenario": "expense", "intent": "query"},
route_json={"selected_agent": AgentName.USER_AGENT.value, "route_reason": "user query"},
permission_level=AgentPermissionLevel.READ.value,
status=AgentRunStatus.SUCCEEDED.value,
result_summary="已返回本周报销金额和风险摘要。",
started_at=datetime(2026, 5, 11, 8, 35, tzinfo=UTC),
finished_at=datetime(2026, 5, 11, 8, 35, 2, tzinfo=UTC),
)
hermes_run = AgentRun(
run_id="run_hermes_20260511_001",
agent=AgentName.HERMES.value,
source=AgentRunSource.SCHEDULE.value,
user_id=None,
task_id=task_asset.id if task_asset else None,
ontology_json={"scenario": "expense", "intent": "risk_check"},
route_json={
"selected_agent": AgentName.HERMES.value,
"route_reason": "scheduled risk scan",
},
permission_level=AgentPermissionLevel.READ.value,
status=AgentRunStatus.SUCCEEDED.value,
result_summary="Hermes 已生成今日风险巡检摘要。",
started_at=datetime(2026, 5, 11, 9, 0, tzinfo=UTC),
finished_at=datetime(2026, 5, 11, 9, 0, 4, tzinfo=UTC),
)
blocked_run = AgentRun(
run_id="run_user_20260511_002",
agent=AgentName.ORCHESTRATOR.value,
source=AgentRunSource.USER_MESSAGE.value,
user_id="emp_002",
task_id=None,
ontology_json={"scenario": "accounts_payable", "intent": "operate"},
route_json={
"selected_agent": AgentName.USER_AGENT.value,
"route_reason": "payment request",
},
permission_level=AgentPermissionLevel.APPROVAL_REQUIRED.value,
status=AgentRunStatus.BLOCKED.value,
result_summary="动作需要人工确认。",
error_message="直接付款属于高风险动作,已阻断自动执行。",
started_at=datetime(2026, 5, 11, 10, 5, tzinfo=UTC),
finished_at=datetime(2026, 5, 11, 10, 5, 1, tzinfo=UTC),
)
self.db.add_all([user_run, hermes_run, blocked_run])
self.db.flush()
self.db.add_all(
[
AgentToolCall(
run_id=user_run.run_id,
tool_type=AgentToolType.DATABASE.value,
tool_name="expense_claims.lookup",
request_json={"time_range": "this_week", "employee": "all"},
response_json={"claim_count": 3, "total_amount": "7420.00"},
status="succeeded",
duration_ms=48,
),
AgentToolCall(
run_id=hermes_run.run_id,
tool_type=AgentToolType.MCP.value,
tool_name="invoice.verify_mock",
request_json={"claim_no": "EXP-202605-003"},
response_json={
"warning": "external service degraded",
"fallback": "used mock response",
},
status="failed",
duration_ms=132,
error_message="mock upstream timeout",
),
AgentToolCall(
run_id=blocked_run.run_id,
tool_type=AgentToolType.RULE_ENGINE.value,
tool_name="permission.guard",
request_json={"action": "direct_payment"},
response_json={"requires_confirmation": True},
status="succeeded",
duration_ms=5,
),
SemanticParseLog(
run_id=user_run.run_id,
user_id="emp_001",
raw_query="查一下本周报销超标风险",
scenario="expense",
intent="risk_check",
entities_json=[],
time_range_json={"start_date": "2026-05-11", "end_date": "2026-05-17"},
metrics_json=["amount"],
constraints_json=[],
risk_flags_json=["amount_over_limit"],
permission_json={"level": AgentPermissionLevel.READ.value},
confidence=0.93,
),
SemanticParseLog(
run_id=blocked_run.run_id,
user_id="emp_002",
raw_query="帮我直接付款给供应商B",
scenario="accounts_payable",
intent="operate",
entities_json=[{"type": "vendor", "value": "供应商B"}],
time_range_json={},
metrics_json=["amount"],
constraints_json=[],
risk_flags_json=["ap_overdue"],
permission_json={"level": AgentPermissionLevel.APPROVAL_REQUIRED.value},
confidence=0.96,
),
]
)
if self.db.scalar(select(AuditLog.id).limit(1)) is None:
self.db.add_all(
[
AuditLog(
actor="系统初始化",
action="save_rule_markdown",
resource_type="rule",
resource_id="rule.expense.duplicate_expense_check",
before_json=None,
after_json={"version": "v1.0.0"},
request_id="seed-audit-001",
),
AuditLog(
actor="张晓晴",
action="review_rule",
resource_type="rule",
resource_id="rule.expense.duplicate_expense_check",
before_json={"review_status": "pending"},
after_json={"review_status": "approved"},
request_id="seed-audit-002",
),
AuditLog(
actor="系统初始化",
action="activate_rule",
resource_type="rule",
resource_id="rule.expense.duplicate_expense_check",
before_json={"status": "review"},
after_json={"status": "active"},
request_id="seed-audit-003",
),
AuditLog(
actor="Hermes",
action="update_task_status",
resource_type="task",
resource_id="task.hermes.daily_risk_scan",
before_json={"status": "idle"},
after_json={"status": "succeeded"},
request_id="seed-audit-004",
),
]
)
def _top_up_agent_assets(self, existing_codes: set[str]) -> None:
approved_rule = self.db.scalar(
select(AgentAsset).where(AgentAsset.code == "rule.expense.duplicate_expense_check")
)
pending_rule = self.db.scalar(
select(AgentAsset).where(AgentAsset.code == "rule.expense.travel_receipt_requirements")
)
rejected_rule = self.db.scalar(
select(AgentAsset).where(AgentAsset.code == "rule.ap.payment_dual_review")
)
if approved_rule is not None:
self._ensure_asset_version(
approved_rule,
version="v1.1.0",
content=self._markdown_content(
"# 重复报销识别规则\n\n"
"- 检查员工、金额、地点、发生日期是否高度重复。\n"
"- 新增对同项目、同金额、跨单重复提交的识别。\n"
"- 命中后输出 `duplicate_expense` 风险标签。"
),
content_type=AgentAssetContentType.MARKDOWN.value,
change_note="补充跨单重复提交判断。",
created_by="系统初始化",
)
if pending_rule is not None:
self._ensure_asset_version(
pending_rule,
version="v1.0.0",
content=self._markdown_content(
"# 差旅票据完整性规则\n\n"
"- 差旅报销必须具备发票、行程单、住宿凭证。\n"
"- 新增高铁改签和住宿分拆票据的补件说明。\n"
"- 缺失时输出 `invoice_anomaly`。"
),
content_type=AgentAssetContentType.MARKDOWN.value,
change_note="补充差旅特殊票据口径,待审核。",
created_by="高嘉禾",
)
if rejected_rule is not None:
self._ensure_asset_version(
rejected_rule,
version="v0.9.0",
content=self._markdown_content(
"# 付款双人复核规则\n\n"
"- 单笔付款超过阈值时必须双人复核。\n"
"- 新增跨币种付款也进入复核队列。\n"
"- 当前阈值定义仍不清晰,需继续修订。"
),
content_type=AgentAssetContentType.MARKDOWN.value,
change_note="补充跨币种场景,但阈值仍待明确。",
created_by="孙楠",
)
if "skill.ar.aging_summary" not in existing_codes:
asset = self._create_seed_asset(
asset_type=AgentAssetType.SKILL.value,
code="skill.ar.aging_summary",
name="应收账龄汇总技能",
description="按客户、账龄和逾期状态汇总应收风险分布。",
domain=AgentAssetDomain.AR.value,
scenario_json=["accounts_receivable", "query", "aging_summary"],
owner="平台研发组",
reviewer="陈硕",
status=AgentAssetStatus.ACTIVE.value,
current_version="v1.0.0",
config_json={"input_schema": ["customer", "aging_bucket", "status"]},
)
self._ensure_asset_version(
asset,
version="v1.0.0",
content=self._json_content(
{
"inputs": ["customer", "aging_bucket", "status"],
"outputs": ["receivable_total", "overdue_total", "customer_count"],
"dependencies": ["database.accounts_receivable"],
}
),
content_type=AgentAssetContentType.JSON.value,
change_note="初始化应收账龄技能快照。",
created_by="系统初始化",
)
if "mcp.ledger.snapshot_mock" not in existing_codes:
asset = self._create_seed_asset(
asset_type=AgentAssetType.MCP.value,
code="mcp.ledger.snapshot_mock",
name="总账快照 Mock 服务",
description="模拟返回应收、应付和费用汇总快照,供 Agent 查询和巡检。",
domain=AgentAssetDomain.SYSTEM.value,
scenario_json=["expense", "accounts_receivable", "accounts_payable"],
owner="平台研发组",
reviewer="周悦宁",
status=AgentAssetStatus.ACTIVE.value,
current_version="v1.0.0",
config_json={"endpoint": "mock://ledger/snapshot", "timeout_ms": 1500},
)
self._ensure_asset_version(
asset,
version="v1.0.0",
content=self._json_content(
{
"service_type": "mock",
"auth_mode": "service_account",
"degrade_strategy": "return_cached_snapshot_with_warning",
}
),
content_type=AgentAssetContentType.JSON.value,
change_note="初始化总账快照 MCP。",
created_by="系统初始化",
)
if "task.hermes.weekly_ar_summary" not in existing_codes:
asset = self._create_seed_asset(
asset_type=AgentAssetType.TASK.value,
code="task.hermes.weekly_ar_summary",
name="Hermes 每周应收账龄汇总",
description="每周汇总逾期应收、账龄分布和客户风险变化。",
domain=AgentAssetDomain.SYSTEM.value,
scenario_json=["schedule", "accounts_receivable", "summary"],
owner="风控与审计部",
reviewer="顾承宇",
status=AgentAssetStatus.ACTIVE.value,
current_version="v1.0.0",
config_json={"cron": "0 10 * * 1", "agent": AgentName.HERMES.value},
)
self._ensure_asset_version(
asset,
version="v1.0.0",
content=self._json_content(
{
"task_type": "weekly_ar_summary",
"schedule": "0 10 * * 1",
"target_agent": AgentName.HERMES.value,
}
),
content_type=AgentAssetContentType.JSON.value,
change_note="初始化应收账龄汇总任务。",
created_by="系统初始化",
)
if "task.hermes.rule_review_digest" not in existing_codes:
asset = self._create_seed_asset(
asset_type=AgentAssetType.TASK.value,
code="task.hermes.rule_review_digest",
name="Hermes 规则待审摘要",
description="每天汇总待审规则、待补样例和被拒规则修订建议。",
domain=AgentAssetDomain.SYSTEM.value,
scenario_json=["schedule", "rule_center", "review_digest"],
owner="风控与审计部",
reviewer="顾承宇",
status=AgentAssetStatus.ACTIVE.value,
current_version="v1.0.0",
config_json={"cron": "0 18 * * *", "agent": AgentName.HERMES.value},
)
self._ensure_asset_version(
asset,
version="v1.0.0",
content=self._json_content(
{
"task_type": "rule_review_digest",
"schedule": "0 18 * * *",
"target_agent": AgentName.HERMES.value,
}
),
content_type=AgentAssetContentType.JSON.value,
change_note="初始化规则待审摘要任务。",
created_by="系统初始化",
)
def _create_seed_asset(
self,
*,
asset_type: str,
code: str,
name: str,
description: str,
domain: str,
scenario_json: list[str],
owner: str,
reviewer: str,
status: str,
current_version: str,
config_json: dict[str, object],
) -> AgentAsset:
asset = AgentAsset(
asset_type=asset_type,
code=code,
name=name,
description=description,
domain=domain,
scenario_json=scenario_json,
owner=owner,
reviewer=reviewer,
status=status,
current_version=current_version,
config_json=config_json,
)
self.db.add(asset)
self.db.flush()
return asset
def _ensure_asset_version(
self,
asset: AgentAsset,
*,
version: str,
content: str,
content_type: str,
change_note: str,
created_by: str,
) -> None:
existing = self.db.scalar(
select(AgentAssetVersion).where(
AgentAssetVersion.asset_id == asset.id,
AgentAssetVersion.version == version,
)
)
if existing is not None:
return
self.db.add(
AgentAssetVersion(
asset_id=asset.id,
version=version,
content=content,
content_type=content_type,
change_note=change_note,
created_by=created_by,
)
)
@staticmethod
def _markdown_content(content: str) -> str:
return content
@staticmethod
def _json_content(content: dict[str, object]) -> str:
return json.dumps(content, ensure_ascii=False, sort_keys=True, indent=2)

View File

@@ -0,0 +1,168 @@
from __future__ import annotations
import uuid
from datetime import UTC, datetime
from typing import Any
from sqlalchemy.orm import Session
from app.core.agent_enums import AgentPermissionLevel, AgentRunStatus
from app.core.logging import get_logger
from app.models.agent_run import AgentRun, AgentToolCall, SemanticParseLog
from app.repositories.agent_run import AgentRunRepository
from app.schemas.agent_run import AgentRunRead, AgentToolCallRead, SemanticParseRead
from app.services.agent_foundation import AgentFoundationService
logger = get_logger("app.services.agent_runs")
class AgentRunService:
def __init__(self, db: Session) -> None:
self.db = db
self.repository = AgentRunRepository(db)
def list_runs(
self,
*,
agent: str | None = None,
status: str | None = None,
source: str | None = None,
limit: int = 20,
) -> list[AgentRunRead]:
self._ensure_ready()
runs = self.repository.list(agent=agent, status=status, source=source, limit=limit)
return [self._serialize_run(item) for item in runs]
def get_run(self, run_id: str) -> AgentRunRead | None:
self._ensure_ready()
run = self.repository.get_by_run_id(run_id)
if run is None:
return None
return self._serialize_run(run)
def create_run(
self,
*,
agent: str,
source: str,
user_id: str | None = None,
task_id: str | None = None,
ontology_json: dict[str, Any] | None = None,
route_json: dict[str, Any] | None = None,
permission_level: str = AgentPermissionLevel.READ.value,
status: str = AgentRunStatus.RUNNING.value,
result_summary: str | None = None,
error_message: str | None = None,
started_at: datetime | None = None,
finished_at: datetime | None = None,
) -> AgentRunRead:
self._ensure_ready()
run = AgentRun(
run_id=f"run_{uuid.uuid4().hex[:16]}",
agent=agent,
source=source,
user_id=user_id,
task_id=task_id,
ontology_json=ontology_json or {},
route_json=route_json or {},
permission_level=permission_level,
status=status,
result_summary=result_summary,
error_message=error_message,
started_at=started_at or datetime.now(UTC),
finished_at=finished_at,
)
created = self.repository.create_run(run)
logger.info("Created agent run id=%s run_id=%s", created.id, created.run_id)
return self._serialize_run(created)
def record_tool_call(
self,
*,
run_id: str,
tool_type: str,
tool_name: str,
request_json: dict[str, Any] | None = None,
response_json: dict[str, Any] | None = None,
status: str,
duration_ms: int = 0,
error_message: str | None = None,
) -> AgentToolCallRead:
self._ensure_ready()
tool_call = AgentToolCall(
run_id=run_id,
tool_type=tool_type,
tool_name=tool_name,
request_json=request_json or {},
response_json=response_json or {},
status=status,
duration_ms=duration_ms,
error_message=error_message,
)
created = self.repository.create_tool_call(tool_call)
logger.info("Recorded tool call run_id=%s tool=%s", run_id, tool_name)
return AgentToolCallRead.model_validate(created)
def record_semantic_parse(
self,
*,
run_id: str,
user_id: str | None,
raw_query: str,
scenario: str,
intent: str,
entities_json: list[Any] | None = None,
time_range_json: dict[str, Any] | None = None,
metrics_json: list[Any] | None = None,
constraints_json: list[Any] | None = None,
risk_flags_json: list[Any] | None = None,
permission_json: dict[str, Any] | None = None,
confidence: float = 0.0,
) -> SemanticParseRead:
self._ensure_ready()
semantic_parse = SemanticParseLog(
run_id=run_id,
user_id=user_id,
raw_query=raw_query,
scenario=scenario,
intent=intent,
entities_json=entities_json or [],
time_range_json=time_range_json or {},
metrics_json=metrics_json or [],
constraints_json=constraints_json or [],
risk_flags_json=risk_flags_json or [],
permission_json=permission_json or {},
confidence=confidence,
)
created = self.repository.create_semantic_parse(semantic_parse)
logger.info(
"Recorded semantic parse run_id=%s scenario=%s intent=%s", run_id, scenario, intent
)
return SemanticParseRead.model_validate(created)
def _ensure_ready(self) -> None:
AgentFoundationService(self.db).ensure_foundation_ready()
@staticmethod
def _serialize_run(run: AgentRun) -> AgentRunRead:
semantic_parse = run.semantic_parse_logs[0] if run.semantic_parse_logs else None
return AgentRunRead(
id=run.id,
run_id=run.run_id,
agent=run.agent,
source=run.source,
user_id=run.user_id,
task_id=run.task_id,
ontology_json=run.ontology_json,
route_json=run.route_json,
permission_level=run.permission_level,
status=run.status,
result_summary=run.result_summary,
error_message=run.error_message,
started_at=run.started_at,
finished_at=run.finished_at,
tool_calls=[AgentToolCallRead.model_validate(item) for item in run.tool_calls],
semantic_parse=SemanticParseRead.model_validate(semantic_parse)
if semantic_parse
else None,
)

View File

@@ -0,0 +1,70 @@
from __future__ import annotations
import uuid
from typing import Any
from sqlalchemy.orm import Session
from app.core.logging import get_logger
from app.models.audit_log import AuditLog
from app.repositories.audit_log import AuditLogRepository
from app.schemas.audit_log import AuditLogRead
from app.services.agent_foundation import AgentFoundationService
logger = get_logger("app.services.audit")
class AuditLogService:
def __init__(self, db: Session) -> None:
self.db = db
self.repository = AuditLogRepository(db)
def list_logs(
self,
*,
resource_type: str | None = None,
resource_id: str | None = None,
action: str | None = None,
limit: int = 50,
) -> list[AuditLogRead]:
self._ensure_ready()
items = self.repository.list(
resource_type=resource_type,
resource_id=resource_id,
action=action,
limit=limit,
)
return [AuditLogRead.model_validate(item) for item in items]
def log_action(
self,
*,
actor: str,
action: str,
resource_type: str,
resource_id: str,
before_json: dict[str, Any] | None = None,
after_json: dict[str, Any] | None = None,
request_id: str | None = None,
) -> AuditLog:
log = AuditLog(
actor=actor,
action=action,
resource_type=resource_type,
resource_id=resource_id,
before_json=before_json,
after_json=after_json,
request_id=request_id or uuid.uuid4().hex,
)
created = self.repository.create(log)
logger.info(
"Created audit log id=%s action=%s resource=%s:%s",
created.id,
created.action,
created.resource_type,
created.resource_id,
)
return created
def _ensure_ready(self) -> None:
AgentFoundationService(self.db).ensure_foundation_ready()

View File

@@ -8,6 +8,9 @@ src/app/api/router.py
src/app/api/v1/__init__.py
src/app/api/v1/router.py
src/app/api/v1/endpoints/__init__.py
src/app/api/v1/endpoints/agent_assets.py
src/app/api/v1/endpoints/agent_runs.py
src/app/api/v1/endpoints/audit_logs.py
src/app/api/v1/endpoints/auth.py
src/app/api/v1/endpoints/bootstrap.py
src/app/api/v1/endpoints/employees.py
@@ -17,6 +20,7 @@ src/app/api/v1/endpoints/reimbursements.py
src/app/api/v1/endpoints/settings.py
src/app/core/__init__.py
src/app/core/admin_secret.py
src/app/core/agent_enums.py
src/app/core/bootstrap.py
src/app/core/config.py
src/app/core/logging.py
@@ -29,9 +33,13 @@ src/app/db/session.py
src/app/middleware/__init__.py
src/app/middleware/logging.py
src/app/models/__init__.py
src/app/models/agent_asset.py
src/app/models/agent_run.py
src/app/models/approval.py
src/app/models/audit_log.py
src/app/models/employee.py
src/app/models/employee_change_log.py
src/app/models/financial_record.py
src/app/models/organization.py
src/app/models/reimbursement.py
src/app/models/role.py
@@ -39,10 +47,16 @@ src/app/models/system_model_setting.py
src/app/models/system_setting.py
src/app/models/system_setting_secret.py
src/app/repositories/__init__.py
src/app/repositories/agent_asset.py
src/app/repositories/agent_run.py
src/app/repositories/audit_log.py
src/app/repositories/employee.py
src/app/repositories/reimbursement.py
src/app/repositories/settings.py
src/app/schemas/__init__.py
src/app/schemas/agent_asset.py
src/app/schemas/agent_run.py
src/app/schemas/audit_log.py
src/app/schemas/auth.py
src/app/schemas/bootstrap.py
src/app/schemas/employee.py
@@ -50,9 +64,14 @@ src/app/schemas/knowledge.py
src/app/schemas/reimbursement.py
src/app/schemas/settings.py
src/app/services/__init__.py
src/app/services/agent_assets.py
src/app/services/agent_foundation.py
src/app/services/agent_runs.py
src/app/services/audit.py
src/app/services/auth.py
src/app/services/employee.py
src/app/services/employee_seed.py
src/app/services/hermes_sync.py
src/app/services/knowledge.py
src/app/services/model_connectivity.py
src/app/services/reimbursement.py
@@ -62,9 +81,14 @@ src/x_financial_server.egg-info/SOURCES.txt
src/x_financial_server.egg-info/dependency_links.txt
src/x_financial_server.egg-info/requires.txt
src/x_financial_server.egg-info/top_level.txt
tests/test_agent_asset_service.py
tests/test_agent_foundation_endpoints.py
tests/test_auth_service.py
tests/test_config_settings_reload.py
tests/test_employee_service.py
tests/test_env_file_precedence.py
tests/test_imports.py
tests/test_knowledge_onlyoffice_config.py
tests/test_server_start_dependencies.py
tests/test_settings_persistence.py
tests/test_settings_service.py
tests/test_settings_service.py

View File

@@ -0,0 +1,186 @@
from __future__ import annotations
import uuid
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import Session, sessionmaker
from sqlalchemy.pool import StaticPool
from app.core.agent_enums import (
AgentAssetContentType,
AgentAssetDomain,
AgentAssetStatus,
AgentAssetType,
AgentName,
AgentReviewStatus,
AgentRunSource,
AgentRunStatus,
)
from app.db.base import Base
from app.schemas.agent_asset import (
AgentAssetCreate,
AgentAssetReviewCreate,
AgentAssetVersionCreate,
)
from app.services.agent_assets import AgentAssetService
from app.services.agent_runs import AgentRunService
from app.services.audit import AuditLogService
def build_session() -> Session:
engine = create_engine(
"sqlite+pysqlite:///:memory:",
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)
Base.metadata.create_all(bind=engine)
session_factory = sessionmaker(bind=engine, autoflush=False, autocommit=False)
return session_factory()
def test_agent_asset_service_seeds_assets_and_enforces_review_before_activation() -> None:
with build_session() as db:
service = AgentAssetService(db)
rules = service.list_assets(asset_type=AgentAssetType.RULE.value)
assert len(rules) >= 3
pending_rule = next(item for item in rules if item.status == AgentAssetStatus.REVIEW.value)
with pytest.raises(PermissionError):
service.activate_asset(pending_rule.id, actor="pytest")
def test_agent_asset_service_seeds_all_foundation_asset_types() -> None:
with build_session() as db:
service = AgentAssetService(db)
assert len(service.list_assets(asset_type=AgentAssetType.RULE.value)) >= 3
assert len(service.list_assets(asset_type=AgentAssetType.SKILL.value)) >= 2
assert len(service.list_assets(asset_type=AgentAssetType.MCP.value)) >= 2
assert len(service.list_assets(asset_type=AgentAssetType.TASK.value)) >= 3
def test_agent_asset_service_can_activate_rule_after_review() -> None:
with build_session() as db:
service = AgentAssetService(db)
created = service.create_asset(
AgentAssetCreate(
asset_type=AgentAssetType.RULE,
code=f"rule.test.{uuid.uuid4().hex[:8]}",
name="测试规则",
description="用于测试审核和上线流程。",
domain=AgentAssetDomain.EXPENSE,
scenario_json=["expense", "risk_check"],
owner="pytest",
reviewer="reviewer",
status=AgentAssetStatus.DRAFT,
config_json={"enabled": False},
),
actor="pytest",
)
service.create_version(
created.id,
AgentAssetVersionCreate(
version="v1.0.0",
content="# 测试规则\n\n- 仅用于测试。",
content_type=AgentAssetContentType.MARKDOWN,
change_note="初始化版本",
created_by="pytest",
),
actor="pytest",
)
service.create_review(
created.id,
AgentAssetReviewCreate(
version="v1.0.0",
reviewer="reviewer",
review_status=AgentReviewStatus.APPROVED,
review_note="可以上线",
),
actor="reviewer",
)
activated = service.activate_asset(created.id, actor="reviewer")
assert activated.status == AgentAssetStatus.ACTIVE.value
assert activated.current_version == "v1.0.0"
assert activated.latest_review is not None
assert activated.latest_review.review_status == AgentReviewStatus.APPROVED.value
def test_agent_asset_service_returns_recent_versions_for_rule_detail() -> None:
with build_session() as db:
service = AgentAssetService(db)
rule = next(
item
for item in service.list_assets(asset_type=AgentAssetType.RULE.value)
if item.code == "rule.expense.duplicate_expense_check"
)
detail = service.get_asset(rule.id)
assert detail is not None
assert detail.current_version == "v1.1.0"
assert detail.current_version_content_type == AgentAssetContentType.MARKDOWN.value
assert isinstance(detail.current_version_content, str)
assert len(detail.recent_versions) >= 2
assert any(item.is_current for item in detail.recent_versions)
assert {item.version for item in detail.recent_versions} >= {"v1.0.0", "v1.1.0"}
def test_agent_run_service_lists_seeded_trace_data() -> None:
with build_session() as db:
service = AgentRunService(db)
runs = service.list_runs()
assert len(runs) >= 3
assert any(item.tool_calls for item in runs)
assert any(item.semantic_parse is not None for item in runs)
def test_agent_run_service_creates_run_and_persists_error_message() -> None:
with build_session() as db:
service = AgentRunService(db)
created = service.create_run(
agent=AgentName.ORCHESTRATOR.value,
source=AgentRunSource.SYSTEM_EVENT.value,
status=AgentRunStatus.FAILED.value,
error_message="simulated failure",
result_summary="failed to route request",
)
fetched = service.get_run(created.run_id)
assert fetched is not None
assert fetched.run_id.startswith("run_")
assert fetched.status == AgentRunStatus.FAILED.value
assert fetched.error_message == "simulated failure"
assert fetched.result_summary == "failed to route request"
def test_agent_asset_creation_writes_audit_log() -> None:
with build_session() as db:
service = AgentAssetService(db)
created = service.create_asset(
AgentAssetCreate(
asset_type=AgentAssetType.SKILL,
code=f"skill.test.{uuid.uuid4().hex[:8]}",
name="测试技能",
description="用于测试审计日志写入。",
domain=AgentAssetDomain.KNOWLEDGE,
scenario_json=["knowledge", "query"],
owner="pytest",
reviewer="reviewer",
status=AgentAssetStatus.DRAFT,
config_json={"enabled": True},
),
actor="pytest",
)
logs = AuditLogService(db).list_logs(resource_id=created.id)
assert any(item.action == "create_agent_asset" for item in logs)

View File

@@ -0,0 +1,92 @@
from __future__ import annotations
from collections.abc import Generator
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import Session, sessionmaker
from sqlalchemy.pool import StaticPool
from app.api.deps import get_db
from app.core.agent_enums import AgentAssetStatus
from app.db.base import Base
from app.main import create_app
from app.services.agent_assets import AgentAssetService
def build_client() -> tuple[TestClient, sessionmaker[Session]]:
engine = create_engine(
"sqlite+pysqlite:///:memory:",
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)
Base.metadata.create_all(bind=engine)
session_factory = sessionmaker(bind=engine, autoflush=False, autocommit=False)
app = create_app()
def override_db() -> Generator[Session, None, None]:
db = session_factory()
try:
yield db
finally:
db.close()
app.dependency_overrides[get_db] = override_db
return TestClient(app), session_factory
def test_list_agent_assets_endpoint_returns_seeded_items() -> None:
client, _ = build_client()
response = client.get("/api/v1/agent-assets", params={"asset_type": "rule"})
assert response.status_code == 200
payload = response.json()
assert payload
assert all(item["asset_type"] == "rule" for item in payload)
def test_get_agent_asset_detail_endpoint_returns_version_history() -> None:
client, _ = build_client()
list_response = client.get("/api/v1/agent-assets", params={"asset_type": "rule"})
asset_id = list_response.json()[0]["id"]
response = client.get(f"/api/v1/agent-assets/{asset_id}")
assert response.status_code == 200
payload = response.json()
assert payload["recent_versions"]
assert payload["current_version_content_type"] == "markdown"
assert len(payload["recent_versions"]) >= 2
def test_activate_pending_rule_endpoint_is_blocked() -> None:
client, session_factory = build_client()
with session_factory() as db:
pending_rule = next(
item
for item in AgentAssetService(db).list_assets(asset_type="rule")
if item.status == AgentAssetStatus.REVIEW.value
)
response = client.post(
f"/api/v1/agent-assets/{pending_rule.id}/activate",
headers={"x-actor": "pytest"},
)
assert response.status_code == 400
assert "审核" in response.json()["detail"]
def test_list_audit_logs_endpoint_returns_seeded_logs() -> None:
client, _ = build_client()
response = client.get("/api/v1/audit-logs")
assert response.status_code == 200
payload = response.json()
assert payload
assert any(item["action"] == "review_rule" for item in payload)