Spaces:
Running
Running
Download tests/test_agent_dryrun.py from lucashudsn/wavereader: direct link, hf CLI and curl.
- Browser
- Download file 8.77 kB
-
https://huggingface.co/spaces/lucashudsn/wavereader/resolve/main/tests/test_agent_dryrun.py
- Command line
-
hf download hf://spaces/lucashudsn/wavereader/tests/test_agent_dryrun.py
-
curl -L -o test_agent_dryrun.py https://huggingface.co/spaces/lucashudsn/wavereader/resolve/main/tests/test_agent_dryrun.py
8.77 kB
| """Offline dry-run tests for Worker C (no network, no LLM, no token). | |
| Covers: 11 tools importable + plausibly shaped offline, system-prompt token | |
| budget, golden-question routing, depth budget profiles, score_week per-turn | |
| budget guard, llm factory defaults, narrator marker/think stripping. | |
| """ | |
| from __future__ import annotations | |
| import os | |
| import sys | |
| from pathlib import Path | |
| import pytest | |
| ROOT = Path(__file__).resolve().parent.parent | |
| sys.path.insert(0, str(ROOT)) | |
| sys.path.insert(0, str(ROOT / "scripts")) | |
| import eval_agent # noqa: E402 | |
| from wavereader import agent as agent_mod # noqa: E402 | |
| from wavereader import llm as llm_mod # noqa: E402 | |
| from wavereader import narrate as narrate_mod # noqa: E402 | |
| from wavereader import tools as tools_mod # noqa: E402 | |
| EXPECTED_TOOLS = [ | |
| "get_spot_knowledge", | |
| "get_climate_profile", | |
| "get_seafloor_profile", | |
| "get_session_brief", | |
| "score_week", | |
| "rank_region_week", | |
| "explain_score", | |
| "find_best_windows", | |
| "find_spots", | |
| "find_similar_spots", | |
| "list_regions", | |
| ] | |
| def test_eleven_tools_registered(): | |
| assert tools_mod.TOOL_NAMES == EXPECTED_TOOLS | |
| assert len(tools_mod.TOOLS) == 11 | |
| for t in tools_mod.TOOLS: | |
| assert t.description and t.inputs is not None and t.output_type in ("object", "array", "string") | |
| def test_list_regions_offline_shape(): | |
| out = tools_mod.list_regions() | |
| assert isinstance(out, dict) and "states" in out and "regions_by_state" in out | |
| def test_find_spots_offline_shape(): | |
| out = tools_mod.find_spots(query="Queensland", limit=3) | |
| assert isinstance(out, list) and len(out) <= 3 | |
| def test_spot_tools_offline_plausible_shapes(): | |
| # Unknown spot -> typed error dicts (no network touched). | |
| assert "error" in tools_mod.get_spot_knowledge(spot_name="No Such Break XYZ") | |
| assert "error" in tools_mod.score_week(spot_name="No Such Break XYZ") | |
| assert "error" in tools_mod.explain_score(spot_name="No Such Break XYZ") | |
| assert "error" in tools_mod.get_climate_profile(spot_name="No Such Break XYZ") | |
| assert "error" in tools_mod.get_seafloor_profile(spot_name="No Such Break XYZ") | |
| assert "error" in tools_mod.get_session_brief(spot_name="No Such Break XYZ") | |
| assert "error" in tools_mod.rank_region_week(region="") | |
| assert "error" in tools_mod.find_best_windows(spot_name="No Such Break XYZ") | |
| miss = tools_mod.find_similar_spots(spot_name="No Such Break XYZ") | |
| assert isinstance(miss, list) and miss and "error" in miss[0] | |
| def test_known_spot_offline_shapes(): | |
| if not tools_mod.list_regions().get("states"): | |
| return # no dataset present; nothing to resolve against | |
| first_state = tools_mod.list_regions()["states"][0] | |
| spots = tools_mod.find_spots(query=first_state, limit=1) | |
| assert spots, "dataset should yield at least one spot" | |
| name = spots[0]["name"] | |
| region = spots[0].get("region") or first_state | |
| knowledge = tools_mod.get_spot_knowledge(spot_name=name, region=region) | |
| assert knowledge.get("name") == name | |
| scored = tools_mod.score_week(spot_name=name, region=region, skill="intermediate") | |
| assert isinstance(scored.get("scored"), list) and scored["scored"] | |
| assert scored.get("best") and scored.get("daily") | |
| explained = tools_mod.explain_score(spot_name=name, region=region) | |
| assert explained.get("score") is not None and "components" in explained | |
| windows = tools_mod.find_best_windows(spot_name=name, region=region, daypart="morning") | |
| assert isinstance(windows.get("windows"), list) | |
| climate = tools_mod.get_climate_profile(spot_name=name, region=region) | |
| assert "climate" in climate and "findings" in climate | |
| seafloor = tools_mod.get_seafloor_profile(spot_name=name, region=region) | |
| assert seafloor.get("shelf_class") or "error" in seafloor | |
| brief = tools_mod.get_session_brief(spot_name=name, region=region) | |
| assert brief.get("wetsuit") or "error" in brief | |
| ranked = tools_mod.rank_region_week(region=first_state, limit=3) | |
| if "error" in ranked: | |
| pytest.skip(f"forecast backend offline: {ranked['error']}") | |
| assert isinstance(ranked.get("spots"), list) | |
| similar = tools_mod.find_similar_spots(spot_name=name, region=region, limit=2) | |
| assert isinstance(similar, list) | |
| def test_system_prompt_token_budget(): | |
| est = agent_mod.prompt_token_estimate(agent_mod.SYSTEM_PROMPT) | |
| assert est <= 1500, f"system prompt ~{est} tokens, budget is 1500" | |
| def test_golden_routing_10_of_10(): | |
| missed = [] | |
| for question, expected in eval_agent.GOLDEN: | |
| predicted = eval_agent.predict_tools(question) | |
| if not all(t in predicted for t in expected): | |
| missed.append((question, expected, predicted)) | |
| assert len(eval_agent.GOLDEN) == 10 | |
| assert not missed, f"routing misses: {missed}" | |
| def test_budget_profiles(): | |
| quick = agent_mod.resolve_budget("quick")[1] | |
| deep = agent_mod.resolve_budget("deep")[1] | |
| standard = agent_mod.resolve_budget("standard")[1] | |
| bogus_key, standard_fb = agent_mod.resolve_budget("bogus") | |
| assert bogus_key == "standard" and standard_fb is standard | |
| assert quick["max_steps"] < standard["max_steps"] < deep["max_steps"] | |
| assert quick["max_tokens"] < standard["max_tokens"] < deep["max_tokens"] | |
| assert quick["score_week_calls"] < standard["score_week_calls"] < deep["score_week_calls"] | |
| # standard preserves the original legacy constants | |
| assert standard == { | |
| "max_steps": agent_mod.MAX_STEPS, | |
| "max_tokens": agent_mod.MAX_TOKENS, | |
| "score_week_calls": agent_mod.MAX_SCORE_WEEK_CALLS, | |
| } | |
| def test_score_week_budget_guard(): | |
| counter: dict = {} | |
| guarded = agent_mod._guarded_score_week(counter) | |
| kwargs = {"spot_name": "No Such Break XYZ", "region": None, "skill": None} | |
| guarded(**kwargs) | |
| guarded(**kwargs) | |
| third = guarded(**kwargs) | |
| assert counter["score_week"] == 2 | |
| assert "error" in third and "Budget exceeded" in third["error"] | |
| def test_score_week_budget_guard_profile_cap(): | |
| counter: dict = {} | |
| guarded = agent_mod._guarded_score_week(counter, cap=1) # quick profile | |
| kwargs = {"spot_name": "No Such Break XYZ", "region": None, "skill": None} | |
| guarded(**kwargs) | |
| second = guarded(**kwargs) | |
| assert counter["score_week"] == 1 | |
| assert "error" in second | |
| assert "at most 1" in second["error"] and "Budget exceeded" in second["error"] | |
| def test_llm_factory_defaults_no_token(): | |
| env_backup = {k: os.environ.get(k) for k in ("WR_MODEL", "WR_PROVIDER", "WR_BASE_URL")} | |
| try: | |
| for k in ("WR_MODEL", "WR_PROVIDER", "WR_BASE_URL"): | |
| os.environ.pop(k, None) | |
| assert llm_mod.get_model_id() == llm_mod.DEFAULT_MODEL | |
| assert "Nemotron-3-Ultra" in llm_mod.get_model_id() | |
| assert llm_mod.get_provider() == "deepinfra" | |
| assert llm_mod.get_base_url() is None | |
| assert "Lightning" not in llm_mod.get_model_id() and "Qwen" not in llm_mod.get_model_id() | |
| os.environ["WR_MODEL"] = "custom/model" | |
| os.environ["WR_PROVIDER"] = "custom-provider" | |
| os.environ["WR_BASE_URL"] = "http://localhost:11434/v1" | |
| assert llm_mod.get_model_id() == "custom/model" | |
| assert llm_mod.get_provider() == "custom-provider" | |
| assert llm_mod.get_base_url() == "http://localhost:11434/v1" | |
| client = llm_mod.get_client(hf_token="dummy") | |
| assert client is not None | |
| finally: | |
| for k, v in env_backup.items(): | |
| if v is None: | |
| os.environ.pop(k, None) | |
| else: | |
| os.environ[k] = v | |
| def test_narrator_stripping(): | |
| dirty = "<think>planning here</think>@@REPORT@@\n- Sat: fun.\n\n**Recommendation: Sat -- light winds.**\n@@END@@" | |
| clean = narrate_mod._clean_output(dirty) | |
| assert "<think>" not in clean and "@@REPORT@@" not in clean | |
| assert "Recommendation" in clean | |
| assert narrate_mod._clean_output("We need to produce bullets. Real text.") != "" | |
| prompt = narrate_mod.build_report_prompt({"name": "Bells"}, "intermediate", [], None, 1) | |
| assert "Bells" in prompt | |
| assert narrate_mod.narrate_day({"date": "2026-09-07", "score": 7}) .startswith("2026-09-07") | |
| def test_agent_factory_builds_with_injected_model(): | |
| from smolagents import InferenceClientModel | |
| calls: list[dict] = [] | |
| class FakeModel(InferenceClientModel): | |
| def __init__(self): | |
| pass | |
| created = agent_mod.create_agent(model=FakeModel(), max_steps=6) | |
| assert created.max_steps == 6 | |
| tool_names = sorted(t.name for t in created.tools.values()) if isinstance(created.tools, dict) else sorted(t.name for t in created.tools) | |
| # ToolCallingAgent adds its built-in final_answer tool; ours must all be present. | |
| assert all(t in tool_names for t in EXPECTED_TOOLS), tool_names | |