Riy777 commited on
Commit
0156a9b
·
verified ·
1 Parent(s): 5fefc94

Create curator.py

Browse files
Files changed (1) hide show
  1. learning_hub/curator.py +149 -0
learning_hub/curator.py ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # learning_hub/curator.py
2
+ import json
3
+ import asyncio
4
+ from typing import List, Dict, Any, TYPE_CHECKING
5
+ from .schemas import Delta
6
+
7
+ if TYPE_CHECKING:
8
+ from LLM import LLMService
9
+ from .memory_store import MemoryStore
10
+
11
+ class Curator:
12
+ def __init__(self, llm_service: 'LLMService', memory_store: 'MemoryStore'):
13
+ self.llm_service = llm_service
14
+ self.memory_store = memory_store
15
+
16
+ # (This is a configuration parameter from Point 6, not a placeholder)
17
+ self.distill_threshold: int = 50
18
+ self.distilled_rules_key: str = "learning_distilled_rules.json"
19
+ print("✅ Learning Hub Module: Curator (Distiller) loaded")
20
+
21
+ async def check_and_distill_domain(self, domain: str):
22
+ """
23
+ Checks if a domain needs distillation and runs it if the threshold is met.
24
+ (Implements Point 6 - Distillation trigger)
25
+ """
26
+ try:
27
+ deltas_list = await self.memory_store._load_deltas_from_r2(domain)
28
+
29
+ # 1. Filter for approved Deltas only for distillation
30
+ approved_deltas = [d for d in deltas_list if d.get('approved', False)]
31
+
32
+ if len(approved_deltas) >= self.distill_threshold:
33
+ print(f"ℹ️ [Curator] Distillation threshold reached for {domain} ({len(approved_deltas)} approved deltas). Starting...")
34
+ await self.distill_deltas(domain, approved_deltas)
35
+ else:
36
+ print(f"ℹ️ [Curator] {domain} has {len(approved_deltas)}/{self.distill_threshold} approved deltas. Distillation not yet required.")
37
+
38
+ except Exception as e:
39
+ print(f"❌ [Curator] Failed to check distillation for {domain}: {e}")
40
+
41
+ async def distill_deltas(self, domain: str, deltas_to_distill: List[Dict]):
42
+ """
43
+ Runs the LLM distillation process to merge and summarize Deltas.
44
+ (Implements Point 4 - Curator (distillation job))
45
+ """
46
+ try:
47
+ # 1. Create the distillation prompt (Now in English)
48
+ prompt = self._create_distillation_prompt(domain, deltas_to_distill)
49
+
50
+ # 2. Call the LLM
51
+ response_text = await self.llm_service._call_llm(prompt)
52
+
53
+ if not response_text:
54
+ raise ValueError("Distiller LLM call returned no response.")
55
+
56
+ # 3. Parse the response
57
+ distilled_json = self.llm_service._parse_llm_response_enhanced(
58
+ response_text,
59
+ fallback_strategy="distillation",
60
+ symbol=domain
61
+ )
62
+
63
+ if not distilled_json or "distilled_rules" not in distilled_json:
64
+ raise ValueError(f"Failed to parse Distiller LLM response: {response_text}")
65
+
66
+ distilled_rules_text_list = distilled_json.get("distilled_rules", [])
67
+ if not isinstance(distilled_rules_text_list, list):
68
+ raise ValueError(f"Distiller LLM returned 'distilled_rules' not as a list.")
69
+
70
+ # 4. Save the new distilled rules
71
+ await self._save_distilled_rules(domain, distilled_rules_text_list, deltas_to_distill)
72
+
73
+ # 5. Archive (delete) the old approved deltas that were just distilled
74
+ all_deltas = await self.memory_store._load_deltas_from_r2(domain)
75
+ approved_ids_to_archive = {d['id'] for d in deltas_to_distill}
76
+
77
+ # Keep only non-approved (in-review) deltas, or deltas that weren't part of this batch
78
+ remaining_deltas = [
79
+ d for d in all_deltas
80
+ if not (d.get('approved', False) and d.get('id') in approved_ids_to_archive)
81
+ ]
82
+
83
+ await self.memory_store._save_deltas_to_r2(domain, remaining_deltas)
84
+
85
+ print(f"✅ [Curator] Distillation complete for {domain}. Created {len(distilled_rules_text_list)} new rules. Archived {len(approved_ids_to_archive)} old deltas.")
86
+
87
+ except Exception as e:
88
+ print(f"❌ [Curator] Distillation process failed for {domain}: {e}")
89
+
90
+ async def _save_distilled_rules(self, domain: str, new_rules_text: List[str], evidence_deltas: List[Dict]):
91
+ """Saves the new distilled rules as high-priority Deltas."""
92
+
93
+ # We save them back into the main delta file as high-priority,
94
+ # so they get picked up by the get_active_context() function.
95
+
96
+ deltas_list = await self.memory_store._load_deltas_from_r2(domain)
97
+ evidence_ids = [d.get('id', 'N/A') for d in evidence_deltas]
98
+
99
+ for rule_text in new_rules_text:
100
+ if not rule_text: continue # Skip empty strings
101
+
102
+ distilled_delta = Delta(
103
+ text=rule_text,
104
+ domain=domain,
105
+ priority="high", # Distilled rules get high priority
106
+ score=0.95, # High confidence score
107
+ evidence_refs=evidence_ids, # References all the deltas it summarized
108
+ created_by="curator_v1 (distilled)",
109
+ approved=True, # Automatically approved
110
+ usage_count=0
111
+ )
112
+ deltas_list.append(distilled_delta.model_dump())
113
+
114
+ await self.memory_store._save_deltas_to_r2(domain, deltas_list)
115
+
116
+ def _create_distillation_prompt(self, domain: str, deltas: List[Dict]) -> str:
117
+ """
118
+ Creates the (English-only) prompt for the LLM to act as a Distiller/Curator.
119
+ (Implements Point 4 - Curator prompt)
120
+ """
121
+
122
+ deltas_text = "\n".join([f"- {d.get('text')} (Score: {d.get('score', 0.5):.2f})" for d in deltas])
123
+
124
+ prompt = f"""
125
+ SYSTEM: You are an expert "Curator" AI. Your job is to read a list of "Deltas" (learning rules) for crypto trading, identify recurring patterns, and merge them into 3-5 concise, powerful "Golden Rules".
126
+
127
+ DOMAIN: {domain}
128
+
129
+ RAW DELTAS TO ANALYZE ({len(deltas)} rules):
130
+ {deltas_text}
131
+ --- END OF DELTAS ---
132
+
133
+ TASK:
134
+ 1. Analyze the "RAW DELTAS" above.
135
+ 2. Find overlaps, repetitions, and contradictions.
136
+ 3. Generate 3 to 5 new "Distilled Rules" that summarize the core wisdom of these deltas.
137
+ 4. Each new rule must be concise (max 25 words) and actionable.
138
+
139
+ OUTPUT FORMAT (JSON Only):
140
+ {{
141
+ "justification": "A brief explanation of the patterns you found and how you merged them.",
142
+ "distilled_rules": [
143
+ "The first golden rule (e.g., 'Always use ATR trailing stops for breakout strategies.')",
144
+ "The second golden rule (e.g., 'If RSI is overbought on 1H, avoid breakout entries.')",
145
+ "..."
146
+ ]
147
+ }}
148
+ """
149
+ return prompt