fix(tool-search): omit empty search corpus
Problem
After tool discovery, a CompactionPart can remove the discovery conversation while the runtime still keeps the discovered tool callable. The local search_tools function remained exposed with an empty corpus, so a model could retry a search that can only return no matches.
Root Cause
ToolSearchToolset.get_tools() checked whether any deferred tools were searchable before building the corpus. It did not check whether all searchable tools had already been revealed.
Solution
Build the local search tool first and only expose it when its undiscovered corpus is non-empty.
Changes
- Omit
search_toolswhen all searchable deferred tools are already revealed. - Keep revealed tools callable after compaction.
- Add a regression test covering discovery followed by
CompactionPart.
Testing
PYTHONPATH=pydantic_ai_slim;. py -3 -m pytest --import-mode=importlib tests/test_tool_search.py -k "tool_search_toolset or parse_discovered_tools" -q? 37 passed.py -3 -m ruff format --check? passed.py -3 -m ruff check? passed.py -3 -m pyright --pythonpath D:\Python310\python.exe pydantic_ai_slim\pydantic_ai\toolsets\_tool_search.py? passed.git diff --check? passed.- Full
tests/test_tool_search.pywas attempted; 138 passed, 66 unrelated provider/cassette failures and 21 environment errors occurred because this incremental checkout lacks the full test helper/cassette surface and provider fixtures. Those failures are not counted as passed.
Compatibility / Risk
This is a narrow behavior change for the empty-corpus case only. Revealed tools remain available; searches remain available whenever at least one deferred tool is still undiscovered. Native tool-search behavior is unchanged.
Notes for Reviewer
The test models the reported sequence: a typed discovery, then a CompactionPart, with parse_discovered_tools() preserving the callable tool names. The search surface is then omitted because no searchable corpus remains.
Linked Issue
Fixes #7189