File size: 4,729 Bytes
116524e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
"""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