Multi-turn / agentic tool-calling eval
Why (the blind spot)
Everything we’ve measured is single-turn. Our headline (Qwen3-4B-2507 bf16 = 88.7, frontier 98) says nothing about holding a conversation — observing a tool result and acting on it across turns, tracking state. The literature is blunt about the gap:
| Model | single-turn | multi-turn |
|---|---|---|
| Command-R7B | 69% | 5% |
| Llama-3.1-8B | 61% | 9.6% |
| xLAM-2-8b-fc (purpose-tuned) | — | ~69% |
So our 88.7 likely overstates these models as agents by a wide margin — and we
have zero measurement of the real number. This is the prerequisite capability
for everything next: a Jarvis-style assistant (Pace) and self-improving game NPCs
are both multi-turn agents. See
docs/learn/small-model-tool-calling-playbook.md §4.
Goal
A Mac-local, stateful multi-turn tool-calling eval that, for a given model:
- runs a multi-turn task where the model’s calls execute against a backend and results feed back across turns,
- scores end-to-end task completion (final backend state matches gold) — not just per-call AST,
- is frontier-gated (per our eval philosophy: a sound eval → a frontier model aces it; if not, fix the eval before trusting Mac-model numbers),
- reports the single→multi-turn drop for our best 4B vs frontier.
Data + matching
- Source: BFCL-v4
multi_turn_base(200 examples, ~4 turns each) — already on disk at~/.cache/tinygpt/datasets/_external/gorilla-bfcl/.../data/. Each example hasquestion(turn-list),initial_config(backend state),involved_classes(the stateful Python backends, e.g.GorillaFileSystem), and a gold actionpath. - Matching: state-based — instantiate the backend from
initial_config, execute the model’s calls turn by turn, feed results back into the transcript, and at the end compare the resulting state (and/or the executed-call path) to gold. This is BFCL’s own multi-turn protocol; we replicate the executor. - Stretch: a τ-bench-style slice (user-simulator + DB-state check + Pass^k reliability) once the BFCL executor works.
Design / scaffolding
scripts/bfcl_multiturn_eval.py (skeleton in this PR):
- Load example → instantiate
involved_classeswithinitial_config. - For each turn: render transcript (system tools + prior turns + tool results) → model emits call(s) → execute against the backend instances → append results → next turn.
- After the last turn: compare final state / executed path to gold → pass/fail.
- Backends:
local(MLX) +frontier(claude -p), reusingbfcl_ast_eval.py’s call parser.
The hard part is faithfully executing calls against BFCL’s backend classes (import
or vendor them from the gorilla repo). Skeleton marks these as TODO(executor).
Acceptance criteria
- Frontier (
claude -p) scores high (~90%+) on the slice — if not, the eval / golds are broken; fix before reporting Mac numbers (frontier-ceiling gate). - Report our best 4B (Qwen3-4B-2507 bf16) multi-turn vs its single-turn 88.7 — quantify the cliff.
- Run ≥1 small model + frontier; land the numbers in
docs/learn/tool-calling-frontier-parity.md.
Risks / notes
- Executor fidelity is the main cost — replicating BFCL’s stateful backends.
Mitigation: vendor the gorilla
multi_turnexecutor rather than reimplement. - Contamination: BFCL multi-turn backend Python overlaps training data (known issue); note it, don’t over-claim.
- This is a build, not a quick win — hence a PRD, not an inline change.
Next after this
If small models cliff hard (expected), the lever is multi-turn RL in an open-ended environment — which is the sister PR (game-as-RL-environment PoC).