Spaces:
Sleeping
Sleeping
File size: 8,185 Bytes
116524e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 | #!/usr/bin/env python3
"""Focused e2e smoke test for ace β exercises core runners with a real LLM.
Verifies that each runner actually generates insights (skills with non-empty
content), not just that the pipeline runs without errors.
Usage:
ACE_MODEL=anthropic/claude-haiku-4-5-20251001 uv run python examples/ace/smoke_test.py
"""
import os
import sys
import tempfile
from pathlib import Path
import nest_asyncio
nest_asyncio.apply()
# Ensure project root is importable
_root = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(_root))
from dotenv import load_dotenv
load_dotenv(_root / ".env")
from ace import (
ACE,
ACELiteLLM,
Agent,
Reflector,
Sample,
SimpleEnvironment,
Skillbook,
SkillManager,
TraceAnalyser,
)
MODEL = os.getenv("ACE_MODEL", "anthropic/claude-haiku-4-5-20251001")
passed = 0
total = 5
def section(name: str) -> None:
print(f"\n{'='*60}\n {name}\n{'='*60}")
def assert_skills_have_content(skillbook: Skillbook, label: str) -> None:
"""Verify every skill has a non-empty content field."""
for skill in skillbook.skills():
assert (
skill.content and skill.content.strip()
), f"{label}: skill {skill.id} has empty content"
# ββ Shared setup ββββββββββββββββββββββββββββββββββββββββββββ
agent = Agent(MODEL)
reflector = Reflector(MODEL)
skill_manager = SkillManager(MODEL)
# ββ 1. ACE runner (full pipeline) βββββββββββββββββββββββββββ
section("1. ACE runner β 3 samples, 1 epoch")
skillbook = Skillbook()
ace = ACE.from_roles(
agent=agent,
reflector=reflector,
skill_manager=skill_manager,
environment=SimpleEnvironment(),
skillbook=skillbook,
)
results = ace.run(
[
Sample(question="What is the capital of France?", ground_truth="Paris"),
Sample(question="What is the capital of Japan?", ground_truth="Tokyo"),
Sample(question="What is the capital of Brazil?", ground_truth="Brasilia"),
],
epochs=1,
)
assert len(results) == 3, f"Expected 3 results, got {len(results)}"
errors = [r for r in results if r.error]
assert not errors, f"Pipeline errors: {errors}"
# Verify agent produced answers
for r in results:
assert r.output is not None, f"No output for {r.sample.question}"
ao = getattr(r.output, "agent_output", None)
assert ao is not None, f"No agent_output for {r.sample.question}"
assert ao.final_answer.strip(), f"Empty answer for {r.sample.question}"
# Verify insights were generated
ace_skill_count = len(skillbook.skills())
assert ace_skill_count > 0, "ACE runner produced zero skills"
assert_skills_have_content(skillbook, "ACE runner")
print(f" OK β {len(results)} results, {ace_skill_count} skills learned")
for s in skillbook.skills()[:3]:
print(f" [{s.id}] {s.content[:70]}")
passed += 1
# ββ 2. TraceAnalyser ββββββββββββββββββββββββββββββββββββββββ
section("2. TraceAnalyser β 2 pre-recorded traces")
skills_before = len(skillbook.skills())
analyser = TraceAnalyser.from_roles(
reflector=reflector,
skill_manager=skill_manager,
skillbook=skillbook, # continues from ACE run
)
traces = [
{
"question": "Translate 'hello' to Spanish",
"answer": "hola",
"feedback": "Correct! Simple and accurate.",
},
{
"question": "What is 12 * 15?",
"answer": "170",
"feedback": "Incorrect. The correct answer is 180.",
},
]
trace_results = analyser.run(traces, epochs=1)
assert len(trace_results) == 2, f"Expected 2 results, got {len(trace_results)}"
trace_errors = [r for r in trace_results if r.error]
assert not trace_errors, f"TraceAnalyser errors: {trace_errors}"
# Verify new insights were added
skills_after = len(skillbook.skills())
new_skills = skills_after - skills_before
assert (
new_skills > 0
), f"TraceAnalyser added zero new skills (before={skills_before}, after={skills_after})"
assert_skills_have_content(skillbook, "TraceAnalyser")
print(
f" OK β {len(trace_results)} traces, {new_skills} new skills, {skills_after} total"
)
passed += 1
# ββ 3. ACELiteLLM ask + learn_from_feedback βββββββββββββββββ
section("3. ACELiteLLM β ask + learn_from_feedback")
llm_skillbook = Skillbook()
ace_llm = ACELiteLLM(MODEL, skillbook=llm_skillbook)
answer = ace_llm.ask("What colour is the sky on a clear day?")
assert isinstance(answer, str) and len(answer.strip()) > 0, f"Bad answer: {answer!r}"
print(f" ask() β {answer[:80]}")
learned = ace_llm.learn_from_feedback(
feedback="Good answer but could mention why (Rayleigh scattering).",
ground_truth="Blue",
)
assert learned, "learn_from_feedback returned False"
# Verify insights were generated
llm_skill_count = len(ace_llm.skillbook.skills())
assert llm_skill_count > 0, "learn_from_feedback produced zero skills"
assert_skills_have_content(ace_llm.skillbook, "ACELiteLLM")
# Verify as_prompt() returns something useful
prompt = ace_llm.skillbook.as_prompt()
assert (
prompt and len(prompt.strip()) > 0
), "Skillbook as_prompt() is empty after learning"
print(f" learn_from_feedback() β OK, {llm_skill_count} skills")
print(f" as_prompt() β {len(prompt)} chars")
passed += 1
# ββ 4. Skillbook persistence ββββββββββββββββββββββββββββββββ
section("4. Skillbook persistence β save, reload, verify")
with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as f:
tmp_path = f.name
try:
skillbook.save_to_file(tmp_path)
reloaded = Skillbook.load_from_file(tmp_path)
# Verify counts match
orig_stats = skillbook.stats()
new_stats = reloaded.stats()
assert (
orig_stats["skills"] == new_stats["skills"]
), f"Skill count mismatch: {orig_stats} vs {new_stats}"
# Verify content survives round-trip
orig_prompt = skillbook.as_prompt()
reloaded_prompt = reloaded.as_prompt()
assert (
orig_prompt == reloaded_prompt
), f"as_prompt() differs after reload:\n original: {orig_prompt[:100]}...\n reloaded: {reloaded_prompt[:100]}..."
# Verify individual skill content preserved
orig_ids = {s.id for s in skillbook.skills()}
reloaded_ids = {s.id for s in reloaded.skills()}
assert orig_ids == reloaded_ids, f"Skill IDs differ: {orig_ids} vs {reloaded_ids}"
print(
f" OK β saved/loaded {orig_stats['skills']} skills, content round-trip verified"
)
passed += 1
finally:
Path(tmp_path).unlink(missing_ok=True)
# ββ 5. max_retries wiring βββββββββββββββββββββββββββββββββββ
section("5. max_retries wiring")
a = Agent(MODEL, max_retries=5)
r = Reflector(MODEL, max_retries=7)
s = SkillManager(MODEL, max_retries=9)
assert a.max_retries == 5, f"Agent max_retries={a.max_retries}"
assert r.max_retries == 7, f"Reflector max_retries={r.max_retries}"
assert s.max_retries == 9, f"SkillManager max_retries={s.max_retries}"
# Verify defaults
a_default = Agent(MODEL)
r_default = Reflector(MODEL)
s_default = SkillManager(MODEL)
assert a_default.max_retries == 3, f"Agent default max_retries={a_default.max_retries}"
assert (
r_default.max_retries == 3
), f"Reflector default max_retries={r_default.max_retries}"
assert (
s_default.max_retries == 3
), f"SkillManager default max_retries={s_default.max_retries}"
print(" OK β custom: Agent=5, Reflector=7, SkillManager=9; defaults=3")
passed += 1
# ββ Summary βββββββββββββββββββββββββββββββββββββββββββββββββ
section(f"RESULT: {passed}/{total} passed")
sys.exit(0 if passed == total else 1)
|