Spaces:
Running
Running
File size: 8,772 Bytes
bf20e78 a1f52bf bf20e78 a1f52bf bf20e78 a1f52bf bf20e78 a1f52bf bf20e78 a1f52bf bf20e78 a1f52bf bf20e78 a1f52bf bf20e78 a1f52bf bf20e78 a1f52bf bf20e78 a1f52bf bf20e78 a1f52bf bf20e78 a1f52bf bf20e78 | 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 | """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
|