christlurker's picture
FrontierAgent react demo
9d0fd45
Raw
History Blame Contribute Delete
4.01 kB
"""Exception hierarchy for FrontierAgent."""
from __future__ import annotations
from typing import Any
class FrontierAgentError(Exception):
"""Base exception for all FrontierAgent errors."""
# ── Kernel errors ───────────────────────────────────────────────────────────
class KernelError(FrontierAgentError):
"""Errors originating from the OS kernel layer."""
class TaskNotFoundError(KernelError):
def __init__(self, task_id: str) -> None:
super().__init__(f"Task not found: {task_id}")
self.task_id = task_id
class InvalidStateTransition(KernelError):
def __init__(self, task_id: str, current: str, target: str) -> None:
super().__init__(f"Invalid transition for {task_id}: {current} β†’ {target}")
class ServiceNotRegistered(KernelError):
def __init__(self, service_type: type) -> None:
super().__init__(f"Service not registered: {service_type.__name__}")
class PermissionDenied(KernelError):
def __init__(self, role: str, tool: str) -> None:
super().__init__(f"Role '{role}' has no permission for tool '{tool}'")
# LLM request errors
class LLMError(FrontierAgentError):
"""Errors from the LLM/provider layer."""
class LLMReasoningRunaway(LLMError):
"""A live stream spent its semantic budget on reasoning-only output.
Unlike :class:`LLMStreamStalled`, the provider is healthy and actively
emitting chunks. The failure is semantic: no non-whitespace visible text
or tool-call delta appeared before the configured time/token guard fired.
``partial_response`` is intentionally carried separately from provider
usage. Early stream cancellation often happens before the terminal usage
chunk arrives, so its estimated reasoning tokens must never be presented
as authoritative billing data.
"""
def __init__(
self,
*,
elapsed_s: float,
estimated_tokens: int,
trigger: str,
partial_response: Any,
) -> None:
self.elapsed_s = float(elapsed_s)
self.estimated_tokens = int(estimated_tokens)
self.trigger = trigger
self.partial_response = partial_response
super().__init__(
"reasoning-only stream exceeded "
f"{trigger} guard (elapsed={self.elapsed_s:.1f}s, "
f"estimated_tokens={self.estimated_tokens})",
)
class LLMStreamStalled(LLMError, TimeoutError):
"""A streaming LLM call went silent mid-flight.
Subclasses ``asyncio.TimeoutError`` so every existing transient-
timeout handler (retry/backoff in ``call_llm``, chain wrappers,
classification) treats it identically without changes; carried
fields make the distinct failure mode visible in logs and traces.
"""
def __init__(
self, stall_s: float, chunks_seen: int, elapsed_s: float,
) -> None:
self.stall_s = stall_s
self.chunks_seen = chunks_seen
self.elapsed_s = elapsed_s
super().__init__(
f"stream stalled: no chunks for {stall_s:.0f}s "
f"(chunks_seen={chunks_seen}, elapsed={elapsed_s:.0f}s)",
)
class LLMCallExhausted(LLMError, RuntimeError):
"""Raised by ``call_llm`` when retries are exhausted or the error is
structurally unrecoverable (4xx without proxy-wrap, or a chain-aware
fallback signal like ``model_not_found``).
Wraps the last exception encountered so the caller (typically
``run_agent_loop``) can surface it to a chain wrapper for leg
rotation. Carries ``last_exc`` separately because ``raise from`` is
too opaque for chain-aware classification β€” ``provider_chain`` calls
``classify_error(last_exc)`` directly.
"""
def __init__(self, last_exc: BaseException, reason: str) -> None:
self.last_exc = last_exc
self.reason = reason
super().__init__(f"call_llm {reason}: {last_exc!r}")