Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions custom_components/configurable_llm/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,7 @@ def _openai_model_schema(self) -> VolDictType:
options=REASONING_EFFORT_OPTIONS,
translation_key=CONF_REASONING_EFFORT,
mode=SelectSelectorMode.DROPDOWN,
custom_value=True,
)
),
}
Expand Down
11 changes: 10 additions & 1 deletion custom_components/configurable_llm/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,16 @@

# `reasoning_effort` choices offered for OpenAI-compatible providers. Servers and
# models vary in what they accept; "none" means "do not send the parameter".
REASONING_EFFORT_OPTIONS = ["none", "minimal", "low", "medium", "high"]
# The selector also accepts a typed custom value for provider-specific levels.
REASONING_EFFORT_OPTIONS = [
"none",
"minimal",
"low",
"medium",
"high",
"xhigh",
"max",
]

CONF_WEB_FETCH_MAX_USES = "web_fetch_max_uses"
CONF_WEB_SEARCH = "web_search"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,8 @@ async def build_request(
request["top_p"] = top_p

# reasoning_effort is model-dependent; only send when explicitly set.
reasoning_effort = options.get(CONF_REASONING_EFFORT)
# Custom values are typed free text, so trim stray whitespace.
reasoning_effort = (options.get(CONF_REASONING_EFFORT) or "").strip()
if reasoning_effort and reasoning_effort != "none":
request["reasoning_effort"] = reasoning_effort

Expand Down
8 changes: 5 additions & 3 deletions custom_components/configurable_llm/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@
"web_fetch": "Allow the model to retrieve full content from specified web pages and PDF documents to augment its context with live web content.",
"web_fetch_max_uses": "Limit the number of web fetches the model can perform in a single response.",
"tool_search": "Discover Home Assistant tools on demand instead of loading them all upfront. Useful when many tools are exposed.",
"reasoning_effort": "Reasoning effort for OpenAI-compatible reasoning models. Leave at Default if your model or server does not support it."
"reasoning_effort": "Reasoning effort for OpenAI-compatible reasoning models. Leave at Default if your model or server does not support it. You can also type a value your provider documents that is not listed."
}
}
},
Expand Down Expand Up @@ -191,7 +191,7 @@
"web_fetch": "Allow the model to retrieve full content from specified web pages and PDF documents to augment its context with live web content.",
"web_fetch_max_uses": "Limit the number of web fetches the model can perform in a single response.",
"tool_search": "Discover Home Assistant tools on demand instead of loading them all upfront. Useful when many tools are exposed.",
"reasoning_effort": "Reasoning effort for OpenAI-compatible reasoning models. Leave at Default if your model or server does not support it."
"reasoning_effort": "Reasoning effort for OpenAI-compatible reasoning models. Leave at Default if your model or server does not support it. You can also type a value your provider documents that is not listed."
}
}
},
Expand Down Expand Up @@ -248,7 +248,9 @@
"minimal": "Minimal",
"low": "Low",
"medium": "Medium",
"high": "High"
"high": "High",
"xhigh": "Extra high",
"max": "Max"
}
}
},
Expand Down
8 changes: 5 additions & 3 deletions custom_components/configurable_llm/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@
"web_fetch": "Allow the model to retrieve full content from specified web pages and PDF documents to augment its context with live web content.",
"web_fetch_max_uses": "Limit the number of web fetches the model can perform in a single response.",
"tool_search": "Discover Home Assistant tools on demand instead of loading them all upfront. Useful when many tools are exposed.",
"reasoning_effort": "Reasoning effort for OpenAI-compatible reasoning models. Leave at Default if your model or server does not support it."
"reasoning_effort": "Reasoning effort for OpenAI-compatible reasoning models. Leave at Default if your model or server does not support it. You can also type a value your provider documents that is not listed."
}
}
},
Expand Down Expand Up @@ -191,7 +191,7 @@
"web_fetch": "Allow the model to retrieve full content from specified web pages and PDF documents to augment its context with live web content.",
"web_fetch_max_uses": "Limit the number of web fetches the model can perform in a single response.",
"tool_search": "Discover Home Assistant tools on demand instead of loading them all upfront. Useful when many tools are exposed.",
"reasoning_effort": "Reasoning effort for OpenAI-compatible reasoning models. Leave at Default if your model or server does not support it."
"reasoning_effort": "Reasoning effort for OpenAI-compatible reasoning models. Leave at Default if your model or server does not support it. You can also type a value your provider documents that is not listed."
}
}
},
Expand Down Expand Up @@ -248,7 +248,9 @@
"minimal": "Minimal",
"low": "Low",
"medium": "Medium",
"high": "High"
"high": "High",
"xhigh": "Extra high",
"max": "Max"
}
}
},
Expand Down
24 changes: 24 additions & 0 deletions tests/providers/test_openai_chat_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,30 @@ async def test_build_request_reasoning_effort_minimal(hass: HomeAssistant) -> No
assert request["reasoning_effort"] == "minimal"


@pytest.mark.parametrize(
("configured", "expected"),
[
("xhigh", "xhigh"),
("max", "max"),
# Typed custom value for a provider-specific level.
("ultra", "ultra"),
# Stray whitespace from free-text entry is trimmed.
(" low ", "low"),
# Blank custom entries behave like the "none" default.
(" ", None),
("", None),
],
)
async def test_build_request_reasoning_effort_values(
hass: HomeAssistant, configured: str, expected: str | None
) -> None:
"""Preset and custom effort values are forwarded; blanks are omitted."""
options = {**DEFAULT_OPENAI, CONF_REASONING_EFFORT: configured}
request, _ = await provider.build_request(_ctx(options=options))

assert request.get("reasoning_effort") == expected


async def test_build_request_tools(hass: HomeAssistant) -> None:
"""HA tools become Chat Completions function tools."""
request, _ = await provider.build_request(_ctx(with_tools=True))
Expand Down
10 changes: 10 additions & 0 deletions tests/test_config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import anthropic
import httpx
import pytest
import voluptuous as vol
from homeassistant.config_entries import (
ConfigEntry,
ConfigEntryState,
Expand All @@ -30,6 +31,7 @@
CONF_PRESET,
CONF_PROTOCOL,
CONF_PROMPT_CACHING,
CONF_REASONING_EFFORT,
CONF_RECOMMENDED,
CONF_THINKING_BUDGET,
CONF_WEB_SEARCH,
Expand Down Expand Up @@ -861,3 +863,11 @@ async def test_flow_subentry_openai_model_schema(
result = await flow.async_step_model({})

assert result["type"] == FlowResultType.CREATE_ENTRY


@pytest.mark.parametrize("effort", ["none", "minimal", "xhigh", "max", "ultra"])
async def test_flow_subentry_openai_reasoning_effort_values(effort: str) -> None:
"""The reasoning effort selector accepts presets and typed custom values."""
schema = vol.Schema(ConversationSubentryFlowHandler()._openai_model_schema())

assert schema({CONF_REASONING_EFFORT: effort})[CONF_REASONING_EFFORT] == effort
Loading