fix: describe local intent answers to the model instead of replaying them
validate / hassfest (push) Failing after 1m2s
validate / hacs (push) Failing after 1m11s
test / pytest (push) Successful in 1m36s

With prefer_local_intents, Home Assistant puts requests its local intents
answered into the chat log as external calls of intents like
HassGetCurrentTime. The model does not have these tools, and replayed as
its own calls they make it apologise for using tools it does not have and
doubt answers it never gave. They are now passed as a developer note with
the intent, its arguments and its result.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
pc-heiniandClaude Opus 5.5 committed 2026-10-22 10:35:26 +02:00
1 parent e54f0f0fa1
commit 6aed494e44
3 files changed
+77 -1

No files matched your search

+26 -1
View File
@@ -180,13 +180,35 @@ def _convert_content_to_param(
"""Convert any native chat message for this agent to the native format."""
messages: ResponseInputParam = []
reasoning_summary: list[str] = []
# Requests that Home Assistant's local intents answered without the model
# (prefer_local_intents) are in the chat log as calls of intents like
# HassGetCurrentTime, which are not among the model's tools. Replayed as
# the model's own calls, they make it apologise for using tools it does
# not have, so they are described to it as a note instead.
local_calls: dict[str, llm.ToolInput] = {}
for content in chat_content:
if isinstance(content, conversation.ToolResultContent):
if (tool_call := local_calls.pop(content.tool_call_id, None)) is not None:
messages.append(
EasyInputMessageParam(
type="message",
role="developer",
content=(
"Home Assistant's local intent recognition answered "
"the last user request itself, without you: it ran "
f"{tool_call.tool_name} with "
f"{json_dumps(tool_call.tool_args)} and got "
f"{json_dumps(content.tool_result)}. The assistant "
"reply after this note is its wording, not yours. "
"That intent is not one of your tools."
),
)
)
# Earlier web searches are not replayed: the chat log only keeps
# their status, not their results, and a search without results
# makes the model disown the answer it based on that search.
if content.tool_name != "web_search_call":
elif content.tool_name != "web_search_call":
messages.append(
FunctionCallOutput(
type="function_call_output",
@@ -211,6 +233,9 @@ def _convert_content_to_param(
for tool_call in content.tool_calls:
if tool_call.external and tool_call.tool_name == "web_search_call":
continue
if tool_call.external and tool_call.tool_name != "code_interpreter":
local_calls[tool_call.id] = tool_call
continue
messages.append(
ResponseFunctionToolCallParam(
type="function_call",