feat: 补齐任务执行指标与结构化结果摘要
- 补齐 tool_call 和 edge 验证链路的 duration_ms 计算与返回 - 任务详情和任务报告新增 result_summary_detail 结构化摘要 - 摘要中补充最终状态、失败原因、software-a 摘要、审批摘要、验证摘要 - 软件A层术语统一为“最小能力实现” - 同步更新 README、当前进度总结和相关设计文档 - 补充并通过对应自动化测试
This commit is contained in:
parent
62186e7994
commit
5021c8c2ea
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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,
|
||||
)
|
||||
|
||||
@ -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,
|
||||
)
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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 验证和后续工具调用轨迹。
|
||||
|
||||
字段:
|
||||
|
||||
|
||||
@ -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 联调。
|
||||
|
||||
@ -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 拉取任务与结果回传接口。
|
||||
|
||||
|
||||
@ -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 与联调能力继续补齐**
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user