logic-engine / ace /implementations /reflector.py
ghostdrive1's picture
Upload folder using huggingface_hub
116524e verified
Raw
History Blame Contribute Delete
4.73 kB
"""Reflector — analyzes agent outputs to extract lessons and improve strategies.
Uses PydanticAI for structured output validation with automatic retry
and error feedback.
"""
from __future__ import annotations
import logging
from typing import Any, Optional, Union
from pydantic_ai import Agent as PydanticAgent
from pydantic_ai.settings import ModelSettings
from ..core.context import SkillbookView
from ..core.outputs import AgentOutput, ReflectorOutput
from ..core.skillbook import Skillbook
from ..providers.pydantic_ai import resolve_model
from .helpers import format_optional, make_skillbook_excerpt
from .prompts import REFLECTOR_PROMPT
logger = logging.getLogger(__name__)
class Reflector:
"""Analyzes agent outputs to extract lessons and improve strategies.
The Reflector is the second ACE role. It analyzes the Agent's output
and environment feedback to understand what went right or wrong,
classifying which skillbook skills were helpful, harmful, or neutral.
This implementation supports **SIMPLE** mode only (single-pass
reflection). Recursive mode is handled by :mod:`ace.steps.rr`.
The Reflector produces pure analysis — it does not classify or tag
skills. Skill-effectiveness decisions are made by the SkillManager
using ``ctx.injected_skill_ids`` plus the reflection.
Args:
model: Model identifier string. Supports any LiteLLM model
or PydanticAI-native identifier.
prompt_template: Custom prompt template (defaults to
:data:`REFLECTOR_PROMPT`).
max_retries: Maximum retries for structured output validation.
model_settings: Optional PydanticAI ``ModelSettings``.
Example::
reflector = Reflector("gpt-4o-mini")
reflection = reflector.reflect(
question="What is 2+2?",
agent_output=agent_output,
skillbook=skillbook,
ground_truth="4",
feedback="Correct!",
)
print(reflection.key_insight)
"""
def __init__(
self,
model: str,
*,
prompt_template: str = REFLECTOR_PROMPT,
max_retries: int = 3,
model_settings: ModelSettings | None = None,
) -> None:
self._prompt_template = prompt_template
self._agent = PydanticAgent(
resolve_model(model),
output_type=ReflectorOutput,
retries=max_retries,
model_settings=model_settings,
defer_model_check=True,
)
def reflect(
self,
*,
question: str,
agent_output: AgentOutput,
skillbook: Union[SkillbookView, Skillbook],
ground_truth: Optional[str] = None,
feedback: Optional[str] = None,
injected_skill_ids: tuple[str, ...] = (),
**kwargs: Any,
) -> ReflectorOutput:
"""Analyze agent performance and extract learnings.
This method signature matches :class:`ReflectorLike`.
Args:
question: The original question.
agent_output: The agent's output to analyze.
skillbook: Current skillbook (needs ``get_skill``).
ground_truth: Expected correct answer (if available).
feedback: Environment feedback text.
injected_skill_ids: Skills rendered into the agent's prompt
this run. Used to build the "Strategies Applied" excerpt.
**kwargs: Accepted for protocol compatibility but not forwarded.
Returns:
:class:`ReflectorOutput` with pure analysis (no tagging).
"""
skillbook_excerpt = make_skillbook_excerpt(skillbook, injected_skill_ids)
if skillbook_excerpt:
skillbook_context = f"Strategies Applied:\n{skillbook_excerpt}"
else:
skillbook_context = "(No strategies injected - outcome-based learning)"
prompt = self._prompt_template.format(
question=question,
reasoning=agent_output.reasoning,
prediction=agent_output.final_answer,
ground_truth=format_optional(ground_truth),
feedback=format_optional(feedback),
skillbook_excerpt=skillbook_context,
)
result = self._agent.run_sync(prompt)
output = result.output
usage = result.usage()
output.raw = {
"usage": {
"prompt_tokens": usage.input_tokens or 0,
"completion_tokens": usage.output_tokens or 0,
"total_tokens": usage.total_tokens or 0,
},
}
return output