Merge pull request 'The call record: every model call, for the operator's study (M6.6a)' (#179) from call-record into main
This commit was merged in pull request #179.
This commit is contained in:
commit
c1f3d9dafb
22 files changed
+1346
-48
No files matched your search
@@ -467,3 +467,26 @@ def test_runs_from_before_the_driver_read_as_its_defaults() -> None:
|
||||
old["tuning"]["executor"] = old["tuning"].pop("driver")
|
||||
[row] = summarise([old], current)
|
||||
assert row.tuning == ""
|
||||
|
||||
|
||||
def test_a_kept_run_leaves_its_state_and_call_record(tmp_path: Path) -> None:
|
||||
# As the agent records itself (L7), so the status page can open a
|
||||
# benchmark run (X6); each run gets its own record, and the client is
|
||||
# left as it was.
|
||||
from nisse_core.models.calls import CallReader
|
||||
|
||||
[task] = load_tasks(["write-function"])
|
||||
client = Scripted(list(SOLVE_SLUGIFY))
|
||||
record = run_task(task, FACTS, client, Tuning(), host=HOST, keep=tmp_path / "runs")
|
||||
|
||||
[kept] = list((tmp_path / "runs").iterdir())
|
||||
assert kept.name.startswith("write-function-")
|
||||
assert (kept / "state.db").exists()
|
||||
[run] = CallReader(kept / "state.calls.db").runs()
|
||||
assert (run["work_item"], run["role"], run["outcome"]) == (
|
||||
"bench",
|
||||
"driver",
|
||||
"wait",
|
||||
)
|
||||
assert record.success
|
||||
assert not hasattr(client, "call_recorder")
|
||||
@@ -573,3 +573,28 @@ def test_the_vault_is_read() -> None:
|
||||
def test_a_bad_vault_is_refused(vault: dict[str, Any], said: str) -> None:
|
||||
with pytest.raises(ConfigError, match=re.escape(said)):
|
||||
build(vault=vault)
|
||||
|
||||
|
||||
def test_the_call_record_is_on_unless_turned_off() -> None:
|
||||
assert build().call_record is True
|
||||
assert build(call_record=False).call_record is False
|
||||
with pytest.raises(ConfigError, match="call_record"):
|
||||
build(call_record="no")
|
||||
|
||||
|
||||
def test_both_routes_record_into_one_file_beside_the_state_store(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
from nisse_core.models.catalog import ModelCatalog
|
||||
from nisse_dev.loop.build import make_llm_clients
|
||||
|
||||
normalized, passthrough = make_llm_clients(
|
||||
build(state_db=str(tmp_path / "state.db")), ModelCatalog({})
|
||||
)
|
||||
assert normalized.call_recorder is passthrough.call_recorder
|
||||
assert normalized.call_recorder.path == tmp_path / "state.calls.db"
|
||||
off, _ = make_llm_clients(
|
||||
build(state_db=str(tmp_path / "other.db"), call_record=False), ModelCatalog({})
|
||||
)
|
||||
assert off.call_recorder is None
|
||||
assert not (tmp_path / "other.calls.db").exists()
|
||||
@@ -702,3 +702,25 @@ def test_a_bad_request_is_not_given_the_benefit_of_the_doubt(
|
||||
result = build(BadRequestClient([]), store, ws, repo).run(brief="b", branch="d")
|
||||
assert result.status == "failed"
|
||||
assert not result.interrupted
|
||||
|
||||
|
||||
def test_a_delegation_is_recorded_as_a_run_of_its_own(
|
||||
store: StateStore, ws: Workspace, repo: GitRepo
|
||||
) -> None:
|
||||
# Its calls are the helper's turns (L7), inside the run that delegated.
|
||||
from nisse_core.models import calls
|
||||
|
||||
seen: list[calls.Scope] = []
|
||||
|
||||
class Watching(ScriptedClient):
|
||||
def complete(self, *args: Any, **kwargs: Any) -> list[CanonicalEvent]:
|
||||
seen.append(calls.current())
|
||||
return super().complete(*args, **kwargs)
|
||||
|
||||
client = Watching([[hand_back(succeeded=False, changed="nothing")]])
|
||||
with calls.run(None, work_item=ITEM, role="driver"):
|
||||
outer = calls.current().run
|
||||
build(client, store, ws, repo).run(brief="look into it", branch="d1")
|
||||
[scope] = seen
|
||||
assert (scope.role, scope.work_item, scope.parent) == ("helper", ITEM, outer)
|
||||
assert scope.run not in (None, outer)
|
||||
@@ -346,3 +346,27 @@ def test_the_export_script_reads_a_store(tmp_path: Path) -> None:
|
||||
lines = [json.loads(line) for line in run.stdout.splitlines()]
|
||||
assert [line["verdict"] for line in lines] == ["needs_discussion", "safe"]
|
||||
assert lines[0]["input"]["action"]["arguments"] == {"command": "x"}
|
||||
|
||||
|
||||
def test_the_classifiers_calls_are_recorded_as_its_own() -> None:
|
||||
# In the call record (L7), a verdict is the classifier's call, not a
|
||||
# turn of the run that asked for it.
|
||||
from nisse_core.models import calls
|
||||
|
||||
seen: list[calls.Scope] = []
|
||||
|
||||
class Watching(Judge):
|
||||
def complete(self, events, tools, model, system=None) -> list[Any]:
|
||||
seen.append(calls.current())
|
||||
return super().complete(events, tools, model, system)
|
||||
|
||||
classifier = ActionClassifier(Watching(verdict("contained")), "local") # type: ignore[arg-type]
|
||||
with calls.run(None, work_item="w", role="driver"):
|
||||
classifier.judge(Action("run_command", {"command": "make"}), SCOPE, "/w")
|
||||
[scope] = seen
|
||||
assert (scope.role, scope.purpose, scope.work_item) == (
|
||||
"classifier",
|
||||
"classification",
|
||||
"w",
|
||||
)
|
||||
assert scope.run is not None
|
||||
@@ -316,3 +316,19 @@ def test_a_restatement_that_asks_is_sent_back(tmp_path: Path) -> None:
|
||||
if hasattr(e.event, "result")
|
||||
]
|
||||
assert any("a restatement asks no questions" in r for r in results)
|
||||
|
||||
|
||||
def test_the_go_ahead_readers_calls_are_recorded_as_the_classifiers() -> None:
|
||||
from nisse_core.models import calls
|
||||
|
||||
seen: list[calls.Scope] = []
|
||||
|
||||
class Watching(_Model):
|
||||
def complete(self, *args: Any, **kwargs: Any) -> Any:
|
||||
seen.append(calls.current())
|
||||
return super().complete(*args, **kwargs)
|
||||
|
||||
yes = [ToolCall(call_id="v", tool="verdict", arguments={"answer": "yes"})]
|
||||
GoAheadReader(Watching(yes), "m").agrees("add count_words", "ok") # type: ignore[arg-type]
|
||||
[scope] = seen
|
||||
assert (scope.role, scope.purpose) == ("classifier", "go-ahead reading")
|
||||
Reference in new issue
Block a user