feat: 补齐任务执行指标与结构化结果摘要

- 补齐 tool_call 和 edge 验证链路的 duration_ms 计算与返回
- 任务详情和任务报告新增 result_summary_detail 结构化摘要
- 摘要中补充最终状态、失败原因、software-a 摘要、审批摘要、验证摘要
- 软件A层术语统一为“最小能力实现”
- 同步更新 README、当前进度总结和相关设计文档
- 补充并通过对应自动化测试
This commit is contained in:
redbotu 2026-04-08 22:35:25 +08:00
parent 62186e7994
commit 5021c8c2ea
14 changed files with 214 additions and 64 deletions

View File

@ -63,7 +63,7 @@ Current backend includes:
`GET /api/demo/approval/requests/{approval_id}`
`POST /api/demo/approval/requests/{approval_id}/decision`
`GET /api/demo/approval/requests`
4. demo software-a
4. software-a minimal implementation
`POST /api/demo/software-a/deploy-tasks`
`GET /api/demo/software-a/deploy-tasks/{software_a_task_id}`
`POST /api/demo/software-a/permissions/check`
@ -78,16 +78,26 @@ Current execution flow:
1. create task
2. confirm task
3. high-risk task enters approval flow
4. check `software-a demo` permission
5. create `software-a demo` deploy task
4. check `software-a` minimal implementation permission
5. create `software-a` minimal implementation deploy task
6. create default edge verification step
7. edge pulls and reports verification result
8. task reaches `SUCCEEDED` / `FAILED` / `CANCELLED`
9. task detail/report returns software-a status, approval trace, tool trace, verification trace and audit trace
Current execution metrics:
1. `tool_call.duration_ms` is persisted from `started_at` / `finished_at`
2. `verification_trace.duration_ms` is persisted for edge task reports
Current result summary capabilities:
1. task detail/report returns `result_summary_detail`
2. summary includes final status, final reason, software-a result, approval result and verification result
Demo failure semantics currently include:
1. if `app_code` or `version` contains `fail`, `software-a demo` returns a failed deploy task
1. if `app_code` or `version` contains `fail`, the `software-a` minimal implementation returns a failed deploy task
2. approval rejection moves task to `CANCELLED`
3. failed edge report moves task to `FAILED`
@ -97,7 +107,7 @@ Automated tests currently cover:
1. create / confirm / get task
2. high-risk approval path
3. identity and software-a demo APIs
3. identity and software-a minimal implementation APIs
4. edge heartbeat / pull / report
5. edge event report
6. task report trace aggregation
@ -109,7 +119,7 @@ Current baseline: `14 passed`
Recommended next implementation steps:
1. compute and persist `duration_ms`
2. refine richer result summaries and audit details
3. add more idempotency and rollback tests
4. then continue with local edge-agent bootstrap and second-batch OpenAPI
1. add more idempotency and rollback tests
2. continue enriching audit details and task-level aggregate metrics
3. continue toward local edge-agent bootstrap
4. then continue with second-batch OpenAPI

View File

@ -5,7 +5,7 @@ from app.schemas.software_a import CreateDeployTaskRequest
from app.services.software_a_service import SoftwareAService
class DemoSoftwareAAdapter(SoftwareAAdapter):
class MinimalSoftwareAAdapter(SoftwareAAdapter):
def __init__(self, timezone_name: str) -> None:
self.service = SoftwareAService(timezone_name)

View File

@ -15,8 +15,10 @@ from app.repositories.approval_repository import ApprovalRepository
from app.repositories.audit_repository import AuditRepository
from app.repositories.edge_repository import EdgeTaskRepository
from app.repositories.tool_call_repository import ToolCallRepository
from app.adapters.software_a.minimal_adapter import MinimalSoftwareAAdapter
from app.schemas.common import ApiResponse
from app.schemas.task import (
ApprovalSummary,
ApprovalTraceItem,
AuditTraceItem,
CancelTaskRequest,
@ -25,11 +27,14 @@ from app.schemas.task import (
CreateTaskData,
CreateTaskRequest,
ParsedIntent,
ResultSummaryDetail,
SoftwareAResultSummary,
TaskBasic,
TaskDetailData,
TaskReportData,
ToolTraceItem,
ToolCallItem,
VerificationResultSummary,
VerificationTraceItem,
)
from app.services.task_service import TaskConflictError, TaskNotFoundError, TaskService
@ -42,6 +47,54 @@ def build_request_id(header_value: str | None) -> str:
return header_value or f"req-{uuid4().hex[:12]}"
def build_result_summary_detail(task, approval, software_a_detail: dict | None, edge_tasks) -> ResultSummaryDetail:
latest_edge_task = edge_tasks[0] if edge_tasks else None
final_reason = task.summary
if software_a_detail and software_a_detail.get("error_detail"):
final_reason = software_a_detail["error_detail"]
elif latest_edge_task and latest_edge_task.message:
final_reason = latest_edge_task.message
elif approval and approval.approval_status == "REJECTED" and approval.reason:
final_reason = approval.reason
approval_summary = None
if approval:
approval_summary = ApprovalSummary(
approval_id=approval.approval_id,
approval_status=approval.approval_status,
reason=approval.reason,
)
software_a_summary = None
if software_a_detail or task.software_a_task_id or task.software_a_task_status:
software_a_summary = SoftwareAResultSummary(
software_a_task_id=task.software_a_task_id,
task_status=(software_a_detail or {}).get("task_status", task.software_a_task_status),
progress_percent=(software_a_detail or {}).get("progress_percent"),
error_detail=(software_a_detail or {}).get("error_detail"),
started_at=(software_a_detail or {}).get("started_at"),
finished_at=(software_a_detail or {}).get("finished_at"),
)
verification_summary = None
if latest_edge_task:
verification_summary = VerificationResultSummary(
step_id=latest_edge_task.step_id,
step_status=latest_edge_task.step_status,
success=None if latest_edge_task.success is None else bool(latest_edge_task.success),
duration_ms=latest_edge_task.duration_ms,
message=latest_edge_task.message,
)
return ResultSummaryDetail(
final_status=task.task_status,
final_reason=final_reason,
approval=approval_summary,
software_a=software_a_summary,
verification=verification_summary,
)
@router.post("", response_model=ApiResponse[CreateTaskData])
def create_task(
payload: CreateTaskRequest,
@ -183,6 +236,10 @@ def get_task(
edge_tasks = EdgeTaskRepository(db).list_by_task_id(task_id)
tool_calls = ToolCallRepository(db).list_by_task_id(task_id)
approval = ApprovalRepository(db).get_by_task_id(task_id)
software_a_detail = None
if task.software_a_task_id:
software_a_detail = MinimalSoftwareAAdapter(settings.default_timezone).get_deploy_task(task.software_a_task_id)
verification_result = None
if edge_tasks:
latest_edge_task = edge_tasks[0]
@ -216,6 +273,7 @@ def get_task(
],
verification_result=verification_result,
summary=task.summary,
result_summary_detail=build_result_summary_detail(task, approval, software_a_detail, edge_tasks),
),
timestamp=format_now(settings.default_timezone),
)
@ -243,6 +301,9 @@ def get_task_report(
tool_calls = ToolCallRepository(db).list_by_task_id(task_id)
edge_tasks = EdgeTaskRepository(db).list_by_task_id(task_id)
audit_logs = AuditRepository(db).list_by_task_id(task_id)
software_a_detail = None
if task.software_a_task_id:
software_a_detail = MinimalSoftwareAAdapter(settings.default_timezone).get_deploy_task(task.software_a_task_id)
approval_trace = []
if approval:
@ -282,6 +343,7 @@ def get_task_report(
tool_name=item.tool_name,
step_status=item.step_status,
success=None if item.success is None else bool(item.success),
duration_ms=item.duration_ms,
message=item.message,
params=json.loads(item.params_json),
result_data=json.loads(item.result_data_json),
@ -327,6 +389,7 @@ def get_task_report(
tool_trace=tool_trace,
verification_trace=verification_trace,
result_summary=task.summary,
result_summary_detail=build_result_summary_detail(task, approval, software_a_detail, edge_tasks),
audit_trace=audit_trace,
),
timestamp=format_now(settings.default_timezone),

View File

@ -4,6 +4,9 @@ from datetime import UTC, datetime, timedelta, timezone
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
TIME_FORMAT = "%Y-%m-%d %H:%M:%S.%f"
def resolve_timezone(timezone_name: str):
try:
return ZoneInfo(timezone_name)
@ -18,4 +21,22 @@ def resolve_timezone(timezone_name: str):
def format_now(timezone_name: str) -> str:
current = datetime.now(resolve_timezone(timezone_name))
return current.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
return current.strftime(TIME_FORMAT)[:-3]
def parse_timestamp(value: str | None) -> datetime | None:
if not value:
return None
try:
return datetime.strptime(value, TIME_FORMAT)
except ValueError:
return None
def compute_duration_ms(started_at: str | None, finished_at: str | None) -> int | None:
started = parse_timestamp(started_at)
finished = parse_timestamp(finished_at)
if not started or not finished:
return None
duration_ms = int((finished - started).total_seconds() * 1000)
return max(duration_ms, 0)

View File

@ -20,6 +20,7 @@ class EdgeTask(Base):
message: Mapped[str | None] = mapped_column(Text, nullable=True)
result_data_json: Mapped[str] = mapped_column(Text, nullable=False)
evidence_json: Mapped[str] = mapped_column(Text, nullable=False)
duration_ms: Mapped[int | None] = mapped_column(Integer, nullable=True)
expire_at: Mapped[str] = mapped_column(Text, nullable=False)
started_at: Mapped[str | None] = mapped_column(Text, nullable=True)
finished_at: Mapped[str | None] = mapped_column(Text, nullable=True)

View File

@ -59,6 +59,37 @@ class VerificationResult(BaseModel):
log_error_count: int | None = None
class ApprovalSummary(BaseModel):
approval_id: str | None = None
approval_status: str | None = None
reason: str | None = None
class SoftwareAResultSummary(BaseModel):
software_a_task_id: str | None = None
task_status: str | None = None
progress_percent: int | None = None
error_detail: str | None = None
started_at: str | None = None
finished_at: str | None = None
class VerificationResultSummary(BaseModel):
step_id: str | None = None
step_status: str | None = None
success: bool | None = None
duration_ms: int | None = None
message: str | None = None
class ResultSummaryDetail(BaseModel):
final_status: str
final_reason: str | None = None
approval: ApprovalSummary | None = None
software_a: SoftwareAResultSummary | None = None
verification: VerificationResultSummary | None = None
class TaskDetailData(BaseModel):
task_id: str
task_status: str
@ -70,6 +101,7 @@ class TaskDetailData(BaseModel):
tool_calls: list[ToolCallItem]
verification_result: VerificationResult | None = None
summary: str | None = None
result_summary_detail: ResultSummaryDetail | None = None
class TaskBasic(BaseModel):
@ -112,6 +144,7 @@ class VerificationTraceItem(BaseModel):
tool_name: str
step_status: str
success: bool | None = None
duration_ms: int | None = None
message: str | None = None
params: dict[str, Any]
result_data: dict[str, Any]
@ -139,4 +172,5 @@ class TaskReportData(BaseModel):
tool_trace: list[ToolTraceItem]
verification_trace: list[VerificationTraceItem]
result_summary: str | None = None
result_summary_detail: ResultSummaryDetail | None = None
audit_trace: list[AuditTraceItem]

View File

@ -129,7 +129,7 @@ class ApprovalService:
task.approval_status = approval_status
task.updated_at = format_now(self.timezone_name)
if approval_status == APPROVAL_STATUS_APPROVED:
task.summary = "审批已通过,准备调用 software-a demo 执行。"
task.summary = "审批已通过,准备调用 software-a 最小能力实现执行。"
self.task_repository.update(task)
from app.services.task_service import TaskService

View File

@ -19,7 +19,7 @@ from app.core.constants import (
TASK_STATUS_SUCCEEDED,
TASK_STATUS_VERIFYING,
)
from app.core.time import format_now
from app.core.time import compute_duration_ms, format_now
from app.models.edge_node import EdgeNode
from app.models.edge_task import EdgeTask
from app.models.audit_log import AuditLog
@ -103,6 +103,7 @@ class EdgeService:
message=None,
result_data_json="{}",
evidence_json="{}",
duration_ms=None,
expire_at=current_time,
started_at=None,
finished_at=None,
@ -169,6 +170,7 @@ class EdgeService:
edge_task.evidence_json = json.dumps(evidence, ensure_ascii=False)
edge_task.started_at = started_at
edge_task.finished_at = finished_at
edge_task.duration_ms = compute_duration_ms(started_at, finished_at)
edge_task.updated_at = format_now(self.timezone_name)
updated_edge_task = self.edge_task_repository.update(edge_task)
self._write_tool_call(
@ -247,7 +249,7 @@ class EdgeService:
request_payload_json=json.dumps(request_payload, ensure_ascii=False),
response_payload_json=json.dumps(response_payload, ensure_ascii=False),
success=1 if success else 0,
duration_ms=None,
duration_ms=compute_duration_ms(started_at, finished_at),
started_at=started_at,
finished_at=finished_at,
)

View File

@ -27,9 +27,9 @@ from app.core.constants import (
)
from app.schemas.approval import ApprovalOperator, ApprovalTarget, CreateApprovalRequest
from app.schemas.software_a import CreateDeployTaskRequest, DeployOptions, SoftwareAOperator
from app.core.time import format_now
from app.core.time import compute_duration_ms, format_now
from app.adapters.approval.demo_adapter import DemoApprovalAdapter
from app.adapters.software_a.demo_adapter import DemoSoftwareAAdapter
from app.adapters.software_a.minimal_adapter import MinimalSoftwareAAdapter
from app.models.audit_log import AuditLog
from app.models.tool_call import ToolCall
from app.models.task import Task
@ -161,7 +161,7 @@ class TaskService:
else:
task.task_status = TASK_STATUS_RUNNING
task.approval_status = APPROVAL_STATUS_NOT_REQUIRED
task.summary = "任务已确认,准备调用 software-a demo 执行。"
task.summary = "任务已确认,准备调用 software-a 最小能力实现执行。"
updated_task = self.repository.update(task)
self._write_audit_log(
task_id=updated_task.task_id,
@ -191,12 +191,12 @@ class TaskService:
def refresh_software_a_status(self, task: Task) -> Task:
if not task.software_a_task_id:
return task
software_a_task = DemoSoftwareAAdapter(self.timezone_name).get_deploy_task(task.software_a_task_id)
software_a_task = MinimalSoftwareAAdapter(self.timezone_name).get_deploy_task(task.software_a_task_id)
if software_a_task:
task.software_a_task_status = software_a_task["task_status"]
if software_a_task["task_status"] == SOFTWARE_A_TASK_STATUS_FAILED and task.task_status not in {TASK_STATUS_FAILED, TASK_STATUS_CANCELLED}:
task.task_status = TASK_STATUS_FAILED
task.summary = f"software-a demo 执行失败: {software_a_task.get('error_detail') or 'unknown error'}"
task.summary = f"software-a 最小能力实现执行失败: {software_a_task.get('error_detail') or 'unknown error'}"
task.updated_at = format_now(self.timezone_name)
task = self.repository.update(task)
return task
@ -216,7 +216,7 @@ class TaskService:
if self.edge_task_repository.list_active_by_task_id(task.task_id):
raise TaskConflictError("当前任务已存在待处理的 edge 验证步骤,不允许重复调度。")
allowed, reason = DemoSoftwareAAdapter(self.timezone_name).check_permission(
allowed, reason = MinimalSoftwareAAdapter(self.timezone_name).check_permission(
task.action_type or "DEPLOY",
task.env,
task.approval_status,
@ -226,7 +226,7 @@ class TaskService:
if task.action_type == "DEPLOY":
tool_started_at = format_now(self.timezone_name)
deploy_result = DemoSoftwareAAdapter(self.timezone_name).create_deploy_task(
deploy_result = MinimalSoftwareAAdapter(self.timezone_name).create_deploy_task(
CreateDeployTaskRequest(
operator=SoftwareAOperator(user_id="u1001", user_name="alice"),
tenant_id=task.tenant_id,
@ -243,9 +243,9 @@ class TaskService:
deploy_success = deploy_result["task_status"] != SOFTWARE_A_TASK_STATUS_FAILED
task.task_status = TASK_STATUS_RUNNING if deploy_success else TASK_STATUS_FAILED
task.summary = (
"software-a demo 部署任务已创建,等待边缘验证。"
"software-a 最小能力部署任务已创建,等待边缘验证。"
if deploy_success
else f"software-a demo 执行失败: {deploy_result.get('error_detail') or 'unknown error'}"
else f"software-a 最小能力实现执行失败: {deploy_result.get('error_detail') or 'unknown error'}"
)
self._write_tool_call(
task_id=task.task_id,
@ -363,7 +363,7 @@ class TaskService:
request_payload_json=json.dumps(request_payload, ensure_ascii=False),
response_payload_json=json.dumps(response_payload, ensure_ascii=False),
success=1 if success else 0,
duration_ms=duration_ms,
duration_ms=duration_ms if duration_ms is not None else compute_duration_ms(started_at, finished_at),
started_at=started_at,
finished_at=finished_at,
)

View File

@ -38,6 +38,8 @@ def test_task_create_confirm_get() -> None:
assert get_response.json()["data"]["software_a_task_id"] is not None
assert get_response.json()["data"]["software_a_task_status"] == "SUCCEEDED"
assert get_response.json()["data"]["tool_calls"][0]["tool_name"] == "software_a_deploy"
assert get_response.json()["data"]["result_summary_detail"]["final_status"] == "RUNNING"
assert get_response.json()["data"]["result_summary_detail"]["software_a"]["task_status"] == "SUCCEEDED"
def test_high_risk_task_creates_approval_and_can_be_approved() -> None:
@ -274,6 +276,13 @@ def test_task_report_contains_traces() -> None:
assert any(item["request_id"] == "req-report-confirm-001" for item in payload["tool_trace"])
assert any(item["operator_user_name"] == "alice" for item in payload["tool_trace"])
assert any(item["request_id"] == "req-report-create-001" for item in payload["audit_trace"])
assert payload["result_summary_detail"]["final_status"] == "SUCCEEDED"
assert payload["result_summary_detail"]["software_a"]["task_status"] == "SUCCEEDED"
assert payload["result_summary_detail"]["verification"]["success"] is True
deploy_trace = next(item for item in payload["tool_trace"] if item["tool_name"] == "software_a_deploy")
assert deploy_trace["duration_ms"] is not None
verification_trace = payload["verification_trace"][0]
assert verification_trace["duration_ms"] == 100
def test_cancel_running_task() -> None:
@ -473,6 +482,8 @@ def test_task_fails_when_software_a_deploy_fails() -> None:
assert get_response.status_code == 200
assert get_response.json()["data"]["task_status"] == "FAILED"
assert get_response.json()["data"]["software_a_task_status"] == "FAILED"
assert get_response.json()["data"]["result_summary_detail"]["final_status"] == "FAILED"
assert get_response.json()["data"]["result_summary_detail"]["software_a"]["error_detail"] is not None
pull_response = client.post(
"/api/agent/edge/tasks/pull",
@ -599,3 +610,5 @@ def test_edge_failure_marks_task_failed() -> None:
assert get_response.status_code == 200
assert get_response.json()["data"]["task_status"] == "FAILED"
assert get_response.json()["data"]["verification_result"]["http_ok"] is False
assert get_response.json()["data"]["result_summary_detail"]["verification"]["success"] is False
assert get_response.json()["data"]["result_summary_detail"]["final_reason"] == "health check failed"

View File

@ -33,7 +33,7 @@
1. `task` 是主链路核心表。
2. `approval_request` 为高风险任务确认后进入审批预留。
3. `tool_call` 为后续软件 A demo / edge 验证接入预留。
3. `tool_call` 为后续软件 A 最小能力实现 / edge 验证接入预留。
4. `audit_log` 为关键动作审计预留。
---
@ -107,7 +107,7 @@
用途:
1. 记录软件 A demo、edge 验证和后续工具调用轨迹。
1. 记录软件 A 最小能力实现、edge 验证和后续工具调用轨迹。
字段:

View File

@ -12,7 +12,7 @@
2. 项目如何分层和分模块。
3. 哪些模块先实现,哪些模块后实现。
4. 数据如何落库。
5. 如何与软件 A demo、身份 demo、审批 demo、本地 Agent 对接。
5. 如何与软件 A 最小能力实现、身份 demo、审批 demo、本地 Agent 对接。
### 1.2 适用范围
@ -290,7 +290,7 @@ backend/
职责:
1. 封装软件 A demo 调用。
1. 封装软件 A 最小能力实现调用。
2. 封装身份 demo 调用。
3. 封装审批 demo 调用。
4. 封装模型网关调用。
@ -321,7 +321,7 @@ backend/
职责:
1. 执行异步任务。
2. 轮询软件 A demo 状态。
2. 轮询软件 A 最小能力实现状态。
3. 拉起验证流程。
4. 处理长任务和超时任务。
@ -537,13 +537,13 @@ demo 阶段建议至少建立以下表:
1. `workflows/deploy_workflow.py`
2. `services/agent_service.py`
3. `adapters/software_a/demo_adapter.py`
3. `adapters/software_a/minimal_adapter.py`
4. `workers/task_polling_worker.py`
5. `services/verification_service.py`
执行步骤:
1. 调用软件 A demo 创建部署任务。
1. 调用软件 A 最小能力实现创建部署任务。
2. 轮询部署状态。
3. 部署成功后生成验证步骤。
4. 下发到本地 Agent。
@ -663,7 +663,7 @@ demo 阶段正式确认:
## 11.2 第二批完成
1. 软件 A demo adapter。
1. 软件 A 最小能力实现 adapter。
2. 部署工作流。
3. 审批 demo adapter。
4. 身份 demo adapter。
@ -710,7 +710,7 @@ demo 阶段正式确认:
至少覆盖:
1. 软件 A demo 联调。
1. 软件 A 最小能力实现联调。
2. 身份 demo 联调。
3. 审批 demo 联调。
4. 本地 Agent 联调。

View File

@ -10,7 +10,7 @@
1. Agent 对外任务接口。
2. 云端与本地 Agent 的交互接口。
3. 软件 A demo 接口。
3. 软件 A 最小能力实现接口。
4. 身份 demo 接口。
5. 审批 demo 接口。
@ -40,7 +40,7 @@
说明:
1. 软件 A demo、身份 demo、审批 demo、edge 接口继续保留在本文档中作为后续实现输入。
1. 软件 A 最小能力实现、身份 demo、审批 demo、edge 接口继续保留在本文档中作为后续实现输入。
2. 首批代码骨架只要求先打通以上三条主接口。
3. 后续扩展 OpenAPI 时,优先保持当前对象模型和错误码不变。
@ -442,11 +442,11 @@ X-Signature: <signature>
---
## 5. 软件 A Demo 接口
## 5. 软件 A 最小能力实现接口
## 5.1 设计说明
软件 A demo 版本用于支撑 MVP 闭环,其接口语义需尽量贴近未来真实软件 A 的标准能力。
软件 A 最小能力实现用于支撑 MVP 闭环,其接口语义需尽量贴近未来真实软件 A 的标准能力。
建议 base path:
@ -891,9 +891,9 @@ X-Signature: <signature>
2. Agent 完成解析并返回 `task_id` 和结构化结果。
3. 用户调用确认接口。
4. Agent 调用身份 demo 获取操作者信息和权限。
5. Agent 调用软件 A demo 权限校验。
6. Agent 调用软件 A demo 创建部署任务。
7. Agent 轮询软件 A demo 查询状态。
5. Agent 调用软件 A 最小能力实现权限校验。
6. Agent 调用软件 A 最小能力实现创建部署任务。
7. Agent 轮询软件 A 最小能力实现查询状态。
8. Agent 调用 Edge 接口执行本地验证。
9. Agent 汇总结果,更新任务状态并生成报告。
@ -911,7 +911,7 @@ X-Signature: <signature>
## 10. demo 与正式系统的替换原则
1. Agent 上层只依赖统一语义接口,不直接依赖 demo 接口字段差异。
2. 软件 A demo、身份 demo、审批 demo 均应封装在适配层后面。
2. 软件 A 最小能力实现、身份 demo、审批 demo 均应封装在适配层后面。
3. 后续替换真实系统时,优先保持上层对象模型不变。
4. 如真实系统能力不足,应在适配层内做降级,而不是修改编排主链路。
@ -921,7 +921,7 @@ X-Signature: <signature>
1. 先完成通用响应格式、错误码和枚举定义。
2. 再完成 Agent 对外任务接口。
3. 再完成软件 A demo 接口。
3. 再完成软件 A 最小能力实现接口。
4. 再完成身份 demo 和审批 demo。
5. 最后完成本地 Agent 拉取任务与结果回传接口。

View File

@ -25,7 +25,7 @@
用于描述系统架构、模块分层、数据模型、接口建议、安全设计和实施约束。
3. `智能化部署agent-demo接口定义说明.md`
用于描述 demo 阶段的接口协议、统一响应格式、状态枚举、Agent 接口、软件 A demo 接口、身份 demo 接口、审批 demo 接口。
用于描述 demo 阶段的接口协议、统一响应格式、状态枚举、Agent 接口、软件 A 最小能力实现接口、身份 demo 接口、审批 demo 接口。
4. `智能化部署agent-demo后端项目骨架设计.md`
用于描述 demo 后端的推荐技术栈、项目结构、模块职责、数据库表建议、代码落点和开发顺序。
@ -86,7 +86,7 @@ demo 接口定义文档已覆盖:
1. Agent 对外任务接口。
2. 云端与本地 Agent 交互接口。
3. 软件 A demo 接口。
3. 软件 A 最小能力实现接口。
4. 身份 demo 接口。
5. 审批 demo 接口。
6. 内部对象结构。
@ -123,17 +123,19 @@ demo 接口定义文档已覆盖:
3. 已实现 `task``approval_request``tool_call``audit_log` 对应的最小模型和数据库初始化逻辑。
4. 已打通三条主接口:
`POST /api/agent/tasks``POST /api/agent/tasks/{task_id}/confirm``GET /api/agent/tasks/{task_id}`
5. 已实现最小 `identity demo``approval demo``software-a demo` 接口
5. 已实现最小 `identity demo``approval demo``software-a 最小能力实现接口`
6. 已将高风险任务确认后的审批创建流程接入后端主链路。
7. 已实现最小 `edge` 心跳、拉取任务、回传结果接口。
8. 已将默认验证任务接入 edge 调度主链路。
9. 已将 `software-a demo` 部署任务创建接入主执行链。
9. 已将 `software-a` 最小能力部署任务创建接入主执行链。
10. 已将 `tool_call``audit_log` 接入主链路关键动作。
11. 已实现任务报告接口,可返回审批、工具、验证、审计轨迹。
12. 已实现任务取消接口,并将 `request_id``operator` 维度写入关键审计和工具调用记录。
13. 已补充自动化测试,并基于内存 SQLite 完成首轮通过验证。
14. 已完成任务状态机第一轮收紧,补上重复确认、审批后任务状态漂移、edge 重复回传等冲突校验。
15. 已补上首轮失败分支细化,包括 software-a demo 执行失败、审批驳回、edge 验证失败三条主失败路径。
15. 已补上首轮失败分支细化,包括 software-a 最小能力实现执行失败、审批驳回、edge 验证失败三条主失败路径。
16. 已完成 `duration_ms` 第一轮落地,`tool_call` 和 edge 验证轨迹可基于 `started_at` / `finished_at` 自动计算并返回时长。
17. 已完成结果摘要第一轮结构化改造,任务详情和任务报告可返回 `result_summary_detail`,包含最终状态、失败原因、software-a 摘要、审批摘要和验证摘要。
### 3.8 当前代码可运行范围
@ -143,13 +145,17 @@ demo 接口定义文档已覆盖:
2. 高风险任务确认后自动创建审批单。
3. 审批通过后进入执行链,审批驳回后进入取消态。
4. 执行链包含:
software-a 权限校验 -> software-a demo 部署任务创建 -> edge 默认验证任务创建 -> edge 拉取 -> edge 回传。
software-a 权限校验 -> software-a 最小能力部署任务创建 -> edge 默认验证任务创建 -> edge 拉取 -> edge 回传。
5. 任务详情接口可返回:
当前状态、software-a 状态、工具调用摘要、验证结果摘要。
6. 任务报告接口可返回:
`task_basic``intent_snapshot``approval_trace``tool_trace``verification_trace``result_summary``audit_trace`
7. edge 侧已支持:
心跳、拉取任务、回传结果、上报异常事件。
8. 执行指标当前已支持:
`tool_trace.duration_ms``verification_trace.duration_ms`
9. 结果摘要当前已支持:
`result_summary_detail.final_status``final_reason``software_a``approval``verification`
当前测试基线:
@ -174,7 +180,7 @@ demo 接口定义文档已覆盖:
1. 自然语言发起任务。
2. Agent 解析意图并做结构化任务生成。
3. 策略层做风险判断。
4. 调用软件 A demo 执行部署或控制动作。
4. 调用软件 A 最小能力实现执行部署或控制动作。
5. 调用本地 Agent 做验证。
6. 汇总结果,生成报告和审计。
@ -239,7 +245,7 @@ demo 接口定义文档已覆盖:
`CREATED` -> `PENDING_CONFIRM` -> `RUNNING` -> `VERIFYING` -> `SUCCEEDED` / `FAILED` / `CANCELLED`
2. 高风险任务路径为:
`PENDING_CONFIRM` -> `PENDING_APPROVAL` -> `RUNNING`
3. `software-a demo` 当前在任务详情查询时会同步刷新状态,因此:
3. `software-a` 最小能力实现当前在任务详情查询时会同步刷新状态,因此:
确认接口返回的 `software_a_task_status` 可能是 `RUNNING`,而后续查询任务详情时可能已变为 `SUCCEEDED`
4. 当前 demo 中的 operator 默认使用:
`alice(u1001)` 作为任务发起和执行方,`bob(u2001)` 作为审批人
@ -251,7 +257,7 @@ demo 接口定义文档已覆盖:
8. 当前已补上的状态约束包括:
重复确认拦截、重复执行拦截、审批决策前必须仍处于 `PENDING_APPROVAL`、edge 重复回传拦截、非 `RUNNING` 任务不再下发 edge 执行。
9. 当前 demo 已支持可控失败模拟:
`app_code``version` 包含 `fail`,则 `software-a demo` 会返回失败任务,用于联调失败分支。
`app_code``version` 包含 `fail`,则 `software-a` 最小能力实现会返回失败任务,用于联调失败分支。
---
@ -262,7 +268,7 @@ demo 接口定义文档已覆盖:
1. 本地 `edge-agent` 初始化代码与打包脚本。
2. 文件型 SQLite / PostgreSQL 实库运行验证。
3. 身份 demo / 审批 demo 与任务主链路的权限、审批决策联动细化。
4. `duration_ms` 等执行指标的真实计算与回填
4. 任务级聚合指标仍未完成,如总耗时、审批耗时、等待耗时
5. 更真实的验证插件实现。
6. 部署脚本和运行脚本完善。
7. OpenAPI 扩展到第二批接口。
@ -284,12 +290,12 @@ demo 接口定义文档已覆盖:
当前不是继续补基础文档,而是继续补强现有可运行链路。优先级建议收敛为:
1. 回填执行指标:
重点补 `duration_ms`、更完整的执行结果摘要与审计信息。
2. 增补失败路径与幂等性测试:
1. 增补失败路径与幂等性测试:
重点补重复请求、重复回传、异常回滚等场景。
3. 继续丰富结果摘要与审计细节:
让失败原因在详情和报告里更直观可见。
2. 继续丰富审计细节与任务级聚合指标:
让任务级总耗时、审批耗时、等待耗时可直观看到。
3. 再补更多执行指标:
如任务级聚合耗时、审批耗时、等待耗时。
4. 然后再继续:
本地 `edge-agent` 骨架、第二批 OpenAPI、更多联调能力。
@ -303,9 +309,9 @@ demo 接口定义文档已覆盖:
按当前进度,建议后续直接按以下顺序推进:
1. 计算并持久化 `duration_ms`
2. 增补状态冲突、失败回滚、重复上报等测试
3. 丰富结果摘要与失败原因呈现
1. 增补状态冲突、失败回滚、重复上报等测试
2. 再补更多任务级执行指标
3. 继续增强审计细节
4. 再进入本地 `edge-agent` 初始化代码和第二批 OpenAPI。
当前更推荐:
@ -329,9 +335,9 @@ demo 接口定义文档已覆盖:
下一步推荐顺序:
1. 计算并回填 `duration_ms`
2. 再补失败路径和幂等性测试
3. 再补结果摘要和失败原因展示
1. 再补失败路径和幂等性测试
2. 再补任务级执行指标
3. 再补审计细节和聚合摘要
4. 再补本地 Agent 初始化代码或第二批 OpenAPI。
### 7.2 如果上下文快满,有什么影响
@ -364,4 +370,4 @@ set DATABASE_URL=sqlite:///:memory:
当前已经完成从"写文档"切换到"写 demo 代码"的第一步,下一步进入:
**duration_ms 回填 -> 失败结果呈现增强 -> 本地 Agent 与联调能力继续补齐**
**更多执行指标 -> 审计细节增强 -> 本地 Agent 与联调能力继续补齐**