|
@@ -31,6 +31,7 @@ if TYPE_CHECKING:
|
|
|
from agent.core.dream import DreamReport
|
|
from agent.core.dream import DreamReport
|
|
|
|
|
|
|
|
from agent.trace.models import Trace, Message
|
|
from agent.trace.models import Trace, Message
|
|
|
|
|
+from agent.failures import FailureDetail, FailureDisposition
|
|
|
from agent.trace.protocols import TraceAttachmentStore, TraceStore
|
|
from agent.trace.protocols import TraceAttachmentStore, TraceStore
|
|
|
from agent.trace.goal_models import GoalTree
|
|
from agent.trace.goal_models import GoalTree
|
|
|
from agent.trace.compaction import (
|
|
from agent.trace.compaction import (
|
|
@@ -59,6 +60,13 @@ from agent.core.prompts import (
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
+_FAILURE_DISPOSITION_PRIORITY = {
|
|
|
|
|
+ FailureDisposition.RETRY_CALL: 0,
|
|
|
|
|
+ FailureDisposition.REPAIR_ATTEMPT: 1,
|
|
|
|
|
+ FailureDisposition.REPLAN_TASK: 2,
|
|
|
|
|
+ FailureDisposition.ABORT_RUN: 3,
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
|
|
|
@dataclass
|
|
@dataclass
|
|
|
class ContextUsage:
|
|
class ContextUsage:
|
|
@@ -490,6 +498,7 @@ class AgentRunner:
|
|
|
|
|
|
|
|
status = final_trace.status if final_trace else "unknown"
|
|
status = final_trace.status if final_trace else "unknown"
|
|
|
error = final_trace.error_message if final_trace else None
|
|
error = final_trace.error_message if final_trace else None
|
|
|
|
|
+ failure = final_trace.failure if final_trace else None
|
|
|
summary = last_assistant_text or (
|
|
summary = last_assistant_text or (
|
|
|
final_trace.result_summary if final_trace else ""
|
|
final_trace.result_summary if final_trace else ""
|
|
|
)
|
|
)
|
|
@@ -507,6 +516,7 @@ class AgentRunner:
|
|
|
"summary": summary,
|
|
"summary": summary,
|
|
|
"trace_id": trace_id,
|
|
"trace_id": trace_id,
|
|
|
"error": error,
|
|
"error": error,
|
|
|
|
|
+ "failure": failure.to_dict() if failure else None,
|
|
|
"saved_knowledge_ids": saved_knowledge_ids, # 新增:返回保存的知识 ID
|
|
"saved_knowledge_ids": saved_knowledge_ids, # 新增:返回保存的知识 ID
|
|
|
"stats": {
|
|
"stats": {
|
|
|
"total_messages": final_trace.total_messages if final_trace else 0,
|
|
"total_messages": final_trace.total_messages if final_trace else 0,
|
|
@@ -1215,6 +1225,8 @@ class AgentRunner:
|
|
|
)
|
|
)
|
|
|
explicit_terminal_submitted = False
|
|
explicit_terminal_submitted = False
|
|
|
terminal_summary: Optional[str] = None
|
|
terminal_summary: Optional[str] = None
|
|
|
|
|
+ terminal_failure: Optional[FailureDetail] = None
|
|
|
|
|
+ last_failure: Optional[FailureDetail] = None
|
|
|
|
|
|
|
|
# 当前主路径头节点的 sequence(用于设置 parent_sequence)
|
|
# 当前主路径头节点的 sequence(用于设置 parent_sequence)
|
|
|
head_seq = trace.head_sequence
|
|
head_seq = trace.head_sequence
|
|
@@ -2023,6 +2035,12 @@ class AgentRunner:
|
|
|
for res in results:
|
|
for res in results:
|
|
|
tc, tool_args, tool_result = res
|
|
tc, tool_args, tool_result = res
|
|
|
tool_name = tc["function"]["name"]
|
|
tool_name = tc["function"]["name"]
|
|
|
|
|
+ terminal_control = None
|
|
|
|
|
+ if isinstance(tool_result, dict):
|
|
|
|
|
+ terminal_control = tool_result.get("_control")
|
|
|
|
|
+ if terminal_control:
|
|
|
|
|
+ tool_result = dict(tool_result)
|
|
|
|
|
+ tool_result.pop("_control", None)
|
|
|
if tool_args is None:
|
|
if tool_args is None:
|
|
|
history.append(
|
|
history.append(
|
|
|
{
|
|
{
|
|
@@ -2154,6 +2172,30 @@ class AgentRunner:
|
|
|
)
|
|
)
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
self.log.warning(f"[Skill 指定注入] 记录追踪失败: {e}")
|
|
self.log.warning(f"[Skill 指定注入] 记录追踪失败: {e}")
|
|
|
|
|
+
|
|
|
|
|
+ observed_failure = self._control_failure(terminal_control)
|
|
|
|
|
+ if observed_failure is not None:
|
|
|
|
|
+ last_failure = self._stronger_failure(
|
|
|
|
|
+ last_failure, observed_failure
|
|
|
|
|
+ )
|
|
|
|
|
+ if self._failure_terminates_role(
|
|
|
|
|
+ observed_failure, explicit_role
|
|
|
|
|
+ ):
|
|
|
|
|
+ terminal_failure = self._stronger_failure(
|
|
|
|
|
+ terminal_failure, observed_failure
|
|
|
|
|
+ )
|
|
|
|
|
+ terminal_run = True
|
|
|
|
|
+ elif terminal_control and terminal_control.get("terminate_run"):
|
|
|
|
|
+ terminal_summary = (
|
|
|
|
|
+ terminal_control.get("result_summary") or tool_text
|
|
|
|
|
+ )
|
|
|
|
|
+ terminal_run = True
|
|
|
|
|
+ if terminal_summary and terminal_failure is None and self.trace_store:
|
|
|
|
|
+ await self.trace_store.update_trace(
|
|
|
|
|
+ trace_id,
|
|
|
|
|
+ result_summary=terminal_summary,
|
|
|
|
|
+ head_sequence=head_seq,
|
|
|
|
|
+ )
|
|
|
else:
|
|
else:
|
|
|
for tc in tool_calls:
|
|
for tc in tool_calls:
|
|
|
current_goal_id = (
|
|
current_goal_id = (
|
|
@@ -2408,7 +2450,16 @@ class AgentRunner:
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
self.log.warning(f"[Skill 指定注入] 记录追踪失败: {e}")
|
|
self.log.warning(f"[Skill 指定注入] 记录追踪失败: {e}")
|
|
|
|
|
|
|
|
- if terminal_control and terminal_control.get("terminate_run"):
|
|
|
|
|
|
|
+ observed_failure = self._control_failure(terminal_control)
|
|
|
|
|
+ if observed_failure is not None:
|
|
|
|
|
+ last_failure = observed_failure
|
|
|
|
|
+ if self._failure_terminates_role(
|
|
|
|
|
+ observed_failure, explicit_role
|
|
|
|
|
+ ):
|
|
|
|
|
+ terminal_failure = observed_failure
|
|
|
|
|
+ terminal_run = True
|
|
|
|
|
+ break
|
|
|
|
|
+ elif terminal_control and terminal_control.get("terminate_run"):
|
|
|
terminal_run = True
|
|
terminal_run = True
|
|
|
terminal_summary = (
|
|
terminal_summary = (
|
|
|
terminal_control.get("result_summary") or tool_text
|
|
terminal_control.get("result_summary") or tool_text
|
|
@@ -2422,7 +2473,10 @@ class AgentRunner:
|
|
|
break
|
|
break
|
|
|
|
|
|
|
|
if terminal_run:
|
|
if terminal_run:
|
|
|
- if explicit_role in (AgentRole.WORKER, AgentRole.VALIDATOR):
|
|
|
|
|
|
|
+ if (
|
|
|
|
|
+ terminal_failure is None
|
|
|
|
|
+ and explicit_role in (AgentRole.WORKER, AgentRole.VALIDATOR)
|
|
|
|
|
+ ):
|
|
|
explicit_terminal_submitted = True
|
|
explicit_terminal_submitted = True
|
|
|
break
|
|
break
|
|
|
|
|
|
|
@@ -2531,8 +2585,13 @@ class AgentRunner:
|
|
|
final_status = "completed"
|
|
final_status = "completed"
|
|
|
final_error: Optional[str] = None
|
|
final_error: Optional[str] = None
|
|
|
final_summary: Optional[str] = terminal_summary
|
|
final_summary: Optional[str] = terminal_summary
|
|
|
|
|
+ final_failure: Optional[FailureDetail] = None
|
|
|
|
|
|
|
|
- if explicit_role == AgentRole.PLANNER:
|
|
|
|
|
|
|
+ if terminal_failure is not None:
|
|
|
|
|
+ final_status = "failed"
|
|
|
|
|
+ final_failure = terminal_failure
|
|
|
|
|
+ final_error = self._failure_text(terminal_failure)
|
|
|
|
|
+ elif explicit_role == AgentRole.PLANNER:
|
|
|
mission = await self._get_mission_completion(trace_id)
|
|
mission = await self._get_mission_completion(trace_id)
|
|
|
mission_status = self._status_value(mission.get("status"))
|
|
mission_status = self._status_value(mission.get("status"))
|
|
|
if mission_status == "completed":
|
|
if mission_status == "completed":
|
|
@@ -2549,7 +2608,20 @@ class AgentRunner:
|
|
|
elif explicit_role in (AgentRole.WORKER, AgentRole.VALIDATOR):
|
|
elif explicit_role in (AgentRole.WORKER, AgentRole.VALIDATOR):
|
|
|
if not explicit_terminal_submitted:
|
|
if not explicit_terminal_submitted:
|
|
|
final_status = "failed"
|
|
final_status = "failed"
|
|
|
- final_error = "protocol_violation"
|
|
|
|
|
|
|
+ if last_failure is not None:
|
|
|
|
|
+ final_failure = last_failure
|
|
|
|
|
+ final_error = self._failure_text(last_failure)
|
|
|
|
|
+ else:
|
|
|
|
|
+ final_failure = FailureDetail(
|
|
|
|
|
+ code="PROTOCOL_VIOLATION",
|
|
|
|
|
+ message=(
|
|
|
|
|
+ "Worker ended without submit_attempt"
|
|
|
|
|
+ if explicit_role == AgentRole.WORKER
|
|
|
|
|
+ else "Validator ended without submit_validation"
|
|
|
|
|
+ ),
|
|
|
|
|
+ disposition=FailureDisposition.ABORT_RUN,
|
|
|
|
|
+ )
|
|
|
|
|
+ final_error = "protocol_violation"
|
|
|
|
|
|
|
|
# 更新 head_sequence 和 Trace 终态
|
|
# 更新 head_sequence 和 Trace 终态
|
|
|
if self.trace_store:
|
|
if self.trace_store:
|
|
@@ -2557,6 +2629,7 @@ class AgentRunner:
|
|
|
"status": final_status,
|
|
"status": final_status,
|
|
|
"head_sequence": head_seq,
|
|
"head_sequence": head_seq,
|
|
|
"error_message": final_error,
|
|
"error_message": final_error,
|
|
|
|
|
+ "failure": final_failure,
|
|
|
"completed_at": datetime.now(),
|
|
"completed_at": datetime.now(),
|
|
|
}
|
|
}
|
|
|
if final_summary:
|
|
if final_summary:
|
|
@@ -2566,6 +2639,50 @@ class AgentRunner:
|
|
|
if trace_obj:
|
|
if trace_obj:
|
|
|
yield trace_obj
|
|
yield trace_obj
|
|
|
|
|
|
|
|
|
|
+ @staticmethod
|
|
|
|
|
+ def _control_failure(control: Any) -> Optional[FailureDetail]:
|
|
|
|
|
+ if not isinstance(control, dict) or control.get("failure") is None:
|
|
|
|
|
+ return None
|
|
|
|
|
+ try:
|
|
|
|
|
+ return FailureDetail.from_dict(control["failure"])
|
|
|
|
|
+ except (TypeError, ValueError):
|
|
|
|
|
+ logger.error("Runner received an invalid structured tool failure")
|
|
|
|
|
+ return FailureDetail(
|
|
|
|
|
+ code="INVALID_FAILURE_ENVELOPE",
|
|
|
|
|
+ message="The tool returned an invalid failure envelope",
|
|
|
|
|
+ disposition=FailureDisposition.ABORT_RUN,
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ @staticmethod
|
|
|
|
|
+ def _failure_terminates_role(
|
|
|
|
|
+ failure: FailureDetail,
|
|
|
|
|
+ role: Optional[AgentRole],
|
|
|
|
|
+ ) -> bool:
|
|
|
|
|
+ if failure.disposition == FailureDisposition.ABORT_RUN:
|
|
|
|
|
+ return True
|
|
|
|
|
+ return (
|
|
|
|
|
+ failure.disposition == FailureDisposition.REPLAN_TASK
|
|
|
|
|
+ and role in (AgentRole.WORKER, AgentRole.VALIDATOR)
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ @staticmethod
|
|
|
|
|
+ def _stronger_failure(
|
|
|
|
|
+ current: Optional[FailureDetail],
|
|
|
|
|
+ candidate: FailureDetail,
|
|
|
|
|
+ ) -> FailureDetail:
|
|
|
|
|
+ if current is None:
|
|
|
|
|
+ return candidate
|
|
|
|
|
+ if (
|
|
|
|
|
+ _FAILURE_DISPOSITION_PRIORITY[candidate.disposition]
|
|
|
|
|
+ > _FAILURE_DISPOSITION_PRIORITY[current.disposition]
|
|
|
|
|
+ ):
|
|
|
|
|
+ return candidate
|
|
|
|
|
+ return current
|
|
|
|
|
+
|
|
|
|
|
+ @staticmethod
|
|
|
|
|
+ def _failure_text(failure: FailureDetail) -> str:
|
|
|
|
|
+ return f"{failure.code}: {failure.message}"
|
|
|
|
|
+
|
|
|
async def _get_mission_completion(self, root_trace_id: str) -> Dict[str, Any]:
|
|
async def _get_mission_completion(self, root_trace_id: str) -> Dict[str, Any]:
|
|
|
"""Read the coordinator-owned mission completion snapshot."""
|
|
"""Read the coordinator-owned mission completion snapshot."""
|
|
|
method = getattr(self.task_coordinator, "root_completion", None)
|
|
method = getattr(self.task_coordinator, "root_completion", None)
|