Spaces:
Sleeping
Sleeping
| #!/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) | |