multi agent
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from agents.models import Observation, ReflectionAction, ReflectionOutcome, VerificationVerdict
|
||||
from runtime.executor import StepResult
|
||||
from runtime.planner import PlannedStep
|
||||
from semantic.llm_client import AnthropicSemanticClient, EnrichmentUnavailable
|
||||
|
||||
REFLECTOR_SYSTEM_PROMPT = """You are a reflection agent. A step failed to achieve its intended effect despite mechanical success. Your job is to propose either:
|
||||
1. A distinct recovery action (different from the failed step) that might fix the issue
|
||||
2. A replan request if no bounded corrective action is identifiable
|
||||
|
||||
You must NEVER simply re-issue the identical failed step.
|
||||
|
||||
Respond with JSON: {"replan": bool, "action": null or {"action": "...", "description": "...", "args": {...}}, "reasoning": "explanation"}"""
|
||||
|
||||
REFLECTOR_JSON_SCHEMA: dict[str, Any] = {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"required": ["replan", "reasoning"],
|
||||
"properties": {
|
||||
"replan": {"type": "boolean"},
|
||||
"action": {
|
||||
"type": ["object", "null"],
|
||||
"properties": {
|
||||
"action": {"type": "string"},
|
||||
"description": {"type": "string"},
|
||||
"args": {"type": "object"},
|
||||
},
|
||||
},
|
||||
"reasoning": {"type": "string", "minLength": 1},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class Reflector:
|
||||
def __init__(self, *, client: AnthropicSemanticClient | None = None) -> None:
|
||||
self._client = client
|
||||
|
||||
def reflect(
|
||||
self,
|
||||
*,
|
||||
observation: Observation,
|
||||
planned_step: PlannedStep,
|
||||
step_result: StepResult,
|
||||
verdict: VerificationVerdict,
|
||||
) -> ReflectionOutcome:
|
||||
if self._client is None:
|
||||
return self._fallback_reflect(
|
||||
observation=observation,
|
||||
planned_step=planned_step,
|
||||
step_result=step_result,
|
||||
verdict=verdict,
|
||||
)
|
||||
|
||||
try:
|
||||
return self._llm_reflect(
|
||||
observation=observation,
|
||||
planned_step=planned_step,
|
||||
step_result=step_result,
|
||||
verdict=verdict,
|
||||
)
|
||||
except EnrichmentUnavailable:
|
||||
return self._fallback_reflect(
|
||||
observation=observation,
|
||||
planned_step=planned_step,
|
||||
step_result=step_result,
|
||||
verdict=verdict,
|
||||
)
|
||||
|
||||
def _llm_reflect(
|
||||
self,
|
||||
*,
|
||||
observation: Observation,
|
||||
planned_step: PlannedStep,
|
||||
step_result: StepResult,
|
||||
verdict: VerificationVerdict,
|
||||
) -> ReflectionOutcome:
|
||||
user_prompt = json.dumps(
|
||||
{
|
||||
"observation": observation.to_dict(),
|
||||
"planned_step": {
|
||||
"action": planned_step.action,
|
||||
"description": planned_step.description,
|
||||
"args": planned_step.args,
|
||||
"expected_text": planned_step.expected_text,
|
||||
},
|
||||
"step_result": step_result.to_dict(),
|
||||
"verdict": verdict.to_dict(),
|
||||
},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
response = self._client._create_message( # type: ignore[union-attr]
|
||||
{"prompt": user_prompt, "system": REFLECTOR_SYSTEM_PROMPT},
|
||||
timeout=10.0,
|
||||
)
|
||||
payload = _extract_outcome(response)
|
||||
outcome = ReflectionOutcome.from_dict(payload)
|
||||
if not outcome.replan and outcome.action is not None:
|
||||
if outcome.action.action == planned_step.action and outcome.action.args == planned_step.args:
|
||||
return ReflectionOutcome(
|
||||
replan=True,
|
||||
reasoning="Reflector proposed identical action; requesting replan instead.",
|
||||
)
|
||||
return outcome
|
||||
|
||||
def _fallback_reflect(
|
||||
self,
|
||||
*,
|
||||
observation: Observation,
|
||||
planned_step: PlannedStep,
|
||||
step_result: StepResult,
|
||||
verdict: VerificationVerdict,
|
||||
) -> ReflectionOutcome:
|
||||
return ReflectionOutcome(
|
||||
replan=True,
|
||||
reasoning=f"Step '{planned_step.description}' failed semantically; replanning (fallback).",
|
||||
)
|
||||
|
||||
|
||||
def _extract_outcome(response: Any) -> dict[str, Any]:
|
||||
if isinstance(response, dict) and "replan" in response:
|
||||
return response
|
||||
content = getattr(response, "content", None)
|
||||
if isinstance(content, list):
|
||||
for block in content:
|
||||
text = getattr(block, "text", None)
|
||||
if isinstance(text, str):
|
||||
try:
|
||||
decoded = json.loads(text)
|
||||
if isinstance(decoded, dict) and "replan" in decoded:
|
||||
return decoded
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
if isinstance(block, dict) and "replan" in block:
|
||||
return block
|
||||
if isinstance(content, str):
|
||||
try:
|
||||
decoded = json.loads(content)
|
||||
if isinstance(decoded, dict) and "replan" in decoded:
|
||||
return decoded
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
raise ValueError("could not extract reflection outcome from LLM response")
|
||||
Reference in New Issue
Block a user