File size: 11,438 Bytes
56fd459
 
 
 
313f83b
56fd459
 
 
 
 
 
313f83b
56fd459
 
 
 
 
 
313f83b
56fd459
 
 
313f83b
56fd459
313f83b
 
56fd459
313f83b
56fd459
313f83b
56fd459
313f83b
56fd459
 
313f83b
 
56fd459
 
 
 
313f83b
56fd459
 
 
 
313f83b
56fd459
 
 
313f83b
56fd459
313f83b
 
 
 
56fd459
08a09be
313f83b
56fd459
 
313f83b
56fd459
313f83b
 
 
56fd459
 
313f83b
56fd459
 
 
313f83b
56fd459
313f83b
56fd459
 
 
 
313f83b
56fd459
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313f83b
56fd459
 
 
 
 
 
 
313f83b
56fd459
 
 
313f83b
56fd459
 
 
313f83b
56fd459
313f83b
56fd459
313f83b
56fd459
 
 
313f83b
 
 
 
56fd459
313f83b
56fd459
313f83b
56fd459
 
 
 
 
 
 
313f83b
56fd459
 
 
313f83b
56fd459
313f83b
 
56fd459
313f83b
56fd459
313f83b
56fd459
313f83b
56fd459
 
313f83b
 
56fd459
 
 
 
 
 
 
 
 
313f83b
56fd459
313f83b
56fd459
313f83b
56fd459
08a09be
313f83b
56fd459
 
313f83b
56fd459
313f83b
 
 
56fd459
 
313f83b
56fd459
 
 
313f83b
56fd459
313f83b
56fd459
 
 
313f83b
56fd459
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313f83b
56fd459
 
 
 
 
 
 
 
 
 
 
 
313f83b
56fd459
 
 
 
 
 
 
313f83b
56fd459
 
 
313f83b
56fd459
 
313f83b
 
 
 
56fd459
313f83b
56fd459
313f83b
56fd459
 
 
313f83b
 
 
 
56fd459
313f83b
56fd459
313f83b
56fd459
 
 
313f83b
56fd459
313f83b
56fd459
 
313f83b
 
56fd459
 
313f83b
56fd459
313f83b
 
56fd459
313f83b
56fd459
 
 
 
 
 
313f83b
56fd459
313f83b
 
 
 
 
 
56fd459
 
313f83b
56fd459
 
313f83b
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# Enhancement agents for card revision and improvement

import json
import asyncio
from typing import List
from datetime import datetime

from openai import AsyncOpenAI

from ankigen_core.logging import logger
from ankigen_core.models import Card, CardFront, CardBack
from .base import BaseAgentWrapper
from .config import get_config_manager
from .judges import JudgeDecision


class RevisionAgent(BaseAgentWrapper):
    """Agent for revising cards based on judge feedback"""

    def __init__(self, openai_client: AsyncOpenAI):
        config_manager = get_config_manager()
        base_config = config_manager.get_agent_config("revision_agent")

        if not base_config:
            raise ValueError(
                "revision_agent configuration not found - agent system not properly initialized"
            )

        super().__init__(base_config, openai_client)

    async def revise_card(
        self, card: Card, judge_decisions: List[JudgeDecision], max_iterations: int = 3
    ) -> Card:
        """Revise a card based on judge feedback"""
        datetime.now()

        try:
            # Collect all feedback and improvements
            all_feedback = []
            all_improvements = []

            for decision in judge_decisions:
                if not decision.approved:
                    all_feedback.append(f"{decision.judge_name}: {decision.feedback}")
                    all_improvements.extend(decision.improvements)

            if not all_feedback:
                # No revisions needed
                return card

            # Build revision prompt
            user_input = self._build_revision_prompt(
                card, all_feedback, all_improvements
            )

            # Execute revision
            response, usage = await self.execute(user_input)

            # Parse revised card
            revised_card = self._parse_revised_card(response, card)

            # Record successful execution

            logger.info(
                f"RevisionAgent successfully revised card: {card.front.question[:50]}..."
            )
            return revised_card

        except Exception as e:
            logger.error(f"RevisionAgent failed to revise card: {e}")
            return card  # Return original card on failure

    def _build_revision_prompt(
        self, card: Card, feedback: List[str], improvements: List[str]
    ) -> str:
        """Build the revision prompt"""
        feedback_str = "\n".join([f"- {fb}" for fb in feedback])
        improvements_str = "\n".join([f"- {imp}" for imp in improvements])

        return f"""Revise this flashcard based on the provided feedback and improvement suggestions:

Original Card:
Question: {card.front.question}
Answer: {card.back.answer}
Explanation: {card.back.explanation}
Example: {card.back.example}
Type: {card.card_type}
Metadata: {json.dumps(card.metadata, indent=2)}

Judge Feedback:
{feedback_str}

Specific Improvements Needed:
{improvements_str}

Instructions:
1. Address each piece of feedback specifically
2. Implement the suggested improvements
3. Maintain the educational intent and core content
4. Preserve correct information while fixing issues
5. Improve clarity, accuracy, and pedagogical value

Return the revised card as JSON:
{{
    "card_type": "{card.card_type}",
    "front": {{
        "question": "Revised, improved question"
    }},
    "back": {{
        "answer": "Revised, improved answer",
        "explanation": "Revised, improved explanation",
        "example": "Revised, improved example"
    }},
    "metadata": {{
        // Enhanced metadata with improvements
    }},
    "revision_notes": "Summary of changes made based on feedback"
}}"""

    def _parse_revised_card(self, response: str, original_card: Card) -> Card:
        """Parse the revised card response"""
        try:
            if isinstance(response, str):
                data = json.loads(response)
            else:
                data = response

            # Create revised card
            revised_card = Card(
                card_type=data.get("card_type", original_card.card_type),
                front=CardFront(question=data["front"]["question"]),
                back=CardBack(
                    answer=data["back"]["answer"],
                    explanation=data["back"].get("explanation", ""),
                    example=data["back"].get("example", ""),
                ),
                metadata=data.get("metadata", original_card.metadata),
            )

            # Add revision tracking to metadata
            if revised_card.metadata is None:
                revised_card.metadata = {}

            revised_card.metadata["revision_notes"] = data.get(
                "revision_notes", "Revised based on judge feedback"
            )
            revised_card.metadata["last_revised"] = datetime.now().isoformat()

            return revised_card

        except Exception as e:
            logger.error(f"Failed to parse revised card: {e}")
            return original_card


class EnhancementAgent(BaseAgentWrapper):
    """Agent for enhancing cards with additional content and metadata"""

    def __init__(self, openai_client: AsyncOpenAI):
        config_manager = get_config_manager()
        base_config = config_manager.get_agent_config("enhancement_agent")

        if not base_config:
            raise ValueError(
                "enhancement_agent configuration not found - agent system not properly initialized"
            )

        super().__init__(base_config, openai_client)

    async def enhance_card(
        self, card: Card, enhancement_targets: List[str] = None
    ) -> Card:
        """Enhance a card with additional content and metadata"""
        datetime.now()

        try:
            # Default enhancement targets if none specified
            if not enhancement_targets:
                enhancement_targets = [
                    "explanation",
                    "example",
                    "metadata",
                    "learning_outcomes",
                    "prerequisites",
                    "related_concepts",
                ]

            user_input = self._build_enhancement_prompt(card, enhancement_targets)

            # Execute enhancement
            response, usage = await self.execute(user_input)

            # Parse enhanced card
            enhanced_card = self._parse_enhanced_card(response, card)

            # Record successful execution

            logger.info(
                f"EnhancementAgent successfully enhanced card: {card.front.question[:50]}..."
            )
            return enhanced_card

        except Exception as e:
            logger.error(f"EnhancementAgent failed to enhance card: {e}")
            return card  # Return original card on failure

    def _build_enhancement_prompt(
        self, card: Card, enhancement_targets: List[str]
    ) -> str:
        """Build the enhancement prompt"""
        targets_str = ", ".join(enhancement_targets)

        return f"""Enhance this flashcard by adding missing elements and enriching the content:

Current Card:
Question: {card.front.question}
Answer: {card.back.answer}
Explanation: {card.back.explanation}
Example: {card.back.example}
Type: {card.card_type}
Current Metadata: {json.dumps(card.metadata, indent=2)}

Enhancement Targets: {targets_str}

Enhancement Instructions:
1. Add comprehensive explanations with reasoning
2. Provide relevant, practical examples
3. Enrich metadata with appropriate tags and categorization
4. Add learning outcomes and prerequisites if missing
5. Include connections to related concepts
6. Ensure enhancements add value without overwhelming the learner

Return the enhanced card as JSON:
{{
    "card_type": "{card.card_type}",
    "front": {{
        "question": "Enhanced question (if improvements needed)"
    }},
    "back": {{
        "answer": "Enhanced answer",
        "explanation": "Comprehensive explanation with reasoning and context",
        "example": "Relevant, practical example with details"
    }},
    "metadata": {{
        "topic": "specific topic",
        "subject": "subject area",
        "difficulty": "beginner|intermediate|advanced",
        "tags": ["comprehensive", "tag", "list"],
        "learning_outcomes": ["specific learning outcome 1", "outcome 2"],
        "prerequisites": ["prerequisite 1", "prerequisite 2"],
        "related_concepts": ["concept 1", "concept 2"],
        "estimated_time": "time in minutes",
        "common_mistakes": ["mistake 1", "mistake 2"],
        "memory_aids": ["mnemonic or memory aid"],
        "real_world_applications": ["application 1", "application 2"]
    }},
    "enhancement_notes": "Summary of enhancements made"
}}"""

    def _parse_enhanced_card(self, response: str, original_card: Card) -> Card:
        """Parse the enhanced card response"""
        try:
            if isinstance(response, str):
                data = json.loads(response)
            else:
                data = response

            # Create enhanced card
            enhanced_card = Card(
                card_type=data.get("card_type", original_card.card_type),
                front=CardFront(question=data["front"]["question"]),
                back=CardBack(
                    answer=data["back"]["answer"],
                    explanation=data["back"].get(
                        "explanation", original_card.back.explanation
                    ),
                    example=data["back"].get("example", original_card.back.example),
                ),
                metadata=data.get("metadata", original_card.metadata),
            )

            # Add enhancement tracking to metadata
            if enhanced_card.metadata is None:
                enhanced_card.metadata = {}

            enhanced_card.metadata["enhancement_notes"] = data.get(
                "enhancement_notes", "Enhanced with additional content"
            )
            enhanced_card.metadata["last_enhanced"] = datetime.now().isoformat()

            return enhanced_card

        except Exception as e:
            logger.error(f"Failed to parse enhanced card: {e}")
            return original_card

    async def enhance_card_batch(
        self, cards: List[Card], enhancement_targets: List[str] = None
    ) -> List[Card]:
        """Enhance multiple cards in batch"""
        datetime.now()

        try:
            enhanced_cards = []

            # Process cards in parallel for efficiency
            tasks = [self.enhance_card(card, enhancement_targets) for card in cards]

            results = await asyncio.gather(*tasks, return_exceptions=True)

            for card, result in zip(cards, results):
                if isinstance(result, Exception):
                    logger.warning(f"Enhancement failed for card: {result}")
                    enhanced_cards.append(card)  # Keep original
                else:
                    enhanced_cards.append(result)

            # Record batch execution
            successful_enhancements = len(
                [r for r in results if not isinstance(r, Exception)]
            )

            logger.info(
                f"EnhancementAgent batch complete: {successful_enhancements}/{len(cards)} cards enhanced"
            )
            return enhanced_cards

        except Exception as e:
            logger.error(f"EnhancementAgent batch failed: {e}")
            return cards  # Return original cards on failure