Spaces:
Paused
Paused
Commit ·
99717c2
1
Parent(s): c3e9b69
Set TASK_HORIZON to 15 days and align graders, UI, and training prompts.
Browse files- viraltest_environment: TASK_HORIZON=15; scale strategic consistency and tag_discovery targets.
- train_grpo / smoke: assert and system prompt match 15-day episodes.
- inference + run_llm_training + run_training_evidence: dynamic horizon in prompts and plot titles.
- dashboard and training HTML: EPISODE_DAYS=15 and completion checks.
- inference.py +3 -3
- server/dashboard.html +4 -3
- server/training.html +7 -5
- server/viraltest_environment.py +10 -5
- training/run_llm_training.py +7 -4
- training/run_training_evidence.py +5 -2
- training/train_grpo.ipynb +73 -270
- training/train_grpo_smoke.ipynb +21 -21
inference.py
CHANGED
|
@@ -46,9 +46,9 @@ NEAR_ZERO_ENERGY_THRESHOLD = 0.25
|
|
| 46 |
|
| 47 |
# The agent is NOT told peak hours, fatigue rules, or content type tips.
|
| 48 |
# It must discover these via the tool catalog.
|
| 49 |
-
SYSTEM_PROMPT = textwrap.dedent("""\
|
| 50 |
You are an Instagram content strategy agent. Each step is one full day (24 hours).
|
| 51 |
-
You manage a creator account over a
|
| 52 |
|
| 53 |
You receive a SPARSE observation (energy, followers, last reward, notes echo).
|
| 54 |
To learn about the world, you MUST use TOOLS before planning your day.
|
|
@@ -85,7 +85,7 @@ RULES:
|
|
| 85 |
- Empty scheduled_actions = rest all day
|
| 86 |
- Use notes to track hypotheses and observations across days
|
| 87 |
- Tool calls cost API budget (starts at 100). Use wisely.
|
| 88 |
-
- Max 2 collaborations per
|
| 89 |
|
| 90 |
Think strategically: use tools to discover what works, then exploit what you learn.""")
|
| 91 |
|
|
|
|
| 46 |
|
| 47 |
# The agent is NOT told peak hours, fatigue rules, or content type tips.
|
| 48 |
# It must discover these via the tool catalog.
|
| 49 |
+
SYSTEM_PROMPT = textwrap.dedent(f"""\
|
| 50 |
You are an Instagram content strategy agent. Each step is one full day (24 hours).
|
| 51 |
+
You manage a creator account over a {TASK_HORIZON}-day cycle.
|
| 52 |
|
| 53 |
You receive a SPARSE observation (energy, followers, last reward, notes echo).
|
| 54 |
To learn about the world, you MUST use TOOLS before planning your day.
|
|
|
|
| 85 |
- Empty scheduled_actions = rest all day
|
| 86 |
- Use notes to track hypotheses and observations across days
|
| 87 |
- Tool calls cost API budget (starts at 100). Use wisely.
|
| 88 |
+
- Max 2 collaborations per full episode
|
| 89 |
|
| 90 |
Think strategically: use tools to discover what works, then exploit what you learn.""")
|
| 91 |
|
server/dashboard.html
CHANGED
|
@@ -35,7 +35,7 @@ body{background:#0b1326;color:#dae2fd;font-family:'Inter',sans-serif}
|
|
| 35 |
<aside class="flex flex-col sticky top-0 h-screen w-64 border-r border-white/5 bg-surface-lowest shadow-2xl shadow-slate-950/50 shrink-0 z-50">
|
| 36 |
<div class="p-6 pb-4">
|
| 37 |
<div class="text-xl font-black tracking-tighter text-transparent bg-clip-text bg-gradient-to-br from-primary to-primary-ctr mb-1">Growth Copilot</div>
|
| 38 |
-
<div class="text-[9px] font-label uppercase tracking-[.2em] text-on-surface-dim/50">
|
| 39 |
</div>
|
| 40 |
<nav class="flex-1 px-3 space-y-1">
|
| 41 |
<a href="/dashboard" class="flex items-center gap-3 px-4 py-2.5 rounded-lg text-primary font-bold border-r-2 border-primary bg-gradient-to-r from-primary/10 to-transparent transition-all">
|
|
@@ -361,7 +361,7 @@ body{background:#0b1326;color:#dae2fd;font-family:'Inter',sans-serif}
|
|
| 361 |
<div class="flex flex-col items-end gap-0.5">
|
| 362 |
<div class="flex items-center gap-2">
|
| 363 |
<span id="scenarioCount" class="text-[9px] font-label text-primary font-bold">…</span>
|
| 364 |
-
<span class="text-[9px] font-label text-on-surface-dim">
|
| 365 |
</div>
|
| 366 |
<span class="text-[8px] font-label text-on-surface-dim/70 max-w-[16rem] text-right leading-tight">All strategies below — scroll the grid or search. Count updates after load.</span>
|
| 367 |
</div>
|
|
@@ -492,7 +492,8 @@ body{background:#0b1326;color:#dae2fd;font-family:'Inter',sans-serif}
|
|
| 492 |
|
| 493 |
<script>
|
| 494 |
const API=window.location.origin;
|
| 495 |
-
|
|
|
|
| 496 |
const DAYS=["Mon","Tue","Wed","Thu","Fri","Sat","Sun"];
|
| 497 |
function fmtAxisNum(v){
|
| 498 |
const a=Math.abs(v);
|
|
|
|
| 35 |
<aside class="flex flex-col sticky top-0 h-screen w-64 border-r border-white/5 bg-surface-lowest shadow-2xl shadow-slate-950/50 shrink-0 z-50">
|
| 36 |
<div class="p-6 pb-4">
|
| 37 |
<div class="text-xl font-black tracking-tighter text-transparent bg-clip-text bg-gradient-to-br from-primary to-primary-ctr mb-1">Growth Copilot</div>
|
| 38 |
+
<div class="text-[9px] font-label uppercase tracking-[.2em] text-on-surface-dim/50">15-day creator simulation</div>
|
| 39 |
</div>
|
| 40 |
<nav class="flex-1 px-3 space-y-1">
|
| 41 |
<a href="/dashboard" class="flex items-center gap-3 px-4 py-2.5 rounded-lg text-primary font-bold border-r-2 border-primary bg-gradient-to-r from-primary/10 to-transparent transition-all">
|
|
|
|
| 361 |
<div class="flex flex-col items-end gap-0.5">
|
| 362 |
<div class="flex items-center gap-2">
|
| 363 |
<span id="scenarioCount" class="text-[9px] font-label text-primary font-bold">…</span>
|
| 364 |
+
<span class="text-[9px] font-label text-on-surface-dim">15-day episode</span>
|
| 365 |
</div>
|
| 366 |
<span class="text-[8px] font-label text-on-surface-dim/70 max-w-[16rem] text-right leading-tight">All strategies below — scroll the grid or search. Count updates after load.</span>
|
| 367 |
</div>
|
|
|
|
| 492 |
|
| 493 |
<script>
|
| 494 |
const API=window.location.origin;
|
| 495 |
+
/** Must match server.viraltest_environment.TASK_HORIZON */
|
| 496 |
+
const EPISODE_DAYS=15;
|
| 497 |
const DAYS=["Mon","Tue","Wed","Thu","Fri","Sat","Sun"];
|
| 498 |
function fmtAxisNum(v){
|
| 499 |
const a=Math.abs(v);
|
server/training.html
CHANGED
|
@@ -105,7 +105,7 @@ body{background:#0b1326;color:#dae2fd;font-family:'Inter',sans-serif}
|
|
| 105 |
<div class="glass-solid p-5 rounded-xl overflow-hidden">
|
| 106 |
<h3 class="text-sm font-bold mb-1 flex items-center gap-2">
|
| 107 |
<span class="material-symbols-outlined text-secondary text-lg">show_chart</span>
|
| 108 |
-
Reward Trajectories (
|
| 109 |
</h3>
|
| 110 |
<p class="text-[9px] font-label text-on-surface-dim mb-3">Daily reward over the episode for each agent × task. Shows that smart strategies maintain higher rewards throughout.</p>
|
| 111 |
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
|
@@ -169,6 +169,8 @@ const API=window.location.origin;
|
|
| 169 |
const COLORS={"always_rest":"#E53935","spam":"#FF9800","random":"#9E9E9E","minimal":"#42A5F5","smart":"#4CAF50"};
|
| 170 |
const TASK_MAP={"monthly_engage":"engage","monthly_strategic":"strategic","monthly_competitive":"competitive"};
|
| 171 |
const TASK_LABELS={"monthly_engage":"Engage","monthly_strategic":"Strategic","monthly_competitive":"Competitive"};
|
|
|
|
|
|
|
| 172 |
|
| 173 |
let allData=null;
|
| 174 |
|
|
@@ -274,7 +276,7 @@ function renderTrajectories(){
|
|
| 274 |
html+=`<line x1="${pL}" y1="${pT}" x2="${pL}" y2="${H-pB}" stroke="#cbc3d7" stroke-width="0.7"/>`;
|
| 275 |
html+=`<line x1="${pL}" y1="${H-pB}" x2="${W-pR}" y2="${H-pB}" stroke="#cbc3d7" stroke-width="0.7"/>`;
|
| 276 |
html+=`<text x="${pL}" y="${H-10}" fill="#958ea0" font-size="8" font-family="Space Grotesk,sans-serif">Day 1</text>`;
|
| 277 |
-
html+=`<text x="${W-pR}" y="${H-10}" text-anchor="end" fill="#958ea0" font-size="8" font-family="Space Grotesk,sans-serif">Day
|
| 278 |
html+=`<text x="${pL+plotW/2}" y="${H-2}" text-anchor="middle" fill="#958ea0" font-size="7" font-family="Space Grotesk,sans-serif" opacity="0.75">day</text>`;
|
| 279 |
|
| 280 |
taskResults.forEach(r=>{
|
|
@@ -317,7 +319,7 @@ function renderTable(){
|
|
| 317 |
const scoreColor=r.grader_score>=0.5?"text-primary":r.grader_score>=0.2?"text-secondary":"text-tertiary";
|
| 318 |
const energyColor=r.final_energy>=0.5?"text-secondary":r.final_energy>0?"text-tertiary":"text-error";
|
| 319 |
const deltaColor=r.follower_delta>0?"text-secondary":r.follower_delta<0?"text-tertiary":"text-on-surface-dim";
|
| 320 |
-
const status=r.burned_out?'<span class="text-tertiary font-bold">BURNED</span>':r.steps>=
|
| 321 |
return `<tr class="border-b border-white/5 hover:bg-white/[.02]">
|
| 322 |
<td class="px-4 py-2"><div class="flex items-center gap-2"><span class="w-2 h-2 rounded-full" style="background:${color}"></span><span class="text-on-surface font-bold">${r.scenario}</span></div></td>
|
| 323 |
<td class="px-4 py-2 text-on-surface-dim">${TASK_LABELS[r.task]||r.task}</td>
|
|
@@ -351,13 +353,13 @@ function renderTakeaways(){
|
|
| 351 |
const ratio=worst.avg>0?(best.avg/worst.avg).toFixed(1):"∞";
|
| 352 |
|
| 353 |
const burnedOut=allData.results.filter(r=>r.burned_out);
|
| 354 |
-
const completed=allData.results.filter(r=>!r.burned_out&&r.steps>=
|
| 355 |
|
| 356 |
const points=[
|
| 357 |
`<span class="text-on-surface font-bold">Best agent: ${best.label}</span> (avg score ${best.avg.toFixed(4)}) — ${ratio}× better than worst (${worst.label}, avg ${worst.avg.toFixed(4)}).`,
|
| 358 |
`<span class="text-on-surface font-bold">Score spread:</span> The environment produces a ${(avgs[0].avg-avgs[avgs.length-1].avg).toFixed(4)} spread between best and worst agents, proving the reward is informative and not flat.`,
|
| 359 |
`<span class="text-on-surface font-bold">${burnedOut.length} burnout events</span> across ${allData.results.length} runs — the burnout penalty correctly punishes unsustainable strategies (spam, no-rest).`,
|
| 360 |
-
`<span class="text-on-surface font-bold">${completed.length}/${allData.results.length} episodes completed</span> all
|
| 361 |
`<span class="text-on-surface font-bold">Reward is hard to game:</span> Spamming posts burns out immediately (score ≈ 0). Always resting loses followers. The optimal strategy requires balancing multiple objectives.`,
|
| 362 |
`<span class="text-on-surface font-bold">Grader difficulty scales correctly:</span> All agents score lower on Competitive than on Engage, confirming the three-tier difficulty progression works.`,
|
| 363 |
];
|
|
|
|
| 105 |
<div class="glass-solid p-5 rounded-xl overflow-hidden">
|
| 106 |
<h3 class="text-sm font-bold mb-1 flex items-center gap-2">
|
| 107 |
<span class="material-symbols-outlined text-secondary text-lg">show_chart</span>
|
| 108 |
+
Reward Trajectories (15-day episodes)
|
| 109 |
</h3>
|
| 110 |
<p class="text-[9px] font-label text-on-surface-dim mb-3">Daily reward over the episode for each agent × task. Shows that smart strategies maintain higher rewards throughout.</p>
|
| 111 |
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
|
|
|
| 169 |
const COLORS={"always_rest":"#E53935","spam":"#FF9800","random":"#9E9E9E","minimal":"#42A5F5","smart":"#4CAF50"};
|
| 170 |
const TASK_MAP={"monthly_engage":"engage","monthly_strategic":"strategic","monthly_competitive":"competitive"};
|
| 171 |
const TASK_LABELS={"monthly_engage":"Engage","monthly_strategic":"Strategic","monthly_competitive":"Competitive"};
|
| 172 |
+
/** Must match server.viraltest_environment.TASK_HORIZON */
|
| 173 |
+
const EPISODE_DAYS=15;
|
| 174 |
|
| 175 |
let allData=null;
|
| 176 |
|
|
|
|
| 276 |
html+=`<line x1="${pL}" y1="${pT}" x2="${pL}" y2="${H-pB}" stroke="#cbc3d7" stroke-width="0.7"/>`;
|
| 277 |
html+=`<line x1="${pL}" y1="${H-pB}" x2="${W-pR}" y2="${H-pB}" stroke="#cbc3d7" stroke-width="0.7"/>`;
|
| 278 |
html+=`<text x="${pL}" y="${H-10}" fill="#958ea0" font-size="8" font-family="Space Grotesk,sans-serif">Day 1</text>`;
|
| 279 |
+
html+=`<text x="${W-pR}" y="${H-10}" text-anchor="end" fill="#958ea0" font-size="8" font-family="Space Grotesk,sans-serif">Day ${EPISODE_DAYS}</text>`;
|
| 280 |
html+=`<text x="${pL+plotW/2}" y="${H-2}" text-anchor="middle" fill="#958ea0" font-size="7" font-family="Space Grotesk,sans-serif" opacity="0.75">day</text>`;
|
| 281 |
|
| 282 |
taskResults.forEach(r=>{
|
|
|
|
| 319 |
const scoreColor=r.grader_score>=0.5?"text-primary":r.grader_score>=0.2?"text-secondary":"text-tertiary";
|
| 320 |
const energyColor=r.final_energy>=0.5?"text-secondary":r.final_energy>0?"text-tertiary":"text-error";
|
| 321 |
const deltaColor=r.follower_delta>0?"text-secondary":r.follower_delta<0?"text-tertiary":"text-on-surface-dim";
|
| 322 |
+
const status=r.burned_out?'<span class="text-tertiary font-bold">BURNED</span>':r.steps>=EPISODE_DAYS?'<span class="text-secondary">DONE</span>':'<span class="text-on-surface-dim">EARLY</span>';
|
| 323 |
return `<tr class="border-b border-white/5 hover:bg-white/[.02]">
|
| 324 |
<td class="px-4 py-2"><div class="flex items-center gap-2"><span class="w-2 h-2 rounded-full" style="background:${color}"></span><span class="text-on-surface font-bold">${r.scenario}</span></div></td>
|
| 325 |
<td class="px-4 py-2 text-on-surface-dim">${TASK_LABELS[r.task]||r.task}</td>
|
|
|
|
| 353 |
const ratio=worst.avg>0?(best.avg/worst.avg).toFixed(1):"∞";
|
| 354 |
|
| 355 |
const burnedOut=allData.results.filter(r=>r.burned_out);
|
| 356 |
+
const completed=allData.results.filter(r=>!r.burned_out&&r.steps>=EPISODE_DAYS);
|
| 357 |
|
| 358 |
const points=[
|
| 359 |
`<span class="text-on-surface font-bold">Best agent: ${best.label}</span> (avg score ${best.avg.toFixed(4)}) — ${ratio}× better than worst (${worst.label}, avg ${worst.avg.toFixed(4)}).`,
|
| 360 |
`<span class="text-on-surface font-bold">Score spread:</span> The environment produces a ${(avgs[0].avg-avgs[avgs.length-1].avg).toFixed(4)} spread between best and worst agents, proving the reward is informative and not flat.`,
|
| 361 |
`<span class="text-on-surface font-bold">${burnedOut.length} burnout events</span> across ${allData.results.length} runs — the burnout penalty correctly punishes unsustainable strategies (spam, no-rest).`,
|
| 362 |
+
`<span class="text-on-surface font-bold">${completed.length}/${allData.results.length} episodes completed</span> all ${EPISODE_DAYS} days — agents that manage energy survive; those that don't burn out early.`,
|
| 363 |
`<span class="text-on-surface font-bold">Reward is hard to game:</span> Spamming posts burns out immediately (score ≈ 0). Always resting loses followers. The optimal strategy requires balancing multiple objectives.`,
|
| 364 |
`<span class="text-on-surface font-bold">Grader difficulty scales correctly:</span> All agents score lower on Competitive than on Engage, confirming the three-tier difficulty progression works.`,
|
| 365 |
];
|
server/viraltest_environment.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
"""
|
| 2 |
Viraltest Environment v2 — Theme #3.1 World-Modeling Simulation.
|
| 3 |
|
| 4 |
-
|
| 5 |
- Mosseri-aligned engagement signals (watch_time, sends, saves, likes)
|
| 6 |
- Discoverable tool catalog (partial observability)
|
| 7 |
- Piecewise-linear sleep model (Van Dongen 2003)
|
|
@@ -92,7 +92,12 @@ _HEATMAP_GRID: Dict[int, List[float]] = {
|
|
| 92 |
# Constants (research-backed, Tier 1-3 sources)
|
| 93 |
# ---------------------------------------------------------------------------
|
| 94 |
|
| 95 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
|
| 97 |
# Socialinsider 2026 (31M posts)
|
| 98 |
CONTENT_ENERGY_COST = {
|
|
@@ -1184,14 +1189,14 @@ class ViraltestEnvironment(Environment):
|
|
| 1184 |
norm_eng = min(1.0, self._total_engagement / theoretical_max) if theoretical_max > 0 else 0.0
|
| 1185 |
|
| 1186 |
positive_tags = sum(1 for t in self._unique_tags_used if self._tag_performance_avg(t) > 0)
|
| 1187 |
-
tag_discovery = min(1.0, positive_tags /
|
| 1188 |
top_perfs = sorted([self._tag_performance_avg(t) for t in self._unique_tags_used], reverse=True)[:3]
|
| 1189 |
tag_exploitation = (sum(top_perfs) / len(top_perfs)) if top_perfs else 0.0
|
| 1190 |
tag_exploitation = min(1.0, tag_exploitation / 2.0)
|
| 1191 |
tag_score = 0.4 * tag_discovery + 0.6 * tag_exploitation
|
| 1192 |
|
| 1193 |
avg_energy = sum(self._energy_history) / len(self._energy_history) if self._energy_history else 0.0
|
| 1194 |
-
consistency = len(self._days_with_good_posts) /
|
| 1195 |
|
| 1196 |
raw = 0.35 * norm_eng + 0.25 * tag_score + 0.25 * avg_energy + 0.15 * consistency
|
| 1197 |
|
|
@@ -1213,7 +1218,7 @@ class ViraltestEnvironment(Environment):
|
|
| 1213 |
norm_eng = min(1.0, self._total_engagement / theoretical_max) if theoretical_max > 0 else 0.0
|
| 1214 |
|
| 1215 |
positive_tags = sum(1 for t in self._unique_tags_used if self._tag_performance_avg(t) > 0)
|
| 1216 |
-
tag_discovery = min(1.0, positive_tags /
|
| 1217 |
top_perfs = sorted([self._tag_performance_avg(t) for t in self._unique_tags_used], reverse=True)[:3]
|
| 1218 |
tag_exploitation = (sum(top_perfs) / len(top_perfs)) if top_perfs else 0.0
|
| 1219 |
tag_exploitation = min(1.0, tag_exploitation / 2.0)
|
|
|
|
| 1 |
"""
|
| 2 |
Viraltest Environment v2 — Theme #3.1 World-Modeling Simulation.
|
| 3 |
|
| 4 |
+
Multi-day creator optimization with:
|
| 5 |
- Mosseri-aligned engagement signals (watch_time, sends, saves, likes)
|
| 6 |
- Discoverable tool catalog (partial observability)
|
| 7 |
- Piecewise-linear sleep model (Van Dongen 2003)
|
|
|
|
| 92 |
# Constants (research-backed, Tier 1-3 sources)
|
| 93 |
# ---------------------------------------------------------------------------
|
| 94 |
|
| 95 |
+
# Episode length in daily env steps. Graders and UI should stay consistent with this value.
|
| 96 |
+
TASK_HORIZON = 15
|
| 97 |
+
|
| 98 |
+
# Distinct positive tags for full tag_discovery score in strategic/competitive graders.
|
| 99 |
+
# Caps at 30 (original month-scale bar); scales down only for very short horizons.
|
| 100 |
+
TAG_DISCOVERY_POSITIVE_TARGET = float(max(6, min(30, TASK_HORIZON * 2)))
|
| 101 |
|
| 102 |
# Socialinsider 2026 (31M posts)
|
| 103 |
CONTENT_ENERGY_COST = {
|
|
|
|
| 1189 |
norm_eng = min(1.0, self._total_engagement / theoretical_max) if theoretical_max > 0 else 0.0
|
| 1190 |
|
| 1191 |
positive_tags = sum(1 for t in self._unique_tags_used if self._tag_performance_avg(t) > 0)
|
| 1192 |
+
tag_discovery = min(1.0, positive_tags / TAG_DISCOVERY_POSITIVE_TARGET)
|
| 1193 |
top_perfs = sorted([self._tag_performance_avg(t) for t in self._unique_tags_used], reverse=True)[:3]
|
| 1194 |
tag_exploitation = (sum(top_perfs) / len(top_perfs)) if top_perfs else 0.0
|
| 1195 |
tag_exploitation = min(1.0, tag_exploitation / 2.0)
|
| 1196 |
tag_score = 0.4 * tag_discovery + 0.6 * tag_exploitation
|
| 1197 |
|
| 1198 |
avg_energy = sum(self._energy_history) / len(self._energy_history) if self._energy_history else 0.0
|
| 1199 |
+
consistency = len(self._days_with_good_posts) / float(max(1, TASK_HORIZON))
|
| 1200 |
|
| 1201 |
raw = 0.35 * norm_eng + 0.25 * tag_score + 0.25 * avg_energy + 0.15 * consistency
|
| 1202 |
|
|
|
|
| 1218 |
norm_eng = min(1.0, self._total_engagement / theoretical_max) if theoretical_max > 0 else 0.0
|
| 1219 |
|
| 1220 |
positive_tags = sum(1 for t in self._unique_tags_used if self._tag_performance_avg(t) > 0)
|
| 1221 |
+
tag_discovery = min(1.0, positive_tags / TAG_DISCOVERY_POSITIVE_TARGET)
|
| 1222 |
top_perfs = sorted([self._tag_performance_avg(t) for t in self._unique_tags_used], reverse=True)[:3]
|
| 1223 |
tag_exploitation = (sum(top_perfs) / len(top_perfs)) if top_perfs else 0.0
|
| 1224 |
tag_exploitation = min(1.0, tag_exploitation / 2.0)
|
training/run_llm_training.py
CHANGED
|
@@ -146,9 +146,9 @@ def run_episode(task, plan_fn, seed=42):
|
|
| 146 |
|
| 147 |
# ─── Ollama LLM interface ─────────────────────────────────────────────
|
| 148 |
|
| 149 |
-
BASE_SYSTEM_PROMPT = textwrap.dedent("""\
|
| 150 |
You are an Instagram content strategy agent. Each step is one day.
|
| 151 |
-
You manage a creator account over a
|
| 152 |
|
| 153 |
RESPONSE FORMAT — return ONLY valid JSON, no markdown, no explanation:
|
| 154 |
{
|
|
@@ -319,8 +319,11 @@ def plot_baseline_leaderboard(baseline_results):
|
|
| 319 |
axes[i].text(bar.get_width() + 0.005, bar.get_y() + bar.get_height() / 2,
|
| 320 |
f"{score:.4f}", va="center", fontsize=9)
|
| 321 |
axes[0].set_ylabel("Agent")
|
| 322 |
-
fig.suptitle(
|
| 323 |
-
|
|
|
|
|
|
|
|
|
|
| 324 |
fig.tight_layout()
|
| 325 |
fig.savefig(PLOTS_DIR / "baseline_leaderboard.png", dpi=150, bbox_inches="tight")
|
| 326 |
plt.close(fig)
|
|
|
|
| 146 |
|
| 147 |
# ─── Ollama LLM interface ─────────────────────────────────────────────
|
| 148 |
|
| 149 |
+
BASE_SYSTEM_PROMPT = textwrap.dedent(f"""\
|
| 150 |
You are an Instagram content strategy agent. Each step is one day.
|
| 151 |
+
You manage a creator account over a {TASK_HORIZON}-day cycle.
|
| 152 |
|
| 153 |
RESPONSE FORMAT — return ONLY valid JSON, no markdown, no explanation:
|
| 154 |
{
|
|
|
|
| 319 |
axes[i].text(bar.get_width() + 0.005, bar.get_y() + bar.get_height() / 2,
|
| 320 |
f"{score:.4f}", va="center", fontsize=9)
|
| 321 |
axes[0].set_ylabel("Agent")
|
| 322 |
+
fig.suptitle(
|
| 323 |
+
f"Viraltest v2 — Heuristic Baseline Leaderboard ({TASK_HORIZON}-day episodes)",
|
| 324 |
+
fontsize=14,
|
| 325 |
+
fontweight="bold",
|
| 326 |
+
)
|
| 327 |
fig.tight_layout()
|
| 328 |
fig.savefig(PLOTS_DIR / "baseline_leaderboard.png", dpi=150, bbox_inches="tight")
|
| 329 |
plt.close(fig)
|
training/run_training_evidence.py
CHANGED
|
@@ -325,8 +325,11 @@ def plot_baseline_leaderboard(baseline_results: Dict):
|
|
| 325 |
f"{score:.4f}", va="center", fontsize=9)
|
| 326 |
|
| 327 |
axes[0].set_ylabel("Agent")
|
| 328 |
-
fig.suptitle(
|
| 329 |
-
|
|
|
|
|
|
|
|
|
|
| 330 |
fig.tight_layout()
|
| 331 |
path = PLOTS_DIR / "baseline_leaderboard.png"
|
| 332 |
fig.savefig(path, dpi=150, bbox_inches="tight")
|
|
|
|
| 325 |
f"{score:.4f}", va="center", fontsize=9)
|
| 326 |
|
| 327 |
axes[0].set_ylabel("Agent")
|
| 328 |
+
fig.suptitle(
|
| 329 |
+
f"Viraltest v2 — Heuristic Baseline Leaderboard ({TASK_HORIZON}-day episodes)",
|
| 330 |
+
fontsize=14,
|
| 331 |
+
fontweight="bold",
|
| 332 |
+
)
|
| 333 |
fig.tight_layout()
|
| 334 |
path = PLOTS_DIR / "baseline_leaderboard.png"
|
| 335 |
fig.savefig(path, dpi=150, bbox_inches="tight")
|
training/train_grpo.ipynb
CHANGED
|
@@ -25,31 +25,9 @@
|
|
| 25 |
},
|
| 26 |
{
|
| 27 |
"cell_type": "code",
|
| 28 |
-
"execution_count":
|
| 29 |
"metadata": {},
|
| 30 |
-
"outputs": [
|
| 31 |
-
{
|
| 32 |
-
"name": "stdout",
|
| 33 |
-
"output_type": "stream",
|
| 34 |
-
"text": [
|
| 35 |
-
"\n",
|
| 36 |
-
"\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m25.3\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m26.0.1\u001b[0m\n",
|
| 37 |
-
"\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n",
|
| 38 |
-
"\n",
|
| 39 |
-
"\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m25.3\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m26.0.1\u001b[0m\n",
|
| 40 |
-
"\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n",
|
| 41 |
-
"\n",
|
| 42 |
-
"\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m25.3\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m26.0.1\u001b[0m\n",
|
| 43 |
-
"\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n",
|
| 44 |
-
"\n",
|
| 45 |
-
"\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m25.3\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m26.0.1\u001b[0m\n",
|
| 46 |
-
"\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n",
|
| 47 |
-
"\n",
|
| 48 |
-
"\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m25.3\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m26.0.1\u001b[0m\n",
|
| 49 |
-
"\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n"
|
| 50 |
-
]
|
| 51 |
-
}
|
| 52 |
-
],
|
| 53 |
"source": [
|
| 54 |
"# Cell 1: Install dependencies (quote versions — zsh treats `>` as redirect otherwise)\n",
|
| 55 |
"!pip install -q torch torchvision torchaudio\n",
|
|
@@ -61,22 +39,9 @@
|
|
| 61 |
},
|
| 62 |
{
|
| 63 |
"cell_type": "code",
|
| 64 |
-
"execution_count":
|
| 65 |
"metadata": {},
|
| 66 |
-
"outputs": [
|
| 67 |
-
{
|
| 68 |
-
"name": "stdout",
|
| 69 |
-
"output_type": "stream",
|
| 70 |
-
"text": [
|
| 71 |
-
"Mode: local\n",
|
| 72 |
-
"Repo root: /Users/anurag.c/viral-posts-env\n",
|
| 73 |
-
"Working dir: /Users/anurag.c/viral-posts-env\n",
|
| 74 |
-
"Branch: hack1\n",
|
| 75 |
-
"Commit: aedc9c7\n",
|
| 76 |
-
"Plots dir: /Users/anurag.c/viral-posts-env/plots\n"
|
| 77 |
-
]
|
| 78 |
-
}
|
| 79 |
-
],
|
| 80 |
"source": [
|
| 81 |
"# Cell 2: Resolve repo path (Colab: fresh clone. Local: auto-detect project root)\n",
|
| 82 |
"import os\n",
|
|
@@ -156,26 +121,9 @@
|
|
| 156 |
},
|
| 157 |
{
|
| 158 |
"cell_type": "code",
|
| 159 |
-
"execution_count":
|
| 160 |
"metadata": {},
|
| 161 |
-
"outputs": [
|
| 162 |
-
{
|
| 163 |
-
"name": "stdout",
|
| 164 |
-
"output_type": "stream",
|
| 165 |
-
"text": [
|
| 166 |
-
"/Users/anurag.c/viral-posts-env/.venv/lib/python3.14/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
|
| 167 |
-
" from .autonotebook import tqdm as notebook_tqdm\n"
|
| 168 |
-
]
|
| 169 |
-
},
|
| 170 |
-
{
|
| 171 |
-
"name": "stdout",
|
| 172 |
-
"output_type": "stream",
|
| 173 |
-
"text": [
|
| 174 |
-
"GPU: CPU\n",
|
| 175 |
-
"Tags: 114, Topics: 100, Horizon: 30 days\n"
|
| 176 |
-
]
|
| 177 |
-
}
|
| 178 |
-
],
|
| 179 |
"source": [
|
| 180 |
"# Cell 3: Imports (with runtime validation)\n",
|
| 181 |
"import json, random, time, textwrap, copy, os, sys\n",
|
|
@@ -219,6 +167,12 @@
|
|
| 219 |
"print(f\"GPU: {torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'CPU'}\")\n",
|
| 220 |
"print(f\"Tags: {len(TAG_POOL)}, Topics: {len(ALL_TOPICS)}, Horizon: {TASK_HORIZON} days\")\n",
|
| 221 |
"\n",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
"# Same sanity as syntax_only.ipynb (kernel parses modern Python)\n",
|
| 223 |
"import ast\n",
|
| 224 |
"ast.parse(\"def _t(x: int) -> str: return f'{x}'\")\n",
|
|
@@ -236,17 +190,9 @@
|
|
| 236 |
},
|
| 237 |
{
|
| 238 |
"cell_type": "code",
|
| 239 |
-
"execution_count":
|
| 240 |
"metadata": {},
|
| 241 |
-
"outputs": [
|
| 242 |
-
{
|
| 243 |
-
"name": "stdout",
|
| 244 |
-
"output_type": "stream",
|
| 245 |
-
"text": [
|
| 246 |
-
"Agents and episode runner defined.\n"
|
| 247 |
-
]
|
| 248 |
-
}
|
| 249 |
-
],
|
| 250 |
"source": [
|
| 251 |
"# Cell 4: Define heuristic agents + episode runner\n",
|
| 252 |
"_rng = random.Random(42)\n",
|
|
@@ -295,7 +241,8 @@
|
|
| 295 |
" topic=ALL_TOPICS[(day*2+1)%len(ALL_TOPICS)],\n",
|
| 296 |
" tags=[TAG_POOL[(day*6+3+i)%len(TAG_POOL)] for i in range(3)],\n",
|
| 297 |
" intent=INTENTS[(day*2+1)%4]),\n",
|
| 298 |
-
" ]
|
|
|
|
| 299 |
"\n",
|
| 300 |
"BASELINE_AGENTS = {\n",
|
| 301 |
" \"always_rest\": plan_always_rest, \"spam\": plan_spam,\n",
|
|
@@ -326,47 +273,9 @@
|
|
| 326 |
},
|
| 327 |
{
|
| 328 |
"cell_type": "code",
|
| 329 |
-
"execution_count":
|
| 330 |
"metadata": {},
|
| 331 |
-
"outputs": [
|
| 332 |
-
{
|
| 333 |
-
"name": "stdout",
|
| 334 |
-
"output_type": "stream",
|
| 335 |
-
"text": [
|
| 336 |
-
"Running heuristic baselines (5 agents × 3 tasks)...\n",
|
| 337 |
-
"======================================================================\n",
|
| 338 |
-
" always_rest | monthly_engage | score=0.0000 | energy=1.00\n",
|
| 339 |
-
" always_rest | monthly_strategic | score=0.1750 | energy=1.00\n",
|
| 340 |
-
" always_rest | monthly_competitive | score=0.0350 | energy=1.00\n",
|
| 341 |
-
"\n",
|
| 342 |
-
" spam | monthly_engage | score=0.0042 | energy=0.00\n",
|
| 343 |
-
" spam | monthly_strategic | score=0.0075 | energy=0.00\n",
|
| 344 |
-
" spam | monthly_competitive | score=0.0000 | energy=0.00\n",
|
| 345 |
-
"\n",
|
| 346 |
-
" random | monthly_engage | score=0.5389 | energy=0.92\n",
|
| 347 |
-
" random | monthly_strategic | score=0.6403 | energy=0.92\n",
|
| 348 |
-
" random | monthly_competitive | score=0.6678 | energy=0.92\n",
|
| 349 |
-
"\n",
|
| 350 |
-
" minimal | monthly_engage | score=0.4145 | energy=1.00\n",
|
| 351 |
-
" minimal | monthly_strategic | score=0.7220 | energy=1.00\n",
|
| 352 |
-
" minimal | monthly_competitive | score=0.3850 | energy=1.00\n",
|
| 353 |
-
"\n",
|
| 354 |
-
" smart | monthly_engage | score=0.7883 | energy=1.00\n",
|
| 355 |
-
" smart | monthly_strategic | score=0.8932 | energy=1.00\n",
|
| 356 |
-
" smart | monthly_competitive | score=0.8986 | energy=1.00\n",
|
| 357 |
-
"\n",
|
| 358 |
-
"\n",
|
| 359 |
-
"LEADERBOARD\n",
|
| 360 |
-
"Agent Engage Strategic Competitive Avg\n",
|
| 361 |
-
"------------------------------------------------------------\n",
|
| 362 |
-
"always_rest 0.0000 0.1750 0.0350 0.0700\n",
|
| 363 |
-
"spam 0.0042 0.0075 0.0000 0.0039\n",
|
| 364 |
-
"random 0.5389 0.6403 0.6678 0.6157\n",
|
| 365 |
-
"minimal 0.4145 0.7220 0.3850 0.5072\n",
|
| 366 |
-
"smart 0.7883 0.8932 0.8986 0.8600\n"
|
| 367 |
-
]
|
| 368 |
-
}
|
| 369 |
-
],
|
| 370 |
"source": [
|
| 371 |
"# Cell 5: Run baselines (safe)\n",
|
| 372 |
"print(\"Running heuristic baselines (5 agents × 3 tasks)...\")\n",
|
|
@@ -405,20 +314,9 @@
|
|
| 405 |
},
|
| 406 |
{
|
| 407 |
"cell_type": "code",
|
| 408 |
-
"execution_count":
|
| 409 |
"metadata": {},
|
| 410 |
-
"outputs": [
|
| 411 |
-
{
|
| 412 |
-
"data": {
|
| 413 |
-
"image/png": "iVBORw0KGgoAAAANSUhEUgAABjIAAAHvCAYAAAD+XUa3AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAef9JREFUeJzt3QWYXNX5OP4TSAga3AnBXRKKa5Aiwd0JDsWtuBcrRYsUSvECwa0tULQ4wQnuBQrBITgE5v+85/u/85vd7G52l0327uzn8zw3mZ25M3NlZs699z3ve3pUKpVKAgAAAAAAKKFxOnsBAAAAAAAAmiOQAQAAAAAAlJZABgAAAAAAUFoCGQAAAAAAQGkJZAAAAAAAAKUlkAEAAAAAAJSWQAYAAAAAAFBaAhkAAAAAAEBpCWQAAAAAAAClJZABAHQZ2267berRo0eeBg4cOFbe87777qu+Z0xvv/32WHlfaItZZpml+hk9+uijx+jGu+SSSxp8J/j1v1+12zO2L13797sz2qqxTdsIAIxtAhkAwFiz+uqrVy/uTD755OmHH35ocr5KpZJmn3326rwDBgwo5V4q04WcsX0h9LXXXkunn356Wm+99dJ8882XpphiijTeeOOl6aefPq299trplltuSV3hon9zFxnj/mKemL+7KEuQovF3q5jGHXfcNOmkk6aFF1447bHHHunVV1/ttGXsThrvD8EWAADGtp5j/R0BgG4reqnecccd+fYXX3yR/vGPf6QNN9xwlPkeeuih9OabbzZ4Xthss83SAgsskG/37dt3rC03ozr11FPT+eefP8r9w4cPz/s1pt122y2dc845Nt9YcNhhh6Uvv/wy31566aXH6Hsttthi6U9/+lPqDL/88ksaMWJEeu655/J08cUX54vssUxdWe327OrrAgAAY4JABgAw1kTv/ckmmywHMcJll13WZCAj7i/06tUrbbnlltWMjpjaKi589unT51ctO02bddZZ8z6ZYYYZ0osvvpiuueaa9PPPP+fHzj333LT++uunVVZZxeYbQ7766qs0ySSTpJ122mmsbeP5558/T2PTpptumhZddNE0cuTINHTo0HTjjTfm+7/99tt0/PHHp5tuuil1ZQcccEBnLwIdoF7amnpZDwCgvigtBQCMNeOPP37Oqijcdttt6dNPP20wT5Sbuvbaa6t/r7XWWmmqqaZqc435m2++OfdMn3jiidPMM8+c5/nss8/SgQcemFZeeeVcLiguAEc5pGmnnTb99re/TZdffnkua9Ua8T4rrrjiKBf1i2UoskgKzz77bNp+++1zyawJJpggL1eUzDrhhBPSN998M8rr//e//0277LJLmnPOOfP8se1mnHHGtMwyy6T99tsvvfTSSw1KINXabrvtWl0W6cILL6zOO9FEE42yLBF0ivcu5rniiivy/ZEZc+utt6Y33ngjBywOP/zwdOWVV6YLLrigwfNjH9ej+JyeffbZafnll29QVmvjjTdOjzzyyCjzx7gVze2TKElW+/mNDIPmnhffl9133z3NNNNMucxS7L/RjZERZb4i2BSf8wgMxgXK+BxGYPHEE0/MWQ7FMsRnp1btchWvO7ryUxFsuOiii9Kqq66a3zO2zdRTT52WXHLJdMwxx7Rre8fyx8X+gw8+ON1www3VzKzw8ssvN5j3mWeeydlASyyxRP7OFN+ffv365YDIgw8+2OQyn3HGGWmppZbKwdaePXumKaecMgdsttlmmzRkyJBRnvPhhx+mQw89NPXv3z//lsR7zDHHHHn/vPPOO21av+bKNjXe1vG5i8DNXHPNlXr37p0/B7FdmivTF9/RddddN382Yz9ESb+VVlopf49b+1v3a7R1G7Vn34X4Xuy666758xbPiaDX1VdfPdrli89+/O7HZ3WaaaapflbXXHPN9K9//Wu0JbZef/31dMopp6R5550374/4rDTlo48+SjvuuGOabrrp8vosssgiTX6mwnfffZfL9sVvfeyvoo0aNGhQDhQ39dk94ogj8uPxvY7Pb3zP4/O73HLLpbPOOiv99NNPo/3Nid+SWK7YfvG79mu3LQBAh6sAAIxFjz76aFw9q05nn312g8evvfbaBo/ffPPN1ccGDx5cvX+FFVZo8Lza5yy33HIN/p500knzPMOGDWtwf1PTdttt1+B177333gaPv/XWW6O8X1NTLGvh3HPPrfTs2bPZeeebb77KBx98UJ3/ww8/rEw99dQtvv5f/vKXPG9sh5bm69evX4v7Y8SIEZUJJ5ywOv+VV17Z4PELL7ywwXb89ttvW3y9r776qsH777HHHpWyiW3S3OeoULtdG2/Djz76qNK/f/9mt/k444xTOeOMMxo856ijjmr29eIzVfv8+Mw19bypppqqMs888zSY9/TTTx9lneI5hYsvvni0n9XvvvtulGVoaipet/Fr1vr0008riy22WLOvUXwXR6fx9y7eM4wcObLyyCOPVPr06dPsPjzrrLNaXI8ePXpUX6+p35ampiWWWKLB/A8//HDeHy2t5/3339/se7T0+1W7bI239bLLLtvk+2299dYNXu/nn3/O97W0ThtvvHHenr9mf7SkPduoPfvu888/H+V7UUxrrrlmk7/fIX7LVllllRbfb7/99mtxOzRua9Zdd91R9nX8vs8yyyxNvv6pp57a4PWjHZh//vlbXKYNN9yw8tNPPzX7m9vUFOtZu68bf98br8fCCy/8q7YtAMCYoLQUADBWRU/b6L1aZBREGanondtUWanoIRu9TNvqgQceyFkckf0RvVJfeOGFfP8444yT33vxxRfPPWOj5+r333+fnn766dxzOa4nRs396H0a84yupn1kI5x33nnV+6LncfSgDUWP8YcffjgPShw9f0P0So/e5VES6NJLL02ffPJJLskUPXn//e9/53muv/769PHHH+fb8XrRSz7W4/3338+9z2P9Cr/73e9y1srvf//7UcrwhBgYuSXRS3qjjTaqbvfIqth8882rj8ffhdie0SO3JY17x49uO3a2d999N/eobur+5my99da553ix/bbYYovcMz7Gdrn99tvzvt53333zPohe1R0lPisxRamueN34jEQv6Zb85S9/aTD2QnxWogd3rN9jjz1W/R5GVkl8pp944okGva1rx25ozdgbsW0ef/zx6t/xfYvvcPRWj+9ZvGd7xHegcbZI8Z2u/eyHeK/4nkUWQHxvIvspxg+5++6787LF93z//ffP35P4PH/99dfp73//e/X5Ue4ueqbHcyIz6j//+c8oZXcimyX2RSiyBeK1rrvuuvx7E8+N13nttddG+x1si8hIiHJt8803X86qiJ71IW6fdNJJucRbOPnkk3OmQYge97EsMUD6W2+9le+PHvqR+RbbKH63Olp7t1Fb912IbLDa350VVlghT/F9/Oc//9nsMsZ39K677sq3I+shft8iA27YsGF528R7nXbaaek3v/lN/o43JX6LI2tn7bXXzvNHllRj8fse6xfvF/sispWK8oqRYbTOOuvkLJUQZRSL9irEb3Ps6zvvvLOa6RXtQ2TyHXnkkfnveM3ZZpstb7fIYok2I/ZvbJNYj/i+x3rG8zbZZJNm1yP2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ElcXXXRRk71N42BywIABafzxx099+/ZNhxxySHr55Zerr99U2m+kCceyxPzjjTdeLtuxyiqr5ItEANAZom0q7L///mnHHXdMK620UlpzzTXTgQcemDMx/vnPf1Z7SdZmY4Tadq9oW2vb23//+985gyKCIz179kwXXHBBnicyKlZeeeV8MWLiiSfO7eIMM8yQ1l9//QZtdHEsUGRjhGOOOabJsSp+/vnndP755+fjg2hj4zX79euXdtppp9wzs6kLSAcccEB+3ziuWGyxxXJpj+bqb49ujIybb745X3yKklzx3lNNNVXOZqlddgCo7UxQBDFClJa68MILc1sYbfHgwYPTkCFD0vPPP58mnHDCPE+cm0Z7tOKKK+Zz3Th3jXYnzmfvvvvuUTZubTsd58m/+93v0tRTT50mmWSStPbaa+dMy3jNWJbIbujdu3ead95587LUaurcfIsttsjnzbFsyy+/fHrooYdGef/Wts1FG1tkY4QoSd24PW6qjY7/G59/x/FK4/EwmhojI87Pi/tOPPHEUY4TYjsVjxdZmeGll17Kx0xxfBPXBPr06ZPb/HhdhXSgRpSWAuiqLr744ogKVKeW3HvvvdX5+vTpU5lmmmkaPDemqaaaqjJixIjqc4YNG5bnbTxfv379KlNMMUX173jtwqabbjrK/LXT3nvv3WC5Lr300ibnW2SRRZpdt3POOacyzjjjNPsehxxySIdtYwBorc0226zaFs0999yVIUOGVIYPH97kvCussEKL7WXRtkabW9w355xzNpjn9NNPz/NMO+20zb5Ojx49Ktdff/0oxwJNTbFM4dtvv62suOKKzc432WSTVR577LHquvz888+VVVZZpcn37t+/f/XvwYMHN3kMU7xv+OWXXyrbbrtts++97rrr+kACMIpod4u2YqWVVhrtFho5cmRlvfXWa7FdPP744xs8p/axueaaa5T5Z5111squu+7a5Gs9/PDD1depbY+jTZ1hhhlGmX+88car3HfffdXntKVtbnydoPFUtMdHHXXUKPfF/y09N1678XFMcd+dd95ZvW+BBRZosO2uuuqq6mPLLbdc9f4bb7yxMv744zf7fltuuWU+NgAqFRkZQF2p7SFSTLWDiBZGjBiRMyyuvPLKXCuzKD/1ySef5PsKe++9d543TD/99LkX5HXXXZef+9lnnzW5DOuss04677zzcmbEvffem3uyRE+Y6EkZzj777DR8+PB8++uvv0577bVX9blLL710HoD0L3/5S3rjjTeafP0ozbHnnnvmQdwiXfiwww7LPVSjZ0r0SgnR++Oee+75FVsSANouMi8Kr7zyStpss81yz86ZZpop91KMtrHoWXjWWWela6+9dpQSkcUUmYqNvfbaa7lH5T/+8Y90zTXX5EFLi0E4o42ObI/o5XnHHXekE044IT8W7xclrkK8Zrz2GmusUX3NeL3iPWOZih6a0YYXvTDjWCHa2l133bU6SHmsTwxkHqKn6V133ZVvx7HH73//+/Svf/0r964sBhVvrcgyKXp2ho022igfe8S2O/zww3PPVwBoXGox2t3CqquuOtoNdM455+RzzxCZGMcee2y17SrEuWZttmWtjz/+OJ/nRhtYlHmMrIg4F95jjz3ya8X5beHPf/5zk68TbWqcj0e7fvXVV+fxJsKPP/6Ydt555+pxQ1va5kGDBuV2vXZw70MPPbTa3sd6NScea1yyOo5XiufGazcnskOLsTQi8yWyVgq11xl22GGH6jaMKhHff/99/jvW5fbbb0+XX355zjQJsX1jXYHGXXwBupjR9bSo7a3ZuBfm0KFDq69T22tkv/32y/d9/PHHDea/4YYbqvM///zzTfYaDZ988knloIMOqiy44IKViSaaKPfGbLxMt9xyS573uuuua9DjpLbX6tlnn93gOYX999+/el/0/nzggQeq0/bbb199LHrFAsDYtvvuuzfZ9tVmFBQ9C996660m27patRkZG2ywQZPzvPDCC5Vtttkm9wTt3bt3k+9bm3FZ29syemPWimWbeuqpq4+fdtppDdra6aefvvrY7bffnp+z1lprVe9bZ511GrzeoosuOkpvz5YyMmrnX3/99du1DwDoXt57770Gbd4FF1ww2ucMGDCgOv+ee+7Z4LHatmi33Xar3l/7Hueee271/kGDBlXvX3zxxav3X3vttQ0qDhQan5vH+XXhiSeeaPDYU0891a62ubmsiVpNZWQ0ta5xvNJYc699zDHHVO8/8MAD832ffvpppVevXvm+qPjwzTff5PvPOuusBhkctet02GGHVR9bcsklR7s/oTvoKZoD1Ptg31FnsrGoTRm1qwtRi7NQZFoUg5UWokZlYf7558/jX0Svj1oxmGnMV9sbpimff/55tWdpYfbZZ0/TTjtt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UFoCGQAAAAAAQGkJZAAAAAAAAKUlkAEAAAAAAJSWQAYAAAAAAFBaAhkAAAAAAEBpCWQAAAAAAAClJZABAAAAAACUlkAGAAAAAABQWgIZAAAAAABAaQlkAAAAAAAApSWQAQB0ip9++intscceafLJJ09TTDFF2nPPPdPIkSObnHfiiSduME055ZQNHv/f//6X1ltvvXz/VFNNlTbZZJP08ccft/rxeO++ffumPn36pBlnnDHts88+6ccffxyDaw8A9d12N257Bw8e3ODxN954I62xxhr5taLtPfnkkxs8vtFGG6Xpp58+t82zzjprOu6446qPvfrqq2n99ddP0003XZpsssnSMssskx566KExtNYA0D3b7k022SR98sknrW67X3zxxbTyyivnx6ON3nnnndO3337bYJ6//e1vae65504TTTRRmmWWWdLNN9/c6nURyOgiBg4cmC+qAEC9iAsSDz74YD7YeeGFF9IDDzyQTjjhhCbn/frrrxtMceBTa/fdd8////e//01vvfVW+v7779Nee+3V6sd322239PLLL6cRI0akZ599Nk+ND8oAoLtrS9vduO394Ycfqo/9/PPPaZ111kmLLLJI+uijj9I999yTzj777HTllVdW5znqqKPS22+/ndvm//znP/mxv//97/mxL774Il9IGTZsWPr000/TtttumwYNGtTgYgsAkH5V2x3nzQceeGCr2+4tttgin6t/+OGHuY2O8+o//OEP1cf/+te/plNPPTUNGTIkn9c/9thjacEFF2z1bhLIKDm9QQGoVxdddFE6/PDDc2/LmA477LB04YUXjvZ5Q4cOzUGHWm+++WbuLRLZGpNMMknadNNN84FTax+fd955c4+QUKlU0jjjjJNee+21Dl1fAOhObXfjtneDDTaoPvbKK6/kKYIVvXr1yhc9dthhh3yBoxAXNnr37p1v9+jRo0HbvPjii+denlNPPXUad9xx00477ZT/f+6558b4NgCA7tJ2b7rppjkAEqINHl3bHc/faqut0njjjZfb6Ah8FOfdEQg58sgj05lnnpkGDBiQ2/Zpp502zTbbbK1eF4GM0bjuuuvyAdQEE0yQ02pWWWWV9M033+QeH5FqExGs2OiRznrsscfm1Jzf//73OVVnpplmShdffHGD1zvooIPSXHPNlSaccMK8o4444oic4lM4+uijU//+/XOaTaTPjj/++Pm9ogdK7OjYyTFFzxQA6Ko+//zz9N577+U2rxC333nnnfTll1+2+Nw46Prtb3/b4L799tsvXXvttfm50UvzqquuSmuvvXarHw8nnXRSPmCbZpppcs+RSLkFANrXdjdue+PcuvDLL79UOw/U3tc4EBEZk3HuPPPMM+eem3Fu3JS4SPLVV1+l+eabz+4CgA5qu+O8efXVV291233AAQekyy67LH333Xdp+PDh6cYbb6yed0cQJDI1nnrqqVxSKq6bR0eEyLxsLYGMFnzwwQdp8803T9tvv3166aWX0n333Zd7kRQ7LFJo3n///XT//fen0047LUek1lprrVwHLFJjdt1117TLLrvkD0wholmXXHJJjmZFYOKCCy5Ip59+eoP3ff3119P111+fbrjhhvTMM8/k+ZZaaqm8c2OZYoo63gDQVcXFiBAdAQrF7bgQ0ZzoTBBpqNtss02D+6M2dqS3FnU/44DtkEMOafXj4eCDD87LFW10tOFR0xMAaF/b3bjtjQsihejFGRcxomdmlJyKUhfRY7TxxYxzzz03v+/jjz+e2/54rcbidTfbbLN06KGHarsBoAPb7jhvjuBGmHPOOUfbdkfZxyhjFde/I/sjrl/HdfXw2Wef5f/vuuuu9MQTT+Rr3lG+at99902tJZDRgggYRIZFBC9iR0VmRvQIid6aIXbon//853wQFjsl/o8BTOIAKnZuXCCJVJrYgYVI5Vl66aXz60VEKiJV11xzzSjlpCJ6FWk2Cy20UJp00knz60RPlLioElOkzTYlPkjxAaqdAKBsira0thdIcTsOepoTvUOiPVxttdUa9AKJDI046CrG0Ijbq666aqsebyzKTC288MLN9vrsaNpuAOqt7W6q7V1iiSWqj0dJihjc8+mnn86DhW655ZZpu+22y1UQGouSUosuumh+jzh/rhXvH8cEyy67bK5uMLZouwHoDm33Msssk9Zff/1Wtd0R9IhKRtERP66PR+AiyjdHqanaZYnr5TGQeExx+9Zbb231+ghktCAuYsRI6xHA2HjjjXP2ROyUwvzzz58PqgpRYqp2gJIINsTOjEhW4eqrr84fgghGxA6MwEak89Tq169friPWHieeeGIOfBSTzA0Ayih6eEQqafTCKMTtaLei/WpOlF4cPHhw6tmzZ/W+OECKwchi8O4IcsQUZaEiOzIG/Rzd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d+/eDX6D4zezMGLEiLTFFlvk15922mnTH/7wB7sWugmBjEaWXnrp9MEHH6RJJ5201RvxzDPPTJdccklH7xugThx33HHpwQcfzAdsL7zwQnrggQfSCSec0OJzjjzyyNSvX79R7o/gxsknn5zWWWedZp/7yy+/5JPrZZZZpsnHH3744fT111/n6dlnn23HGgEA1PcxWXGsVEzzzjtv2myzzfJjcfFu+umnT3fddVe+oBbngvvvv3/697//XX1uBC8effTR3EHu2GOPTZtvvnl+30L8HRfwIuhx7bXXpt///vfpP//5z1jaEgBj7vdzt912Sy+//HL+fYzzzZjiHLYQQYi55547ffjhh2nYsGH58cbBiD/+8Y8NfoNnmGGGBoGSzz77LL3zzjt5OSIQfNlll9ml0A0IZDQSPZjjgDJ6LbdWBD0a95oGKEQPvcMPPzyf8MZ02GGHpQsvvLDZDfTkk0+m22+/PR100EGjPDZ48OC0xhpr5N4nzfnzn/+cT7ZXWGEFOwEAoJ3HZIWhQ4fmi3eRIR8iiyKCE7PPPns+b1xyySXTiiuumC/yhdlmmy0dcMABOSNjnHHGSWuvvXa+aBeBjfDGG2/keU888cT8WksssUTO4KjN6gDoqr+fcS4av22hUqnk38HXXnut+vibb76Zttpqq3z9beqpp86d9CKg0RqRuTFkyJAcWInrcHPNNVcObLTmtxzo+uo+kBEpafGjFqlskbYWaWcRrf3mm2/Sdtttl1OBo4fzbbfd1mRpqehdEz+Od9xxR/4xjpS21VdfPWdtNFfyqa3vGX7++ee0ww475PTkCSaYIB/oRqYH0LV9/vnn6b333kv9+/ev3he3o/fIl19+Ocr80cMvsinOOeecfGDXVv/973/zb8ef/vSnZueJMgZxwBjpvMUJNQBAPWvrMVmtuEAWHUlqewTX+v7773Owoyg91VhkXbz00kvVx5977rl8ITDOE2uXJe4HqIffz5NOOilfP5tmmmlyxkVcIytEoDcyKKKc8vDhw9ONN96YA761IlARJawiu6022+KVV17JZaoaL4vfT+ge6j6QES699NI01VRT5YPL+PH83e9+lzbeeONcRuqpp55Kq666atp6660b1OSrFfefcsop6fLLL0/3339//rGOH96OfM8oBRM9diKtOHr7RFmZQw89NF1zzTVtWteo1xrpe7UT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AAAAAEpLIAMAAAAAACgtgQwAAAAAAKC0BDIAAAAAAIDSEsgAAAAAAABKSyADAAAAAAAoLYEMAAAAAACgtHp29gIwdvz9739PE0wwgc0NjDXbbbedrQ2/grYb6Cq0+fB/tN1APdLOUxYyMgAAAAAAgNISyAAAAAAAAEpLIAMAAAAAACgtgQwAAAAAAKC0BDIAAAAAAIDSEsgAAAAAAABKSyADAAAAAAAoLYEMAAAAAACgtAQyAAAAAACA0hLIAAAAAAAA6ieQsdJKK6UvvvhilPtHjBiRHwMAAAAAAOi0QMZ9992Xfvzxx1Hu//7779MDDzzQUcsFAAAAAACQerZ2Gzz33HPV2y+++GIaPnx49e+ff/453X777WnGGWe0SQEAAAAAgLEfyOjfv3/q0aNHnpoqITXBBBOks846q+OWDAAAAAAA6PZaHch46623UqVSSbPNNlsaOnRomnrqqauPjTfeeGmaaaZJ4447brffoAAAAAAAQCcEMvr165f//+WXXzrw7QEAAAAAADogkFHrtddeS/fee2/66KOPRglsHHnkke15SQAAAAAAgF8fyLjgggvS7373uzTVVFOl6aabLo+ZUYjbAhkAAAAAAECnBTKOO+64dPzxx6eDDjqowxYCAAAAAACgKeOkNvr888/Txhtv3NanAQAAAAAAjPlARgQx/v3vf7f9nQAAAAAAAMZ0aak55pgjHXHEEenRRx9NCy64YOrVq1eDx/faa6+2viQAAAAAAEDHBDL++te/poknnjj95z//yVOtGOxbIAMAAAAAAOi00lJvvfVWs9Obb77ZYQvWlW277bZpvfXW6+zFACiVn376Ke2xxx5p8sknT1NMMUXac88908iRI5v9HR1vvPFy4LyYHnnkkerj8dy+ffumPn36pBlnnDHts88+6ccff6w+/uKLL6aVV145v9d0002Xdt555/Ttt99WH3/yySfTsssum58/22yzpcsuu2wMrz0AdB9tafPDLbfckvr3758mmmiiNMMMM6TzzjtvlHk+/PDD/FoxX633338/DRo0KD935plnThdccEH1sR9++CENHDgwTTPNNLnNn2eeeXLHPACgPO383/72tzT33HPnx2eZZZZ088035/sfeOCBBtcEYhpnnHEadKJ/8MEH05JLLpkmnXTSfG3gkEMOSb/88ovdW6faHMgoxAWjV155pcUPKgAUjjvuuHyQEUGGF154IR+UnHDCCc1uoN122y19/fXX1WmppZZq8NjLL7+cRowYkZ599tk8nXzyydXHt9hii3wgFBc9hg0blh//wx/+kB/74osv8gWPrbbaKn3++efpqquuygdesWwAwNht82+//fbcrp9xxhm5XY/5I/jQWFwwGTBgwCj3b7755rnTwkcffZSuvfba9Pvf/75aOaBnz57prLPOysGOeO0bbrghl0mO5QEAOr+djw4Gp556ahoyZEg+73/sscfyUAZhueWWa3BN4I033kjjjjtu2myzzfLjP//8c1p33XXz9Nlnn6WHHnoov05tpwa6eSAjerTusMMOacIJJ0zzzz9/euedd/L9cRHopJNOSl1Fbc9dAMa8iy66KB1++OFp+umnz9Nhhx2WLrzwwna91rzzzpt7a4RKpZJ7Zbz22mvVxyNDMAIVkdUx9dRTp3XWWScHNMLDDz+cevfunXbdddd8ELTEEkukDTbYIPcCAQDGbpsfgYUjjzwyX9SIdjl6d0bmRK3omRkXKLbeeusG98cFjbiQcuKJJ+bjgmjTt9xyy/z+IV4vLoZEQKMohRzT66+/bjcDQCe38xGIiMfOPPPM3Fkh2uhpp502V01oyqWXXprmnHPOtPTSS+e/v/zyy3x8MHjw4Pzakc2xyiqrVM/9qT9tDmREik70bL3vvvvS+OOPX70/PihXX311Kqv4wkQvnig/MtVUU6XVVlstnXbaafnANg56o0RJ0fu3cMkll6TJJpss3XHHHfmiWaQwrb766umDDz6ozhNfuv322y/PN+WUU6YDDzwwX1SrFSnNkfYUKc2xzaKcyeOPP159PLZlfFnjfeKLO8EEE6SVVlop9yq67bbb8ntHKnT0MK4tjQLQVUTmw3vvvdegHETcjmB4HHw0Jco9RZpqBM2jh0bj9NAInsfvcvy2RrsUAfXCAQcckJ//3XffpeHDh6cbb7wxrb322vmxeJ3Gv9Nx33PPPdfBaw0A3U9b2vxvvvkml3v83//+l+aaa66cWbHxxhs3ON+K58T5VlPlpqLtjgsocdGj9r0at+lrrbVWPg+bb7758rzrr79+B681AHQPHdnOR6WfqKLw1FNP5SDETDPNlHbaaaecudFcACU61xfiesH222+fgyhR7io6ONx1111pzTXXHGPrTxcLZNx0003p7LPPzhfj4+J7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AAAAAAClJZABAAAAAACUlkAGAAAAAABQWgIZAAAAAABAaQlkAAAAAAAApSWQ0V1cNGlnLwEA0Na2+/wethkAAADdnkAGAAAAAABQWgIZAAAAAABAaQlkAAAAAAAApSWQAQAAAAAAlJZABgAAAAAAUFoCGQAAAAAAQGkJZAAAAAAAAKUlkAEAAAAAAJSWQAYAAAAAAFBaAhkAAAAAAEBpCWQAAAAAAAClJZABAAAAAACUlkAGAAAAAABQWgIZAAAAAABAaQlkAAAAAAAApSWQAQAAAAAAlJZABgAAAAAAUFoCGQAAAAAAQGkJZAAAAAAAAKUlkAEAAAAAAJSWQAYAAAAAAFBaAhkAAAAAAEBpCWQAAAAAAAClJZDRzf30009pjz32SJNPPnmaYoop0p577plGjhzZrnlb+1rfffddmmOOOdJkk01Wve+jjz5KW265ZZpppplSnz590oABA9Itt9wyhtYaALq2sdl+TzzxxA2mXr16pYUWWqj6+LbbbpvGG2+8BvM88sgjY3gLAEC5jc22uqPO1QGgzAQyurnjjjsuPfjgg+nFF19ML7zwQnrggQfSCSec0K55W/taRx55ZOrXr1+D+77++uscvHj00UfTF198kY499ti0+eab59cCADqv/Y42unaad95502abbdbgPXbbbbcG8yy11FJ2GQDd2thsqzvqXB0ASq1CXfvyyy8rsZu/PL3pXT3TTDNVrr322urf11xzTWXmmWdu17ytea0nnniissACC1TuuOOOyqSTTtrisg8YMKBy4YUXtmItAei27duXX1bquu0+rxztd+Gxxx6rjDvuuJX//e9/1fsGDx5c2Xvvvdu4lgB0N92i7a5Zt7HZVndkWw8AZW2/ZWS0wXXXXZcWXHDBNMEEE6Qpp5wyrbLKKumbb77JJRXWW2+9dMwxx6Spp546l0badddd048//lh97u23356WXXbZXE4pnrvWWmulN954o/r422+/nXr06JGuueaatNxyy+X3WGyxxdKrr76aHn/88bTooovmUg1rrLFG+vjjjzskiPX555+n9957L/Xv3796X9x+55130pdfftmmeVvzWpG6utNOO6Vzzjknl6BoSZSaeumllxqUrgAAxn77XevCCy/MxyIzzDBDg/svu+yyXKpi/vnnT6eeemr65Zdf7CoAuq2x2VZ3dFsPAGUlkNFKH3zwQS51tP322+cL7Pfdd1/aYIMNoptkfvzuu++u3n/VVVelG264IQc2ChHw2G+//dITTzyR5x1nnHHS+uuvP8qJ/lFHHZUOP/zw9NRTT6WePXumLbbYIh144IHpzDPPzOmfr7/+ei7N1JwffvghjRgxosHUnCj9EGrHqihuf/XVV22atzWv9ac//SmXj1p++eVb2NIpB4CiZMUmm2ySAzgAUM/a0nZ3RvtdeywzZMiQtOOOOza4f6+99kqvvPJK7mgRgY44ZokJALpr2z022+qObOsBoMwEMtoQyIiMgghezDLLLDkzI+pBR5ZEiAyDiy66KPdEXHPNNfMYD3/+85+rgYoNN9wwPzcGuY7eDzHvsGHDRhkD4oADDkirrbZarj+99957pyeffDIdccQRaZlllslBgB122CHde++9zS7niSeemCaddNLq1Ldv32bnLZa9thdGcXuSSSZp07yjezwCMOedd14OZowuiLHRRhulCSecMF1wwQUtzgsA9aAtbffYbr9rXXvttbl9juOcWossskjOSB133HHTkksumQ4++OB09dVXt2kbAEA9td1js63uyLYeAMpMIKOVFl544bTyyivnAMbGG2+cL7JHimbt43FyX4hBLqPnw7vvvpv/fu2113JGx2yzzZZLT0UwJEQ6Z63aUkrTTjtt/j/es/a+KLvUnEMOOSQflBRT8f5NmXzyydNMM82Unnnmmep9cTsOwuJgrC3zju7xGFjsww8/THPNNVeaaqqp0rrrrpt7rcTtxx57rBrEiG0b/19//fWjLT8FAPWgLW332G6/a/3tb39LgwcPzhmjLYmsUwDozm332GyrO7KtB4Ayc6bZStHL8M4770y33XZbmm+++dJZZ52V5p577vTWW2+16vlrr712+uyzz3IAJC7c1168r9WrV6/q7Rgzo6n7Wqo73bt37xwoqZ1ast1226Xjjz8+DR8+PE8nnHDCKCUjWjtvS49HmajIyogDppjiYkj0/ojbkWny008/5XmibMVNN92U1wMAuoO2tt1js/0uROmohx9+OGeGNhbje0XnhCi3GSU0TzrppJyJCgDdue0em211R7T1AFB2LXepo4EIIkSJp5hinIp+/fqlG2+8MT/27LPPpu+++y4P0h0effTRnMIZvRw+/fTTfAEgghgxkHeIDIUyiLJVsXxRyipstdVW6dBDD823Y8DyECWhRjfv6B6PbJXajJUoQRHbM3qGhP/85z/p5ptvTuOPP37O0ijE82vfAwAYe+13Ica+iGOYOeecc5TNf/bZZ6edd945l+CcccYZc+nN/fff324CoFsbm211R7T1AFB2PSrFaNW0KDIoYpDuVVddNU0zzTT572j8I3sg6kBHKaTIuoiBut9+++08KHj0eojamZFBEc9ZY4018mDeUU4q6kc//vjjORCy3nrr5efMOuus6emnn85jaIQYOHzFFVfMJayKwbguueSStM8++6QvvviiVXssekhGuuiXp6fUZx+7GoD6UG3fvvyyVRkMXUmDtjv6R+yi/Qag6+sWbXcdrhsA3duIErVxMjJaKXbU/fffn84444y8AyMb49RTT83BiQhkxPgZ0Utx+eWXTz/88EMeD+Poo4+u1ooeMmRI2muvvdICCyyQS1LFQOADBw4ck/sWAAAAAAC6PBkZHWDbbbfNGRKRnVE2MjIAqEdl6hXS0WRkAFCPukXbXYfrBkD3NqJEbZzBvgEAAAAAgNISyAAAAAAAAErLGBkdIAbgBgAAAAAAOp6MDAAAAAAAoLQEMgAAAAAAgNISyAAAAAAAAEpLIAMAAAAAACgtgQwAAAAAAKC0BDIAAAAAAIDSEsgAAAAAAABKSyADAAAAAAAoLYEMAAAAAACgtAQyAAAAAACA0hLIAAAAAAAASksgAwAAAAAAKC2BDAAAAAAAoLQEMgAAAAAAgNISyAAAAAAAAEpLIAMAAAAAACgtgQwAAAAAAKC0BDIAAAAAAIDSEsgAAAAAAABKSyADAAAAAAAorZ6dvQCMJdt/aVMDQFdru/v06eylAAAAgE4nIwMAAAAAACgtgQwAAAAAAKC0BDIAAAAAAIDSEsgAAAAAAABKSyADAAAAAAAoLYEMAAAAAACgtAQyAAAAAACA0hLIAAAAAAAASksgAwAAAAAAKC2BDAAAAAAAoLQEMgAAAAAAgNISyAAAAAAAAEpLIAMAAAAAACitnp29AAAAjOqDVVZMX/cc16b5FWZ4eKjtB0CXaLu1WQDQMhkZAAAAAABAaQlkAAAAAAAApSWQAQAAAAAAlJZABgAAAAAAUFoCGQAAAAAAQGkJZAAAAAAAAKUlkAEAAAAAAJSWQAYAAAAAAFBaAhkAAAAAAEBpCWQAAAAAAAClJZABAAAAAACUlkAGAAAAAABQWgIZAAAAAABAaQlkAAAAAAAApSWQAQAAAAAAlJZABgAAAAAAUFoCGQAAAAAAQGkJZAAAAAAAAKUlkAEAAAAAAJSWQAYAAAAAAFBaAhkAAAAAAEBpCWQAAAAAAAClJZABAAAAAACUlkAGAAAAAABQWqUNZLz99tupR48e6ZlnnunsRak7P/30U9pjjz3S5JNPnqaYYoq05557ppEjR7Zr3jH9OABAR2nLccfZZ5+dFl100dS7d++03nrrNXjsnXfeSRNPPHGDqWfPnmmdddapzjNw4MD83Np53n///erjI0aMSFtssUXq06dPmnbaadMf/vAHOxqAdrdbo5s3/u7bt29ud2accca0zz77pB9//FG7BUCXUdpABg3FyXAcaHSE4447Lj344IPpxRdfTC+88EJ64IEH0gknnNCuecf04wAAHaUtxx0zzDBDOvzww9NOO+00ymMzzzxz+vrrr6vTZ599liabbLK02WabNZjvj3/8Y4P54jVrLyjF8yIoEstxwQUXpMsuu8zOBqBd7dbo5t1tt93Syy+/nAPpzz77bJ5OPvlk7RYAXYZAxlhQ28uhDC666KJ8Yj799NPn6bDDDksXXnhhu+Yd048DAHSUthx3bLDBBjkTY6qpphrt6950003pl19+yc9pjW+//TYNGTIkX3SKAMhcc82VAxuOgQBob7s1unnnnXfeNNFEE+XblUoljTPOOOm1117TbgHQZXRqIOP2229Pyy67bD6Bm3LKKdNaa62V3njjjSbnjdT+U045pfp3nFj26tUr924L7733Xi5F9frrr+e/L7/88vycSSaZJE033XQ5df+jjz6qNtpzzDFHg9cLUcaqeI2Y5+ijj8497qIsQPSg22uvvVq1XrPMMksuD7DNNtvktM2dd9453x+9I5Zbbrk0wQQT5JTOeL1vvvmm+rxzzz03zTnnnGn88cfPJQY22mijfP+2226b/vOf/6QzzzwzL19MUXqrPT7//PO8rfr371+9L25Hb8Avv/yyTfOO6ccBADrKmDzuiAtFW265ZT6GqxWBiijvMWDAgAbZFq+88kru6NJ4WZ577rlftRwA1I+OPHcvnHTSSbnU4TTTTJMzMiKIXku7BUCZdWogIy7i77fffumJJ55Id999d+4RsP766+cebY2tsMIK6b777su3I8gQaZIRAIngQIgL/VHnMQIURX3ICCZE4xy95OLCfwQEQgQCtt9++3TxxRc3eI/4e/nll8+vcf3116fTTz89nX/++bmXQrzGggsu2Op1iyDJwgsvnJ5++ul0xBFH5ADN6quvnjbccMN8knr11VfnZY8aliG2QQQ2jj322HxyG0GeWJYQAYyllloqlzb44IMP8hSBkKb88MMPOVW0dqpVBH5i2xWK21999VWb5h3TjwNAdzC6tpuOMaaOO/773/+mu+66K+24444N7j/xxBPz8d+HH36YLxzFxaIbb7yxuizRKzbG1ahdFsc/AF3D2Gi7O/LcvXDwwQfneaP81K677po7fRa0WwCUXacGMuKifqTgR+AgegtEKuSwYcNyo9rUGBFx4f/nn3/OgYDxxhsv93wrghvxfwQ7ChGoWGONNdJss82WllxyyfTnP/853XbbbdUGPoIaETAYOnRoNfBx5ZVX5ueF6LkQjfoqq6ySszIWX3zxJmskN2ellVZK+++/f5p99tnzFAcFsbwxzkVkXSy99NJ5maJ33vfff5/fL05oIyulX79+uedekQEy6aST5vWdcMIJ8zLFNO644zb5vvE+MX8xNQ54RO+LUNsro7gd2SttmXdMPw4A3cHo2m46xpg67oiOMHHcFh1YakUnlNifkUG82mqrpV122SV3ZCmWJcpL1Q7CGsvi+AegaxgbbXdHnrs3FmWmot0qOnsG7RYAZdepgYzIdNh8881zsCFKMEVJphAX9RuLkkzRkyAyHCL7IoIWEdwoAhlxX/xdePLJJ9Paa6+dgxDRcBdBjuK1o1TUmmuumYMn4dZbb829KjbeeOP8d/z/3Xff5WWLAEb0oKs92RydKGtVKzJDLrnkknyAUUxxUhvZJ2+99Vb67W9/mwMY8X5bb711uuKKK/IJblsdcsgh+YClmN59990Gj08++eRppplmymW0CnE7DrziAKwt847pxwGgOxhd203HGBPHHXEcF4GMxtkYTYnM48Lcc8+dAxxxfFi7LG3J/gWgvtvujjx3b0p05mxpjAztFgBl06mBjAg0fPbZZ+mCCy5Ijz32WJ6aGxw70iKjx0AELoqgRZReisDGq6++mhvgIlgRJasiSBDBkQgIPP7449VU/trXjpPOGGgxAhZxErrpppvmrIcQDX5kbMS4FTGmxW677ZbfLxr71igG0SpEJkj0xIuDiWKKk9dY7sjYiGDLU089la666qo8MNeR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3HJp8sknz9Mqq6wy2v1a9nW64YYb0qKLLpomm2yyNNFEE6X+/funyy+/PNXDd6owZMiQ/Blcb731Uldep0suuSSvR+0Uz+vq++mLL75Iu+++e5p++ulT796901xzzVW637+2rNPAgQNH2U8xrbnmmqkr76f4DZ977rnTBBNMkPr27Zv23Xff9P3336d6U49td1dQj21xV1CP7WpXUI/tZFeg3atf2u5yb3Ptduds91ra7bG3zbXZHUOb3QVV6NKGDBlSGW+88SoXXXRR5YUXXqjstNNOlckmm6zy4YcfNjn/Qw89VBl33HErJ598cuXFF1+sHH744ZVevXpVhg0bVumq6zR06NDKAQccULnqqqsq0003XeX000+vlE1b12mLLbaonHPOOZWnn3668tJLL1W23XbbyqSTTlp57733Kl11ne69997KDTfckD93r7/+euWMM87In8Xbb7+9UiZtXa/CW2+9VZlxxhkryy23XGXdddetdOV1uvjiiyt9+vSpfPDBB9Vp+PDhla68Tj/88ENl0UUXrQwaNKjy4IMP5v113333VZ555plKV12nTz/9tME+ev755/N3KvZfV12nK664otK7d+/8f+yjO+64ozL99NNX9t1330o9qce2uyuox7a4K6jHdrUrqMd2sivQ7tUvbXf5t7l2u3O2e0G7Pfa2uTa7Y2izuyaBjC5u8cUXr+y+++7Vv3/++efKDDPMUDnxxBObnH+TTTaprLnmmg3uW2KJJSq77LJLpauuU61+/fqVMpDxa9YpjBw5sjLJJJNULr300kq9rFMYMGBAviBXJu1Zr9g/Sy+9dOVvf/tbZfDgwaW74NLWdYoL4XGxrszauk5/+ctfKrPNNlvlxx9/rJTVr/1OxW9f/E58/fXXla66TjHvSiut1OC+/fbbr7LMMstU6kk9tt1dQT22xV1BPbarXUE9tpNdgXavfmm7y7/NG9Nuj73trt3+dbTZnUOb3TUpLdWF/fjjj+nJJ5/MpQ4K44wzTv77kUceafI5cX/t/GG11VZrdv6usE5l1xHr9O2336affvopTTHFFKke1imCqHfffXd65ZVX0vLLL5/Kor3rdeyxx6Zpppkm7bDDDqls2rtOX3/9derXr18u7bPuuuvmEm5deZ1uueWWtNRSS+WSGdNOO21aYIEF0gknnJB+/vnnVC+/ExdeeGHabLPNcum2rrpOSy+9dH5OkUr95ptv5rImgwYNSvWiHtvurqAe2+KuoB7b1a6gHtvJrkC7V7+03V1jmzem3R5721273X7a7M6hze66enb2AtB+n3zyST65iJONWvH3yy+/3ORzhg8f3uT8cX9XXaey64h1Ouigg9IMM8wwyoWsrrZOX375ZZpxxhnTDz/8kMYdd9x07rnnpt/+9repLNqzXg8++GC+gPzMM8+kMmrPOsX4BBdddFFaaKGF8j475ZRT8gXmCGbMNNNMqSuuU1wQv+eee9KWW26ZL4y//vrrabfddssXJY866qjU1X8n4sL/888/nz+LZdGeddpiiy3y85Zddtkc8Bw5cmTadddd06GHHprqRT223V1BPbbFXUE9tqtdQT22k12Bdq9+abu7xjZvTLs9dra7dvvX0WZ3Dm121yUjA0rupJNOyoNm3XjjjaUccLktJplkknxh4vHHH0/HH3982m+//dJ9992Xuqqvvvoqbb311nlguammmirVi+iRuc022+QB2VdYYYU8UPvUU0+dzj///NRV/fLLL7l371//+tf0m9/8Jm266abpsMMOS+edd16qB3HRLwaAXnzxxVNXFr8H0QM4gpxPPfVU/uz985//TH/4wx86e9Ho5uqpLS6zem1Xu4J6byfLSrsHY4Z2e+zQbncObXbn0GaXg4yMLixO8KJX+4cfftjg/vh7uumma/I5cX9b5u8K61R2v2adoid8HITddddduXd8V1+nSEudY4458u24SP7SSy+lE088MQ0cODB1xfV644030ttvv53WXnvtBgcVoWfPnrl01uyzz566+neqV69eacCAAbl3Zhm0Z52mn376vB7xvMK8886be7RHWul4442Xuup++uabb/IF1kjpLpP2rNMRRxyRL2LuuOOO+e8IzsT67bzzzvmCWvyGdHX12HZ3BfXYFncF9diudgX12E52Bdq9+qXt7hrbvKDdHnvbXbv962mzO4c2u+vq+lcEurE4oYgeUzHWQO3JXvwdPaqbEvfXzh/uvPPOZufvCutUdu1dp5NPPjn3Qr799tvToosumsqko/ZTPCfKTHXV9ZpnnnnSsGHDcpZJMa2zzjppxRVXzLdjfIl62FeRXhzrGRc5yqA967TMMsvkQExxQSy8+uqreZ3KcHHm1+yna6+9Nn+Pttpqq1Qm7VmnqGXcOFhRXFSLUlP1oB7b7q6gHtvirqAe29WuoB7bya5Au1e/tN1dY5sH7fbY3e7a7V9Pm905tNldWGePNs6vM2TIkErv3r0rl1xySeXFF1+s7LzzzpXJJpusMnz48Pz41ltvXTn44IOr8z/00EOVnj17Vk455ZTKSy+9VDnqqKMqvXr1qgwbNqzLrtMPP/xQefrpp/M0/fTTVw444IB8+7XXXqt01XU66aSTKuONN17luuuuq3zwwQfV6auvvqp01XU64YQTKv/+978rb7zxRp4/PoPxWbzgggsqZdLW9Wps8ODBlXXXXbfSldfpmGOOqdxxxx15Xz355JOVzTbbrDL++ONXXnjhhUpXXad33nmnMskkk1T22GOPyiuvvFL5xz/+UZlmmmkqxx13XKWrf/aWXXbZyqabblopo7auU7RJsZ+uuuqqyptvvpl/M2afffbKJptsUqkn9dh2dwX12BZ3BfXYrnYF9dhOdgXavfql7S7/Ntdud852b0y7Pea3uTa7Y2izuyaBjDpw1llnVWaeeeZ8sr344otXHn300epjK6ywQm5Ial1zzTWVueaaK88///zzV/75z39WuvI6vfXWW9FNd5Qp5uuq69SvX78m1ykuXnXVdTrssMMqc8wxR74gPvnkk1eWWmqp3HDUw3eqKxy4tWWd9tlnn+q80047bWXQoEGVp556qtLV99PDDz9cWWKJJfJB4myzzVY5/vjjKyNHjqx05XV6+eWX829DXPAvq7as008//VQ5+uijc/Aifiv69u1b2W233Sqff/55pd7UY9vdFdRjW9wV1GO72hXUYzvZFWj36pe2u9zbXLvdOdu9Me322Nnm2uyOoc3uenrEP52dFQIAAAAAANAUY2QAAAAAAAClJZABAAAAAACUlkAGAAAAAABQWgIZAAAAAABAaQlkAAAAAAAApSWQAQAAAAAAlJZABgAAAAAAUFoCGQAAAAAAQGkJZAAAAAAAAKUlkAEAAAAAAJSWQAYAAAAAAFBaAhkAAAAAAEAqq/8P64QIdBSqlmgAAAAASUVORK5CYII=",
|
| 414 |
-
"text/plain": [
|
| 415 |
-
"<Figure size 1600x500 with 3 Axes>"
|
| 416 |
-
]
|
| 417 |
-
},
|
| 418 |
-
"metadata": {},
|
| 419 |
-
"output_type": "display_data"
|
| 420 |
-
}
|
| 421 |
-
],
|
| 422 |
"source": [
|
| 423 |
"# Cell 6: Baseline plots\n",
|
| 424 |
"fig, axes = plt.subplots(1, 3, figsize=(16, 5), sharey=True)\n",
|
|
@@ -449,41 +347,9 @@
|
|
| 449 |
},
|
| 450 |
{
|
| 451 |
"cell_type": "code",
|
| 452 |
-
"execution_count":
|
| 453 |
"metadata": {},
|
| 454 |
-
"outputs": [
|
| 455 |
-
{
|
| 456 |
-
"name": "stdout",
|
| 457 |
-
"output_type": "stream",
|
| 458 |
-
"text": [
|
| 459 |
-
"Loading Qwen/Qwen2.5-1.5B-Instruct without 4-bit (bitsandbytes/CUDA unavailable).\n",
|
| 460 |
-
" On Colab: run `pip install -U bitsandbytes>=0.46.1` and use a GPU runtime.\n",
|
| 461 |
-
" On Mac: use fp16 on MPS or fp32 on CPU.\n"
|
| 462 |
-
]
|
| 463 |
-
},
|
| 464 |
-
{
|
| 465 |
-
"ename": "KeyboardInterrupt",
|
| 466 |
-
"evalue": "",
|
| 467 |
-
"output_type": "error",
|
| 468 |
-
"traceback": [
|
| 469 |
-
"\u001b[31m---------------------------------------------------------------------------\u001b[39m",
|
| 470 |
-
"\u001b[31mKeyboardInterrupt\u001b[39m Traceback (most recent call last)",
|
| 471 |
-
"\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[7]\u001b[39m\u001b[32m, line 44\u001b[39m\n\u001b[32m 40\u001b[39m \u001b[33m\" On Colab: run `pip install -U bitsandbytes>=0.46.1` and use a GPU runtime.\\n\"\u001b[39m\n\u001b[32m 41\u001b[39m \u001b[33m\" On Mac: use fp16 on MPS or fp32 on CPU.\"\u001b[39m\n\u001b[32m 42\u001b[39m )\n\u001b[32m 43\u001b[39m dtype = torch.float16 \u001b[38;5;28;01mif\u001b[39;00m (torch.cuda.is_available() \u001b[38;5;28;01mor\u001b[39;00m getattr(torch.backends, \u001b[33m\"mps\"\u001b[39m, \u001b[38;5;28;01mNone\u001b[39;00m) \u001b[38;5;28;01mand\u001b[39;00m torch.backends.mps.is_available()) \u001b[38;5;28;01melse\u001b[39;00m torch.float32\n\u001b[32m---> \u001b[39m\u001b[32m44\u001b[39m model = AutoModelForCausalLM.from_pretrained(\n\u001b[32m 45\u001b[39m MODEL_NAME,\n\u001b[32m 46\u001b[39m trust_remote_code=\u001b[38;5;28;01mTrue\u001b[39;00m,\n\u001b[32m 47\u001b[39m dtype=dtype,\n",
|
| 472 |
-
"\u001b[36mFile \u001b[39m\u001b[32m~/viral-posts-env/.venv/lib/python3.14/site-packages/transformers/models/auto/auto_factory.py:394\u001b[39m, in \u001b[36m_BaseAutoModelClass.from_pretrained\u001b[39m\u001b[34m(cls, pretrained_model_name_or_path, *model_args, **kwargs)\u001b[39m\n\u001b[32m 392\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mhasattr\u001b[39m(parent_config, \u001b[33m\"\u001b[39m\u001b[33mquantization_config\u001b[39m\u001b[33m\"\u001b[39m):\n\u001b[32m 393\u001b[39m config.quantization_config = parent_config.quantization_config\n\u001b[32m--> \u001b[39m\u001b[32m394\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[30;43mmodel_class\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mfrom_pretrained\u001b[39;49m\u001b[30;43m(\u001b[39;49m\n\u001b[32m 395\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mpretrained_model_name_or_path\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43mmodel_args\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mconfig\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mconfig\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43mhub_kwargs\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43mkwargs\u001b[39;49m\n\u001b[32m 396\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 397\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\n\u001b[32m 398\u001b[39m \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mUnrecognized configuration class \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mconfig.\u001b[34m__class__\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m for this kind of AutoModel: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mcls\u001b[39m.\u001b[34m__name__\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m.\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[33m\"\u001b[39m\n\u001b[32m 399\u001b[39m \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mModel type should be one of \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[33m'\u001b[39m\u001b[33m, \u001b[39m\u001b[33m'\u001b[39m.join(c.\u001b[34m__name__\u001b[39m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mfor\u001b[39;00m\u001b[38;5;250m \u001b[39mc\u001b[38;5;250m \u001b[39m\u001b[38;5;129;01min\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28mcls\u001b[39m._model_mapping)\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m.\u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 400\u001b[39m )\n",
|
| 473 |
-
"\u001b[36mFile \u001b[39m\u001b[32m~/viral-posts-env/.venv/lib/python3.14/site-packages/transformers/modeling_utils.py:4118\u001b[39m, in \u001b[36mPreTrainedModel.from_pretrained\u001b[39m\u001b[34m(cls, pretrained_model_name_or_path, config, cache_dir, ignore_mismatched_sizes, force_download, local_files_only, token, revision, use_safetensors, weights_only, fusion_config, disable_mmap, *model_args, **kwargs)\u001b[39m\n\u001b[32m 4113\u001b[39m logger.warning_once(\n\u001b[32m 4114\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mA kernel_config was provided but use_kernels is False; setting use_kernels=True automatically. To suppress this warning, explicitly set use_kernels to True.\u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 4115\u001b[39m )\n\u001b[32m 4116\u001b[39m use_kernels = \u001b[38;5;28;01mTrue\u001b[39;00m\n\u001b[32m-> \u001b[39m\u001b[32m4118\u001b[39m checkpoint_files, sharded_metadata = \u001b[30;43m_get_resolved_checkpoint_files\u001b[39;49m\u001b[30;43m(\u001b[39;49m\n\u001b[32m 4119\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mpretrained_model_name_or_path\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mpretrained_model_name_or_path\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 4120\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mvariant\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mvariant\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 4121\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mgguf_file\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mgguf_file\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 4122\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43muse_safetensors\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43muse_safetensors\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 4123\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mdownload_kwargs\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mdownload_kwargs_with_commit\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 4124\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43muser_agent\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43muser_agent\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 4125\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mis_remote_code\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mcls\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mis_remote_code\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43m)\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 4126\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mtransformers_explicit_filename\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mgetattr\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43mconfig\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43mtransformers_weights\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43;01mNone\u001b[39;49;00m\u001b[30;43m)\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 4127\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mtqdm_class\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mtqdm_class\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 4128\u001b[39m \u001b[30;43m\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 4130\u001b[39m is_quantized = hf_quantizer \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m 4132\u001b[39m \u001b[38;5;66;03m# Find the correct dtype based on current state\u001b[39;00m\n",
|
| 474 |
-
"\u001b[36mFile \u001b[39m\u001b[32m~/viral-posts-env/.venv/lib/python3.14/site-packages/transformers/modeling_utils.py:660\u001b[39m, in \u001b[36m_get_resolved_checkpoint_files\u001b[39m\u001b[34m(pretrained_model_name_or_path, variant, gguf_file, use_safetensors, user_agent, is_remote_code, transformers_explicit_filename, download_kwargs, tqdm_class)\u001b[39m\n\u001b[32m 648\u001b[39m can_auto_convert = (\n\u001b[32m 649\u001b[39m \u001b[38;5;129;01mnot\u001b[39;00m is_offline_mode() \u001b[38;5;66;03m# for obvious reasons\u001b[39;00m\n\u001b[32m 650\u001b[39m \u001b[38;5;66;03m# If we are in a CI environment or in a pytest run, we prevent the conversion\u001b[39;00m\n\u001b[32m (...)\u001b[39m\u001b[32m 653\u001b[39m \u001b[38;5;129;01mand\u001b[39;00m subfolder == \u001b[33m\"\u001b[39m\u001b[33m\"\u001b[39m \u001b[38;5;66;03m# converter bot does not work on subfolders\u001b[39;00m\n\u001b[32m 654\u001b[39m )\n\u001b[32m 656\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m 657\u001b[39m \u001b[38;5;66;03m# Load from URL or cache if already cached\u001b[39;00m\n\u001b[32m 658\u001b[39m \u001b[38;5;66;03m# Since we set _raise_exceptions_for_missing_entries=False, we don't get an exception but a None\u001b[39;00m\n\u001b[32m 659\u001b[39m \u001b[38;5;66;03m# result when internet is up, the repo and revision exist, but the file does not.\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m660\u001b[39m resolved_archive_file = \u001b[30;43mcached_file\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43mpretrained_model_name_or_path\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mfilename\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43mcached_file_kwargs\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 662\u001b[39m \u001b[38;5;66;03m# Try safetensors files first if not already found\u001b[39;00m\n\u001b[32m 663\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m resolved_archive_file \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m filename == _add_variant(SAFE_WEIGHTS_NAME, variant):\n\u001b[32m 664\u001b[39m \u001b[38;5;66;03m# Maybe the checkpoint is sharded, we try to grab the index name in this case.\u001b[39;00m\n",
|
| 475 |
-
"\u001b[36mFile \u001b[39m\u001b[32m~/viral-posts-env/.venv/lib/python3.14/site-packages/transformers/utils/hub.py:278\u001b[39m, in \u001b[36mcached_file\u001b[39m\u001b[34m(path_or_repo_id, filename, **kwargs)\u001b[39m\n\u001b[32m 223\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mcached_file\u001b[39m(\n\u001b[32m 224\u001b[39m path_or_repo_id: \u001b[38;5;28mstr\u001b[39m | os.PathLike,\n\u001b[32m 225\u001b[39m filename: \u001b[38;5;28mstr\u001b[39m,\n\u001b[32m 226\u001b[39m **kwargs,\n\u001b[32m 227\u001b[39m ) -> \u001b[38;5;28mstr\u001b[39m | \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m 228\u001b[39m \u001b[38;5;250m \u001b[39m\u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m 229\u001b[39m \u001b[33;03m Tries to locate a file in a local folder and repo, downloads and cache it if necessary.\u001b[39;00m\n\u001b[32m 230\u001b[39m \n\u001b[32m (...)\u001b[39m\u001b[32m 276\u001b[39m \u001b[33;03m ```\u001b[39;00m\n\u001b[32m 277\u001b[39m \u001b[33;03m \"\"\"\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m278\u001b[39m file = \u001b[30;43mcached_files\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43mpath_or_repo_id\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mpath_or_repo_id\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mfilenames\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43m[\u001b[39;49m\u001b[30;43mfilename\u001b[39;49m\u001b[30;43m]\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43mkwargs\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 279\u001b[39m file = file[\u001b[32m0\u001b[39m] \u001b[38;5;28;01mif\u001b[39;00m file \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;28;01melse\u001b[39;00m file\n\u001b[32m 280\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m file\n",
|
| 476 |
-
"\u001b[36mFile \u001b[39m\u001b[32m~/viral-posts-env/.venv/lib/python3.14/site-packages/transformers/utils/hub.py:422\u001b[39m, in \u001b[36mcached_files\u001b[39m\u001b[34m(path_or_repo_id, filenames, cache_dir, force_download, proxies, token, revision, local_files_only, subfolder, repo_type, user_agent, _raise_exceptions_for_gated_repo, _raise_exceptions_for_missing_entries, _raise_exceptions_for_connection_errors, _commit_hash, tqdm_class, **deprecated_kwargs)\u001b[39m\n\u001b[32m 419\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m 420\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mlen\u001b[39m(full_filenames) == \u001b[32m1\u001b[39m:\n\u001b[32m 421\u001b[39m \u001b[38;5;66;03m# This is slightly better for only 1 file\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m422\u001b[39m \u001b[30;43mhf_hub_download\u001b[39;49m\u001b[30;43m(\u001b[39;49m\n\u001b[32m 423\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mpath_or_repo_id\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 424\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mfilenames\u001b[39;49m\u001b[30;43m[\u001b[39;49m\u001b[30;43m0\u001b[39;49m\u001b[30;43m]\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 425\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43msubfolder\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43;01mNone\u001b[39;49;00m\u001b[30;43m \u001b[39;49m\u001b[30;43;01mif\u001b[39;49;00m\u001b[30;43m \u001b[39;49m\u001b[30;43mlen\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43msubfolder\u001b[39;49m\u001b[30;43m)\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43m==\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43m0\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43;01melse\u001b[39;49;00m\u001b[30;43m \u001b[39;49m\u001b[30;43msubfolder\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 426\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mrepo_type\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mrepo_type\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 427\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mrevision\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mrevision\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 428\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mcache_dir\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mcache_dir\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 429\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43muser_agent\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43muser_agent\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 430\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mforce_download\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mforce_download\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 431\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mproxies\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mproxies\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 432\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mtoken\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mtoken\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 433\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mlocal_files_only\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mlocal_files_only\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 434\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mtqdm_class\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mtqdm_class\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 435\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 436\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 437\u001b[39m snapshot_download(\n\u001b[32m 438\u001b[39m path_or_repo_id,\n\u001b[32m 439\u001b[39m allow_patterns=full_filenames,\n\u001b[32m (...)\u001b[39m\u001b[32m 448\u001b[39m tqdm_class=tqdm_class,\n\u001b[32m 449\u001b[39m )\n",
|
| 477 |
-
"\u001b[36mFile \u001b[39m\u001b[32m~/viral-posts-env/.venv/lib/python3.14/site-packages/huggingface_hub/utils/_validators.py:88\u001b[39m, in \u001b[36mvalidate_hf_hub_args.<locals>._inner_fn\u001b[39m\u001b[34m(*args, **kwargs)\u001b[39m\n\u001b[32m 84\u001b[39m validate_repo_id(arg_value)\n\u001b[32m 86\u001b[39m kwargs = smoothly_deprecate_legacy_arguments(fn_name=fn.\u001b[34m__name__\u001b[39m, kwargs=kwargs)\n\u001b[32m---> \u001b[39m\u001b[32m88\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[30;43mfn\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43margs\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43m*\u001b[39;49m\u001b[30;43mkwargs\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n",
|
| 478 |
-
"\u001b[36mFile \u001b[39m\u001b[32m~/viral-posts-env/.venv/lib/python3.14/site-packages/huggingface_hub/file_download.py:995\u001b[39m, in \u001b[36mhf_hub_download\u001b[39m\u001b[34m(repo_id, filename, subfolder, repo_type, revision, library_name, library_version, cache_dir, local_dir, user_agent, force_download, etag_timeout, token, local_files_only, headers, endpoint, tqdm_class, dry_run)\u001b[39m\n\u001b[32m 974\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m _hf_hub_download_to_local_dir(\n\u001b[32m 975\u001b[39m \u001b[38;5;66;03m# Destination\u001b[39;00m\n\u001b[32m 976\u001b[39m local_dir=local_dir,\n\u001b[32m (...)\u001b[39m\u001b[32m 992\u001b[39m dry_run=dry_run,\n\u001b[32m 993\u001b[39m )\n\u001b[32m 994\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m995\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[30;43m_hf_hub_download_to_cache_dir\u001b[39;49m\u001b[30;43m(\u001b[39;49m\n\u001b[32m 996\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43;03m# Destination\u001b[39;49;00m\n\u001b[32m 997\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mcache_dir\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mcache_dir\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 998\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43;03m# File info\u001b[39;49;00m\n\u001b[32m 999\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mrepo_id\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mrepo_id\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 1000\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mfilename\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mfilename\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 1001\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mrepo_type\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mrepo_type\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 1002\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mrevision\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mrevision\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 1003\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43;03m# HTTP info\u001b[39;49;00m\n\u001b[32m 1004\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mendpoint\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mendpoint\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 1005\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43metag_timeout\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43metag_timeout\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 1006\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mheaders\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mhf_headers\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 1007\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mtoken\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mtoken\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 1008\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43;03m# Additional options\u001b[39;49;00m\n\u001b[32m 1009\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mlocal_files_only\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mlocal_files_only\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 1010\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mforce_download\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mforce_download\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 1011\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mtqdm_class\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mtqdm_class\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 1012\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mdry_run\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mdry_run\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 1013\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m)\u001b[39;49m\n",
|
| 479 |
-
"\u001b[36mFile \u001b[39m\u001b[32m~/viral-posts-env/.venv/lib/python3.14/site-packages/huggingface_hub/file_download.py:1213\u001b[39m, in \u001b[36m_hf_hub_download_to_cache_dir\u001b[39m\u001b[34m(cache_dir, repo_id, filename, repo_type, revision, endpoint, etag_timeout, headers, token, local_files_only, force_download, tqdm_class, dry_run)\u001b[39m\n\u001b[32m 1209\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m pointer_path\n\u001b[32m 1211\u001b[39m \u001b[38;5;66;03m# Local file doesn't exist or etag isn't a match => retrieve file from remote (or cache)\u001b[39;00m\n\u001b[32m-> \u001b[39m\u001b[32m1213\u001b[39m \u001b[30;43m\u001b[39;49m\u001b[30;43;01mwith\u001b[39;49;00m\u001b[30;43m \u001b[39;49m\u001b[30;43mWeakFileLock\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43mlock_path\u001b[39;49m\u001b[30;43m)\u001b[39;49m\u001b[30;43m:\u001b[39;49m\n\u001b[32m 1214\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m_download_to_tmp_and_move\u001b[39;49m\u001b[30;43m(\u001b[39;49m\n\u001b[32m 1215\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mincomplete_path\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mPath\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43mblob_path\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43m+\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m.incomplete\u001b[39;49m\u001b[30;43m\"\u001b[39;49m\u001b[30;43m)\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 1216\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mdestination_path\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mPath\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43mblob_path\u001b[39;49m\u001b[30;43m)\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m (...)\u001b[39m\u001b[32m 1224\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43mtqdm_class\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mtqdm_class\u001b[39;49m\u001b[30;43m,\u001b[39;49m\n\u001b[32m 1225\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 1226\u001b[39m \u001b[30;43m \u001b[39;49m\u001b[30;43;01mif\u001b[39;49;00m\u001b[30;43m \u001b[39;49m\u001b[30;43;01mnot\u001b[39;49;00m\u001b[30;43m \u001b[39;49m\u001b[30;43mos\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mpath\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mexists\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43mpointer_path\u001b[39;49m\u001b[30;43m)\u001b[39;49m\u001b[30;43m:\u001b[39;49m\n",
|
| 480 |
-
"\u001b[36mFile \u001b[39m\u001b[32m/opt/homebrew/Cellar/python@3.14/3.14.2_1/Frameworks/Python.framework/Versions/3.14/lib/python3.14/contextlib.py:141\u001b[39m, in \u001b[36m_GeneratorContextManager.__enter__\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 139\u001b[39m \u001b[38;5;28;01mdel\u001b[39;00m \u001b[38;5;28mself\u001b[39m.args, \u001b[38;5;28mself\u001b[39m.kwds, \u001b[38;5;28mself\u001b[39m.func\n\u001b[32m 140\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m141\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[30;43mnext\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mgen\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 142\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mStopIteration\u001b[39;00m:\n\u001b[32m 143\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mRuntimeError\u001b[39;00m(\u001b[33m\"\u001b[39m\u001b[33mgenerator didn\u001b[39m\u001b[33m'\u001b[39m\u001b[33mt yield\u001b[39m\u001b[33m\"\u001b[39m) \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m\n",
|
| 481 |
-
"\u001b[36mFile \u001b[39m\u001b[32m~/viral-posts-env/.venv/lib/python3.14/site-packages/huggingface_hub/utils/_fixes.py:99\u001b[39m, in \u001b[36mWeakFileLock\u001b[39m\u001b[34m(lock_file, timeout)\u001b[39m\n\u001b[32m 96\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m Timeout(\u001b[38;5;28mstr\u001b[39m(lock_file))\n\u001b[32m 98\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m---> \u001b[39m\u001b[32m99\u001b[39m \u001b[30;43mlock\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43macquire\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43mtimeout\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mmin\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43mlog_interval\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mtimeout\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43m-\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43melapsed_time\u001b[39;49m\u001b[30;43m)\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43;01mif\u001b[39;49;00m\u001b[30;43m \u001b[39;49m\u001b[30;43mtimeout\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43;01melse\u001b[39;49;00m\u001b[30;43m \u001b[39;49m\u001b[30;43mlog_interval\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 100\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m Timeout:\n\u001b[32m 101\u001b[39m logger.info(\n\u001b[32m 102\u001b[39m \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mStill waiting to acquire lock on \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mlock_file\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m (elapsed: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mtime.time()\u001b[38;5;250m \u001b[39m-\u001b[38;5;250m \u001b[39mstart_time\u001b[38;5;132;01m:\u001b[39;00m\u001b[33m.1f\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m seconds)\u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 103\u001b[39m )\n",
|
| 482 |
-
"\u001b[36mFile \u001b[39m\u001b[32m~/viral-posts-env/.venv/lib/python3.14/site-packages/filelock/_api.py:513\u001b[39m, in \u001b[36mBaseFileLock.acquire\u001b[39m\u001b[34m(self, timeout, poll_interval, poll_intervall, blocking, cancel_check)\u001b[39m\n\u001b[32m 511\u001b[39m msg = \u001b[33m\"\u001b[39m\u001b[33mLock \u001b[39m\u001b[38;5;132;01m%s\u001b[39;00m\u001b[33m not acquired on \u001b[39m\u001b[38;5;132;01m%s\u001b[39;00m\u001b[33m, waiting \u001b[39m\u001b[38;5;132;01m%s\u001b[39;00m\u001b[33m seconds ...\u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 512\u001b[39m _LOGGER.debug(msg, lock_id, lock_filename, poll_interval)\n\u001b[32m--> \u001b[39m\u001b[32m513\u001b[39m \u001b[30;43mtime\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43msleep\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43mpoll_interval\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 514\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m:\n\u001b[32m 515\u001b[39m \u001b[38;5;28mself\u001b[39m._context.lock_counter = \u001b[38;5;28mmax\u001b[39m(\u001b[32m0\u001b[39m, \u001b[38;5;28mself\u001b[39m._context.lock_counter - \u001b[32m1\u001b[39m)\n",
|
| 483 |
-
"\u001b[31mKeyboardInterrupt\u001b[39m: "
|
| 484 |
-
]
|
| 485 |
-
}
|
| 486 |
-
],
|
| 487 |
"source": [
|
| 488 |
"# Cell 7: Load model (4-bit on CUDA Colab; fp16/fp32 fallback if bitsandbytes missing)\n",
|
| 489 |
"from transformers import AutoTokenizer, AutoModelForCausalLM\n",
|
|
@@ -559,42 +425,26 @@
|
|
| 559 |
"# Cell 8: LLM agent functions\n",
|
| 560 |
"SYSTEM_PROMPT = textwrap.dedent(\"\"\"\\\n",
|
| 561 |
"You are an Instagram content strategy agent. Each step is one day.\n",
|
| 562 |
-
"You manage a creator account over a
|
| 563 |
"\n",
|
| 564 |
"RESPONSE FORMAT — return ONLY valid JSON, no markdown:\n",
|
| 565 |
"{\n",
|
| 566 |
-
" \"tool_calls\": [{\"name\": \"
|
| 567 |
" \"scheduled_actions\": [\n",
|
| 568 |
-
" {\"hour\":
|
| 569 |
-
" \"
|
| 570 |
-
" \"topic\": \"<string>\", \"tags\": [\"...\"],\n",
|
| 571 |
-
" \"intent\": \"send_bait|save_bait|watch_bait|like_bait\"}\n",
|
| 572 |
" ],\n",
|
|
|
|
| 573 |
" \"notes\": \"strategy notes\"\n",
|
| 574 |
"}\n",
|
| 575 |
"\n",
|
| 576 |
-
"
|
| 577 |
-
"-
|
| 578 |
-
"-
|
| 579 |
-
"-
|
| 580 |
-
"-
|
| 581 |
-
"-
|
| 582 |
-
"-
|
| 583 |
-
"- query_creator_pool() cost=1 list collab partners with audience overlap\n",
|
| 584 |
-
"- propose_collab(partner_id, content_type, hour) cost=5 co-author the post at that hour (max 2/month)\n",
|
| 585 |
-
"\n",
|
| 586 |
-
"ACTION SCHEMA:\n",
|
| 587 |
-
"- hour: 0..23 (unlisted hours = rest)\n",
|
| 588 |
-
"- action_type: post (publish) | create_content (build queue, no publish)\n",
|
| 589 |
-
"- content_type: reel | story | carousel | text_post\n",
|
| 590 |
-
"- intent: which Mosseri signal the post optimises for\n",
|
| 591 |
-
" send_bait -> DM shares (strongest discovery signal)\n",
|
| 592 |
-
" save_bait -> bookmarks (content quality)\n",
|
| 593 |
-
" watch_bait -> reels watch time\n",
|
| 594 |
-
" like_bait -> likes from existing followers\n",
|
| 595 |
-
"- tags: up to 5 hashtags\n",
|
| 596 |
-
"- topic: free-form string\n",
|
| 597 |
-
"- empty scheduled_actions = full day rest\"\"\")\n",
|
| 598 |
"\n",
|
| 599 |
"\n",
|
| 600 |
"def format_obs(obs):\n",
|
|
@@ -609,32 +459,15 @@
|
|
| 609 |
" tool_str = \"\"\n",
|
| 610 |
" for tr in getattr(obs, \"tool_results\", []):\n",
|
| 611 |
" if tr.success:\n",
|
| 612 |
-
" tool_str += f\" {tr.name}: {json.dumps(tr.data)}\\n\"\n",
|
| 613 |
-
" if not tool_str:\n",
|
| 614 |
-
" tool_str = \" (none)\\n\"\n",
|
| 615 |
" return (f\"Day: {day_name} | days_elapsed={obs.days_elapsed}\\n\"\n",
|
| 616 |
" f\"Energy: {obs.creator_energy:.2f} | Followers: {obs.follower_count}\\n\"\n",
|
| 617 |
" f\"Engagement: {obs.engagement_rate:.3f} | Queue: {obs.content_queue_size}\\n\"\n",
|
| 618 |
" f\"{signals_str}\"\n",
|
| 619 |
-
" f\"Tool results:\\n{tool_str}\"\n",
|
| 620 |
" f\"Plan your actions (JSON only):\")\n",
|
| 621 |
"\n",
|
| 622 |
"\n",
|
| 623 |
-
"def is_well_formed_response(text):\n",
|
| 624 |
-
" try:\n",
|
| 625 |
-
" t = text.strip()\n",
|
| 626 |
-
" if \"```\" in t:\n",
|
| 627 |
-
" t = \"\\n\".join(l for l in t.split(\"\\n\") if not l.strip().startswith(\"```\")).strip()\n",
|
| 628 |
-
" s, e = t.find(\"{\"), t.rfind(\"}\") + 1\n",
|
| 629 |
-
" d = json.loads(t[s:e])\n",
|
| 630 |
-
" for tc in d.get(\"tool_calls\", []):\n",
|
| 631 |
-
" if not isinstance(tc, dict) or not isinstance(tc.get(\"arguments\", {}), dict):\n",
|
| 632 |
-
" return False\n",
|
| 633 |
-
" return True\n",
|
| 634 |
-
" except Exception:\n",
|
| 635 |
-
" return False\n",
|
| 636 |
-
"\n",
|
| 637 |
-
"\n",
|
| 638 |
"def parse_model_output(text):\n",
|
| 639 |
" text = text.strip()\n",
|
| 640 |
" if \"```\" in text:\n",
|
|
@@ -645,32 +478,24 @@
|
|
| 645 |
" text = text[start:end]\n",
|
| 646 |
" try:\n",
|
| 647 |
" data = json.loads(text)\n",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 648 |
" except Exception:\n",
|
|
|
|
| 649 |
" return ViraltestAction(scheduled_actions=[])\n",
|
| 650 |
-
" tool_calls = []\n",
|
| 651 |
-
" for tc in data.get(\"tool_calls\", []):\n",
|
| 652 |
-
" if not isinstance(tc, dict) or \"name\" not in tc:\n",
|
| 653 |
-
" continue\n",
|
| 654 |
-
" args = tc.get(\"arguments\", {})\n",
|
| 655 |
-
" if isinstance(args, list) and args and isinstance(args[0], dict):\n",
|
| 656 |
-
" args = args[0]\n",
|
| 657 |
-
" if not isinstance(args, dict):\n",
|
| 658 |
-
" continue\n",
|
| 659 |
-
" try:\n",
|
| 660 |
-
" tool_calls.append(ToolCall(name=tc[\"name\"], arguments=args))\n",
|
| 661 |
-
" except Exception:\n",
|
| 662 |
-
" pass\n",
|
| 663 |
-
" scheduled = []\n",
|
| 664 |
-
" for a in data.get(\"scheduled_actions\", []):\n",
|
| 665 |
-
" try:\n",
|
| 666 |
-
" scheduled.append(ScheduledAction(**a))\n",
|
| 667 |
-
" except Exception:\n",
|
| 668 |
-
" pass\n",
|
| 669 |
-
" return ViraltestAction(\n",
|
| 670 |
-
" tool_calls=tool_calls,\n",
|
| 671 |
-
" scheduled_actions=scheduled,\n",
|
| 672 |
-
" notes=data.get(\"notes\"),\n",
|
| 673 |
-
" )\n",
|
| 674 |
"\n",
|
| 675 |
"\n",
|
| 676 |
"def _infer_model_device(m):\n",
|
|
@@ -684,10 +509,10 @@
|
|
| 684 |
" return torch.device(\"cpu\")\n",
|
| 685 |
"\n",
|
| 686 |
"\n",
|
| 687 |
-
"def generate_action(mdl, tok, obs, history, temperature=0.7
|
| 688 |
" prompt = format_obs(obs)\n",
|
| 689 |
" messages = [{\"role\": \"system\", \"content\": SYSTEM_PROMPT}]\n",
|
| 690 |
-
" messages.extend(history[-
|
| 691 |
" messages.append({\"role\": \"user\", \"content\": prompt})\n",
|
| 692 |
" text_input = tok.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)\n",
|
| 693 |
" inputs = tok(text_input, return_tensors=\"pt\").to(_infer_model_device(mdl))\n",
|
|
@@ -695,27 +520,21 @@
|
|
| 695 |
" out = mdl.generate(**inputs, max_new_tokens=512, temperature=temperature,\n",
|
| 696 |
" do_sample=True, top_p=0.9, pad_token_id=tok.eos_token_id)\n",
|
| 697 |
" resp = tok.decode(out[0][inputs[\"input_ids\"].shape[1]:], skip_special_tokens=True)\n",
|
| 698 |
-
" if debug:\n",
|
| 699 |
-
" print(\"=\" * 60)\n",
|
| 700 |
-
" print(f\"[LLM PROMPT] tokens={inputs['input_ids'].shape[1]}\")\n",
|
| 701 |
-
" print(prompt)\n",
|
| 702 |
-
" print(\"-\" * 60)\n",
|
| 703 |
-
" print(f\"[LLM RESPONSE] tokens={out.shape[1] - inputs['input_ids'].shape[1]}\")\n",
|
| 704 |
-
" print(resp)\n",
|
| 705 |
-
" print(\"=\" * 60)\n",
|
| 706 |
" return resp, parse_model_output(resp)\n",
|
| 707 |
"\n",
|
| 708 |
"\n",
|
| 709 |
-
"def run_llm_episode(mdl, tok, task, seed=42, verbose=False
|
| 710 |
" env = ViraltestEnvironment()\n",
|
| 711 |
" obs = env.reset(task=task, seed=seed)\n",
|
| 712 |
" rewards, energies = [], [obs.creator_energy]\n",
|
| 713 |
" history, pairs = [], []\n",
|
| 714 |
" for day in range(1, TASK_HORIZON + 1):\n",
|
| 715 |
" if obs.done: break\n",
|
| 716 |
-
" if
|
| 717 |
-
"
|
| 718 |
-
"
|
|
|
|
|
|
|
| 719 |
" prompt = format_obs(obs)\n",
|
| 720 |
" pairs.append({\"prompt\": prompt, \"response\": resp})\n",
|
| 721 |
" obs = env.step(action)\n",
|
|
@@ -729,17 +548,9 @@
|
|
| 729 |
" print(f\" Day {day:2d}: r={r:.4f} e={obs.creator_energy:.2f} posts={n_p} tools={len(action.tool_calls)}\")\n",
|
| 730 |
" if obs.done: break\n",
|
| 731 |
" gs = (obs.metadata or {}).get(\"grader_score\", 0.0)\n",
|
| 732 |
-
" # Per-step credit assignment: G_t = r_t + gamma * G_{t+1}, terminal = grader_score * w\n",
|
| 733 |
-
" GAMMA, TERMINAL_W = 0.95, 5.0\n",
|
| 734 |
-
" G, returns = gs * TERMINAL_W, [0.0] * len(rewards)\n",
|
| 735 |
-
" for t in reversed(range(len(rewards))):\n",
|
| 736 |
-
" G = rewards[t] + GAMMA * G\n",
|
| 737 |
-
" returns[t] = G\n",
|
| 738 |
-
" for i, pr in enumerate(pairs):\n",
|
| 739 |
-
" pr[\"return\"] = returns[i] if i < len(returns) else 0.0\n",
|
| 740 |
" return {\"task\": task, \"grader_score\": gs, \"total_reward\": sum(rewards),\n",
|
| 741 |
" \"final_energy\": obs.creator_energy, \"rewards\": rewards,\n",
|
| 742 |
-
" \"
|
| 743 |
" \"follower_delta\": obs.follower_count - 10000,\n",
|
| 744 |
" \"burned_out\": obs.creator_energy <= 0}\n",
|
| 745 |
"\n",
|
|
@@ -854,45 +665,37 @@
|
|
| 854 |
" episode_graders.append(result[\"grader_score\"])\n",
|
| 855 |
"\n",
|
| 856 |
" for pr in result[\"pairs\"]:\n",
|
| 857 |
-
" if not is_well_formed_response(pr[\"response\"]):\n",
|
| 858 |
-
" continue\n",
|
| 859 |
" text = (f\"<|im_start|>system\\n{SYSTEM_PROMPT}<|im_end|>\\n\"\n",
|
| 860 |
" f\"<|im_start|>user\\n{pr['prompt']}<|im_end|>\\n\"\n",
|
| 861 |
" f\"<|im_start|>assistant\\n{pr['response']}<|im_end|>\")\n",
|
| 862 |
-
" all_pairs.append({\"text\": text, \"reward\":
|
| 863 |
"\n",
|
| 864 |
-
" rets = result[\"returns\"]\n",
|
| 865 |
" print(f\" ep {ep+1}/{EPISODES_PER_ROUND}: {task.split('_')[-1]:>11s} \"\n",
|
| 866 |
-
" f\"grader={result['grader_score']:.4f} reward={ep_reward:.3f}
|
| 867 |
-
" f\"return[min={min(rets):.2f} max={max(rets):.2f} mean={np.mean(rets):.2f}]\")\n",
|
| 868 |
"\n",
|
| 869 |
" avg_r = np.mean(episode_rewards)\n",
|
| 870 |
" avg_g = np.mean(episode_graders)\n",
|
| 871 |
" print(f\" Avg reward={avg_r:.3f} Avg grader={avg_g:.4f}\")\n",
|
| 872 |
"\n",
|
| 873 |
-
" # Filter to top-K
|
| 874 |
" threshold = np.percentile([p[\"reward\"] for p in all_pairs], (1 - TOP_K_FRACTION) * 100)\n",
|
| 875 |
" filtered = [p for p in all_pairs if p[\"reward\"] >= threshold] or all_pairs\n",
|
| 876 |
-
" print(f\" Filtered to {len(filtered)}/{len(all_pairs)} samples
|
| 877 |
"\n",
|
| 878 |
" dataset = Dataset.from_list([{\"text\": p[\"text\"]} for p in filtered])\n",
|
| 879 |
"\n",
|
| 880 |
" # SFT training (real gradient updates)\n",
|
| 881 |
" sft_config = SFTConfig(\n",
|
| 882 |
" output_dir=f\"./checkpoints/round_{round_idx}\",\n",
|
| 883 |
-
"
|
| 884 |
-
" per_device_train_batch_size=
|
| 885 |
-
" gradient_accumulation_steps=
|
| 886 |
" learning_rate=2e-5,\n",
|
| 887 |
-
"
|
| 888 |
-
" logging_steps=
|
| 889 |
" save_strategy=\"no\",\n",
|
| 890 |
-
" max_length=
|
| 891 |
-
"
|
| 892 |
-
" gradient_checkpointing=False,\n",
|
| 893 |
-
" dataloader_num_workers=4,\n",
|
| 894 |
-
" dataloader_pin_memory=True,\n",
|
| 895 |
-
" optim=\"adamw_torch_fused\",\n",
|
| 896 |
" report_to=\"none\",\n",
|
| 897 |
" )\n",
|
| 898 |
"\n",
|
|
@@ -1136,7 +939,7 @@
|
|
| 1136 |
"name": "python",
|
| 1137 |
"nbconvert_exporter": "python",
|
| 1138 |
"pygments_lexer": "ipython3",
|
| 1139 |
-
"version": "3.
|
| 1140 |
}
|
| 1141 |
},
|
| 1142 |
"nbformat": 4,
|
|
|
|
| 25 |
},
|
| 26 |
{
|
| 27 |
"cell_type": "code",
|
| 28 |
+
"execution_count": null,
|
| 29 |
"metadata": {},
|
| 30 |
+
"outputs": [],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
"source": [
|
| 32 |
"# Cell 1: Install dependencies (quote versions — zsh treats `>` as redirect otherwise)\n",
|
| 33 |
"!pip install -q torch torchvision torchaudio\n",
|
|
|
|
| 39 |
},
|
| 40 |
{
|
| 41 |
"cell_type": "code",
|
| 42 |
+
"execution_count": null,
|
| 43 |
"metadata": {},
|
| 44 |
+
"outputs": [],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
"source": [
|
| 46 |
"# Cell 2: Resolve repo path (Colab: fresh clone. Local: auto-detect project root)\n",
|
| 47 |
"import os\n",
|
|
|
|
| 121 |
},
|
| 122 |
{
|
| 123 |
"cell_type": "code",
|
| 124 |
+
"execution_count": null,
|
| 125 |
"metadata": {},
|
| 126 |
+
"outputs": [],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
"source": [
|
| 128 |
"# Cell 3: Imports (with runtime validation)\n",
|
| 129 |
"import json, random, time, textwrap, copy, os, sys\n",
|
|
|
|
| 167 |
"print(f\"GPU: {torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'CPU'}\")\n",
|
| 168 |
"print(f\"Tags: {len(TAG_POOL)}, Topics: {len(ALL_TOPICS)}, Horizon: {TASK_HORIZON} days\")\n",
|
| 169 |
"\n",
|
| 170 |
+
"# Hard stop if stale repo/code is loaded\n",
|
| 171 |
+
"assert TASK_HORIZON == 15, (\n",
|
| 172 |
+
" f\"Expected TASK_HORIZON=15, got {TASK_HORIZON}. \"\n",
|
| 173 |
+
" \"Restart runtime and run from Cell 1 again (clean clone on hack1).\"\n",
|
| 174 |
+
")\n",
|
| 175 |
+
"\n",
|
| 176 |
"# Same sanity as syntax_only.ipynb (kernel parses modern Python)\n",
|
| 177 |
"import ast\n",
|
| 178 |
"ast.parse(\"def _t(x: int) -> str: return f'{x}'\")\n",
|
|
|
|
| 190 |
},
|
| 191 |
{
|
| 192 |
"cell_type": "code",
|
| 193 |
+
"execution_count": null,
|
| 194 |
"metadata": {},
|
| 195 |
+
"outputs": [],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
"source": [
|
| 197 |
"# Cell 4: Define heuristic agents + episode runner\n",
|
| 198 |
"_rng = random.Random(42)\n",
|
|
|
|
| 241 |
" topic=ALL_TOPICS[(day*2+1)%len(ALL_TOPICS)],\n",
|
| 242 |
" tags=[TAG_POOL[(day*6+3+i)%len(TAG_POOL)] for i in range(3)],\n",
|
| 243 |
" intent=INTENTS[(day*2+1)%4]),\n",
|
| 244 |
+
" ],\n",
|
| 245 |
+
" replies=[{\"post_hour\": 12, \"reply_hour\": 13}])\n",
|
| 246 |
"\n",
|
| 247 |
"BASELINE_AGENTS = {\n",
|
| 248 |
" \"always_rest\": plan_always_rest, \"spam\": plan_spam,\n",
|
|
|
|
| 273 |
},
|
| 274 |
{
|
| 275 |
"cell_type": "code",
|
| 276 |
+
"execution_count": null,
|
| 277 |
"metadata": {},
|
| 278 |
+
"outputs": [],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 279 |
"source": [
|
| 280 |
"# Cell 5: Run baselines (safe)\n",
|
| 281 |
"print(\"Running heuristic baselines (5 agents × 3 tasks)...\")\n",
|
|
|
|
| 314 |
},
|
| 315 |
{
|
| 316 |
"cell_type": "code",
|
| 317 |
+
"execution_count": null,
|
| 318 |
"metadata": {},
|
| 319 |
+
"outputs": [],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 320 |
"source": [
|
| 321 |
"# Cell 6: Baseline plots\n",
|
| 322 |
"fig, axes = plt.subplots(1, 3, figsize=(16, 5), sharey=True)\n",
|
|
|
|
| 347 |
},
|
| 348 |
{
|
| 349 |
"cell_type": "code",
|
| 350 |
+
"execution_count": null,
|
| 351 |
"metadata": {},
|
| 352 |
+
"outputs": [],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 353 |
"source": [
|
| 354 |
"# Cell 7: Load model (4-bit on CUDA Colab; fp16/fp32 fallback if bitsandbytes missing)\n",
|
| 355 |
"from transformers import AutoTokenizer, AutoModelForCausalLM\n",
|
|
|
|
| 425 |
"# Cell 8: LLM agent functions\n",
|
| 426 |
"SYSTEM_PROMPT = textwrap.dedent(\"\"\"\\\n",
|
| 427 |
"You are an Instagram content strategy agent. Each step is one day.\n",
|
| 428 |
+
"You manage a creator account over a 15-day cycle.\n",
|
| 429 |
"\n",
|
| 430 |
"RESPONSE FORMAT — return ONLY valid JSON, no markdown:\n",
|
| 431 |
"{\n",
|
| 432 |
+
" \"tool_calls\": [{\"name\": \"query_trends\", \"arguments\": {\"niche\": \"tech\"}}],\n",
|
| 433 |
" \"scheduled_actions\": [\n",
|
| 434 |
+
" {\"hour\": 12, \"action_type\": \"post\", \"content_type\": \"reel\",\n",
|
| 435 |
+
" \"topic\": \"AI tools\", \"tags\": [\"ai\", \"coding\"], \"intent\": \"watch_bait\"}\n",
|
|
|
|
|
|
|
| 436 |
" ],\n",
|
| 437 |
+
" \"replies\": [{\"post_hour\": 12, \"reply_hour\": 13}],\n",
|
| 438 |
" \"notes\": \"strategy notes\"\n",
|
| 439 |
"}\n",
|
| 440 |
"\n",
|
| 441 |
+
"RULES:\n",
|
| 442 |
+
"- content_type: reel|story|carousel|text_post\n",
|
| 443 |
+
"- intent: send_bait|save_bait|watch_bait|like_bait\n",
|
| 444 |
+
"- 1-2 posts/day optimal. More = fatigue.\n",
|
| 445 |
+
"- Empty scheduled_actions = rest (recovers energy).\n",
|
| 446 |
+
"- Vary content types and topics for diversity bonus.\n",
|
| 447 |
+
"- Reply within 90 min of post for reach bonus.\"\"\")\n",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 448 |
"\n",
|
| 449 |
"\n",
|
| 450 |
"def format_obs(obs):\n",
|
|
|
|
| 459 |
" tool_str = \"\"\n",
|
| 460 |
" for tr in getattr(obs, \"tool_results\", []):\n",
|
| 461 |
" if tr.success:\n",
|
| 462 |
+
" tool_str += f\" {tr.name}: {json.dumps(tr.data)[:200]}\\n\"\n",
|
|
|
|
|
|
|
| 463 |
" return (f\"Day: {day_name} | days_elapsed={obs.days_elapsed}\\n\"\n",
|
| 464 |
" f\"Energy: {obs.creator_energy:.2f} | Followers: {obs.follower_count}\\n\"\n",
|
| 465 |
" f\"Engagement: {obs.engagement_rate:.3f} | Queue: {obs.content_queue_size}\\n\"\n",
|
| 466 |
" f\"{signals_str}\"\n",
|
| 467 |
+
" f\"Tool results:\\n{tool_str if tool_str else ' (none)\\n'}\"\n",
|
| 468 |
" f\"Plan your actions (JSON only):\")\n",
|
| 469 |
"\n",
|
| 470 |
"\n",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 471 |
"def parse_model_output(text):\n",
|
| 472 |
" text = text.strip()\n",
|
| 473 |
" if \"```\" in text:\n",
|
|
|
|
| 478 |
" text = text[start:end]\n",
|
| 479 |
" try:\n",
|
| 480 |
" data = json.loads(text)\n",
|
| 481 |
+
" tool_calls = [ToolCall(name=tc[\"name\"], arguments=tc.get(\"arguments\", {}))\n",
|
| 482 |
+
" for tc in data.get(\"tool_calls\", []) if isinstance(tc, dict) and \"name\" in tc]\n",
|
| 483 |
+
" scheduled = []\n",
|
| 484 |
+
" for a in data.get(\"scheduled_actions\", []):\n",
|
| 485 |
+
" try:\n",
|
| 486 |
+
" scheduled.append(ScheduledAction(**a))\n",
|
| 487 |
+
" except Exception:\n",
|
| 488 |
+
" # Same as original bare `except:`: skip invalid scheduled_actions entries\n",
|
| 489 |
+
" pass\n",
|
| 490 |
+
" return ViraltestAction(\n",
|
| 491 |
+
" tool_calls=tool_calls,\n",
|
| 492 |
+
" scheduled_actions=scheduled,\n",
|
| 493 |
+
" replies=data.get(\"replies\", []),\n",
|
| 494 |
+
" notes=data.get(\"notes\"),\n",
|
| 495 |
+
" )\n",
|
| 496 |
" except Exception:\n",
|
| 497 |
+
" # Same behavior as original bare `except:`: any parse/validation failure -> empty action\n",
|
| 498 |
" return ViraltestAction(scheduled_actions=[])\n",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 499 |
"\n",
|
| 500 |
"\n",
|
| 501 |
"def _infer_model_device(m):\n",
|
|
|
|
| 509 |
" return torch.device(\"cpu\")\n",
|
| 510 |
"\n",
|
| 511 |
"\n",
|
| 512 |
+
"def generate_action(mdl, tok, obs, history, temperature=0.7):\n",
|
| 513 |
" prompt = format_obs(obs)\n",
|
| 514 |
" messages = [{\"role\": \"system\", \"content\": SYSTEM_PROMPT}]\n",
|
| 515 |
+
" messages.extend(history[-4:])\n",
|
| 516 |
" messages.append({\"role\": \"user\", \"content\": prompt})\n",
|
| 517 |
" text_input = tok.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)\n",
|
| 518 |
" inputs = tok(text_input, return_tensors=\"pt\").to(_infer_model_device(mdl))\n",
|
|
|
|
| 520 |
" out = mdl.generate(**inputs, max_new_tokens=512, temperature=temperature,\n",
|
| 521 |
" do_sample=True, top_p=0.9, pad_token_id=tok.eos_token_id)\n",
|
| 522 |
" resp = tok.decode(out[0][inputs[\"input_ids\"].shape[1]:], skip_special_tokens=True)\n",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 523 |
" return resp, parse_model_output(resp)\n",
|
| 524 |
"\n",
|
| 525 |
"\n",
|
| 526 |
+
"def run_llm_episode(mdl, tok, task, seed=42, verbose=False):\n",
|
| 527 |
" env = ViraltestEnvironment()\n",
|
| 528 |
" obs = env.reset(task=task, seed=seed)\n",
|
| 529 |
" rewards, energies = [], [obs.creator_energy]\n",
|
| 530 |
" history, pairs = [], []\n",
|
| 531 |
" for day in range(1, TASK_HORIZON + 1):\n",
|
| 532 |
" if obs.done: break\n",
|
| 533 |
+
" if obs.creator_energy <= 0.25:\n",
|
| 534 |
+
" action = ViraltestAction(scheduled_actions=[])\n",
|
| 535 |
+
" resp = '{\"scheduled_actions\": []}'\n",
|
| 536 |
+
" else:\n",
|
| 537 |
+
" resp, action = generate_action(mdl, tok, obs, history)\n",
|
| 538 |
" prompt = format_obs(obs)\n",
|
| 539 |
" pairs.append({\"prompt\": prompt, \"response\": resp})\n",
|
| 540 |
" obs = env.step(action)\n",
|
|
|
|
| 548 |
" print(f\" Day {day:2d}: r={r:.4f} e={obs.creator_energy:.2f} posts={n_p} tools={len(action.tool_calls)}\")\n",
|
| 549 |
" if obs.done: break\n",
|
| 550 |
" gs = (obs.metadata or {}).get(\"grader_score\", 0.0)\n",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 551 |
" return {\"task\": task, \"grader_score\": gs, \"total_reward\": sum(rewards),\n",
|
| 552 |
" \"final_energy\": obs.creator_energy, \"rewards\": rewards,\n",
|
| 553 |
+
" \"energies\": energies, \"pairs\": pairs,\n",
|
| 554 |
" \"follower_delta\": obs.follower_count - 10000,\n",
|
| 555 |
" \"burned_out\": obs.creator_energy <= 0}\n",
|
| 556 |
"\n",
|
|
|
|
| 665 |
" episode_graders.append(result[\"grader_score\"])\n",
|
| 666 |
"\n",
|
| 667 |
" for pr in result[\"pairs\"]:\n",
|
|
|
|
|
|
|
| 668 |
" text = (f\"<|im_start|>system\\n{SYSTEM_PROMPT}<|im_end|>\\n\"\n",
|
| 669 |
" f\"<|im_start|>user\\n{pr['prompt']}<|im_end|>\\n\"\n",
|
| 670 |
" f\"<|im_start|>assistant\\n{pr['response']}<|im_end|>\")\n",
|
| 671 |
+
" all_pairs.append({\"text\": text, \"reward\": ep_reward})\n",
|
| 672 |
"\n",
|
|
|
|
| 673 |
" print(f\" ep {ep+1}/{EPISODES_PER_ROUND}: {task.split('_')[-1]:>11s} \"\n",
|
| 674 |
+
" f\"grader={result['grader_score']:.4f} reward={ep_reward:.3f}\")\n",
|
|
|
|
| 675 |
"\n",
|
| 676 |
" avg_r = np.mean(episode_rewards)\n",
|
| 677 |
" avg_g = np.mean(episode_graders)\n",
|
| 678 |
" print(f\" Avg reward={avg_r:.3f} Avg grader={avg_g:.4f}\")\n",
|
| 679 |
"\n",
|
| 680 |
+
" # Filter to top-K\n",
|
| 681 |
" threshold = np.percentile([p[\"reward\"] for p in all_pairs], (1 - TOP_K_FRACTION) * 100)\n",
|
| 682 |
" filtered = [p for p in all_pairs if p[\"reward\"] >= threshold] or all_pairs\n",
|
| 683 |
+
" print(f\" Filtered to {len(filtered)}/{len(all_pairs)} samples\")\n",
|
| 684 |
"\n",
|
| 685 |
" dataset = Dataset.from_list([{\"text\": p[\"text\"]} for p in filtered])\n",
|
| 686 |
"\n",
|
| 687 |
" # SFT training (real gradient updates)\n",
|
| 688 |
" sft_config = SFTConfig(\n",
|
| 689 |
" output_dir=f\"./checkpoints/round_{round_idx}\",\n",
|
| 690 |
+
" num_train_epochs=2,\n",
|
| 691 |
+
" per_device_train_batch_size=1,\n",
|
| 692 |
+
" gradient_accumulation_steps=4,\n",
|
| 693 |
" learning_rate=2e-5,\n",
|
| 694 |
+
" warmup_steps=5,\n",
|
| 695 |
+
" logging_steps=5,\n",
|
| 696 |
" save_strategy=\"no\",\n",
|
| 697 |
+
" max_length=1024,\n",
|
| 698 |
+
" fp16=True,\n",
|
|
|
|
|
|
|
|
|
|
|
|
|
| 699 |
" report_to=\"none\",\n",
|
| 700 |
" )\n",
|
| 701 |
"\n",
|
|
|
|
| 939 |
"name": "python",
|
| 940 |
"nbconvert_exporter": "python",
|
| 941 |
"pygments_lexer": "ipython3",
|
| 942 |
+
"version": "3.14.2"
|
| 943 |
}
|
| 944 |
},
|
| 945 |
"nbformat": 4,
|
training/train_grpo_smoke.ipynb
CHANGED
|
@@ -1,17 +1,4 @@
|
|
| 1 |
{
|
| 2 |
-
"nbformat": 4,
|
| 3 |
-
"nbformat_minor": 4,
|
| 4 |
-
"metadata": {
|
| 5 |
-
"kernelspec": {
|
| 6 |
-
"display_name": "Python 3",
|
| 7 |
-
"language": "python",
|
| 8 |
-
"name": "python3"
|
| 9 |
-
},
|
| 10 |
-
"language_info": {
|
| 11 |
-
"name": "python",
|
| 12 |
-
"version": "3.10.0"
|
| 13 |
-
}
|
| 14 |
-
},
|
| 15 |
"cells": [
|
| 16 |
{
|
| 17 |
"cell_type": "markdown",
|
|
@@ -26,8 +13,8 @@
|
|
| 26 |
},
|
| 27 |
{
|
| 28 |
"cell_type": "code",
|
| 29 |
-
"metadata": {},
|
| 30 |
"execution_count": null,
|
|
|
|
| 31 |
"outputs": [],
|
| 32 |
"source": [
|
| 33 |
"# Cell 1: Minimal deps (quoted versions for zsh / shell safety)\n",
|
|
@@ -37,8 +24,8 @@
|
|
| 37 |
},
|
| 38 |
{
|
| 39 |
"cell_type": "code",
|
| 40 |
-
"metadata": {},
|
| 41 |
"execution_count": null,
|
|
|
|
| 42 |
"outputs": [],
|
| 43 |
"source": [
|
| 44 |
"# Cell 2: Repo path (same logic as main notebook)\n",
|
|
@@ -91,8 +78,8 @@
|
|
| 91 |
},
|
| 92 |
{
|
| 93 |
"cell_type": "code",
|
| 94 |
-
"metadata": {},
|
| 95 |
"execution_count": null,
|
|
|
|
| 96 |
"outputs": [],
|
| 97 |
"source": [
|
| 98 |
"# Cell 3: Core imports + TASK_HORIZON check\n",
|
|
@@ -120,15 +107,15 @@
|
|
| 120 |
" TOPIC_CATEGORIES,\n",
|
| 121 |
")\n",
|
| 122 |
"\n",
|
| 123 |
-
"assert TASK_HORIZON ==
|
| 124 |
"print(\"OK: TASK_HORIZON =\", TASK_HORIZON)\n",
|
| 125 |
"print(\"OK: tags =\", len(TAG_POOL), \"niches =\", len(TOPIC_CATEGORIES))"
|
| 126 |
]
|
| 127 |
},
|
| 128 |
{
|
| 129 |
"cell_type": "code",
|
| 130 |
-
"metadata": {},
|
| 131 |
"execution_count": null,
|
|
|
|
| 132 |
"outputs": [],
|
| 133 |
"source": [
|
| 134 |
"# Cell 4: One minimal episode (syntax + env wiring)\n",
|
|
@@ -173,13 +160,13 @@
|
|
| 173 |
"r = run_episode(\"monthly_engage\", plan_minimal, seed=42)\n",
|
| 174 |
"print(\"Episode result:\", r)\n",
|
| 175 |
"assert r[\"steps\"] == TASK_HORIZON, f\"Expected {TASK_HORIZON} steps, got {r['steps']}\"\n",
|
| 176 |
-
"print(\"OK: full
|
| 177 |
]
|
| 178 |
},
|
| 179 |
{
|
| 180 |
"cell_type": "code",
|
| 181 |
-
"metadata": {},
|
| 182 |
"execution_count": null,
|
|
|
|
| 183 |
"outputs": [],
|
| 184 |
"source": [
|
| 185 |
"# Cell 5: Optional ML stack (no model download)\n",
|
|
@@ -206,5 +193,18 @@
|
|
| 206 |
"If all cells pass, open `train_grpo.ipynb` and run the full pipeline."
|
| 207 |
]
|
| 208 |
}
|
| 209 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
}
|
|
|
|
| 1 |
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
"cells": [
|
| 3 |
{
|
| 4 |
"cell_type": "markdown",
|
|
|
|
| 13 |
},
|
| 14 |
{
|
| 15 |
"cell_type": "code",
|
|
|
|
| 16 |
"execution_count": null,
|
| 17 |
+
"metadata": {},
|
| 18 |
"outputs": [],
|
| 19 |
"source": [
|
| 20 |
"# Cell 1: Minimal deps (quoted versions for zsh / shell safety)\n",
|
|
|
|
| 24 |
},
|
| 25 |
{
|
| 26 |
"cell_type": "code",
|
|
|
|
| 27 |
"execution_count": null,
|
| 28 |
+
"metadata": {},
|
| 29 |
"outputs": [],
|
| 30 |
"source": [
|
| 31 |
"# Cell 2: Repo path (same logic as main notebook)\n",
|
|
|
|
| 78 |
},
|
| 79 |
{
|
| 80 |
"cell_type": "code",
|
|
|
|
| 81 |
"execution_count": null,
|
| 82 |
+
"metadata": {},
|
| 83 |
"outputs": [],
|
| 84 |
"source": [
|
| 85 |
"# Cell 3: Core imports + TASK_HORIZON check\n",
|
|
|
|
| 107 |
" TOPIC_CATEGORIES,\n",
|
| 108 |
")\n",
|
| 109 |
"\n",
|
| 110 |
+
"assert TASK_HORIZON == 15, f\"Expected TASK_HORIZON=15, got {TASK_HORIZON}\"\n",
|
| 111 |
"print(\"OK: TASK_HORIZON =\", TASK_HORIZON)\n",
|
| 112 |
"print(\"OK: tags =\", len(TAG_POOL), \"niches =\", len(TOPIC_CATEGORIES))"
|
| 113 |
]
|
| 114 |
},
|
| 115 |
{
|
| 116 |
"cell_type": "code",
|
|
|
|
| 117 |
"execution_count": null,
|
| 118 |
+
"metadata": {},
|
| 119 |
"outputs": [],
|
| 120 |
"source": [
|
| 121 |
"# Cell 4: One minimal episode (syntax + env wiring)\n",
|
|
|
|
| 160 |
"r = run_episode(\"monthly_engage\", plan_minimal, seed=42)\n",
|
| 161 |
"print(\"Episode result:\", r)\n",
|
| 162 |
"assert r[\"steps\"] == TASK_HORIZON, f\"Expected {TASK_HORIZON} steps, got {r['steps']}\"\n",
|
| 163 |
+
"print(\"OK: full episode completed\")"
|
| 164 |
]
|
| 165 |
},
|
| 166 |
{
|
| 167 |
"cell_type": "code",
|
|
|
|
| 168 |
"execution_count": null,
|
| 169 |
+
"metadata": {},
|
| 170 |
"outputs": [],
|
| 171 |
"source": [
|
| 172 |
"# Cell 5: Optional ML stack (no model download)\n",
|
|
|
|
| 193 |
"If all cells pass, open `train_grpo.ipynb` and run the full pipeline."
|
| 194 |
]
|
| 195 |
}
|
| 196 |
+
],
|
| 197 |
+
"metadata": {
|
| 198 |
+
"kernelspec": {
|
| 199 |
+
"display_name": ".venv",
|
| 200 |
+
"language": "python",
|
| 201 |
+
"name": "python3"
|
| 202 |
+
},
|
| 203 |
+
"language_info": {
|
| 204 |
+
"name": "python",
|
| 205 |
+
"version": "3.14.2"
|
| 206 |
+
}
|
| 207 |
+
},
|
| 208 |
+
"nbformat": 4,
|
| 209 |
+
"nbformat_minor": 4
|
| 210 |
}
|