Refactor: Tool Annotations pattern for dynamic tool filtering
Context
Toize uses dynamic tool filtering to control LLM behavior based on conversation state. Currently this is done ad-hoc in route.ts, which becomes fragile as cases multiply.
Current Implementation
1. ToolSet Pattern (chat-tools.ts:1044-1079)
Static filtering by userType:
const toolsBySet = {
detection: { getNextStep, detectUserType, setQuestionHint },
buyer: { getNextStep, updateBuyerProfile, searchProperties, getNearbyCities, ... },
seller: { getNextStep, estimateProperty, getCityStats, ... },
landlord: { getNextStep, estimateRent, getCityStats, ... },
};
2. State-based filtering (route.ts:238-249)
Dynamic filtering added for "nearby cities" flow:
const hasNearbyCitiesContext = isNearbyCitiesTurn || !!profile.location.primaryArea;
const filteredTools = hasNearbyCitiesContext && !searchPerformed
? { ...tools, getNearbyCities: undefined, getAreasOverview: undefined }
: tools;
3. Disambiguation mode (route.ts:242)
if (isDisambiguationTurn) return {}; // Force pure text response
Problem
The ad-hoc approach in route.ts led to two bugs during the "Budget First" flow implementation:
- LLM called
getNearbyCitiesdirectly despite server-side orchestration - Tool filtering didn't persist across messages (fixed by checking
primaryArea)
As more flows are added, this pattern becomes harder to maintain.
Proposed Solution: Tool Annotations
Add declarative pre-requisites to tool definitions:
getNearbyCities: {
description: "Find nearby cities...",
// NEW: Declarative availability rules
availability: {
requires: { "profile.userType": ["acheteur", "locataire"] },
excludesWhen: { "profile.location.primaryArea": { exists: true } },
excludesFlags: ["searchPerformed"],
},
inputSchema: getNearbyCitiesSchema,
execute: async (params) => { ... },
}
Then filter tools declaratively:
const availableTools = filterToolsByState(allTools, {
profile: currentProfile,
flags: { searchPerformed, isDisambiguationTurn },
});
Benefits
| Aspect | Current | With Annotations |
|--------|---------|------------------|
| Tool availability logic | Scattered in route.ts | Co-located with tool definition |
| Adding new constraints | Edit multiple files | Edit tool definition only |
| Understanding tool behavior | Read route.ts + chat-tools.ts | Read tool definition |
| Testing | Integration tests only | Unit test per tool |
Scope
- [ ] Design
ToolAvailabilityschema - [ ] Add annotations to existing tools
- [ ] Create
filterToolsByState()utility - [ ] Migrate ad-hoc filtering from
route.ts - [ ] Add unit tests for tool availability
References
- Related patterns: Tool Gating, ReWOO (Reasoning WithOut Observation)
- Current implementation:
src/lib/ai/chat-tools.ts,src/app/api/chat/route.ts - Orchestration:
src/lib/ai/orchestration/
---
🤖 Generated with Claude Code
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗