- harness/es.py: append-only event store + проекции (образец — кооп-портал) - harness/levels.py: уровни 1с/1м/1ч/1д, контекст ∝ масштабу - harness/scheduler.py: уровневые потоки, идемпотентные тики, absorbed-стабы - harness/isolated_runner.py + _llm_worker.py: LLM-тик в сабпроцессе (глобальный asyncio-рантайм Strands не переживает 2+ create_harness) - cli.py: init/task/tasks/status/events + requeue (написан самим минутным агентом) - 9router: отдельный ключ sk-simbiot, модель auto (glm-5.3 первым)
137 lines
5 KiB
Python
137 lines
5 KiB
Python
"""Agent result handling for SDK.
|
|
|
|
This module defines the AgentResult class which encapsulates the complete response from an agent's processing cycle.
|
|
"""
|
|
|
|
from collections.abc import Sequence
|
|
from dataclasses import dataclass
|
|
from typing import Any, cast
|
|
|
|
from pydantic import BaseModel
|
|
|
|
from ..experimental.checkpoint import Checkpoint
|
|
from ..interrupt import Interrupt
|
|
from ..telemetry.metrics import EventLoopMetrics
|
|
from ..types.content import Message
|
|
from ..types.session import decode_bytes_values, encode_bytes_values
|
|
from ..types.streaming import StopReason
|
|
|
|
|
|
@dataclass
|
|
class AgentResult:
|
|
"""Represents the last result of invoking an agent with a prompt.
|
|
|
|
Attributes:
|
|
stop_reason: The reason why the agent's processing stopped.
|
|
message: The last message generated by the agent.
|
|
metrics: Performance metrics collected during processing.
|
|
state: Additional state information from the event loop.
|
|
interrupts: List of interrupts if raised by user.
|
|
structured_output: Parsed structured output when structured_output_model was specified.
|
|
checkpoint: Checkpoint captured when the agent paused for durable execution.
|
|
Populated only when stop_reason == "checkpoint". See
|
|
strands.experimental.checkpoint for usage.
|
|
"""
|
|
|
|
stop_reason: StopReason
|
|
message: Message
|
|
metrics: EventLoopMetrics
|
|
state: Any
|
|
interrupts: Sequence[Interrupt] | None = None
|
|
structured_output: BaseModel | None = None
|
|
checkpoint: Checkpoint | None = None
|
|
|
|
@property
|
|
def context_size(self) -> int | None:
|
|
"""Most recent context size in tokens from the last LLM call.
|
|
|
|
Returns:
|
|
The total prompt the model processed on the most recent cycle, including cached tokens, or
|
|
None if no data is available.
|
|
"""
|
|
return self.metrics.latest_context_size
|
|
|
|
@property
|
|
def projected_context_size(self) -> int | None:
|
|
"""Projected context size for the next model call.
|
|
|
|
Returns:
|
|
The projected token count (total prompt including cached tokens plus generated output), or
|
|
None if no data is available.
|
|
"""
|
|
return self.metrics.projected_context_size
|
|
|
|
def __str__(self) -> str:
|
|
"""Return a string representation of the agent result.
|
|
|
|
Priority order:
|
|
1. Interrupts (if present) → stringified list of interrupt dicts
|
|
2. Structured output (if present) → JSON string
|
|
3. Text content from message → concatenated text blocks
|
|
|
|
Returns:
|
|
String representation based on the priority order above.
|
|
"""
|
|
if self.interrupts:
|
|
return str([interrupt.to_dict() for interrupt in self.interrupts])
|
|
|
|
if self.structured_output:
|
|
return self.structured_output.model_dump_json()
|
|
|
|
content_array = self.message.get("content", [])
|
|
result = ""
|
|
for item in content_array:
|
|
if isinstance(item, dict):
|
|
if "text" in item:
|
|
result += item.get("text", "") + "\n"
|
|
elif "citationsContent" in item:
|
|
citations_block = item["citationsContent"]
|
|
if "content" in citations_block:
|
|
for content in citations_block["content"]:
|
|
if isinstance(content, dict) and "text" in content:
|
|
result += content.get("text", "") + "\n"
|
|
|
|
return result
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: dict[str, Any]) -> "AgentResult":
|
|
"""Rehydrate an AgentResult from persisted JSON.
|
|
|
|
Args:
|
|
data: Dictionary containing the serialized AgentResult data
|
|
Returns:
|
|
AgentResult instance
|
|
Raises:
|
|
TypeError: If the data format is invalid
|
|
"""
|
|
if data.get("type") != "agent_result":
|
|
raise TypeError(f"AgentResult.from_dict: unexpected type {data.get('type')!r}")
|
|
|
|
message = cast(Message, decode_bytes_values(data.get("message")))
|
|
stop_reason = cast(StopReason, data.get("stop_reason"))
|
|
checkpoint_data = data.get("checkpoint")
|
|
checkpoint = Checkpoint.from_dict(checkpoint_data) if checkpoint_data else None
|
|
|
|
return cls(
|
|
message=message,
|
|
stop_reason=stop_reason,
|
|
metrics=EventLoopMetrics(),
|
|
state={},
|
|
checkpoint=checkpoint,
|
|
)
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
"""Convert this AgentResult to JSON-serializable dictionary.
|
|
|
|
Binary values in ``message`` (for example Converse ``redactedContent`` blobs)
|
|
are base64-encoded with the same helpers used by session persistence.
|
|
|
|
Returns:
|
|
Dictionary containing serialized AgentResult data
|
|
"""
|
|
return {
|
|
"type": "agent_result",
|
|
"message": encode_bytes_values(self.message),
|
|
"stop_reason": self.stop_reason,
|
|
"checkpoint": self.checkpoint.to_dict() if self.checkpoint else None,
|
|
}
|