- Rewrite system prompt with negative constraints and step-by-step process - Set temperature to 0.1 for deterministic tool calling - Sort room groups to top of device list (address positional bias) - Add [ROOM GROUP] marker in list_devices output - Update tool docstrings with explicit entity_id= parameter examples - Add optimization findings doc (experiment log: 0% → 100% success) - Add test script for room group detection regression testing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
10 KiB
Housekeeper Agent Optimization Findings
Background
Research with Gemini identified key issues with mistral-nemo and tool calling:
- "Pre-computation Hallucination" - model answers before using tools
- High default temperature (0.7-0.8) causes wandering
- Model is "chatty and confident" - needs explicit constraints
Key Recommendations from Gemini Research
- Temperature 0.0 for tool-calling agents (deterministic, follows schema)
- Chain of Thought (CoT) - force step-by-step reasoning
- Negative constraints - tell model what NOT to do (Nemo responds better)
- Explicit tool descriptions - verbose docstrings with "never estimate yourself"
- "Strictly tool-based assistant" pattern - NO internal knowledge claim
Experiment Log
Baseline (v1.8.6)
- Date: 2025-12-17
- Configuration: Default temperature, improved prompt requiring list_devices first
- Results:
- Called list_devices first ✓
- Still hallucinated
light.study_deskdespite seeing list with onlylight.studyandlight.study_main - Partial success: turned off
light.study_main, failed on hallucinated entity
- Success rate: ~50% (1 of 2 study lights controlled correctly)
Experiment 1: Temperature 0.0
-
Date: 2025-12-18
-
Change: Set
model_settings=ModelSettings(temperature=0.0)for Housekeeper -
Hypothesis: Deterministic output will force model to use exact entity IDs from tool results
-
Results:
Study lights test:
- Called
list_devices()first ✓ (but no domain filter) - Used wrong parameter
device_idinstead ofentity_id(recovered after validation error) - Only identified
light.studeerlampas "study" related (Dutch name) - Missed
light.studyandlight.study_main- didn't match English "study" - Turned off 1 wrong light, missed 2 actual study lights
Kitchen lights test:
- Called
list_devices()first ✓ (no domain filter) - Saw full device list including
light.kitchen - Used wrong parameter
device_idinstead ofentity_id(recovered after validation) - After correction, dropped domain prefix: used
kitcheninstead oflight.kitchen - 404 error - device not found
- Called
-
Success rate: 0% (no target lights successfully controlled)
-
Observations:
- Temperature 0.0 alone is insufficient
- Model consistently confuses
device_idvsentity_idparameter name - After validation error correction, model truncates entity_id (drops domain prefix)
- Semantic matching of room names to devices is weak
- Model doesn't understand entity_id format:
domain.name
Experiment 2: Negative Constraints + CoT
-
Date: 2025-12-18
-
Change: Complete prompt rewrite with:
- "You have NO Internal Knowledge" - negative framing
- Explicit entity_id format with WRONG/RIGHT examples
- Step-by-step process (ALWAYS FOLLOW)
- Explicit parameter names section
- "What NOT To Do" negative constraints
-
Hypothesis: Negative constraints work better with Mistral-Nemo
-
Results:
Study lights test:
- Called
list_devices(domain="light")✓ with domain filter (improvement!) - Still used
device_idfirst, recovered toentity_idafter validation error - After recovery, used correct full format:
light.studeerlamp - Still only matched
studeerlampnotlight.studyorlight.study_main
Kitchen lights test:
- Called
list_devices(domain="light")✓ - Called
turn_off(entity_id="light.kitchen")✓ correct format! - All 4 kitchen lights turned off (light.kitchen is a group)
- 100% success for kitchen!
- Called
-
Success rate:
- Study: 0% (wrong semantic match)
- Kitchen: 100% (4/4 lights off)
- Combined: ~50% (1 of 2 tests successful)
-
Observations:
- Domain filter now consistently used ✓
- Entity_id format correct after recovery ✓
- Semantic matching still fails for "study" → prefers Dutch "studeerlamp" over English "study"
- Parameter name confusion persists (
device_idvsentity_id) - Simple room names (kitchen) work; mixed language fails (study/studeerlamp)
Experiment 3: Temperature 0.1 + Explicit Tool Docstrings
- Date: 2025-12-18
- Change:
- Temperature 0.1
- Updated turn_on/turn_off docstrings with explicit
entity_id=in examples
- Results:
- Still uses
device_idfirst, recovers toentity_idafter validation - Still picks wrong entity (studeerlamp over study)
- Still uses
- Success rate: 0%
Experiment 4: Room Group Priority (with explicit examples)
-
Date: 2025-12-18
-
Change: Updated prompt with:
- Explicit instruction: "Look for EXACT match
light.<room_name>first!" - Concrete examples: "For 'study lights' → look for
light.study" - Working example showing
turn_off(entity_id="light.study")
- Explicit instruction: "Look for EXACT match
-
Hypothesis: Explicit examples will guide model to use room groups
-
Results:
Test 1 & 2 (consecutive):
- Called
list_devices(domain="light")✓ - Device list clearly shows
light.studyat the bottom - First call:
turn_off({"devices":["studeerlamp"]})- wrong param AND wrong device - After validation error:
turn_off(entity_id="light.studeerlamp")- correct param, still wrong device - Completely ignored
light.studydespite prompt explicitly saying to use it
- Called
-
Success rate: 0% (wrong device controlled)
-
Observations:
- Model ignores explicit step-by-step instructions in favor of substring matching
- Dutch "studeerlamp" contains "studer" which the model prefers over exact "study" match
- Even when prompt has a literal example
turn_off(entity_id="light.study"), model useslight.studeerlamp - Positional bias possible -
light.studyappears at end of 21-item list - Fundamental limitation: Mistral-Nemo cannot follow explicit matching rules
Experiment 5: Room Groups First (Tool Output Ordering)
- Date: 2025-12-18
- Change: Modified
list_devicesto sort room groups to top of list using HA attributes (is_hue_group,hue_type="room") - Hypothesis: Positional bias - model focuses on items earlier in list
- Results:
- Room groups (
light.study,light.kitchen, etc.) now appear first in device list - Combined with improved prompt, model now consistently uses room groups
- 70% success rate (7/10 tests) with default q4 quantization
- Room groups (
Experiment 6: Model Quantization (q5_1)
-
Date: 2025-12-18
-
Change: Upgraded from default Mistral-Nemo quantization (q4) to
mistral-nemo:12b-instruct-2407-q5_1 -
Hypothesis: Higher precision weights improve tool calling accuracy
-
Results:
Test Action Result 1 Turn off study PASS 2 Turn on study PASS 3 Toggle study PASS 4 Turn off kitchen PASS 5 Turn on kitchen PASS 6 Toggle kitchen PASS 7 Turn off bedroom PASS 8 Turn on bedroom PASS 9 Turn off living room PASS 10 Turn on living room PASS -
Success rate: 100% (10/10 tests)
-
Observations:
- q5_1 quantization dramatically improves tool calling accuracy
- All room groups correctly identified and used
- No parameter confusion (
entity_idused correctly) - No entity_id truncation issues
- Toggle operations now work reliably
- Model fits within 10GB VRAM (q6 did not)
Experiment 7: Device List in System Prompt (Context Injection)
- Date: [PENDING]
- Change: Store device list in database (per user/household) and inject into system prompt
- Approach:
- Periodically sync device list from Home Assistant to PostgreSQL
- On each Housekeeper invocation, fetch device list and include in prompt
- Remove need for model to call list_devices() - just match from context
- Hypothesis:
- Eliminates tool call step where errors occur
- Reduces context size by not returning full device list as tool output
- Makes entity matching a language task (in prompt) rather than tool result parsing
- Trade-offs:
- Stale data if sync is infrequent
- Prompt size increase (but less than tool call response)
- Need sync mechanism and storage
- Results: [TO BE RECORDED]
- Success rate: [TO BE RECORDED]
Key Problem Identified (Solved)
The model struggled with:
- Parameter schema adherence - uses
device_idwhen schema requiresentity_id - Value preservation - truncates values after validation errors (drops
light.prefix) - Semantic matching - prefers substring matches ("studeerlamp" contains "studer") over exact matches (
light.study) - Following explicit instructions - ignores step-by-step processes even when examples are provided
- Positional bias - may not "see" items at the end of long lists
Solution: These issues were resolved by:
- Using q5_1 quantization instead of default q4 (higher precision weights)
- Sorting room groups to top of device list (address positional bias)
- Explicit prompt guidance with negative constraints and examples
Potential Next Experiments
Experiment 5: Room Groups First (List Ordering)
- Hypothesis: Positional bias - model focuses on items earlier in list
- Change: Sort device list to put room groups (entities matching
light.<single_word>) at the TOP - Effort: Low - modify list_devices output formatting
- Risk: May affect other use cases where individual devices are needed
Experiment 6: Simplified Device List Format
- Hypothesis: Markdown formatting adds noise that confuses the model
- Change: Return simple list:
light.study (Study - GROUP), light.study_main (Ceiling light), ... - Effort: Low - modify list_devices output
- Risk: Less human-readable responses
Learnings to Apply Elsewhere
- Quantization matters - q5_1 dramatically outperforms q4 for tool calling (100% vs 70%)
- Positional bias is real - sort important items to top of lists
- Smaller models need simpler workflows - fewer tool calls, more context injection
- Validation errors don't teach - model often makes worse mistakes on retry
- Entity IDs are hard - domain.name format confuses the model
- Consider pre-computation - move matching logic to code, not LLM
- Use explicit negative constraints - "NEVER do X" works better than "always do Y"
Notes
- Librarian may need higher temperature for creative synthesis
- All "action" agents (Housekeeper, future agents) should use low temperature
- Consider testing with Gemma 2 9B for better function calling (Google, open weights)