ai: research mode runs on the compact tool catalog

Research mode switches to nine kind-generic tools: search_files,
get_file, read_content, search_zims, get_zim_entry, get_inventory,
list_files, read_file, search_places.  Tool definitions drop from 20
tools / 8,947 chars (~2,240 tokens) to 9 / 3,881 (~970) on every
request, a sixth of the 8k context the small tier runs with instead of
a quarter.  The remaining descriptions are trimmed toward 300 chars.

The replaced per-kind endpoints are removed (four searches, three
getters, three readers, search_suggestions, the slug inventory route);
their tests move onto the generic routes.  The research prompt, its
worked example, and the empty-search hint no longer tell the model to
list collections first: channels resolve by name from the library map
that is already in the prompt.  list_collections, list_tags, and
get_map_overview stay as endpoints for the MCP wrappers but leave the
chat allowlist; the MCP search_suggestions tool goes since search_files
returns the same matches inline.

scripts/ai_eval.py expects search_files and treats "what channels do I
have" as answerable from context.  On the 1.7B model against the dev
library both catalogs score 14/18, but the compact one answers with one
tool call where the old one needed up to five, no longer invents the
example's channel, and keeps kinds straight when asked for videos.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Roland 2026-09-10 16:28:16 -06:00
parent 6dcd5d323b
commit e65e28bfc2
No known key found for this signature in database
GPG key ID: 1BA620CE6C9A4680
13 changed files with 102 additions and 681 deletions

View file

@ -198,14 +198,16 @@ CATALOG = [
dict(name='ai/collections', method='GET', path='/api/ai/collections'),
dict(name='ai/inventories', method='GET', path='/api/ai/inventories'),
dict(name='ai/zims', method='GET', path='/api/ai/zims'),
dict(name='ai/search', method='POST', path='/api/ai/search', body={'search_str': '{q}', 'limit': 5}),
dict(name='ai/videos/search', method='POST', path='/api/ai/videos/search', body={'search_str': '{q}', 'limit': 5}),
dict(name='ai/archives/search', method='POST', path='/api/ai/archives/search',
body={'search_str': '{q}', 'limit': 5}),
dict(name='ai/docs/search', method='POST', path='/api/ai/docs/search', body={'search_str': '{q}', 'limit': 5}),
dict(name='ai/files/search', method='POST', path='/api/ai/files/search', body={'search_str': '{q}', 'limit': 5}),
dict(name='ai/files/search video', method='POST', path='/api/ai/files/search',
body={'search_str': '{q}', 'kind': 'video', 'limit': 5}),
dict(name='ai/files/search archive', method='POST', path='/api/ai/files/search',
body={'search_str': '{q}', 'kind': 'archive', 'limit': 5}),
dict(name='ai/files/search doc', method='POST', path='/api/ai/files/search',
body={'search_str': '{q}', 'kind': 'doc', 'limit': 5}),
dict(name='ai/zims/search', method='POST', path='/api/ai/zims/search', body={'search_str': '{q}', 'limit': 5}),
dict(name='ai/videos/<id>', method='GET', path='/api/ai/videos/{video_id}', needs='video_id'),
dict(name='ai/archives/<id>', method='GET', path='/api/ai/archives/{archive_id}', needs='archive_id'),
dict(name='ai/files/<video id>', method='GET', path='/api/ai/files/{video_id}', needs='video_id'),
dict(name='ai/files/<archive id>', method='GET', path='/api/ai/files/{archive_id}', needs='archive_id'),
dict(name='chat/modes', method='GET', path='/api/chat/modes'),
]