Spaces:
Running
Running
File size: 6,384 Bytes
a170f20 7f94735 a170f20 7f94735 470138f 7f94735 8e3a1fd 7f94735 8e3a1fd 470138f 8e3a1fd 7f94735 470138f 7f94735 470138f | 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 | from agent.loop import _humanize, _parse_action, answer_agentic
from agent.schemas import Answer
def test_parse_action_extracts_json_from_noise():
assert _parse_action('sure! {"action":"search_docs","query":"sgd"} ')["query"] == "sgd"
assert _parse_action("no json here")["action"] == "answer"
assert _parse_action('{"action":"bogus"}')["action"] == "answer"
def test_humanize_renders_each_step_and_falls_back():
# a search step β query + the last segment of each heading path, capped at 4
line = "search_docs('sgd momentum') β ['torch.optim > SGD', 'SGD > Per-parameter options']"
out = _humanize(line)
assert "π" in out and "sgd momentum" in out
assert "SGD" in out and "Per-parameter options" in out
assert "torch.optim >" not in out # only the leaf heading is shown
assert _humanize("read_page(http://x) β full page added").startswith("π")
assert _humanize("ask_source β 2 referral links").startswith("π")
# an unrecognised shape degrades to the raw line, never crashes
assert "weird step" in _humanize("weird step")
def test_loop_streams_a_progress_trace(monkeypatch):
scripted = ScriptedAgent(
['{"action":"search_docs","query":"dataloader"}', '{"action":"answer"}'], None
)
monkeypatch.setattr("agent.llm._raw_completion", scripted.plan)
monkeypatch.setattr(
"agent.tools.search_docs",
lambda q, library=None, kind=None, k=8: {"query": q, "sections": [], "titles": ["H"]},
)
monkeypatch.setattr(
"agent.grounded.answer_from_sections",
lambda q, s, referrals=None, provider=None, client=None: Answer(answer_md="ok"),
)
seen: list[str] = []
answer_agentic("how do I load data?", progress=seen.append)
# seed search + planner search are both surfaced, and the final writing step
assert sum(1 for line in seen if line.startswith("π")) == 2
assert seen[-1].startswith("βοΈ")
class ScriptedAgent:
"""Drives the loop: a queue of planner actions, then a final Answer JSON."""
def __init__(self, plan_actions, final_answer):
self._plans = list(plan_actions)
self._final = final_answer
def plan(self, prompt, system, provider=None, client=None):
return self._plans.pop(0)
def final(self, *a, **k):
return self._final
def test_loop_decomposes_and_answers(monkeypatch):
# planner asks for two searches, then answers
scripted = ScriptedAgent(
['{"action":"search_docs","query":"cnn image classification"}',
'{"action":"search_docs","query":"dataloader images"}',
'{"action":"answer"}'],
None,
)
calls = {"search": 0}
def fake_search(query, library=None, kind=None, k=8):
calls["search"] += 1
return {"query": query, "sections": [{"url": f"u{calls['search']}", "anchor": "",
"heading_path": "H", "content": "text"}], "titles": ["H"]}
monkeypatch.setattr("agent.llm._raw_completion", scripted.plan)
monkeypatch.setattr("agent.tools.search_docs", fake_search)
captured = {}
def fake_answer(question, sections, referrals=None, provider=None, client=None):
captured["sections"] = sections
captured["referrals"] = referrals
return Answer(answer_md="done")
monkeypatch.setattr("agent.grounded.answer_from_sections", fake_answer)
result = answer_agentic("how do I build a CNN for images?")
assert result.answer_md == "done"
# 1 seed search + 2 planner-driven searches, all distinct results accumulated
assert calls["search"] == 3
assert len(captured["sections"]) == 3
def test_loop_source_question_adds_referrals(monkeypatch):
scripted = ScriptedAgent(
['{"action":"ask_source","question":"conv2d internals"}', '{"action":"answer"}'],
None,
)
monkeypatch.setattr("agent.llm._raw_completion", scripted.plan)
# seed search runs first β stub it so the test stays offline
monkeypatch.setattr(
"agent.tools.search_docs",
lambda q, library=None, kind=None, k=8: {"query": q, "sections": [], "titles": []},
)
captured = {}
def fake_answer(question, sections, referrals=None, provider=None, client=None):
captured["referrals"] = referrals
return Answer(answer_md="see source")
monkeypatch.setattr("agent.grounded.answer_from_sections", fake_answer)
answer_agentic("how is conv2d implemented?")
assert captured["referrals"] # ask_source contributed referral links
assert any("deepwiki" in r.url for r in captured["referrals"])
def test_loop_stops_at_budget(monkeypatch):
# planner always wants to search; budget must cap it
scripted = ScriptedAgent(
['{"action":"search_docs","query":"x"}'] * 20, None
)
monkeypatch.setattr("agent.llm._raw_completion", scripted.plan)
monkeypatch.setattr(
"agent.tools.search_docs",
lambda q, library=None, kind=None, k=8: {"query": q, "sections": [], "titles": []},
)
captured = {}
monkeypatch.setattr(
"agent.grounded.answer_from_sections",
lambda q, s, referrals=None, provider=None, client=None: captured.setdefault("hit", True)
or Answer(answer_md="x"),
)
answer_agentic("q") # must terminate (MAX_STEPS / budget), not loop forever
assert captured["hit"]
def test_planner_kind_reaches_search_docs(monkeypatch):
# the planner decides the content space; its kind must reach the tool
scripted = ScriptedAgent(
['{"action":"search_docs","query":"cross entropy loss","kind":"api"}',
'{"action":"answer"}'],
None,
)
seen = []
def fake_search(query, library=None, kind=None, k=8):
seen.append((query, kind))
return {"query": query, "sections": [], "titles": []}
monkeypatch.setattr("agent.llm._raw_completion", scripted.plan)
monkeypatch.setattr("agent.tools.search_docs", fake_search)
monkeypatch.setattr(
"agent.grounded.answer_from_sections",
lambda q, s, referrals=None, provider=None, client=None: Answer(answer_md="ok"),
)
answer_agentic("what loss functions exist for classification?")
# seed search first (no kind), then the planner's api-scoped search
assert seen[0] == ("what loss functions exist for classification?", None)
assert seen[1] == ("cross entropy loss", "api")
|