logic-engine / examples /ace /smoke_test.py
ghostdrive1's picture
Upload folder using huggingface_hub
116524e verified
Raw
History Blame Contribute Delete
8.19 kB
#!/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)