perf(tools): stop embedding the layer list in every map-tool description (#225) - #271
Conversation
…ion (#225) Each layer-targeting map tool re-embedded the full layer roster via formatLayerList(), frozen at boot. That was 10 copies of a strict subset of the list DatasetCatalog.generatePromptCatalog() already injects once into the system prompt (which carries id + title + type + versions + default filters — strictly richer). Same composite `${ds.id}/${assetId}` IDs on both sides, so the per-tool copies added no information — just ~N×30-50 chars × 10 tools of redundant, boot-frozen text on every call, and extra tool-description surface that nudges wrong-tool selection. Drop the per-tool appends and the now-dead allLayers/vectorLayers/ formatLayerList helpers. Keep pickLayerNudge — that's the per-tool disambiguation rule, not the list. The roster still reaches the agent via the system-prompt catalog (browser: main.js; headless runner: generatePromptCatalog in its system prompt), and live truth is available via get_map_state. Test: replace the 'lists vector layers' assertion with one that locks in the new contract (no embedded list; nudge retained).
Coverage Report
File Coverage
|
||||||||||||||||||||||||||||||||||||||
🔬 Eval workflow & merge ordering (pickup notes)This PR should not merge on faith — validate it on the open model collection first, via the headless matrix. Steps, in order: 1. Merge the lever first. boettiger-lab/open-llm-proxy#43 adds 2. Run the A/B (from cat > runs/q.txt <<'Q'
show me protected areas in California
filter to GAP status 1
hide the carbon layer
Q
# baseline = current main
TAG=baseline GEO_AGENT_BRANCH=main \
QUESTIONS_FILE=runs/q.txt ./run-matrix-k8s.sh boettiger-lab/geo-agent-template
# fix = this branch
TAG=tooldesc GEO_AGENT_BRANCH=perf/tool-desc-drop-layer-list-225 \
QUESTIONS_FILE=runs/q.txt ./run-matrix-k8s.sh boettiger-lab/geo-agent-template3. Compare (after SELECT
regexp_extract(origin, 'agent_runner_(\w+)', 1) AS arm,
count(*) AS llm_calls,
round(avg(json_extract(tokens,'$.prompt_tokens')::BIGINT)) AS avg_prompt_tokens,
round(avg(json_extract(tokens,'$.completion_tokens')::BIGINT)) AS avg_completion_tokens,
sum(len(tool_calls)) AS total_tool_calls
FROM read_ndjson_auto('/tmp/open-llm-proxy-logs/*/*.jsonl', union_by_name=true)
WHERE type='response'
AND (origin LIKE '%agent_runner_baseline' OR origin LIKE '%agent_runner_tooldesc')
GROUP BY 1 ORDER BY 1;4. Decision gate. Merge this PR if the
|
✅ A/B eval results (open models, via open-llm-proxy headless matrix)Ran the A/B from the "Eval workflow & merge ordering" plan: Verdict: passes the gate — recommend merge. 1. Prefill tokens — the expected win ✅First-turn prefill (system prompt + tool defs + first user msg) dropped ~24% (~9.5k tokens), near-identical across every model with data — exactly the signature of a fixed-size layer roster that was embedded across 10 tool descriptions and is now sent once via the system-prompt catalog:
This is paid every turn, so the saving compounds over a multi-turn session. 2. Tool-call behavior — neutral-or-better ✅Within the cleanly-completed cells: same correct tool for each task in both arms ( Caveats (honest)
The prefill win is unambiguous and large; the correctness signal is neutral-or-better everywhere it could be measured. Reproduction details + queries live in |
Closes #225.
What
Each layer-targeting map tool (
show_layer,hide_layer,set_filter,clear_filter,reset_filter,set_tooltip,reset_tooltip,set_style,reset_style,filter_by_query— 10 sites) re-embedded the full layer roster viaformatLayerList(allLayers()/vectorLayers()). This drops those appends and the now-deadallLayers/vectorLayers/formatLayerListhelpers.Why it's safe (loses no information)
The layer roster is already injected once into the system prompt by
DatasetCatalog.generatePromptCatalog()— and richer:layer_id+ title + type + versions + default filters, vs. the per-toolid — displayNamesubset. Both sides key on the same composite${ds.id}/${assetId}ID (dataset-catalog.js:727 ==getLayerSummaries()keys), so there was no ID-format divergence — just redundancy.generatePromptCatalog()to the system prompt.open-llm-proxy/headless): same — its system prompt issystem-prompt.md + generatePromptCatalog() + MCP prompt.get_map_state.The per-tool copies were also frozen at boot (descriptions built once in
createMapTools), so runtime-addedhex-…layers never appeared in them anyway — dropping them removes a stale surface.pickLayerNudgeis kept per-tool — that's the disambiguation rule, not the list (per the issue's non-goals).Test
Replaced the
set_tooltip description lists vector layers onlyassertion with one that locks in the new contract across 5 tools (no embedded list, noAvailable layers:/Vector layers:headers) plus a check that the nudge is retained. Full suite: 344 passing.Validation plan (before fleet pin)
Apps pin stable releases, so merging here doesn't propagate. Before cutting a release, A/B this branch vs
mainacross the open-model collection via theopen-llm-proxyheadless matrix (prefill tokens + tool-call correctness). That requires a smallGEO_AGENT_BRANCHparam in the matrix Job — separate PR in open-llm-proxy.