mirror of
https://github.com/home-assistant/core.git
synced 2026-10-08 07:19:32 -04:00
Remove support for Anthropic retired models (#182979)
This commit is contained in:
@@ -56,9 +56,7 @@ class AnthropicTaskEntity(
|
||||
chat_log: conversation.ChatLog,
|
||||
) -> ai_task.GenDataTaskResult:
|
||||
"""Handle a generate data task."""
|
||||
await self._async_handle_chat_log(
|
||||
chat_log, task.name, task.structure, max_iterations=1000
|
||||
)
|
||||
await self._async_handle_chat_log(chat_log, task.structure, max_iterations=1000)
|
||||
|
||||
if not isinstance(chat_log.content[-1], conversation.AssistantContent):
|
||||
raise HomeAssistantError(
|
||||
|
||||
@@ -37,10 +37,9 @@ async def async_create_client(
|
||||
@callback
|
||||
def model_alias(model_id: str) -> str:
|
||||
"""Resolve alias from versioned model name."""
|
||||
if model_id[-2:-1] != "-" and not model_id.endswith("-preview"):
|
||||
model_id = model_id[:-9]
|
||||
if model_id.endswith("-4"):
|
||||
return model_id + "-0"
|
||||
model, _, version = model_id.rpartition("-")
|
||||
if len(version) == 8 and version.isdecimal():
|
||||
return model
|
||||
return model_id
|
||||
|
||||
|
||||
|
||||
@@ -57,9 +57,6 @@ from anthropic.types import (
|
||||
ThinkingConfigDisabledParam,
|
||||
ThinkingConfigEnabledParam,
|
||||
ThinkingDelta,
|
||||
ToolChoiceAnyParam,
|
||||
ToolChoiceAutoParam,
|
||||
ToolChoiceToolParam,
|
||||
ToolParam,
|
||||
ToolSearchToolBm25_20251119Param,
|
||||
ToolSearchToolResultBlock,
|
||||
@@ -111,7 +108,7 @@ from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import device_registry as dr, llm
|
||||
from homeassistant.helpers.json import json_dumps
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
from homeassistant.util import dt as dt_util, slugify
|
||||
from homeassistant.util import dt as dt_util
|
||||
from homeassistant.util.json import JsonArrayType, JsonObjectType
|
||||
|
||||
from .const import (
|
||||
@@ -528,12 +525,10 @@ class AnthropicDeltaStream:
|
||||
self,
|
||||
chat_log: conversation.ChatLog,
|
||||
stream: AsyncStream[MessageStreamEvent],
|
||||
output_tool: str | None = None,
|
||||
) -> None:
|
||||
"""Initialize the delta stream."""
|
||||
self._chat_log: conversation.ChatLog = chat_log
|
||||
self._stream: AsyncStream[MessageStreamEvent] = stream
|
||||
self._output_tool: str | None = output_tool
|
||||
|
||||
self._buffer: deque[
|
||||
conversation.AssistantContentDeltaDict
|
||||
@@ -666,15 +661,6 @@ class AnthropicDeltaStream:
|
||||
input=input,
|
||||
)
|
||||
self._current_tool_args = ""
|
||||
if name == self._output_tool:
|
||||
if self._first_block or self._content_details.has_content():
|
||||
if self._content_details:
|
||||
self._content_details.delete_empty()
|
||||
self._buffer.append({"native": self._content_details})
|
||||
self._content_details = ContentDetails()
|
||||
self._content_details.add_citation_detail()
|
||||
self._buffer.append({"role": "assistant"})
|
||||
self._first_block = False
|
||||
|
||||
def on_text_block(self, text: str, citations: list[TextCitation] | None) -> None:
|
||||
"""Handle TextBlock."""
|
||||
@@ -814,14 +800,7 @@ class AnthropicDeltaStream:
|
||||
|
||||
def on_input_json_delta(self, partial_json: str) -> None:
|
||||
"""Handle InputJSONDelta."""
|
||||
if (
|
||||
self._current_tool_block is not None
|
||||
and self._current_tool_block["name"] == self._output_tool
|
||||
):
|
||||
self._content_details.citation_details[-1].length += len(partial_json)
|
||||
self._buffer.append({"content": partial_json})
|
||||
else:
|
||||
self._current_tool_args += partial_json
|
||||
self._current_tool_args += partial_json
|
||||
|
||||
def on_text_delta(self, text: str) -> None:
|
||||
"""Handle TextDelta."""
|
||||
@@ -845,9 +824,6 @@ class AnthropicDeltaStream:
|
||||
def on_content_block_stop_event(self, index: int) -> None:
|
||||
"""Handle RawContentBlockStopEvent."""
|
||||
if self._current_tool_block is not None:
|
||||
if self._current_tool_block["name"] == self._output_tool:
|
||||
self._current_tool_block = None
|
||||
return
|
||||
tool_args = (
|
||||
json.loads(self._current_tool_args) if self._current_tool_args else {}
|
||||
)
|
||||
@@ -928,12 +904,11 @@ class AnthropicBaseLLMEntity(CoordinatorEntity[AnthropicCoordinator]):
|
||||
entry_type=dr.DeviceEntryType.SERVICE,
|
||||
)
|
||||
|
||||
async def _get_model_args( # noqa: C901
|
||||
async def _get_model_args(
|
||||
self,
|
||||
chat_log: conversation.ChatLog,
|
||||
structure_name: str | None = None,
|
||||
structure: probatio.Schema | None = None,
|
||||
) -> tuple[MessageCreateParamsStreaming, str | None]:
|
||||
) -> MessageCreateParamsStreaming:
|
||||
"""Get the model arguments."""
|
||||
options: dict[str, Any] = DEFAULT | self.subentry.data
|
||||
|
||||
@@ -1110,77 +1085,21 @@ class AnthropicBaseLLMEntity(CoordinatorEntity[AnthropicCoordinator]):
|
||||
)
|
||||
)
|
||||
|
||||
if structure and structure_name:
|
||||
if (
|
||||
self.model_info.capabilities
|
||||
and self.model_info.capabilities.structured_outputs.supported
|
||||
):
|
||||
# Native structured output for those models who support it.
|
||||
structure_name = None
|
||||
model_args.setdefault("output_config", OutputConfigParam())[
|
||||
"format"
|
||||
] = JSONOutputFormatParam(
|
||||
if structure:
|
||||
model_args.setdefault("output_config", OutputConfigParam())["format"] = (
|
||||
JSONOutputFormatParam(
|
||||
type="json_schema",
|
||||
schema={
|
||||
**probatio.to_openapi(
|
||||
schema=anthropic.transform_schema(
|
||||
probatio.to_openapi(
|
||||
structure,
|
||||
custom_serializer=chat_log.llm_api.custom_serializer
|
||||
if chat_log.llm_api
|
||||
else llm.selector_serializer,
|
||||
openapi_version="3.1.0",
|
||||
),
|
||||
"additionalProperties": False,
|
||||
},
|
||||
)
|
||||
),
|
||||
)
|
||||
elif model_args["thinking"]["type"] == "disabled":
|
||||
structure_name = slugify(structure_name)
|
||||
if not tools:
|
||||
# Simplest case: no tools and no extended thinking
|
||||
# Add a tool and force its use
|
||||
model_args["tool_choice"] = ToolChoiceToolParam(
|
||||
type="tool",
|
||||
name=structure_name,
|
||||
)
|
||||
else:
|
||||
# Second case: tools present but no extended thinking
|
||||
# Allow the model to use any tool but not text response
|
||||
# The model should know to use the right tool by its description
|
||||
model_args["tool_choice"] = ToolChoiceAnyParam(
|
||||
type="any",
|
||||
)
|
||||
else:
|
||||
# Extended thinking is enabled. With extended thinking, we cannot
|
||||
# force tool use or disable text responses, so we add a hint to the
|
||||
# system prompt instead. With extended thinking, the model should be
|
||||
# smart enough to use the tool.
|
||||
structure_name = slugify(structure_name)
|
||||
model_args["tool_choice"] = ToolChoiceAutoParam(
|
||||
type="auto",
|
||||
)
|
||||
|
||||
model_args["system"].append( # type: ignore[union-attr]
|
||||
TextBlockParam(
|
||||
type="text",
|
||||
text=f"Claude MUST use the '{structure_name}' tool to provide "
|
||||
"the final answer instead of plain text.",
|
||||
)
|
||||
)
|
||||
|
||||
if structure_name:
|
||||
tools.append(
|
||||
ToolParam(
|
||||
name=structure_name,
|
||||
description="Use this tool to reply to the user",
|
||||
input_schema=probatio.to_openapi(
|
||||
structure,
|
||||
custom_serializer=chat_log.llm_api.custom_serializer
|
||||
if chat_log.llm_api
|
||||
else llm.selector_serializer,
|
||||
openapi_version="3.1.0",
|
||||
),
|
||||
)
|
||||
)
|
||||
preloaded_tools.append(structure_name)
|
||||
)
|
||||
|
||||
if tools:
|
||||
if options[CONF_TOOL_SEARCH] and len(tools) > len(preloaded_tools) + 1:
|
||||
@@ -1196,19 +1115,16 @@ class AnthropicBaseLLMEntity(CoordinatorEntity[AnthropicCoordinator]):
|
||||
|
||||
model_args["tools"] = tools
|
||||
|
||||
return model_args, structure_name
|
||||
return model_args
|
||||
|
||||
async def _async_handle_chat_log(
|
||||
self,
|
||||
chat_log: conversation.ChatLog,
|
||||
structure_name: str | None = None,
|
||||
structure: probatio.Schema | None = None,
|
||||
max_iterations: int = MAX_TOOL_ITERATIONS,
|
||||
) -> None:
|
||||
"""Generate an answer for the chat log."""
|
||||
model_args, structure_name = await self._get_model_args(
|
||||
chat_log, structure_name, structure
|
||||
)
|
||||
model_args = await self._get_model_args(chat_log, structure)
|
||||
coordinator = self.entry.runtime_data
|
||||
client = coordinator.client
|
||||
|
||||
@@ -1222,11 +1138,7 @@ class AnthropicBaseLLMEntity(CoordinatorEntity[AnthropicCoordinator]):
|
||||
content
|
||||
async for content in chat_log.async_add_delta_content_stream(
|
||||
self.entity_id,
|
||||
AnthropicDeltaStream(
|
||||
chat_log,
|
||||
stream,
|
||||
output_tool=structure_name or None,
|
||||
),
|
||||
AnthropicDeltaStream(chat_log, stream),
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
@@ -53,6 +53,154 @@ from anthropic.types.web_fetch_tool_result_block import (
|
||||
)
|
||||
|
||||
model_list = [
|
||||
ModelInfo(
|
||||
id="claude-opus-5-5",
|
||||
capabilities=ModelCapabilities(
|
||||
batch=CapabilitySupport(supported=True),
|
||||
citations=CapabilitySupport(supported=True),
|
||||
code_execution=CapabilitySupport(supported=True),
|
||||
context_management=ContextManagementCapability(
|
||||
clear_thinking_20251015=CapabilitySupport(supported=True),
|
||||
clear_tool_uses_20250919=CapabilitySupport(supported=True),
|
||||
compact_20260112=CapabilitySupport(supported=True),
|
||||
supported=True,
|
||||
),
|
||||
effort=EffortCapability(
|
||||
high=CapabilitySupport(supported=True),
|
||||
low=CapabilitySupport(supported=True),
|
||||
max=CapabilitySupport(supported=True),
|
||||
medium=CapabilitySupport(supported=True),
|
||||
supported=True,
|
||||
xhigh=CapabilitySupport(supported=True),
|
||||
),
|
||||
image_input=CapabilitySupport(supported=True),
|
||||
pdf_input=CapabilitySupport(supported=True),
|
||||
structured_outputs=CapabilitySupport(supported=True),
|
||||
thinking=ThinkingCapability(
|
||||
supported=True,
|
||||
types=ThinkingTypes(
|
||||
adaptive=CapabilitySupport(supported=True),
|
||||
enabled=CapabilitySupport(supported=False),
|
||||
),
|
||||
),
|
||||
),
|
||||
created_at=datetime.datetime(2026, 9, 21, 16, 24, tzinfo=datetime.UTC),
|
||||
display_name="Claude Opus 5.5",
|
||||
max_input_tokens=1000000,
|
||||
max_tokens=128000,
|
||||
type="model",
|
||||
),
|
||||
ModelInfo(
|
||||
id="claude-fable-5-1",
|
||||
capabilities=ModelCapabilities(
|
||||
batch=CapabilitySupport(supported=True),
|
||||
citations=CapabilitySupport(supported=True),
|
||||
code_execution=CapabilitySupport(supported=True),
|
||||
context_management=ContextManagementCapability(
|
||||
clear_thinking_20251015=CapabilitySupport(supported=True),
|
||||
clear_tool_uses_20250919=CapabilitySupport(supported=True),
|
||||
compact_20260112=CapabilitySupport(supported=True),
|
||||
supported=True,
|
||||
),
|
||||
effort=EffortCapability(
|
||||
high=CapabilitySupport(supported=True),
|
||||
low=CapabilitySupport(supported=True),
|
||||
max=CapabilitySupport(supported=True),
|
||||
medium=CapabilitySupport(supported=True),
|
||||
supported=True,
|
||||
xhigh=CapabilitySupport(supported=True),
|
||||
),
|
||||
image_input=CapabilitySupport(supported=True),
|
||||
pdf_input=CapabilitySupport(supported=True),
|
||||
structured_outputs=CapabilitySupport(supported=True),
|
||||
thinking=ThinkingCapability(
|
||||
supported=True,
|
||||
types=ThinkingTypes(
|
||||
adaptive=CapabilitySupport(supported=True),
|
||||
enabled=CapabilitySupport(supported=False),
|
||||
),
|
||||
),
|
||||
),
|
||||
created_at=datetime.datetime(2026, 8, 28, 0, 0, tzinfo=datetime.UTC),
|
||||
display_name="Claude Fable 5.1",
|
||||
max_input_tokens=1000000,
|
||||
max_tokens=128000,
|
||||
type="model",
|
||||
),
|
||||
ModelInfo(
|
||||
id="claude-opus-5",
|
||||
capabilities=ModelCapabilities(
|
||||
batch=CapabilitySupport(supported=True),
|
||||
citations=CapabilitySupport(supported=True),
|
||||
code_execution=CapabilitySupport(supported=True),
|
||||
context_management=ContextManagementCapability(
|
||||
clear_thinking_20251015=CapabilitySupport(supported=True),
|
||||
clear_tool_uses_20250919=CapabilitySupport(supported=True),
|
||||
compact_20260112=CapabilitySupport(supported=True),
|
||||
supported=True,
|
||||
),
|
||||
effort=EffortCapability(
|
||||
high=CapabilitySupport(supported=True),
|
||||
low=CapabilitySupport(supported=True),
|
||||
max=CapabilitySupport(supported=True),
|
||||
medium=CapabilitySupport(supported=True),
|
||||
supported=True,
|
||||
xhigh=CapabilitySupport(supported=True),
|
||||
),
|
||||
image_input=CapabilitySupport(supported=True),
|
||||
pdf_input=CapabilitySupport(supported=True),
|
||||
structured_outputs=CapabilitySupport(supported=True),
|
||||
thinking=ThinkingCapability(
|
||||
supported=True,
|
||||
types=ThinkingTypes(
|
||||
adaptive=CapabilitySupport(supported=True),
|
||||
enabled=CapabilitySupport(supported=False),
|
||||
),
|
||||
),
|
||||
),
|
||||
created_at=datetime.datetime(2026, 7, 24, 0, 0, tzinfo=datetime.UTC),
|
||||
display_name="Claude Opus 5",
|
||||
max_input_tokens=1000000,
|
||||
max_tokens=128000,
|
||||
type="model",
|
||||
),
|
||||
ModelInfo(
|
||||
id="claude-sonnet-5",
|
||||
capabilities=ModelCapabilities(
|
||||
batch=CapabilitySupport(supported=True),
|
||||
citations=CapabilitySupport(supported=True),
|
||||
code_execution=CapabilitySupport(supported=True),
|
||||
context_management=ContextManagementCapability(
|
||||
clear_thinking_20251015=CapabilitySupport(supported=True),
|
||||
clear_tool_uses_20250919=CapabilitySupport(supported=True),
|
||||
compact_20260112=CapabilitySupport(supported=True),
|
||||
supported=True,
|
||||
),
|
||||
effort=EffortCapability(
|
||||
high=CapabilitySupport(supported=True),
|
||||
low=CapabilitySupport(supported=True),
|
||||
max=CapabilitySupport(supported=True),
|
||||
medium=CapabilitySupport(supported=True),
|
||||
supported=True,
|
||||
xhigh=CapabilitySupport(supported=True),
|
||||
),
|
||||
image_input=CapabilitySupport(supported=True),
|
||||
pdf_input=CapabilitySupport(supported=True),
|
||||
structured_outputs=CapabilitySupport(supported=True),
|
||||
thinking=ThinkingCapability(
|
||||
supported=True,
|
||||
types=ThinkingTypes(
|
||||
adaptive=CapabilitySupport(supported=True),
|
||||
enabled=CapabilitySupport(supported=False),
|
||||
),
|
||||
),
|
||||
),
|
||||
created_at=datetime.datetime(2026, 6, 29, 0, 0, tzinfo=datetime.UTC),
|
||||
display_name="Claude Sonnet 5",
|
||||
max_input_tokens=1000000,
|
||||
max_tokens=128000,
|
||||
type="model",
|
||||
),
|
||||
ModelInfo(
|
||||
id="claude-fable-5",
|
||||
capabilities=ModelCapabilities(
|
||||
@@ -349,117 +497,6 @@ model_list = [
|
||||
max_tokens=64000,
|
||||
type="model",
|
||||
),
|
||||
ModelInfo(
|
||||
id="claude-opus-4-1-20250805",
|
||||
capabilities=ModelCapabilities(
|
||||
batch=CapabilitySupport(supported=True),
|
||||
citations=CapabilitySupport(supported=True),
|
||||
code_execution=CapabilitySupport(supported=False),
|
||||
context_management=ContextManagementCapability(
|
||||
clear_thinking_20251015=CapabilitySupport(supported=True),
|
||||
clear_tool_uses_20250919=CapabilitySupport(supported=True),
|
||||
compact_20260112=CapabilitySupport(supported=False),
|
||||
supported=True,
|
||||
),
|
||||
effort=EffortCapability(
|
||||
high=CapabilitySupport(supported=False),
|
||||
low=CapabilitySupport(supported=False),
|
||||
max=CapabilitySupport(supported=False),
|
||||
medium=CapabilitySupport(supported=False),
|
||||
supported=False,
|
||||
xhigh=CapabilitySupport(supported=False),
|
||||
),
|
||||
image_input=CapabilitySupport(supported=True),
|
||||
pdf_input=CapabilitySupport(supported=True),
|
||||
structured_outputs=CapabilitySupport(supported=True),
|
||||
thinking=ThinkingCapability(
|
||||
supported=True,
|
||||
types=ThinkingTypes(
|
||||
adaptive=CapabilitySupport(supported=False),
|
||||
enabled=CapabilitySupport(supported=True),
|
||||
),
|
||||
),
|
||||
),
|
||||
created_at=datetime.datetime(2025, 8, 5, 0, 0, tzinfo=datetime.UTC),
|
||||
display_name="Claude Opus 4.1",
|
||||
max_input_tokens=200000,
|
||||
max_tokens=32000,
|
||||
type="model",
|
||||
),
|
||||
ModelInfo(
|
||||
id="claude-opus-4-20250514",
|
||||
capabilities=ModelCapabilities(
|
||||
batch=CapabilitySupport(supported=True),
|
||||
citations=CapabilitySupport(supported=True),
|
||||
code_execution=CapabilitySupport(supported=False),
|
||||
context_management=ContextManagementCapability(
|
||||
clear_thinking_20251015=CapabilitySupport(supported=True),
|
||||
clear_tool_uses_20250919=CapabilitySupport(supported=True),
|
||||
compact_20260112=CapabilitySupport(supported=False),
|
||||
supported=True,
|
||||
),
|
||||
effort=EffortCapability(
|
||||
high=CapabilitySupport(supported=False),
|
||||
low=CapabilitySupport(supported=False),
|
||||
max=CapabilitySupport(supported=False),
|
||||
medium=CapabilitySupport(supported=False),
|
||||
supported=False,
|
||||
xhigh=CapabilitySupport(supported=False),
|
||||
),
|
||||
image_input=CapabilitySupport(supported=True),
|
||||
pdf_input=CapabilitySupport(supported=True),
|
||||
structured_outputs=CapabilitySupport(supported=False),
|
||||
thinking=ThinkingCapability(
|
||||
supported=True,
|
||||
types=ThinkingTypes(
|
||||
adaptive=CapabilitySupport(supported=False),
|
||||
enabled=CapabilitySupport(supported=True),
|
||||
),
|
||||
),
|
||||
),
|
||||
created_at=datetime.datetime(2025, 5, 22, 0, 0, tzinfo=datetime.UTC),
|
||||
display_name="Claude Opus 4",
|
||||
max_input_tokens=200000,
|
||||
max_tokens=32000,
|
||||
type="model",
|
||||
),
|
||||
ModelInfo(
|
||||
id="claude-sonnet-4-20250514",
|
||||
capabilities=ModelCapabilities(
|
||||
batch=CapabilitySupport(supported=True),
|
||||
citations=CapabilitySupport(supported=True),
|
||||
code_execution=CapabilitySupport(supported=False),
|
||||
context_management=ContextManagementCapability(
|
||||
clear_thinking_20251015=CapabilitySupport(supported=True),
|
||||
clear_tool_uses_20250919=CapabilitySupport(supported=True),
|
||||
compact_20260112=CapabilitySupport(supported=False),
|
||||
supported=True,
|
||||
),
|
||||
effort=EffortCapability(
|
||||
high=CapabilitySupport(supported=False),
|
||||
low=CapabilitySupport(supported=False),
|
||||
max=CapabilitySupport(supported=False),
|
||||
medium=CapabilitySupport(supported=False),
|
||||
supported=False,
|
||||
xhigh=CapabilitySupport(supported=False),
|
||||
),
|
||||
image_input=CapabilitySupport(supported=True),
|
||||
pdf_input=CapabilitySupport(supported=True),
|
||||
structured_outputs=CapabilitySupport(supported=False),
|
||||
thinking=ThinkingCapability(
|
||||
supported=True,
|
||||
types=ThinkingTypes(
|
||||
adaptive=CapabilitySupport(supported=False),
|
||||
enabled=CapabilitySupport(supported=True),
|
||||
),
|
||||
),
|
||||
),
|
||||
created_at=datetime.datetime(2025, 5, 22, 0, 0, tzinfo=datetime.UTC),
|
||||
display_name="Claude Sonnet 4",
|
||||
max_input_tokens=1000000,
|
||||
max_tokens=64000,
|
||||
type="model",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -54,273 +54,3 @@
|
||||
}),
|
||||
})
|
||||
# ---
|
||||
# name: test_generate_structured_data_legacy
|
||||
dict({
|
||||
'container': None,
|
||||
'max_tokens': 3000,
|
||||
'messages': list([
|
||||
dict({
|
||||
'content': 'Generate test data',
|
||||
'role': 'user',
|
||||
}),
|
||||
dict({
|
||||
'content': '{"characters": ["Mario", "Luigi"]}',
|
||||
'role': 'assistant',
|
||||
}),
|
||||
]),
|
||||
'model': 'claude-sonnet-4-0',
|
||||
'stream': True,
|
||||
'system': list([
|
||||
dict({
|
||||
'cache_control': dict({
|
||||
'type': 'ephemeral',
|
||||
}),
|
||||
'text': '''
|
||||
You are a Home Assistant expert and help users with their tasks.
|
||||
Current time is 04:00:00. Today's date is 2026-01-01.
|
||||
''',
|
||||
'type': 'text',
|
||||
}),
|
||||
]),
|
||||
'thinking': dict({
|
||||
'type': 'disabled',
|
||||
}),
|
||||
'tool_choice': dict({
|
||||
'name': 'test_task',
|
||||
'type': 'tool',
|
||||
}),
|
||||
'tools': list([
|
||||
dict({
|
||||
'description': 'Use this tool to reply to the user',
|
||||
'input_schema': dict({
|
||||
'additionalProperties': False,
|
||||
'properties': dict({
|
||||
'characters': dict({
|
||||
'items': dict({
|
||||
'type': 'string',
|
||||
}),
|
||||
'type': 'array',
|
||||
}),
|
||||
}),
|
||||
'required': list([
|
||||
'characters',
|
||||
]),
|
||||
'type': 'object',
|
||||
}),
|
||||
'name': 'test_task',
|
||||
}),
|
||||
]),
|
||||
})
|
||||
# ---
|
||||
# name: test_generate_structured_data_legacy_extended_thinking
|
||||
dict({
|
||||
'container': None,
|
||||
'max_tokens': 3000,
|
||||
'messages': list([
|
||||
dict({
|
||||
'content': 'Generate test data',
|
||||
'role': 'user',
|
||||
}),
|
||||
dict({
|
||||
'content': list([
|
||||
dict({
|
||||
'signature': 'ErUBCkYIARgCIkCYXaVNJShe3A86Hp7XUzh9YsCYBbJTbQsrklTAPtJ2sP/NoB6tSzpK/nTL6CjSo2R6n0KNBIg5MH6asM2R/kmaEgyB/X1FtZq5OQAC7jUaDEPWCdcwGQ4RaBy5wiIwmRxExIlDhoY6tILoVPnOExkC/0igZxHEwxK8RU/fmw0b+o+TwAarzUitwzbo21E5Kh3pa3I6yqVROf1t2F8rFocNUeCegsWV/ytwYV+ayA==',
|
||||
'thinking': "Let's use the tool to respond",
|
||||
'type': 'thinking',
|
||||
}),
|
||||
dict({
|
||||
'text': '{"characters": ["Mario", "Luigi"]}',
|
||||
'type': 'text',
|
||||
}),
|
||||
]),
|
||||
'role': 'assistant',
|
||||
}),
|
||||
]),
|
||||
'model': 'claude-sonnet-4-0',
|
||||
'stream': True,
|
||||
'system': list([
|
||||
dict({
|
||||
'cache_control': dict({
|
||||
'type': 'ephemeral',
|
||||
}),
|
||||
'text': '''
|
||||
You are a Home Assistant expert and help users with their tasks.
|
||||
Current time is 04:00:00. Today's date is 2026-01-01.
|
||||
''',
|
||||
'type': 'text',
|
||||
}),
|
||||
dict({
|
||||
'text': "Claude MUST use the 'test_task' tool to provide the final answer instead of plain text.",
|
||||
'type': 'text',
|
||||
}),
|
||||
]),
|
||||
'thinking': dict({
|
||||
'budget_tokens': 1500,
|
||||
'display': 'summarized',
|
||||
'type': 'enabled',
|
||||
}),
|
||||
'tool_choice': dict({
|
||||
'type': 'auto',
|
||||
}),
|
||||
'tools': list([
|
||||
dict({
|
||||
'description': 'Use this tool to reply to the user',
|
||||
'input_schema': dict({
|
||||
'additionalProperties': False,
|
||||
'properties': dict({
|
||||
'characters': dict({
|
||||
'items': dict({
|
||||
'type': 'string',
|
||||
}),
|
||||
'type': 'array',
|
||||
}),
|
||||
}),
|
||||
'required': list([
|
||||
'characters',
|
||||
]),
|
||||
'type': 'object',
|
||||
}),
|
||||
'name': 'test_task',
|
||||
}),
|
||||
]),
|
||||
})
|
||||
# ---
|
||||
# name: test_generate_structured_data_legacy_extra_text_block
|
||||
dict({
|
||||
'container': None,
|
||||
'max_tokens': 3000,
|
||||
'messages': list([
|
||||
dict({
|
||||
'content': 'Generate test data',
|
||||
'role': 'user',
|
||||
}),
|
||||
dict({
|
||||
'content': list([
|
||||
dict({
|
||||
'signature': 'ErUBCkYIARgCIkCYXaVNJShe3A86Hp7XUzh9YsCYBbJTbQsrklTAPtJ2sP/NoB6tSzpK/nTL6CjSo2R6n0KNBIg5MH6asM2R/kmaEgyB/X1FtZq5OQAC7jUaDEPWCdcwGQ4RaBy5wiIwmRxExIlDhoY6tILoVPnOExkC/0igZxHEwxK8RU/fmw0b+o+TwAarzUitwzbo21E5Kh3pa3I6yqVROf1t2F8rFocNUeCegsWV/ytwYV+ayA==',
|
||||
'thinking': "Let's use the tool to respond",
|
||||
'type': 'thinking',
|
||||
}),
|
||||
dict({
|
||||
'text': 'Sure!',
|
||||
'type': 'text',
|
||||
}),
|
||||
dict({
|
||||
'text': '{"characters": ["Mario", "Luigi"]}',
|
||||
'type': 'text',
|
||||
}),
|
||||
]),
|
||||
'role': 'assistant',
|
||||
}),
|
||||
]),
|
||||
'model': 'claude-sonnet-4-0',
|
||||
'stream': True,
|
||||
'system': list([
|
||||
dict({
|
||||
'cache_control': dict({
|
||||
'type': 'ephemeral',
|
||||
}),
|
||||
'text': '''
|
||||
You are a Home Assistant expert and help users with their tasks.
|
||||
Current time is 04:00:00. Today's date is 2026-01-01.
|
||||
''',
|
||||
'type': 'text',
|
||||
}),
|
||||
dict({
|
||||
'text': "Claude MUST use the 'test_task' tool to provide the final answer instead of plain text.",
|
||||
'type': 'text',
|
||||
}),
|
||||
]),
|
||||
'thinking': dict({
|
||||
'budget_tokens': 1500,
|
||||
'display': 'summarized',
|
||||
'type': 'enabled',
|
||||
}),
|
||||
'tool_choice': dict({
|
||||
'type': 'auto',
|
||||
}),
|
||||
'tools': list([
|
||||
dict({
|
||||
'description': 'Use this tool to reply to the user',
|
||||
'input_schema': dict({
|
||||
'additionalProperties': False,
|
||||
'properties': dict({
|
||||
'characters': dict({
|
||||
'items': dict({
|
||||
'type': 'string',
|
||||
}),
|
||||
'type': 'array',
|
||||
}),
|
||||
}),
|
||||
'required': list([
|
||||
'characters',
|
||||
]),
|
||||
'type': 'object',
|
||||
}),
|
||||
'name': 'test_task',
|
||||
}),
|
||||
]),
|
||||
})
|
||||
# ---
|
||||
# name: test_generate_structured_data_legacy_tools
|
||||
dict({
|
||||
'container': None,
|
||||
'max_tokens': 3000,
|
||||
'messages': list([
|
||||
dict({
|
||||
'content': 'Generate test data',
|
||||
'role': 'user',
|
||||
}),
|
||||
dict({
|
||||
'content': '{"characters": ["Mario", "Luigi"]}',
|
||||
'role': 'assistant',
|
||||
}),
|
||||
]),
|
||||
'model': 'claude-sonnet-4-0',
|
||||
'stream': True,
|
||||
'system': list([
|
||||
dict({
|
||||
'cache_control': dict({
|
||||
'type': 'ephemeral',
|
||||
}),
|
||||
'text': '''
|
||||
You are a Home Assistant expert and help users with their tasks.
|
||||
Current time is 04:00:00. Today's date is 2026-01-01.
|
||||
''',
|
||||
'type': 'text',
|
||||
}),
|
||||
]),
|
||||
'thinking': dict({
|
||||
'type': 'disabled',
|
||||
}),
|
||||
'tool_choice': dict({
|
||||
'type': 'any',
|
||||
}),
|
||||
'tools': list([
|
||||
dict({
|
||||
'max_uses': 5,
|
||||
'name': 'web_search',
|
||||
'type': 'web_search_20250305',
|
||||
}),
|
||||
dict({
|
||||
'description': 'Use this tool to reply to the user',
|
||||
'input_schema': dict({
|
||||
'additionalProperties': False,
|
||||
'properties': dict({
|
||||
'characters': dict({
|
||||
'items': dict({
|
||||
'type': 'string',
|
||||
}),
|
||||
'type': 'array',
|
||||
}),
|
||||
}),
|
||||
'required': list([
|
||||
'characters',
|
||||
]),
|
||||
'type': 'object',
|
||||
}),
|
||||
'name': 'test_task',
|
||||
}),
|
||||
]),
|
||||
})
|
||||
# ---
|
||||
|
||||
@@ -1,6 +1,22 @@
|
||||
# serializer version: 1
|
||||
# name: test_model_list
|
||||
list([
|
||||
dict({
|
||||
'label': 'Claude Opus 5.5',
|
||||
'value': 'claude-opus-5-5',
|
||||
}),
|
||||
dict({
|
||||
'label': 'Claude Fable 5.1',
|
||||
'value': 'claude-fable-5-1',
|
||||
}),
|
||||
dict({
|
||||
'label': 'Claude Opus 5',
|
||||
'value': 'claude-opus-5',
|
||||
}),
|
||||
dict({
|
||||
'label': 'Claude Sonnet 5',
|
||||
'value': 'claude-sonnet-5',
|
||||
}),
|
||||
dict({
|
||||
'label': 'Claude Fable 5',
|
||||
'value': 'claude-fable-5',
|
||||
@@ -33,17 +49,5 @@
|
||||
'label': 'Claude Sonnet 4.5',
|
||||
'value': 'claude-sonnet-4-5',
|
||||
}),
|
||||
dict({
|
||||
'label': 'Claude Opus 4.1',
|
||||
'value': 'claude-opus-4-1',
|
||||
}),
|
||||
dict({
|
||||
'label': 'Claude Opus 4',
|
||||
'value': 'claude-opus-4-0',
|
||||
}),
|
||||
dict({
|
||||
'label': 'Claude Sonnet 4',
|
||||
'value': 'claude-sonnet-4-0',
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
|
||||
@@ -1831,7 +1831,9 @@
|
||||
'result': dict({
|
||||
'data': dict({
|
||||
'content': dict({
|
||||
'citations': None,
|
||||
'citations': dict({
|
||||
'enabled': True,
|
||||
}),
|
||||
'source': dict({
|
||||
'data': '''
|
||||
Home Assistant new version is out!
|
||||
@@ -1889,8 +1891,8 @@
|
||||
'cited_text': 'Anthropic integration now supports web fetch tool.',
|
||||
'document_index': 0,
|
||||
'document_title': 'Latest Home Assistant Release Notes',
|
||||
'end_char_index': 105,
|
||||
'start_char_index': 56,
|
||||
'end_char_index': 104,
|
||||
'start_char_index': 54,
|
||||
'type': 'char_location',
|
||||
}),
|
||||
]),
|
||||
@@ -1936,7 +1938,9 @@
|
||||
dict({
|
||||
'content': dict({
|
||||
'content': dict({
|
||||
'citations': None,
|
||||
'citations': dict({
|
||||
'enabled': True,
|
||||
}),
|
||||
'source': dict({
|
||||
'data': '''
|
||||
Home Assistant new version is out!
|
||||
@@ -1982,8 +1986,8 @@
|
||||
'cited_text': 'Anthropic integration now supports web fetch tool.',
|
||||
'document_index': 0,
|
||||
'document_title': 'Latest Home Assistant Release Notes',
|
||||
'end_char_index': 105,
|
||||
'start_char_index': 56,
|
||||
'end_char_index': 104,
|
||||
'start_char_index': 54,
|
||||
'type': 'char_location',
|
||||
}),
|
||||
]),
|
||||
|
||||
@@ -11,15 +11,11 @@ import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components import ai_task, media_source
|
||||
from homeassistant.components.anthropic.const import (
|
||||
CONF_CHAT_MODEL,
|
||||
CONF_THINKING_BUDGET,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry as er, selector
|
||||
|
||||
from . import create_content_block, create_thinking_block, create_tool_use_block
|
||||
from . import create_content_block
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
@@ -112,254 +108,14 @@ async def test_stream_wrong_type(
|
||||
)
|
||||
|
||||
|
||||
@freeze_time("2026-01-01 12:00:00")
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_generate_structured_data_legacy(
|
||||
async def test_generate_invalid_structured_data(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_create_stream: AsyncMock,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test AI Task structured data generation with legacy method."""
|
||||
for subentry in mock_config_entry.subentries.values():
|
||||
hass.config_entries.async_update_subentry(
|
||||
mock_config_entry,
|
||||
subentry,
|
||||
data={
|
||||
CONF_CHAT_MODEL: "claude-sonnet-4-0",
|
||||
CONF_THINKING_BUDGET: 0,
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
mock_create_stream.return_value = [
|
||||
create_tool_use_block(
|
||||
0,
|
||||
"toolu_0123456789AbCdEfGhIjKlM",
|
||||
"test_task",
|
||||
['{"charac', 'ters": ["Mario', '", "Luigi"]}'],
|
||||
),
|
||||
]
|
||||
|
||||
result = await ai_task.async_generate_data(
|
||||
hass,
|
||||
task_name="Test Task",
|
||||
entity_id="ai_task.claude_ai_task",
|
||||
instructions="Generate test data",
|
||||
structure=probatio.Schema(
|
||||
{
|
||||
probatio.Required("characters"): selector.selector(
|
||||
{
|
||||
"text": {
|
||||
"multiple": True,
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
assert result.data == {"characters": ["Mario", "Luigi"]}
|
||||
assert mock_create_stream.call_args.kwargs.copy() == snapshot
|
||||
|
||||
|
||||
@freeze_time("2026-01-01 12:00:00")
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_generate_structured_data_legacy_tools(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_create_stream: AsyncMock,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test AI Task structured data generation with legacy method and tools enabled."""
|
||||
mock_create_stream.return_value = [
|
||||
create_tool_use_block(
|
||||
0,
|
||||
"toolu_0123456789AbCdEfGhIjKlM",
|
||||
"test_task",
|
||||
['{"charac', 'ters": ["Mario', '", "Luigi"]}'],
|
||||
),
|
||||
]
|
||||
|
||||
for subentry in mock_config_entry.subentries.values():
|
||||
hass.config_entries.async_update_subentry(
|
||||
mock_config_entry,
|
||||
subentry,
|
||||
data={
|
||||
"chat_model": "claude-sonnet-4-0",
|
||||
"web_search": True,
|
||||
"thinking_budget": 0,
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
result = await ai_task.async_generate_data(
|
||||
hass,
|
||||
task_name="Test Task",
|
||||
entity_id="ai_task.claude_ai_task",
|
||||
instructions="Generate test data",
|
||||
structure=probatio.Schema(
|
||||
{
|
||||
probatio.Required("characters"): selector.selector(
|
||||
{
|
||||
"text": {
|
||||
"multiple": True,
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
assert result.data == {"characters": ["Mario", "Luigi"]}
|
||||
assert mock_create_stream.call_args.kwargs.copy() == snapshot
|
||||
|
||||
|
||||
@freeze_time("2026-01-01 12:00:00")
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_generate_structured_data_legacy_extended_thinking(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_create_stream: AsyncMock,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test AI Task structured data generation.
|
||||
|
||||
Uses legacy method with extended_thinking.
|
||||
"""
|
||||
mock_create_stream.return_value = [
|
||||
(
|
||||
*create_thinking_block(
|
||||
0,
|
||||
["Let's use the tool to respond"],
|
||||
),
|
||||
*create_tool_use_block(
|
||||
1,
|
||||
"toolu_0123456789AbCdEfGhIjKlM",
|
||||
"test_task",
|
||||
['{"charac', 'ters": ["Mario', '", "Luigi"]}'],
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
for subentry in mock_config_entry.subentries.values():
|
||||
hass.config_entries.async_update_subentry(
|
||||
mock_config_entry,
|
||||
subentry,
|
||||
data={
|
||||
"chat_model": "claude-sonnet-4-0",
|
||||
"thinking_budget": 1500,
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
result = await ai_task.async_generate_data(
|
||||
hass,
|
||||
task_name="Test Task",
|
||||
entity_id="ai_task.claude_ai_task",
|
||||
instructions="Generate test data",
|
||||
structure=probatio.Schema(
|
||||
{
|
||||
probatio.Required("characters"): selector.selector(
|
||||
{
|
||||
"text": {
|
||||
"multiple": True,
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
assert result.data == {"characters": ["Mario", "Luigi"]}
|
||||
assert mock_create_stream.call_args.kwargs.copy() == snapshot
|
||||
|
||||
|
||||
@freeze_time("2026-01-01 12:00:00")
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_generate_structured_data_legacy_extra_text_block(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_create_stream: AsyncMock,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test AI Task structured data generation.
|
||||
|
||||
Uses legacy method with extra text block.
|
||||
"""
|
||||
mock_create_stream.return_value = [
|
||||
(
|
||||
*create_thinking_block(
|
||||
0,
|
||||
["Let's use the tool to respond"],
|
||||
),
|
||||
*create_content_block(1, ["Sure!"]),
|
||||
*create_tool_use_block(
|
||||
2,
|
||||
"toolu_0123456789AbCdEfGhIjKlM",
|
||||
"test_task",
|
||||
['{"charac', 'ters": ["Mario', '", "Luigi"]}'],
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
for subentry in mock_config_entry.subentries.values():
|
||||
hass.config_entries.async_update_subentry(
|
||||
mock_config_entry,
|
||||
subentry,
|
||||
data={
|
||||
"chat_model": "claude-sonnet-4-0",
|
||||
"thinking_budget": 1500,
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
result = await ai_task.async_generate_data(
|
||||
hass,
|
||||
task_name="Test Task",
|
||||
entity_id="ai_task.claude_ai_task",
|
||||
instructions="Generate test data",
|
||||
structure=probatio.Schema(
|
||||
{
|
||||
probatio.Required("characters"): selector.selector(
|
||||
{
|
||||
"text": {
|
||||
"multiple": True,
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
assert result.data == {"characters": ["Mario", "Luigi"]}
|
||||
assert mock_create_stream.call_args.kwargs.copy() == snapshot
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_generate_invalid_structured_data_legacy(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_create_stream: AsyncMock,
|
||||
) -> None:
|
||||
"""Test AI Task with invalid JSON response with legacy method."""
|
||||
for subentry in mock_config_entry.subentries.values():
|
||||
hass.config_entries.async_update_subentry(
|
||||
mock_config_entry,
|
||||
subentry,
|
||||
data={
|
||||
CONF_CHAT_MODEL: "claude-sonnet-4-0",
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
"""Test AI Task with an invalid JSON response."""
|
||||
mock_create_stream.return_value = [
|
||||
create_tool_use_block(
|
||||
0,
|
||||
"toolu_0123456789AbCdEfGhIjKlM",
|
||||
"test_task",
|
||||
"INVALID JSON RESPONSE",
|
||||
)
|
||||
create_content_block(0, ["INVALID JSON RESPONSE"])
|
||||
]
|
||||
|
||||
with pytest.raises(
|
||||
|
||||
@@ -378,7 +378,7 @@ async def test_subentry_web_search_user_location(
|
||||
type="message",
|
||||
id="mock_message_id",
|
||||
role="assistant",
|
||||
model="claude-sonnet-4-0",
|
||||
model="claude-sonnet-4-5",
|
||||
usage=types.Usage(input_tokens=100, output_tokens=100),
|
||||
content=[
|
||||
types.TextBlock(
|
||||
|
||||
@@ -9,6 +9,7 @@ from anthropic import RateLimitError
|
||||
from anthropic.types import (
|
||||
CitationCharLocation,
|
||||
CitationCharLocationParam,
|
||||
CitationsConfig,
|
||||
CitationsWebSearchResultLocation,
|
||||
CitationWebSearchResultLocationParam,
|
||||
DocumentBlock,
|
||||
@@ -1018,7 +1019,7 @@ async def test_web_search(
|
||||
next(iter(mock_config_entry.subentries.values())),
|
||||
data={
|
||||
CONF_LLM_HASS_API: llm.LLM_API_ASSIST,
|
||||
CONF_CHAT_MODEL: "claude-sonnet-4-0",
|
||||
CONF_CHAT_MODEL: "claude-sonnet-4-5",
|
||||
CONF_WEB_SEARCH: True,
|
||||
CONF_WEB_SEARCH_MAX_USES: 5,
|
||||
CONF_WEB_SEARCH_USER_LOCATION: True,
|
||||
@@ -1164,7 +1165,7 @@ async def test_web_search_error(
|
||||
next(iter(mock_config_entry.subentries.values())),
|
||||
data={
|
||||
CONF_LLM_HASS_API: llm.LLM_API_ASSIST,
|
||||
CONF_CHAT_MODEL: "claude-sonnet-4-0",
|
||||
CONF_CHAT_MODEL: "claude-sonnet-4-5",
|
||||
CONF_WEB_SEARCH: True,
|
||||
CONF_WEB_SEARCH_MAX_USES: 5,
|
||||
CONF_WEB_SEARCH_USER_LOCATION: True,
|
||||
@@ -1878,13 +1879,13 @@ async def test_web_fetch(
|
||||
mock_create_stream: AsyncMock,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test web fetch."""
|
||||
"""Test web fetch with interleaved thinking and citation parsing."""
|
||||
hass.config_entries.async_update_subentry(
|
||||
mock_config_entry,
|
||||
next(iter(mock_config_entry.subentries.values())),
|
||||
data={
|
||||
CONF_LLM_HASS_API: llm.LLM_API_ASSIST,
|
||||
CONF_CHAT_MODEL: "claude-haiku-4-5",
|
||||
CONF_CHAT_MODEL: "claude-sonnet-4-6",
|
||||
CONF_WEB_FETCH: True,
|
||||
CONF_WEB_FETCH_MAX_USES: 5,
|
||||
},
|
||||
@@ -1896,7 +1897,7 @@ async def test_web_fetch(
|
||||
url="https://www.home-assistant.io/latest-release-notes/",
|
||||
content=DocumentBlock(
|
||||
type="document",
|
||||
citations=None,
|
||||
citations=CitationsConfig(enabled=True),
|
||||
source=PlainTextSource(
|
||||
type="text",
|
||||
data="Home Assistant new version is out!\nMany new features.\n"
|
||||
@@ -1950,8 +1951,8 @@ async def test_web_fetch(
|
||||
type="char_location",
|
||||
document_index=0,
|
||||
document_title="Latest Home Assistant Release Notes",
|
||||
start_char_index=56,
|
||||
end_char_index=105,
|
||||
start_char_index=54,
|
||||
end_char_index=104,
|
||||
cited_text="Anthropic integration now supports web fetch tool.",
|
||||
),
|
||||
],
|
||||
@@ -1969,6 +1970,15 @@ async def test_web_fetch(
|
||||
agent_id="conversation.claude_conversation",
|
||||
)
|
||||
|
||||
request = mock_create_stream.call_args.kwargs
|
||||
assert request["model"] == "claude-sonnet-4-6"
|
||||
assert request["thinking"] == {"type": "adaptive", "display": "summarized"}
|
||||
assert {
|
||||
"name": "web_fetch",
|
||||
"type": "web_fetch_20250910",
|
||||
"max_uses": 5,
|
||||
} in request["tools"]
|
||||
|
||||
chat_log = hass.data.get(conversation.chat_log.DATA_CHAT_LOGS).get(
|
||||
result.conversation_id
|
||||
)
|
||||
|
||||
@@ -12,6 +12,7 @@ from homeassistant.components.anthropic.const import DOMAIN
|
||||
from homeassistant.components.anthropic.coordinator import (
|
||||
UPDATE_INTERVAL_CONNECTED,
|
||||
UPDATE_INTERVAL_DISCONNECTED,
|
||||
model_alias,
|
||||
)
|
||||
from homeassistant.config_entries import SOURCE_REAUTH
|
||||
from homeassistant.core import Context, HomeAssistant
|
||||
@@ -21,6 +22,25 @@ from homeassistant.util import dt as dt_util
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("model_id", "expected_alias"),
|
||||
[
|
||||
pytest.param("claude-opus-4-5-20251101", "claude-opus-4-5", id="dated_model"),
|
||||
pytest.param("claude-opus-4-7", "claude-opus-4-7", id="version_alias"),
|
||||
pytest.param("claude-opus-5", "claude-opus-5", id="major_version_alias"),
|
||||
pytest.param(
|
||||
"claude-opus-4-10", "claude-opus-4-10", id="multi_digit_version_alias"
|
||||
),
|
||||
pytest.param(
|
||||
"claude-mythos-preview", "claude-mythos-preview", id="preview_alias"
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_model_alias(model_id: str, expected_alias: str) -> None:
|
||||
"""Test model aliases preserve versions and remove date suffixes."""
|
||||
assert model_alias(model_id) == expected_alias
|
||||
|
||||
|
||||
@patch("anthropic.resources.models.AsyncModels.list", new_callable=AsyncMock)
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_auth_error_handling(
|
||||
|
||||
Reference in New Issue
Block a user