feat: deliver agent foundation day 1
This commit is contained in:
@@ -11,6 +11,25 @@
|
|||||||
- 建设 Agent Orchestrator,统一负责路由、权限、工具调用、审计和失败处理。
|
- 建设 Agent Orchestrator,统一负责路由、权限、工具调用、审计和失败处理。
|
||||||
- 让规则中心、MCP、知识库、数据库查询和任务系统使用同一套语义协议。
|
- 让规则中心、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)
|
1. [01_overall_architecture.md](./01_overall_architecture.md)
|
||||||
@@ -28,6 +47,7 @@
|
|||||||
13. [13_rule_formation_lifecycle.md](./13_rule_formation_lifecycle.md)
|
13. [13_rule_formation_lifecycle.md](./13_rule_formation_lifecycle.md)
|
||||||
14. [14_financial_document_canonical_model.md](./14_financial_document_canonical_model.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)
|
15. [15_feedback_learning_loop.md](./15_feedback_learning_loop.md)
|
||||||
|
16. [weekly_execution_details/README.md](./weekly_execution_details/README.md)
|
||||||
|
|
||||||
开发原则:
|
开发原则:
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,13 @@ OCR、MCP、用户填写、业务数据库可能都描述同一张发票,但
|
|||||||
- Hermes 难以批量统计。
|
- Hermes 难以批量统计。
|
||||||
- MCP 返回结果难以合并。
|
- MCP 返回结果难以合并。
|
||||||
|
|
||||||
|
这里要区分两层:
|
||||||
|
|
||||||
|
- 标准模型:定义 Agent、规则、MCP、OCR、数据库之间统一交换的数据结构。
|
||||||
|
- 业务数据库表:定义 MVP 阶段真正落库存储、查询和统计所依赖的最小业务表。
|
||||||
|
|
||||||
|
如果只有标准模型,没有最小业务表,User Agent 和 Hermes 仍然无法完成报销、应收、应付的查询、解释、巡检和统计。
|
||||||
|
|
||||||
## 2. 标准对象
|
## 2. 标准对象
|
||||||
|
|
||||||
第一版建议定义这些对象:
|
第一版建议定义这些对象:
|
||||||
@@ -20,6 +27,8 @@ Invoice
|
|||||||
Receipt
|
Receipt
|
||||||
ReimbursementRequest
|
ReimbursementRequest
|
||||||
PaymentRequest
|
PaymentRequest
|
||||||
|
AccountsReceivableRecord
|
||||||
|
AccountsPayableRecord
|
||||||
BankTransaction
|
BankTransaction
|
||||||
Contract
|
Contract
|
||||||
Customer
|
Customer
|
||||||
@@ -57,11 +66,17 @@ CostCenter
|
|||||||
"request_id": "",
|
"request_id": "",
|
||||||
"request_no": "",
|
"request_no": "",
|
||||||
"employee_id": "",
|
"employee_id": "",
|
||||||
|
"employee_name": "",
|
||||||
"department_id": "",
|
"department_id": "",
|
||||||
|
"department_name": "",
|
||||||
|
"project_code": "",
|
||||||
"expense_type": "",
|
"expense_type": "",
|
||||||
|
"reason": "",
|
||||||
|
"location": "",
|
||||||
"amount": 0,
|
"amount": 0,
|
||||||
"currency": "CNY",
|
"currency": "CNY",
|
||||||
"status": "",
|
"status": "",
|
||||||
|
"occurred_at": "",
|
||||||
"submitted_at": "",
|
"submitted_at": "",
|
||||||
"approval_stage": "",
|
"approval_stage": "",
|
||||||
"invoices": [],
|
"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
|
```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 推断字段必须标记来源和置信度。
|
LLM 推断字段必须标记来源和置信度。
|
||||||
|
|
||||||
## 7. 与语义本体关系
|
## 10. 与语义本体关系
|
||||||
|
|
||||||
语义本体识别的是用户意图和对象。
|
语义本体识别的是用户意图和对象。
|
||||||
|
|
||||||
@@ -112,15 +301,28 @@ ontology.entities[].type = invoice
|
|||||||
-> 映射到 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
|
```text
|
||||||
Step 1: 定义 Invoice 标准模型
|
Step 1: 定义 Invoice 标准模型
|
||||||
Step 2: 定义 ReimbursementRequest 标准模型
|
Step 2: 定义 ReimbursementRequest 标准模型
|
||||||
Step 3: OCR 输出映射到 Invoice
|
Step 3: 定义 AccountsReceivableRecord / AccountsPayableRecord 标准模型
|
||||||
Step 4: MCP 输出映射到 Invoice
|
Step 4: 设计 MVP 最小业务表 expense_claims / expense_claim_items / accounts_receivable / accounts_payable
|
||||||
Step 5: 规则中心基于标准模型执行
|
Step 5: OCR 输出映射到 Invoice
|
||||||
Step 6: 扩展 AR/AP 标准模型
|
Step 6: MCP 输出映射到 Invoice 或 AR/AP 标准模型
|
||||||
Step 7: 建立字段血缘和置信度
|
Step 7: 规则中心基于标准模型执行
|
||||||
|
Step 8: 建立字段血缘和置信度
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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 4:Orchestrator 运行时
|
||||||
|
|
||||||
|
路线图:
|
||||||
|
|
||||||
|
- `document/development/agent week plan/day_4_orchestrator_runtime.md`
|
||||||
|
|
||||||
|
执行细则:
|
||||||
|
|
||||||
|
- [day_4_orchestrator_runtime.md](./day_4_orchestrator_runtime.md)
|
||||||
|
|
||||||
|
核心完成物:
|
||||||
|
|
||||||
|
- [ ] Orchestrator 入口。
|
||||||
|
- [ ] Agent 路由。
|
||||||
|
- [ ] 权限拦截。
|
||||||
|
- [ ] 工具调用封装。
|
||||||
|
- [ ] Trace 查询。
|
||||||
|
- [ ] 降级返回。
|
||||||
|
|
||||||
|
## Day 5:User 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 6:Hermes 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 独立推进开发。
|
||||||
@@ -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 的验收证据。
|
||||||
|
- [ ] 遇到阻塞时,在当天文档的“阻塞记录”下新增一条说明。
|
||||||
|
- [ ] 每天收尾时更新当天文档的“日终交接”。
|
||||||
@@ -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 保留为数据与治理底座。~~
|
||||||
@@ -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 语义本体需要复用的资产数据。
|
||||||
@@ -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 可以直接复用的响应结构。
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
# Day 4:Orchestrator 运行时 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 需要实现的接口契约。
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
# Day 5:User 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 可以复用的规则、风险、知识接口。
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
# Day 6:Hermes 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 调用 Orchestrator,source 为 `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 需要重点回归的路径。
|
||||||
@@ -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] ~~...~~` 标记。
|
||||||
|
|
||||||
|
## 测试记录
|
||||||
|
|
||||||
|
- [ ] 后端测试:未运行。
|
||||||
|
- [ ] 前端构建:未运行。
|
||||||
|
- [ ] 语义评测:未运行。
|
||||||
|
- [ ] 手动验收:未运行。
|
||||||
|
|
||||||
|
## 阻塞记录
|
||||||
|
|
||||||
|
- [ ] 暂无。
|
||||||
|
|
||||||
|
## 日终交接
|
||||||
|
|
||||||
|
- [ ] 写明本周最终完成内容。
|
||||||
|
- [ ] 写明未完成内容。
|
||||||
|
- [ ] 写明生产化前必须补齐内容。
|
||||||
|
- [ ] 写明下一周建议优先级。
|
||||||
@@ -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
|
1. 先读 [MASTER_TODO.md](./MASTER_TODO.md),确认 7 天节奏。
|
||||||
- [x] ~~建立 AgentAsset 数据模型~~
|
2. 进入当天 daily 文档,看当天主题、交付结果和验收门槛。
|
||||||
```
|
3. 点击 daily 中的“对应执行细则”,进入 `agent plan/weekly_execution_details`。
|
||||||
|
4. 在执行细则中按 `- [ ]` 一项一项开发。
|
||||||
|
5. 完成细则后回到 daily 文档,确认当天是否达到交付门槛。
|
||||||
|
|
||||||
执行要求:
|
## 一周总体目标
|
||||||
|
|
||||||
- [ ] 每次只处理一个最小 TODO。
|
- Day 1:先把资产、版本、审核、运行日志、审计日志等基础地基建好。
|
||||||
- [ ] 完成后先自测,再改成 `[x]`。
|
- Day 2:把任务规则中心和后端资产体系打通。
|
||||||
- [ ] 改成 `[x]` 时,同时用 `~~` 画线。
|
- Day 3:建立语义本体 MVP,让用户问题能变成稳定结构。
|
||||||
- [ ] 不能因为代码写完就标完成,必须满足该 TODO 的验收证据。
|
- Day 4:建立 Orchestrator,让请求能被统一路由、审计、降级。
|
||||||
- [ ] 遇到阻塞时,在当天文档的“阻塞记录”下新增一条说明。
|
- Day 5:建立 User Agent MVP,处理用户查询、解释和草稿生成。
|
||||||
- [ ] 每天收尾时更新当天文档的“日终交接”。
|
- Day 6:建立 Hermes MVP,处理定时巡检、统计、知识和规则草稿。
|
||||||
|
- Day 7:做加固、测试、演示、验收和下一阶段交接。
|
||||||
|
|
||||||
## 文档顺序
|
## 一周暂不完成
|
||||||
|
|
||||||
先看总控清单,再进入每天的执行文档:
|
- 完整 OCR 生产识别引擎。
|
||||||
|
- 完整发票验真 MCP 深度接入。
|
||||||
1. [MASTER_TODO.md](./MASTER_TODO.md)
|
- 完整 LLM Wiki 向量检索。
|
||||||
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)
|
- 完整 CI/CD 质量门禁。
|
||||||
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,只做本地构建、核心测试和验收脚本。
|
|
||||||
|
|
||||||
## 生产底线
|
## 生产底线
|
||||||
|
|
||||||
以下底线不得被 MVP 名义绕过:
|
- 所有写操作必须有审计日志。
|
||||||
|
- 所有 Agent 执行必须生成 `run_id`。
|
||||||
- [ ] 所有写操作必须记录审计日志。
|
- 所有规则必须有版本。
|
||||||
- [ ] 所有 Agent 执行必须生成 `run_id`。
|
- 未审核规则不能上线。
|
||||||
- [ ] 所有规则必须有版本。
|
- 高风险动作只能生成草稿或建议,不能自动提交。
|
||||||
- [ ] 未审核规则不能上线。
|
- 外部能力失败必须有降级结果。
|
||||||
- [ ] 高风险动作只能生成草稿或建议,不能自动提交。
|
- 语义解析结果必须可回放。
|
||||||
- [ ] 外部服务失败必须有降级结果。
|
|
||||||
- [ ] 语义解析结果必须落库或落日志,便于回放。
|
|
||||||
- [ ] 前端不能只写静态 UI,必须至少对接 Mock 或真实 API。
|
|
||||||
|
|
||||||
## 每日固定流程
|
|
||||||
|
|
||||||
上午:
|
|
||||||
|
|
||||||
- [ ] 读取当天文档。
|
|
||||||
- [ ] 检查前一天遗留阻塞。
|
|
||||||
- [ ] 确认数据库模型、API、服务边界。
|
|
||||||
- [ ] 完成后端主路径。
|
|
||||||
|
|
||||||
下午:
|
|
||||||
|
|
||||||
- [ ] 完成前端联调。
|
|
||||||
- [ ] 接入 Agent 或 Orchestrator 流程。
|
|
||||||
- [ ] 完成权限、审计、错误态。
|
|
||||||
|
|
||||||
傍晚:
|
|
||||||
|
|
||||||
- [ ] 运行测试和构建。
|
|
||||||
- [ ] 按当天验收清单逐项验收。
|
|
||||||
- [ ] 更新 TODO 完成状态。
|
|
||||||
- [ ] 填写日终交接。
|
|
||||||
|
|
||||||
## Codex 执行约束
|
|
||||||
|
|
||||||
- [ ] 修改代码前先读相关文件,不凭空创建重复模块。
|
|
||||||
- [ ] 优先复用现有 FastAPI、SQLAlchemy、Vue、PrimeVue 写法。
|
|
||||||
- [ ] API 命名必须稳定,不能一天一个风格。
|
|
||||||
- [ ] 数据模型新增字段必须写清楚用途。
|
|
||||||
- [ ] 前端状态、空态、错误态、加载态都要覆盖。
|
|
||||||
- [ ] 每天结束必须能给出可运行证据。
|
|
||||||
|
|||||||
@@ -1,119 +1,46 @@
|
|||||||
# Agent Week Plan 总控 TODO
|
# Agent Week Plan 总览
|
||||||
|
|
||||||
本文件用于控制 7 天开发顺序。每个大项完成后,再进入对应天的详细文档。
|
本文件只放一周路线图,不放细节 TODO。具体执行步骤在 `agent plan/weekly_execution_details`。
|
||||||
|
|
||||||
完成标记规则:
|
## 快速浏览
|
||||||
|
|
||||||
```md
|
- HTML 总览:[agent_week_plan_html/index.html](<../agent_week_plan_html/index.html>)
|
||||||
- [x] ~~已完成的任务~~
|
- 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`。
|
| Day | 状态 | 主题 | 主要交付 | 快速视图 | 对应执行细则 |
|
||||||
- [ ] 阅读 `document/development/agent plan/02_semantic_ontology.md`。
|
| --- | --- | --- | --- | --- | --- |
|
||||||
- [ ] 阅读 `document/development/agent plan/06_data_contracts_and_governance.md`。
|
| Day 1 | 已完成(2026-05-11) | 基础模型与工程骨架 | 资产、版本、审核、运行日志、审计日志、基础 API、最小财务数据源 | [HTML](<../agent_week_plan_html/day-1.html>) | [细则](<../agent plan/weekly_execution_details/day_1_foundation_models.md>) |
|
||||||
- [ ] 阅读 `document/development/agent plan/07_capability_registry.md`。
|
| Day 2 | 未开始 | 任务规则中心联调 | 规则/技能/MCP/任务列表与详情、Markdown、版本、审核 | [HTML](<../agent_week_plan_html/day-2.html>) | [细则](<../agent plan/weekly_execution_details/day_2_rule_center_integration.md>) |
|
||||||
- [ ] 阅读 `document/development/agent plan/08_permission_confirmation.md`。
|
| Day 3 | 未开始 | 语义本体 MVP | 8 字段语义解析、日志、评测入口 | [HTML](<../agent_week_plan_html/day-3.html>) | [细则](<../agent plan/weekly_execution_details/day_3_semantic_ontology_mvp.md>) |
|
||||||
- [ ] 阅读 `document/development/agent plan/09_observability_and_trace.md`。
|
| Day 4 | 未开始 | Orchestrator 运行时 | 统一入口、路由、权限、工具调用、Trace | [HTML](<../agent_week_plan_html/day-4.html>) | [细则](<../agent plan/weekly_execution_details/day_4_orchestrator_runtime.md>) |
|
||||||
- [ ] 完成统一资产模型 `AgentAsset`。
|
| Day 5 | 未开始 | User Agent MVP | 用户问答、财务查询、规则解释、草稿生成 | [HTML](<../agent_week_plan_html/day-5.html>) | [细则](<../agent plan/weekly_execution_details/day_5_user_agent_mvp.md>) |
|
||||||
- [ ] 完成资产版本模型 `AgentAssetVersion`。
|
| Day 6 | 未开始 | Hermes MVP | 定时任务、风险巡检、日报、知识候选、规则草稿 | [HTML](<../agent_week_plan_html/day-6.html>) | [细则](<../agent plan/weekly_execution_details/day_6_hermes_mvp.md>) |
|
||||||
- [ ] 完成资产审核模型 `AgentAssetReview`。
|
| Day 7 | 未开始 | 加固、演示和验收 | 回归、测试、演示脚本、交付说明 | [HTML](<../agent_week_plan_html/day-7.html>) | [细则](<../agent plan/weekly_execution_details/day_7_hardening_demo_acceptance.md>) |
|
||||||
- [ ] 完成 Agent 运行日志模型 `AgentRun`。
|
|
||||||
- [ ] 完成工具调用日志模型 `AgentToolCall`。
|
|
||||||
- [ ] 完成语义解析日志模型 `SemanticParseLog`。
|
|
||||||
- [ ] 完成审计日志模型 `AuditLog`。
|
|
||||||
- [ ] 完成基础 API 路由骨架。
|
|
||||||
- [ ] 完成种子数据。
|
|
||||||
- [ ] 完成 Day 1 验收。
|
|
||||||
|
|
||||||
## Day 2:任务规则中心联调
|
## 当前完成情况
|
||||||
|
|
||||||
- [ ] 阅读 `document/development/agent plan/13_rule_formation_lifecycle.md`。
|
- Day 1 已完成,后端基础模型、审计和最小财务数据源已可供 Day 2 前端联调使用。
|
||||||
- [ ] 阅读 `document/development/agent plan/07_capability_registry.md`。
|
- Day 2 到 Day 7 保持原排期,下一步直接进入规则中心联调。
|
||||||
- [ ] 对接规则、技能、MCP、任务资产列表 API。
|
|
||||||
- [ ] 对接资产详情 API。
|
|
||||||
- [ ] 对接规则 Markdown 读取和保存 API。
|
|
||||||
- [ ] 对接版本列表和版本切换 API。
|
|
||||||
- [ ] 对接审核者信息和审核状态。
|
|
||||||
- [ ] 对接规则上线前审核拦截。
|
|
||||||
- [ ] 完成前端筛选、搜索、详情、弹窗状态。
|
|
||||||
- [ ] 完成 Day 2 验收。
|
|
||||||
|
|
||||||
## Day 3:语义本体 MVP
|
## 关键依赖顺序
|
||||||
|
|
||||||
- [ ] 阅读 `document/development/agent plan/02_semantic_ontology.md`。
|
1. Day 1 必须先完成,因为后面所有能力都依赖资产、版本、审核、日志。
|
||||||
- [ ] 阅读 `document/development/agent plan/14_financial_document_canonical_model.md`。
|
2. Day 2 必须在 Day 3 前完成,因为语义和 Agent 需要读取规则、技能、MCP、任务资产。
|
||||||
- [ ] 定义 8 个核心字段的数据结构。
|
3. Day 3 必须在 Day 4 前完成,因为 Orchestrator 依赖语义本体做路由。
|
||||||
- [ ] 实现语义解析服务。
|
4. Day 4 必须在 Day 5 / Day 6 前完成,因为 User Agent 和 Hermes 都应该由 Orchestrator 调用。
|
||||||
- [ ] 实现语义解析 API。
|
5. Day 5 和 Day 6 可以部分并行,但都必须遵守权限、审计、Trace。
|
||||||
- [ ] 实现解析日志保存。
|
6. Day 7 不新增大功能,只做加固、验收和交接。
|
||||||
- [ ] 实现场景、意图、对象、时间、指标、约束、风险、权限字段。
|
|
||||||
- [ ] 接入 User Agent 查询入口。
|
|
||||||
- [ ] 完成最小评测集。
|
|
||||||
- [ ] 完成 Day 3 验收。
|
|
||||||
|
|
||||||
## Day 4:Orchestrator 运行时
|
## 最终验收
|
||||||
|
|
||||||
- [ ] 阅读 `document/development/agent plan/04_orchestrator_and_runtime_flow.md`。
|
- 任务规则中心能看到规则、技能、MCP、任务。
|
||||||
- [ ] 阅读 `document/development/agent plan/08_permission_confirmation.md`。
|
- 规则详情能编辑 Markdown、查看最近 5 个版本、切换版本。
|
||||||
- [ ] 阅读 `document/development/agent plan/09_observability_and_trace.md`。
|
- 未审核规则不能上线。
|
||||||
- [ ] 实现 Orchestrator 入口服务。
|
- 用户问题能解析出语义本体 8 字段。
|
||||||
- [ ] 实现语义本体到 Agent 路由。
|
- Orchestrator 能路由到 User Agent 和 Hermes。
|
||||||
- [ ] 实现权限级别判断。
|
- User Agent 能完成查询、解释、草稿生成。
|
||||||
- [ ] 实现工具调用封装。
|
- Hermes 能执行一次风险巡检或日报任务。
|
||||||
- [ ] 实现运行 Trace。
|
- AgentRun、ToolCall、AuditLog 都能追溯。
|
||||||
- [ ] 实现降级和错误返回。
|
- 有演示脚本和下一阶段交接文档。
|
||||||
- [ ] 完成 Day 4 验收。
|
|
||||||
|
|
||||||
## Day 5:User Agent MVP
|
|
||||||
|
|
||||||
- [ ] 阅读 `document/development/agent plan/03_agent_responsibilities.md`。
|
|
||||||
- [ ] 阅读 `document/development/agent plan/12_llm_wiki_knowledge_architecture.md`。
|
|
||||||
- [ ] 实现用户自然语言入口。
|
|
||||||
- [ ] 实现报销查询解释流程。
|
|
||||||
- [ ] 实现应收账款查询解释流程。
|
|
||||||
- [ ] 实现应付账款查询解释流程。
|
|
||||||
- [ ] 实现规则引用解释。
|
|
||||||
- [ ] 实现建议草稿输出。
|
|
||||||
- [ ] 完成前端对话或操作入口。
|
|
||||||
- [ ] 完成 Day 5 验收。
|
|
||||||
|
|
||||||
## Day 6:Hermes 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。
|
|
||||||
- [ ] 能查看工具调用日志。
|
|
||||||
- [ ] 能查看审计日志。
|
|
||||||
- [ ] 能运行核心测试。
|
|
||||||
- [ ] 能完成演示脚本。
|
|
||||||
|
|||||||
@@ -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 智能。
|
||||||
|
|
||||||
- [ ] 确认当前分支和工作区状态。
|
核心是把后面 6 天都会用到的基础对象建出来:资产、版本、审核、运行日志、工具调用日志、语义解析日志、审计日志,以及最小财务业务数据来源。
|
||||||
- [ ] 确认后端目录位置,例如 `/app/server`。
|
|
||||||
- [ ] 确认前端目录位置,例如 `/app/web`。
|
|
||||||
- [ ] 确认后端使用的框架、ORM、迁移方式。
|
|
||||||
- [ ] 找到现有数据库模型目录。
|
|
||||||
- [ ] 找到现有 API 路由目录。
|
|
||||||
- [ ] 找到现有启动入口。
|
|
||||||
- [ ] 找到现有测试目录。
|
|
||||||
- [ ] 找到现有种子数据或初始化脚本。
|
|
||||||
- [ ] 记录不应修改的无关文件。
|
|
||||||
|
|
||||||
## 1. 统一命名和边界
|
## 为什么第一天做这个
|
||||||
|
|
||||||
- [ ] 确认统一模块名使用 `agent_assets`。
|
如果没有稳定的数据模型,后面的任务规则中心、语义本体、Orchestrator、User Agent、Hermes 都会各自临时造结构,后期会很难合并。
|
||||||
- [ ] 确认资产类型枚举为 `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`。
|
|
||||||
- [ ] 确认所有主键、外键、时间字段命名符合现有代码风格。
|
|
||||||
|
|
||||||
验收证据:
|
## 今天主要交付
|
||||||
|
|
||||||
- [ ] 枚举命名在模型、Schema、服务层保持一致。
|
- [x] ~~统一资产模型:规则、技能、MCP、任务。~~
|
||||||
- [ ] 没有同时出现 `schedule` 和 `task` 两套用户可见命名。
|
- [x] ~~版本模型:规则 Markdown 和其他资产配置快照。~~
|
||||||
|
- [x] ~~审核模型:未审核不能上线。~~
|
||||||
|
- [x] ~~Agent 运行日志:所有 Agent 执行都有 `run_id`。~~
|
||||||
|
- [x] ~~工具调用日志:MCP、数据库、LLM、OCR、规则引擎调用都可追踪。~~
|
||||||
|
- [x] ~~语义解析日志:后续语义本体结果可回放。~~
|
||||||
|
- [x] ~~审计日志:所有写操作可追责。~~
|
||||||
|
- [x] ~~最小财务业务数据来源:报销、应收、应付。~~
|
||||||
|
|
||||||
## 2. 建立 AgentAsset 模型
|
## 实际落地结果
|
||||||
|
|
||||||
- [ ] 新增或扩展模型文件,定义 `AgentAsset`。
|
- [x] ~~新增 `AgentAsset`、`AgentAssetVersion`、`AgentAssetReview`、`AgentRun`、`AgentToolCall`、`SemanticParseLog`、`AuditLog`、`ExpenseClaim`、`ExpenseClaimItem`、`AccountsReceivableRecord`、`AccountsPayableRecord`。~~
|
||||||
- [ ] 增加字段 `id`。
|
- [x] ~~新增 `/api/v1/agent-assets`、`/api/v1/agent-runs`、`/api/v1/audit-logs` 相关接口。~~
|
||||||
- [ ] 增加字段 `asset_type`,取值 `rule | skill | mcp | task`。
|
- [x] ~~种子数据已覆盖 3 条规则、2 条技能、2 条 MCP、3 条任务,以及报销 / 应收 / 应付示例数据。~~
|
||||||
- [ ] 增加字段 `code`,作为业务编码。
|
- [x] ~~旧开发库启动时会自动补齐新增资产和版本,不需要手动清库。~~
|
||||||
- [ ] 增加字段 `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` 增加索引。
|
|
||||||
|
|
||||||
验收证据:
|
## 对应执行细则
|
||||||
|
|
||||||
- [ ] 能创建一条规则资产。
|
- [Day 1 执行细则](<../agent plan/weekly_execution_details/day_1_foundation_models.md>)
|
||||||
- [ ] 能创建一条技能资产。
|
|
||||||
- [ ] 能创建一条 MCP 资产。
|
|
||||||
- [ ] 能创建一条任务资产。
|
|
||||||
|
|
||||||
## 3. 建立 AgentAssetVersion 模型
|
相关架构文档:
|
||||||
|
|
||||||
- [ ] 定义 `AgentAssetVersion`。
|
- [整体架构](<../agent plan/01_overall_architecture.md>)
|
||||||
- [ ] 增加字段 `id`。
|
- [语义本体](<../agent plan/02_semantic_ontology.md>)
|
||||||
- [ ] 增加字段 `asset_id`。
|
- [数据契约与治理](<../agent plan/06_data_contracts_and_governance.md>)
|
||||||
- [ ] 增加字段 `version`,例如 `v1.0.0`。
|
- [能力注册](<../agent plan/07_capability_registry.md>)
|
||||||
- [ ] 增加字段 `content`。
|
- [权限与确认](<../agent plan/08_permission_confirmation.md>)
|
||||||
- [ ] 增加字段 `content_type`,取值 `markdown | json`。
|
- [观测与 Trace](<../agent plan/09_observability_and_trace.md>)
|
||||||
- [ ] 增加字段 `change_note`。
|
- [财务单据标准模型](<../agent plan/14_financial_document_canonical_model.md>)
|
||||||
- [ ] 增加字段 `created_by`。
|
|
||||||
- [ ] 增加字段 `created_at`。
|
|
||||||
- [ ] 增加 `asset_id + version` 唯一约束。
|
|
||||||
- [ ] 建立 `AgentAsset` 到 `AgentAssetVersion` 的关系。
|
|
||||||
- [ ] 约定规则资产的 `content` 保存 Markdown。
|
|
||||||
- [ ] 约定技能、MCP、任务资产的 `content` 保存 JSON 快照。
|
|
||||||
|
|
||||||
验收证据:
|
## 当天验收门槛
|
||||||
|
|
||||||
- [ ] 同一个资产不能重复创建同一个版本号。
|
- [x] ~~数据库或等价存储能创建基础对象。~~
|
||||||
- [ ] 资产详情能拿到最近 5 个版本。
|
- [x] ~~API 服务能启动。~~
|
||||||
|
- [x] ~~资产列表能返回规则、技能、MCP、任务。~~
|
||||||
|
- [x] ~~规则资产能关联 Markdown 当前版本。~~
|
||||||
|
- [x] ~~未审核规则不能上线。~~
|
||||||
|
- [x] ~~AgentRun 能保存一条运行记录。~~
|
||||||
|
- [x] ~~AuditLog 能保存一条写操作记录。~~
|
||||||
|
|
||||||
## 4. 建立 AgentAssetReview 模型
|
## Day 2 联调入口
|
||||||
|
|
||||||
- [ ] 定义 `AgentAssetReview`。
|
- `GET /api/v1/agent-assets`
|
||||||
- [ ] 增加字段 `id`。
|
- `GET /api/v1/agent-assets/{asset_id}`
|
||||||
- [ ] 增加字段 `asset_id`。
|
- `GET /api/v1/agent-assets/{asset_id}/versions?limit=5`
|
||||||
- [ ] 增加字段 `version`。
|
- `POST /api/v1/agent-assets/{asset_id}/reviews`
|
||||||
- [ ] 增加字段 `reviewer`。
|
- `POST /api/v1/agent-assets/{asset_id}/activate`
|
||||||
- [ ] 增加字段 `review_status`。
|
- `GET /api/v1/audit-logs`
|
||||||
- [ ] 增加字段 `review_note`。
|
|
||||||
- [ ] 增加字段 `reviewed_at`。
|
|
||||||
- [ ] 增加字段 `created_at`。
|
|
||||||
- [ ] 建立资产、版本、审核之间的查询关系。
|
|
||||||
- [ ] 增加服务层校验:没有 `approved` 审核时不能把规则置为 `active`。
|
|
||||||
|
|
||||||
验收证据:
|
## 今天不做
|
||||||
|
|
||||||
- [ ] `pending` 规则上线会被拒绝。
|
- 不做完整 Agent 对话。
|
||||||
- [ ] `rejected` 规则上线会被拒绝。
|
- 不做完整 Hermes 调度。
|
||||||
- [ ] `approved` 规则可以上线。
|
- 不做真实 OCR。
|
||||||
|
- 不做复杂规则推理。
|
||||||
## 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 前端联调需要使用的接口地址。
|
|
||||||
|
|||||||
@@ -1,220 +1,49 @@
|
|||||||
# Day 2:任务规则中心联调 TODO
|
# Day 2:任务规则中心联调
|
||||||
|
|
||||||
目标:把任务规则中心从静态 UI 改成可对接后端的生产形态,覆盖规则、技能、MCP、任务四类资产。重点是规则 Markdown 编辑、版本切换、审核者信息、上线约束。
|
## 今天的大开发点
|
||||||
|
|
||||||
参考文档:
|
把任务规则中心从静态页面改成可和后端资产体系联动的生产形态。
|
||||||
|
|
||||||
- `document/development/agent plan/07_capability_registry.md`
|
重点是规则、技能、MCP、任务四类资产的列表和详情,以及规则 Markdown、版本、审核、上线约束。
|
||||||
- `document/development/agent plan/13_rule_formation_lifecycle.md`
|
|
||||||
- `document/development/agent plan/06_data_contracts_and_governance.md`
|
|
||||||
|
|
||||||
## 0. 开始前检查
|
## 为什么第二天做这个
|
||||||
|
|
||||||
- [ ] 确认 Day 1 API 已可访问。
|
任务规则中心是业务人员管理 Agent 能力的入口。后续语义本体、Orchestrator、User Agent、Hermes 都要读取这里注册的规则、技能、MCP 和任务。
|
||||||
- [ ] 确认前端任务规则中心文件位置。
|
|
||||||
- [ ] 确认现有路由名称和导航名称。
|
|
||||||
- [ ] 确认现有 UI 风格,不重新做大改版。
|
|
||||||
- [ ] 确认当前页面已有页签:规则、技能、MCP、任务。
|
|
||||||
- [ ] 确认详情页隐藏顶部 title bar 的逻辑仍然有效。
|
|
||||||
- [ ] 确认返回列表栏高度没有被重新拉高。
|
|
||||||
|
|
||||||
## 1. API Client
|
## 今天主要交付
|
||||||
|
|
||||||
- [ ] 新增或扩展资产列表请求函数。
|
- 规则、技能、MCP、任务四个页签对接资产 API。
|
||||||
- [ ] 新增资产详情请求函数。
|
- 列表支持搜索、筛选、状态展示。
|
||||||
- [ ] 新增版本列表请求函数。
|
- 规则详情展示 Markdown 内容。
|
||||||
- [ ] 新增规则 Markdown 保存请求函数。
|
- 管理员可编辑规则 Markdown。
|
||||||
- [ ] 新增审核请求函数。
|
- 规则版本展示最近 5 个版本。
|
||||||
- [ ] 新增上线请求函数。
|
- 版本切换需要弹窗确认。
|
||||||
- [ ] 新增运行日志请求函数。
|
- 审核者信息放在标题区域。
|
||||||
- [ ] 给所有请求增加加载态。
|
- 右侧只保留版本信息。
|
||||||
- [ ] 给所有请求增加错误态。
|
- 未审核规则上线时被后端拦截。
|
||||||
- [ ] 给所有写请求增加成功提示。
|
|
||||||
|
|
||||||
验收证据:
|
## 对应执行细则
|
||||||
|
|
||||||
- [ ] 前端不再只依赖本地硬编码资产数据。
|
- [Day 2 执行细则](<../agent plan/weekly_execution_details/day_2_rule_center_integration.md>)
|
||||||
- [ ] 后端不可用时页面有明确错误提示。
|
|
||||||
|
|
||||||
## 2. 列表页数据接入
|
相关架构文档:
|
||||||
|
|
||||||
- [ ] 规则页签请求 `asset_type=rule`。
|
- [能力注册](<../agent plan/07_capability_registry.md>)
|
||||||
- [ ] 技能页签请求 `asset_type=skill`。
|
- [规则形成生命周期](<../agent plan/13_rule_formation_lifecycle.md>)
|
||||||
- [ ] MCP 页签请求 `asset_type=mcp`。
|
- [数据契约与治理](<../agent plan/06_data_contracts_and_governance.md>)
|
||||||
- [ ] 任务页签请求 `asset_type=task`。
|
|
||||||
- [ ] 搜索框传递关键词或本地过滤。
|
|
||||||
- [ ] 类型下拉和搜索框可以同时生效。
|
|
||||||
- [ ] 状态筛选可以过滤 `draft | review | active | disabled`。
|
|
||||||
- [ ] 列表卡片展示名称。
|
|
||||||
- [ ] 列表卡片展示摘要。
|
|
||||||
- [ ] 列表卡片展示状态。
|
|
||||||
- [ ] 列表卡片展示负责人。
|
|
||||||
- [ ] 列表卡片展示最近更新时间。
|
|
||||||
- [ ] 空数据时展示空态。
|
|
||||||
- [ ] 加载中时展示骨架或加载状态。
|
|
||||||
|
|
||||||
验收证据:
|
## 当天验收门槛
|
||||||
|
|
||||||
- [ ] 四个页签都能切换。
|
- 四个页签可切换并有真实 API 或 Mock API 数据。
|
||||||
- [ ] 四个页签都有数据或空态。
|
- 规则详情可编辑 Markdown。
|
||||||
- [ ] 搜索和筛选不会互相覆盖。
|
- Markdown 保存后刷新不丢失。
|
||||||
|
- 版本卡片可切换版本。
|
||||||
|
- 未审核规则不能上线。
|
||||||
|
- 前端构建通过。
|
||||||
|
|
||||||
## 3. 规则详情页主信息
|
## 今天不做
|
||||||
|
|
||||||
- [ ] 打开规则资产时请求详情 API。
|
- 不做规则自动生成。
|
||||||
- [ ] Hero title 展示规则名称。
|
- 不做完整 MCP 真实调用。
|
||||||
- [ ] Hero title 下方展示审核者。
|
- 不做复杂权限矩阵。
|
||||||
- [ ] Hero title 下方展示审核状态。
|
- 不重做 UI 风格,只在现有风格上微调。
|
||||||
- [ ] 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 语义本体需要复用的资产数据。
|
|
||||||
|
|||||||
@@ -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`
|
这一天的目标不是做到 LLM 全能理解,而是先让报销、应收、应付、知识和风险相关问题能进入稳定结构。
|
||||||
- `document/development/agent plan/14_financial_document_canonical_model.md`
|
|
||||||
- `document/development/agent plan/06_data_contracts_and_governance.md`
|
|
||||||
|
|
||||||
## 0. 开始前检查
|
## 为什么第三天做这个
|
||||||
|
|
||||||
- [ ] 确认 Day 1 的 `SemanticParseLog` 可用。
|
Orchestrator 不能直接根据原始文本做可靠路由。它需要先拿到结构化语义,再决定调用 User Agent、Hermes、规则、MCP 或知识库。
|
||||||
- [ ] 确认 Day 1 的 `AgentRun` 可用。
|
|
||||||
- [ ] 确认 Day 2 的资产 API 可用。
|
|
||||||
- [ ] 找到后端服务层目录。
|
|
||||||
- [ ] 找到现有 LLM 调用或 Mock 调用方式。
|
|
||||||
- [ ] 确认当前是否允许真实调用 LLM。
|
|
||||||
- [ ] 如果不能调用真实 LLM,准备规则解析加 Mock 解析。
|
|
||||||
|
|
||||||
## 1. 定义 8 个核心字段
|
## 今天主要交付
|
||||||
|
|
||||||
- [ ] 定义字段 `scenario`,表示业务场景。
|
- 语义本体 8 字段结构。
|
||||||
- [ ] 定义字段 `intent`,表示用户意图。
|
- 场景识别:报销、应收、应付、知识、未知。
|
||||||
- [ ] 定义字段 `entities`,表示业务对象。
|
- 意图识别:查询、解释、对比、风险检查、草稿、操作。
|
||||||
- [ ] 定义字段 `time_range`,表示时间范围。
|
- 业务对象提取:员工、客户、供应商、部门、项目、单据、金额。
|
||||||
- [ ] 定义字段 `metrics`,表示指标或金额口径。
|
- 时间范围解析。
|
||||||
- [ ] 定义字段 `constraints`,表示过滤条件。
|
- 指标和约束解析。
|
||||||
- [ ] 定义字段 `risk_flags`,表示风险信号。
|
- 风险信号和权限级别判断。
|
||||||
- [ ] 定义字段 `permission`,表示动作权限。
|
- 语义解析 API。
|
||||||
- [ ] 为每个字段写清楚类型。
|
- 解析日志和最小评测集。
|
||||||
- [ ] 为每个字段写清楚是否必填。
|
|
||||||
- [ ] 为每个字段写清楚默认值。
|
|
||||||
- [ ] 为每个字段写清楚示例。
|
|
||||||
|
|
||||||
验收证据:
|
## 对应执行细则
|
||||||
|
|
||||||
- [ ] 8 个字段在 Schema、服务层、日志中名字一致。
|
- [Day 3 执行细则](<../agent plan/weekly_execution_details/day_3_semantic_ontology_mvp.md>)
|
||||||
|
|
||||||
## 2. 设计字段枚举
|
相关架构文档:
|
||||||
|
|
||||||
- [ ] `scenario` 支持 `expense`。
|
- [语义本体](<../agent plan/02_semantic_ontology.md>)
|
||||||
- [ ] `scenario` 支持 `accounts_receivable`。
|
- [财务单据标准模型](<../agent plan/14_financial_document_canonical_model.md>)
|
||||||
- [ ] `scenario` 支持 `accounts_payable`。
|
- [数据契约与治理](<../agent plan/06_data_contracts_and_governance.md>)
|
||||||
- [ ] `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`。
|
- 输入自然语言问题能返回 8 个字段。
|
||||||
|
- 低置信度问题能返回澄清问题。
|
||||||
|
- 越权动作不会被标记为可直接执行。
|
||||||
|
- 解析结果能写入日志。
|
||||||
|
- 至少覆盖报销、应收、应付三个场景。
|
||||||
|
|
||||||
## 3. 建立 Schema
|
## 今天不做
|
||||||
|
|
||||||
- [ ] 定义 `OntologyParseRequest`。
|
- 不做复杂多轮对话记忆。
|
||||||
- [ ] `OntologyParseRequest` 包含 `query`。
|
- 不做完整 LLM 提示词优化。
|
||||||
- [ ] `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 可以直接复用的响应结构。
|
|
||||||
|
|||||||
@@ -1,183 +1,47 @@
|
|||||||
# Day 4:Orchestrator 运行时 TODO
|
# Day 4:Orchestrator 运行时
|
||||||
|
|
||||||
目标:建立统一调度层,让用户请求和系统任务都先进入 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. 开始前检查
|
没有 Orchestrator,User 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. 路由规则
|
- 不做复杂任务编排 DAG。
|
||||||
|
- 不做多 Agent 协商。
|
||||||
- [ ] `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 需要实现的接口契约。
|
|
||||||
|
|||||||
@@ -1,183 +1,50 @@
|
|||||||
# Day 5:User Agent MVP TODO
|
# Day 5:User Agent MVP
|
||||||
|
|
||||||
目标:实现面向用户的自建 Agent。它负责用户提问、流程辅助、规则解释、查询结果解释和草稿生成,不做自动审批、自动付款、自动上线等高风险动作。
|
## 今天的大开发点
|
||||||
|
|
||||||
参考文档:
|
实现面向用户的自建 Agent。它负责用户提问、流程辅助、规则解释、查询结果解释和草稿生成。
|
||||||
|
|
||||||
- `document/development/agent plan/03_agent_responsibilities.md`
|
User Agent 只能处理用户侧交互,不负责后台定时内循环,也不能自动执行高风险动作。
|
||||||
- `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。
|
Day 1 到 Day 4 已经具备资产、语义、路由和日志基础,此时可以把用户自然语言入口接到真实流程上。
|
||||||
- [ ] 确认语义本体 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 直接包装返回。
|
- [Day 5 执行细则](<../agent plan/weekly_execution_details/day_5_user_agent_mvp.md>)
|
||||||
|
|
||||||
## 2. 查询处理
|
相关架构文档:
|
||||||
|
|
||||||
- [ ] 实现报销查询处理器。
|
- [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>)
|
||||||
- [ ] 查询时记录 ToolCall。
|
|
||||||
- [ ] 查询失败时返回可读错误。
|
|
||||||
- [ ] 查询为空时返回空态解释。
|
|
||||||
- [ ] 查询结果限制返回条数,避免一次返回过大。
|
|
||||||
|
|
||||||
验收证据:
|
## 当天验收门槛
|
||||||
|
|
||||||
- [ ] “查本周报销金额”有可读回答。
|
- 用户能输入自然语言问题。
|
||||||
- [ ] “客户 A 本月应收多少”有可读回答。
|
- 请求必须经过 Orchestrator。
|
||||||
- [ ] “供应商 B 待付款多少”有可读回答。
|
- 至少 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 可以复用的规则、风险、知识接口。
|
|
||||||
|
|||||||
@@ -1,191 +1,47 @@
|
|||||||
# Day 6:Hermes MVP TODO
|
# Day 6:Hermes 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 调用 Orchestrator,source 为 `schedule`。
|
|
||||||
- [ ] Orchestrator 路由到 Hermes。
|
|
||||||
- [ ] Hermes 执行结果写入 AgentRun。
|
|
||||||
- [ ] 任务执行失败时写入错误。
|
|
||||||
- [ ] 任务执行结束后更新任务最近执行时间。
|
|
||||||
- [ ] 任务执行结束后更新任务最近执行状态。
|
|
||||||
|
|
||||||
验收证据:
|
- 至少一个 Hermes 任务可以手动触发。
|
||||||
|
- 风险巡检有结构化结果。
|
||||||
|
- 每日统计有结构化结果。
|
||||||
|
- OCR Mock 调用能记录 ToolCall。
|
||||||
|
- 知识候选只能是草稿。
|
||||||
|
- 规则草稿只能是 draft,不能自动上线。
|
||||||
|
|
||||||
- [ ] 可以手动触发一次 Hermes 任务并看到运行结果。
|
## 今天不做
|
||||||
|
|
||||||
## 3. 每日风险巡检
|
- 不做完整生产调度集群。
|
||||||
|
- 不做真实 OCR 深度集成。
|
||||||
- [ ] 实现重复报销巡检。
|
- 不做自动发布知识。
|
||||||
- [ ] 实现金额超标巡检。
|
- 不做自动上线规则。
|
||||||
- [ ] 实现发票异常巡检占位。
|
|
||||||
- [ ] 实现应收逾期巡检。
|
|
||||||
- [ ] 实现应付异常付款巡检。
|
|
||||||
- [ ] 每个风险项包含风险类型。
|
|
||||||
- [ ] 每个风险项包含业务对象。
|
|
||||||
- [ ] 每个风险项包含触发规则。
|
|
||||||
- [ ] 每个风险项包含建议动作。
|
|
||||||
- [ ] 每个风险项包含风险等级。
|
|
||||||
|
|
||||||
验收证据:
|
|
||||||
|
|
||||||
- [ ] 风险巡检结果可以被用户理解和追溯。
|
|
||||||
|
|
||||||
## 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 需要重点回归的路径。
|
|
||||||
|
|||||||
@@ -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] ~~...~~` 标记。
|
|
||||||
|
|
||||||
## 测试记录
|
|
||||||
|
|
||||||
- [ ] 后端测试:未运行。
|
|
||||||
- [ ] 前端构建:未运行。
|
|
||||||
- [ ] 语义评测:未运行。
|
|
||||||
- [ ] 手动验收:未运行。
|
|
||||||
|
|
||||||
## 阻塞记录
|
|
||||||
|
|
||||||
- [ ] 暂无。
|
|
||||||
|
|
||||||
## 日终交接
|
|
||||||
|
|
||||||
- [ ] 写明本周最终完成内容。
|
|
||||||
- [ ] 写明未完成内容。
|
|
||||||
- [ ] 写明生产化前必须补齐内容。
|
|
||||||
- [ ] 写明下一周建议优先级。
|
|
||||||
|
|||||||
137
document/development/agent_week_plan_html/day-1.html
Normal file
137
document/development/agent_week_plan_html/day-1.html
Normal 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 资产 API,Day 3 解析日志,Day 4 run trace,Day 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>
|
||||||
132
document/development/agent_week_plan_html/day-2.html
Normal file
132
document/development/agent_week_plan_html/day-2.html
Normal 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>
|
||||||
132
document/development/agent_week_plan_html/day-3.html
Normal file
132
document/development/agent_week_plan_html/day-3.html
Normal 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>
|
||||||
133
document/development/agent_week_plan_html/day-4.html
Normal file
133
document/development/agent_week_plan_html/day-4.html
Normal 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 / ToolCall,Day 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>
|
||||||
133
document/development/agent_week_plan_html/day-5.html
Normal file
133
document/development/agent_week_plan_html/day-5.html
Normal 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>
|
||||||
133
document/development/agent_week_plan_html/day-6.html
Normal file
133
document/development/agent_week_plan_html/day-6.html
Normal 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>
|
||||||
132
document/development/agent_week_plan_html/day-7.html
Normal file
132
document/development/agent_week_plan_html/day-7.html
Normal 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>
|
||||||
181
document/development/agent_week_plan_html/index.html
Normal file
181
document/development/agent_week_plan_html/index.html
Normal 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>
|
||||||
426
document/development/agent_week_plan_html/styles.css
Normal file
426
document/development/agent_week_plan_html/styles.css
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
163
server/src/app/api/v1/endpoints/agent_assets.py
Normal file
163
server/src/app/api/v1/endpoints/agent_assets.py
Normal 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)
|
||||||
34
server/src/app/api/v1/endpoints/agent_runs.py
Normal file
34
server/src/app/api/v1/endpoints/agent_runs.py
Normal 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
|
||||||
29
server/src/app/api/v1/endpoints/audit_logs.py
Normal file
29
server/src/app/api/v1/endpoints/audit_logs.py
Normal 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,
|
||||||
|
)
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
from fastapi import APIRouter
|
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.auth import router as auth_router
|
||||||
from app.api.v1.endpoints.bootstrap import router as bootstrap_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.employees import router as employees_router
|
||||||
@@ -12,6 +15,9 @@ router = APIRouter()
|
|||||||
router.include_router(health_router, tags=["health"])
|
router.include_router(health_router, tags=["health"])
|
||||||
router.include_router(bootstrap_router, tags=["bootstrap"])
|
router.include_router(bootstrap_router, tags=["bootstrap"])
|
||||||
router.include_router(auth_router, tags=["auth"])
|
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(knowledge_router, tags=["knowledge"])
|
||||||
router.include_router(employees_router, prefix="/employees", tags=["employees"])
|
router.include_router(employees_router, prefix="/employees", tags=["employees"])
|
||||||
router.include_router(reimbursements_router, prefix="/reimbursements", tags=["reimbursements"])
|
router.include_router(reimbursements_router, prefix="/reimbursements", tags=["reimbursements"])
|
||||||
|
|||||||
70
server/src/app/core/agent_enums.py
Normal file
70
server/src/app/core/agent_enums.py
Normal 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"
|
||||||
@@ -1,7 +1,16 @@
|
|||||||
from app.db.base_class import Base
|
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.approval import ApprovalRecord
|
||||||
|
from app.models.audit_log import AuditLog
|
||||||
from app.models.employee_change_log import EmployeeChangeLog
|
from app.models.employee_change_log import EmployeeChangeLog
|
||||||
from app.models.employee import Employee
|
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.organization import OrganizationUnit
|
||||||
from app.models.reimbursement import ReimbursementRequest
|
from app.models.reimbursement import ReimbursementRequest
|
||||||
from app.models.role import Role
|
from app.models.role import Role
|
||||||
@@ -11,12 +20,23 @@ from app.models.system_setting_secret import SystemSettingSecret
|
|||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"Base",
|
"Base",
|
||||||
|
"AccountsPayableRecord",
|
||||||
|
"AccountsReceivableRecord",
|
||||||
|
"AgentAsset",
|
||||||
|
"AgentAssetReview",
|
||||||
|
"AgentAssetVersion",
|
||||||
|
"AgentRun",
|
||||||
|
"AgentToolCall",
|
||||||
"ApprovalRecord",
|
"ApprovalRecord",
|
||||||
|
"AuditLog",
|
||||||
"Employee",
|
"Employee",
|
||||||
"EmployeeChangeLog",
|
"EmployeeChangeLog",
|
||||||
|
"ExpenseClaim",
|
||||||
|
"ExpenseClaimItem",
|
||||||
"OrganizationUnit",
|
"OrganizationUnit",
|
||||||
"ReimbursementRequest",
|
"ReimbursementRequest",
|
||||||
"Role",
|
"Role",
|
||||||
|
"SemanticParseLog",
|
||||||
"SystemModelSetting",
|
"SystemModelSetting",
|
||||||
"SystemSetting",
|
"SystemSetting",
|
||||||
"SystemSettingSecret",
|
"SystemSettingSecret",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from app.api.router import api_router
|
|||||||
from app.core.config import get_settings
|
from app.core.config import get_settings
|
||||||
from app.core.logging import get_logger, setup_logging
|
from app.core.logging import get_logger, setup_logging
|
||||||
from app.middleware.logging import AccessLogMiddleware
|
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.employee import prepare_employee_directory
|
||||||
from app.services.knowledge import prepare_knowledge_library
|
from app.services.knowledge import prepare_knowledge_library
|
||||||
|
|
||||||
@@ -51,6 +52,7 @@ def create_app() -> FastAPI:
|
|||||||
@app.on_event("startup")
|
@app.on_event("startup")
|
||||||
def _on_startup() -> None:
|
def _on_startup() -> None:
|
||||||
prepare_employee_directory()
|
prepare_employee_directory()
|
||||||
|
prepare_agent_foundation()
|
||||||
prepare_knowledge_library()
|
prepare_knowledge_library()
|
||||||
logger.info(
|
logger.info(
|
||||||
"Server ready - host=%s port=%s prefix=%s",
|
"Server ready - host=%s port=%s prefix=%s",
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
|
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.approval import ApprovalRecord
|
||||||
|
from app.models.audit_log import AuditLog
|
||||||
from app.models.employee_change_log import EmployeeChangeLog
|
from app.models.employee_change_log import EmployeeChangeLog
|
||||||
from app.models.employee import Employee
|
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.organization import OrganizationUnit
|
||||||
from app.models.reimbursement import ReimbursementRequest
|
from app.models.reimbursement import ReimbursementRequest
|
||||||
from app.models.role import Role
|
from app.models.role import Role
|
||||||
@@ -9,12 +18,23 @@ from app.models.system_setting import SystemSetting
|
|||||||
from app.models.system_setting_secret import SystemSettingSecret
|
from app.models.system_setting_secret import SystemSettingSecret
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
"AccountsPayableRecord",
|
||||||
|
"AccountsReceivableRecord",
|
||||||
|
"AgentAsset",
|
||||||
|
"AgentAssetReview",
|
||||||
|
"AgentAssetVersion",
|
||||||
|
"AgentRun",
|
||||||
|
"AgentToolCall",
|
||||||
"ApprovalRecord",
|
"ApprovalRecord",
|
||||||
|
"AuditLog",
|
||||||
"Employee",
|
"Employee",
|
||||||
"EmployeeChangeLog",
|
"EmployeeChangeLog",
|
||||||
|
"ExpenseClaim",
|
||||||
|
"ExpenseClaimItem",
|
||||||
"OrganizationUnit",
|
"OrganizationUnit",
|
||||||
"ReimbursementRequest",
|
"ReimbursementRequest",
|
||||||
"Role",
|
"Role",
|
||||||
|
"SemanticParseLog",
|
||||||
"SystemModelSetting",
|
"SystemModelSetting",
|
||||||
"SystemSetting",
|
"SystemSetting",
|
||||||
"SystemSettingSecret",
|
"SystemSettingSecret",
|
||||||
|
|||||||
79
server/src/app/models/agent_asset.py
Normal file
79
server/src/app/models/agent_asset.py
Normal 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")
|
||||||
86
server/src/app/models/agent_run.py
Normal file
86
server/src/app/models/agent_run.py
Normal 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")
|
||||||
25
server/src/app/models/audit_log.py
Normal file
25
server/src/app/models/audit_log.py
Normal 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())
|
||||||
118
server/src/app/models/financial_record.py
Normal file
118
server/src/app/models/financial_record.py
Normal 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()
|
||||||
|
)
|
||||||
110
server/src/app/repositories/agent_asset.py
Normal file
110
server/src/app/repositories/agent_asset.py
Normal 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
|
||||||
57
server/src/app/repositories/agent_run.py
Normal file
57
server/src/app/repositories/agent_run.py
Normal 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
|
||||||
35
server/src/app/repositories/audit_log.py
Normal file
35
server/src/app/repositories/audit_log.py
Normal 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
|
||||||
115
server/src/app/schemas/agent_asset.py
Normal file
115
server/src/app/schemas/agent_asset.py
Normal 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
|
||||||
61
server/src/app/schemas/agent_run.py
Normal file
61
server/src/app/schemas/agent_run.py
Normal 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
|
||||||
20
server/src/app/schemas/audit_log.py
Normal file
20
server/src/app/schemas/audit_log.py
Normal 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
|
||||||
407
server/src/app/services/agent_assets.py
Normal file
407
server/src/app/services/agent_assets.py
Normal 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,
|
||||||
|
}
|
||||||
977
server/src/app/services/agent_foundation.py
Normal file
977
server/src/app/services/agent_foundation.py
Normal 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)
|
||||||
168
server/src/app/services/agent_runs.py
Normal file
168
server/src/app/services/agent_runs.py
Normal 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,
|
||||||
|
)
|
||||||
70
server/src/app/services/audit.py
Normal file
70
server/src/app/services/audit.py
Normal 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()
|
||||||
@@ -8,6 +8,9 @@ src/app/api/router.py
|
|||||||
src/app/api/v1/__init__.py
|
src/app/api/v1/__init__.py
|
||||||
src/app/api/v1/router.py
|
src/app/api/v1/router.py
|
||||||
src/app/api/v1/endpoints/__init__.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/auth.py
|
||||||
src/app/api/v1/endpoints/bootstrap.py
|
src/app/api/v1/endpoints/bootstrap.py
|
||||||
src/app/api/v1/endpoints/employees.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/api/v1/endpoints/settings.py
|
||||||
src/app/core/__init__.py
|
src/app/core/__init__.py
|
||||||
src/app/core/admin_secret.py
|
src/app/core/admin_secret.py
|
||||||
|
src/app/core/agent_enums.py
|
||||||
src/app/core/bootstrap.py
|
src/app/core/bootstrap.py
|
||||||
src/app/core/config.py
|
src/app/core/config.py
|
||||||
src/app/core/logging.py
|
src/app/core/logging.py
|
||||||
@@ -29,9 +33,13 @@ src/app/db/session.py
|
|||||||
src/app/middleware/__init__.py
|
src/app/middleware/__init__.py
|
||||||
src/app/middleware/logging.py
|
src/app/middleware/logging.py
|
||||||
src/app/models/__init__.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/approval.py
|
||||||
|
src/app/models/audit_log.py
|
||||||
src/app/models/employee.py
|
src/app/models/employee.py
|
||||||
src/app/models/employee_change_log.py
|
src/app/models/employee_change_log.py
|
||||||
|
src/app/models/financial_record.py
|
||||||
src/app/models/organization.py
|
src/app/models/organization.py
|
||||||
src/app/models/reimbursement.py
|
src/app/models/reimbursement.py
|
||||||
src/app/models/role.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.py
|
||||||
src/app/models/system_setting_secret.py
|
src/app/models/system_setting_secret.py
|
||||||
src/app/repositories/__init__.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/employee.py
|
||||||
src/app/repositories/reimbursement.py
|
src/app/repositories/reimbursement.py
|
||||||
src/app/repositories/settings.py
|
src/app/repositories/settings.py
|
||||||
src/app/schemas/__init__.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/auth.py
|
||||||
src/app/schemas/bootstrap.py
|
src/app/schemas/bootstrap.py
|
||||||
src/app/schemas/employee.py
|
src/app/schemas/employee.py
|
||||||
@@ -50,9 +64,14 @@ src/app/schemas/knowledge.py
|
|||||||
src/app/schemas/reimbursement.py
|
src/app/schemas/reimbursement.py
|
||||||
src/app/schemas/settings.py
|
src/app/schemas/settings.py
|
||||||
src/app/services/__init__.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/auth.py
|
||||||
src/app/services/employee.py
|
src/app/services/employee.py
|
||||||
src/app/services/employee_seed.py
|
src/app/services/employee_seed.py
|
||||||
|
src/app/services/hermes_sync.py
|
||||||
src/app/services/knowledge.py
|
src/app/services/knowledge.py
|
||||||
src/app/services/model_connectivity.py
|
src/app/services/model_connectivity.py
|
||||||
src/app/services/reimbursement.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/dependency_links.txt
|
||||||
src/x_financial_server.egg-info/requires.txt
|
src/x_financial_server.egg-info/requires.txt
|
||||||
src/x_financial_server.egg-info/top_level.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_auth_service.py
|
||||||
|
tests/test_config_settings_reload.py
|
||||||
tests/test_employee_service.py
|
tests/test_employee_service.py
|
||||||
|
tests/test_env_file_precedence.py
|
||||||
tests/test_imports.py
|
tests/test_imports.py
|
||||||
|
tests/test_knowledge_onlyoffice_config.py
|
||||||
tests/test_server_start_dependencies.py
|
tests/test_server_start_dependencies.py
|
||||||
tests/test_settings_persistence.py
|
tests/test_settings_persistence.py
|
||||||
tests/test_settings_service.py
|
tests/test_settings_service.py
|
||||||
186
server/tests/test_agent_asset_service.py
Normal file
186
server/tests/test_agent_asset_service.py
Normal 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)
|
||||||
92
server/tests/test_agent_foundation_endpoints.py
Normal file
92
server/tests/test_agent_foundation_endpoints.py
Normal 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)
|
||||||
Reference in New Issue
Block a user