添加 RuntimeContext 类用于捕获模型运行时的日期时间信息, 包括UTC时间、本地时间和时区信息,并在系统提示中显示这些信息。 同时增加最大上下文消息数和工具迭代次数的配置选项, 将验证服务从引擎加载器中移除,并更新相关的数据结构和接口。 BREAKING CHANGE: 移除了验证服务,相关字段被替换为证据状态和接受状态。 - 添加 RuntimeContext 类和相关渲染方法 - 增加 max_context_messages 和 max_tool_iterations 配置 - 移除 ValidationService 相关代码 - 更新消息记录中的验证状态字段 - 添加原始工具调用检测和回退处理
249 lines
9.2 KiB
Python
249 lines
9.2 KiB
Python
"""Models for internal task tracking and user acceptance."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import Any, Literal
|
|
|
|
|
|
ValidationStatus = Literal["accepted", "rejected", "insufficient_evidence", "validator_error"]
|
|
|
|
VALIDATION_STATUSES = {"accepted", "rejected", "insufficient_evidence", "validator_error"}
|
|
TASK_OPEN_STATUSES = {"open", "running", "awaiting_acceptance", "needs_revision"}
|
|
LEGACY_STATUS_MAP = {
|
|
"validating": "running",
|
|
"awaiting_feedback": "awaiting_acceptance",
|
|
"needs_review": "awaiting_acceptance",
|
|
}
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class ValidationResult:
|
|
status: ValidationStatus = "rejected"
|
|
score: float = 0.0
|
|
issues: list[str] = field(default_factory=list)
|
|
missing_requirements: list[str] = field(default_factory=list)
|
|
evidence_gaps: list[str] = field(default_factory=list)
|
|
recommended_revision_prompt: str = ""
|
|
validator: str = "heuristic"
|
|
|
|
def __init__(
|
|
self,
|
|
*,
|
|
status: ValidationStatus | None = None,
|
|
passed: bool | None = None,
|
|
score: float = 0.0,
|
|
issues: list[str] | None = None,
|
|
missing_requirements: list[str] | None = None,
|
|
evidence_gaps: list[str] | None = None,
|
|
recommended_revision_prompt: str = "",
|
|
validator: str = "heuristic",
|
|
) -> None:
|
|
if status is not None and status not in VALIDATION_STATUSES:
|
|
raise ValueError(f"unknown validation status: {status}")
|
|
self.status = status or ("accepted" if passed and score >= 0.75 else "rejected")
|
|
self.score = max(0.0, min(1.0, float(score or 0.0)))
|
|
self.issues = list(issues or [])
|
|
self.missing_requirements = list(missing_requirements or [])
|
|
self.evidence_gaps = list(evidence_gaps or [])
|
|
self.recommended_revision_prompt = recommended_revision_prompt
|
|
self.validator = validator
|
|
|
|
@property
|
|
def passed(self) -> bool:
|
|
return self.status == "accepted"
|
|
|
|
@property
|
|
def accepted(self) -> bool:
|
|
return self.status == "accepted"
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
return {
|
|
"status": self.status,
|
|
"passed": self.passed,
|
|
"score": self.score,
|
|
"issues": list(self.issues),
|
|
"missing_requirements": list(self.missing_requirements),
|
|
"evidence_gaps": list(self.evidence_gaps),
|
|
"recommended_revision_prompt": self.recommended_revision_prompt,
|
|
"validator": self.validator,
|
|
"accepted": self.accepted,
|
|
}
|
|
|
|
@classmethod
|
|
def from_dict(cls, payload: dict[str, Any] | None) -> "ValidationResult | None":
|
|
if not isinstance(payload, dict):
|
|
return None
|
|
raw_status = payload.get("status")
|
|
if "status" in payload and raw_status not in VALIDATION_STATUSES:
|
|
raise ValueError(f"unknown validation status: {raw_status}")
|
|
status: ValidationStatus | None = raw_status if "status" in payload else None
|
|
return cls(
|
|
status=status,
|
|
passed=bool(payload.get("passed")) if "status" not in payload else None,
|
|
score=float(payload.get("score", 0.0) or 0.0),
|
|
issues=[str(item) for item in payload.get("issues") or []],
|
|
missing_requirements=[str(item) for item in payload.get("missing_requirements") or []],
|
|
evidence_gaps=[str(item) for item in payload.get("evidence_gaps") or []],
|
|
recommended_revision_prompt=str(payload.get("recommended_revision_prompt") or ""),
|
|
validator=str(payload.get("validator") or "unknown"),
|
|
)
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class TaskRecord:
|
|
task_id: str
|
|
session_id: str
|
|
description: str
|
|
goal: str
|
|
constraints: list[str]
|
|
priority: int
|
|
status: str
|
|
creator: str
|
|
created_at: str
|
|
updated_at: str
|
|
parent_task_id: str | None = None
|
|
closed_at: str | None = None
|
|
close_reason: str | None = None
|
|
satisfaction: float | None = None
|
|
run_ids: list[str] = field(default_factory=list)
|
|
skill_names: list[str] = field(default_factory=list)
|
|
feedback: list[dict[str, Any]] = field(default_factory=list)
|
|
validation_result: dict[str, Any] | None = None
|
|
metadata: dict[str, Any] = field(default_factory=dict)
|
|
|
|
@property
|
|
def is_open(self) -> bool:
|
|
return self.status in TASK_OPEN_STATUSES
|
|
|
|
@property
|
|
def is_execution_active(self) -> bool:
|
|
return self.status == "running"
|
|
|
|
@property
|
|
def requires_user_action(self) -> bool:
|
|
return self.status in {"awaiting_acceptance", "needs_revision"}
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
return {
|
|
"task_id": self.task_id,
|
|
"session_id": self.session_id,
|
|
"parent_task_id": self.parent_task_id,
|
|
"description": self.description,
|
|
"goal": self.goal,
|
|
"constraints": list(self.constraints),
|
|
"priority": self.priority,
|
|
"status": self.status,
|
|
"creator": self.creator,
|
|
"created_at": self.created_at,
|
|
"updated_at": self.updated_at,
|
|
"closed_at": self.closed_at,
|
|
"close_reason": self.close_reason,
|
|
"satisfaction": self.satisfaction,
|
|
"run_ids": list(self.run_ids),
|
|
"skill_names": list(self.skill_names),
|
|
"acceptance": list(self.feedback),
|
|
"feedback": list(self.feedback),
|
|
"validation_result": self.validation_result,
|
|
"metadata": dict(self.metadata),
|
|
}
|
|
|
|
@classmethod
|
|
def from_dict(cls, payload: dict[str, Any]) -> "TaskRecord":
|
|
return cls(
|
|
task_id=str(payload["task_id"]),
|
|
session_id=str(payload["session_id"]),
|
|
parent_task_id=_optional_str(payload.get("parent_task_id")),
|
|
description=str(payload.get("description") or ""),
|
|
goal=str(payload.get("goal") or payload.get("description") or ""),
|
|
constraints=[str(item) for item in payload.get("constraints") or []],
|
|
priority=int(payload.get("priority", 0) or 0),
|
|
status=LEGACY_STATUS_MAP.get(str(payload.get("status") or "open"), str(payload.get("status") or "open")),
|
|
creator=str(payload.get("creator") or "main-agent"),
|
|
created_at=str(payload.get("created_at") or ""),
|
|
updated_at=str(payload.get("updated_at") or ""),
|
|
closed_at=_optional_str(payload.get("closed_at")),
|
|
close_reason=_optional_str(payload.get("close_reason")),
|
|
satisfaction=_optional_float(payload.get("satisfaction")),
|
|
run_ids=[str(item) for item in payload.get("run_ids") or []],
|
|
skill_names=[str(item) for item in payload.get("skill_names") or []],
|
|
feedback=[
|
|
_normalize_acceptance_entry(dict(item))
|
|
for item in (payload.get("acceptance") or payload.get("feedback") or [])
|
|
if isinstance(item, dict)
|
|
],
|
|
validation_result=dict(payload["validation_result"]) if isinstance(payload.get("validation_result"), dict) else None,
|
|
metadata=dict(payload.get("metadata") or {}),
|
|
)
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class TaskEvent:
|
|
event_id: str
|
|
task_id: str
|
|
session_id: str
|
|
event_type: str
|
|
created_at: str
|
|
run_id: str | None = None
|
|
payload: dict[str, Any] = field(default_factory=dict)
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
return {
|
|
"event_id": self.event_id,
|
|
"task_id": self.task_id,
|
|
"session_id": self.session_id,
|
|
"run_id": self.run_id,
|
|
"event_type": self.event_type,
|
|
"created_at": self.created_at,
|
|
"payload": dict(self.payload),
|
|
}
|
|
|
|
@classmethod
|
|
def from_dict(cls, payload: dict[str, Any]) -> "TaskEvent":
|
|
return cls(
|
|
event_id=str(payload["event_id"]),
|
|
task_id=str(payload["task_id"]),
|
|
session_id=str(payload["session_id"]),
|
|
run_id=_optional_str(payload.get("run_id")),
|
|
event_type=str(payload.get("event_type") or ""),
|
|
created_at=str(payload.get("created_at") or ""),
|
|
payload=dict(payload.get("payload") or {}),
|
|
)
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class MainAgentDecision:
|
|
mode: str
|
|
reason: str
|
|
starts_new_task: bool = False
|
|
closes_task: bool = False
|
|
abandons_task: bool = False
|
|
short_title: str | None = None
|
|
action: str = ""
|
|
|
|
@property
|
|
def is_task(self) -> bool:
|
|
return self.mode == "task"
|
|
|
|
|
|
def _optional_str(value: Any) -> str | None:
|
|
if value in (None, ""):
|
|
return None
|
|
return str(value)
|
|
|
|
|
|
def _optional_float(value: Any) -> float | None:
|
|
if value in (None, ""):
|
|
return None
|
|
return float(value)
|
|
|
|
|
|
def _normalize_acceptance_entry(entry: dict[str, Any]) -> dict[str, Any]:
|
|
if entry.get("acceptance_type") is None and entry.get("feedback_type") is not None:
|
|
feedback_type = str(entry.get("feedback_type") or "")
|
|
entry["acceptance_type"] = "accept" if feedback_type == "satisfied" else feedback_type
|
|
if entry.get("feedback_type") is None and entry.get("acceptance_type") is not None:
|
|
acceptance_type = str(entry.get("acceptance_type") or "")
|
|
entry["feedback_type"] = "satisfied" if acceptance_type == "accept" else acceptance_type
|
|
return entry
|