Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0.0.10 #65

Merged
merged 7 commits into from
Dec 31, 2023
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
14 changes: 14 additions & 0 deletions .github/workflows/hassfest.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: Validate with hassfest

on:
push:
pull_request:
schedule:
- cron: "0 0 * * *"

jobs:
validate:
runs-on: "ubuntu-latest"
steps:
- uses: "actions/checkout@v4"
- uses: "home-assistant/actions/hassfest@master"
18 changes: 18 additions & 0 deletions .github/workflows/validate.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Validate

on:
push:
pull_request:
schedule:
- cron: "0 0 * * *"
workflow_dispatch:

jobs:
validate-hacs:
runs-on: "ubuntu-latest"
steps:
- uses: "actions/checkout@v3"
- name: HACS validation
uses: "hacs/action@main"
with:
category: "integration"
33 changes: 28 additions & 5 deletions custom_components/extended_openai_conversation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
CONF_MAX_FUNCTION_CALLS_PER_CONVERSATION,
CONF_FUNCTIONS,
CONF_BASE_URL,
CONF_API_VERSION,
CONF_SKIP_AUTHENTICATION,
CONF_MODEL_KEY,
DEFAULT_ATTACH_USERNAME,
DEFAULT_CHAT_MODEL,
DEFAULT_MAX_TOKENS,
Expand All @@ -47,6 +50,7 @@
DEFAULT_TOP_P,
DEFAULT_MAX_FUNCTION_CALLS_PER_CONVERSATION,
DEFAULT_CONF_FUNCTIONS,
DEFAULT_SKIP_AUTHENTICATION,
DOMAIN,
)

Expand All @@ -73,12 +77,15 @@
convert_to_template,
validate_authentication,
get_function_executor,
get_api_type,
get_default_model_key,
)


_LOGGER = logging.getLogger(__name__)

CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
AZURE_DOMAIN_PATTERN = r"\.openai\.azure\.com"


# hass.data key for agent.
Expand All @@ -93,6 +100,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass=hass,
api_key=entry.data[CONF_API_KEY],
base_url=entry.data.get(CONF_BASE_URL),
api_version=entry.data.get(CONF_API_VERSION),
skip_authentication=entry.data.get(
CONF_SKIP_AUTHENTICATION, DEFAULT_SKIP_AUTHENTICATION
),
)
except error.AuthenticationError as err:
_LOGGER.error("Invalid API key: %s", err)
Expand Down Expand Up @@ -258,6 +269,10 @@ async def query(
n_requests,
):
"""Process a sentence."""
api_base = self.entry.data.get(CONF_BASE_URL)
api_key = self.entry.data[CONF_API_KEY]
api_type = get_api_type(api_base)
api_version = self.entry.data.get(CONF_API_VERSION)
model = self.entry.options.get(CONF_CHAT_MODEL, DEFAULT_CHAT_MODEL)
max_tokens = self.entry.options.get(CONF_MAX_TOKENS, DEFAULT_MAX_TOKENS)
top_p = self.entry.options.get(CONF_TOP_P, DEFAULT_TOP_P)
Expand All @@ -269,22 +284,30 @@ async def query(
DEFAULT_MAX_FUNCTION_CALLS_PER_CONVERSATION,
):
function_call = "none"
response_format = {"type": "text"}
if len(functions) == 0:
functions = None
function_call = None

model_key = self.entry.options.get(
CONF_MODEL_KEY, get_default_model_key(api_base)
)
model_kwargs = {model_key: model}

_LOGGER.info("Prompt for %s: %s", model, messages)

response = await openai.ChatCompletion.acreate(
api_base=self.entry.data.get(CONF_BASE_URL),
api_key=self.entry.data[CONF_API_KEY],
model=model,
api_base=api_base,
api_key=api_key,
api_type=api_type,
api_version=api_version,
messages=messages,
max_tokens=max_tokens,
top_p=top_p,
temperature=temperature,
user=user_input.conversation_id,
functions=functions,
function_call=function_call,
response_format=response_format,
**model_kwargs,
)

_LOGGER.info("Response %s", response)
Expand Down
147 changes: 90 additions & 57 deletions custom_components/extended_openai_conversation/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@
NumberSelectorConfig,
TemplateSelector,
AttributeSelector,
SelectSelector,
SelectSelectorConfig,
SelectOptionDict,
SelectSelectorMode,
)

from .helpers import validate_authentication
from .helpers import validate_authentication, get_default_model_key

from .const import (
CONF_ATTACH_USERNAME,
Expand All @@ -34,6 +38,10 @@
CONF_MAX_FUNCTION_CALLS_PER_CONVERSATION,
CONF_FUNCTIONS,
CONF_BASE_URL,
CONF_API_VERSION,
CONF_SKIP_AUTHENTICATION,
CONF_MODEL_KEY,
MODEL_KEYS,
DEFAULT_ATTACH_USERNAME,
DEFAULT_CHAT_MODEL,
DEFAULT_MAX_TOKENS,
Expand All @@ -43,6 +51,7 @@
DEFAULT_MAX_FUNCTION_CALLS_PER_CONVERSATION,
DEFAULT_CONF_FUNCTIONS,
DEFAULT_CONF_BASE_URL,
DEFAULT_SKIP_AUTHENTICATION,
DOMAIN,
DEFAULT_NAME,
)
Expand All @@ -54,6 +63,10 @@
vol.Optional(CONF_NAME): str,
vol.Required(CONF_API_KEY): str,
vol.Optional(CONF_BASE_URL, default=DEFAULT_CONF_BASE_URL): str,
vol.Optional(CONF_API_VERSION): str,
vol.Optional(
CONF_SKIP_AUTHENTICATION, default=DEFAULT_SKIP_AUTHENTICATION
): bool,
}
)

Expand All @@ -80,13 +93,21 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> None:
"""
api_key = data[CONF_API_KEY]
base_url = data.get(CONF_BASE_URL)
api_version = data.get(CONF_API_VERSION)
skip_authentication = data.get(CONF_SKIP_AUTHENTICATION)

if base_url == DEFAULT_CONF_BASE_URL:
# Do not set base_url if using OpenAI for case of OpenAI's base_url change
base_url = None
data.pop(CONF_BASE_URL)

await validate_authentication(hass=hass, api_key=api_key, base_url=base_url)
await validate_authentication(
hass=hass,
api_key=api_key,
base_url=base_url,
api_version=api_version,
skip_authentication=skip_authentication,
)


class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
Expand Down Expand Up @@ -146,63 +167,75 @@ async def async_step_init(
return self.async_create_entry(
title=user_input.get(CONF_NAME, DEFAULT_NAME), data=user_input
)
schema = openai_config_option_schema(self.config_entry.options)
schema = self.openai_config_option_schema(self.config_entry.options)
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(schema),
)


def openai_config_option_schema(options: MappingProxyType[str, Any]) -> dict:
"""Return a schema for OpenAI completion options."""
if not options:
options = DEFAULT_OPTIONS
return {
vol.Optional(
CONF_PROMPT,
description={"suggested_value": options[CONF_PROMPT]},
default=DEFAULT_PROMPT,
): TemplateSelector(),
vol.Optional(
CONF_CHAT_MODEL,
description={
# New key in HA 2023.4
"suggested_value": options.get(CONF_CHAT_MODEL, DEFAULT_CHAT_MODEL)
},
default=DEFAULT_CHAT_MODEL,
): str,
vol.Optional(
CONF_MAX_TOKENS,
description={"suggested_value": options[CONF_MAX_TOKENS]},
default=DEFAULT_MAX_TOKENS,
): int,
vol.Optional(
CONF_TOP_P,
description={"suggested_value": options[CONF_TOP_P]},
default=DEFAULT_TOP_P,
): NumberSelector(NumberSelectorConfig(min=0, max=1, step=0.05)),
vol.Optional(
CONF_TEMPERATURE,
description={"suggested_value": options[CONF_TEMPERATURE]},
default=DEFAULT_TEMPERATURE,
): NumberSelector(NumberSelectorConfig(min=0, max=1, step=0.05)),
vol.Optional(
CONF_MAX_FUNCTION_CALLS_PER_CONVERSATION,
description={
"suggested_value": options[CONF_MAX_FUNCTION_CALLS_PER_CONVERSATION]
},
default=DEFAULT_MAX_FUNCTION_CALLS_PER_CONVERSATION,
): int,
vol.Optional(
CONF_FUNCTIONS,
description={"suggested_value": options.get(CONF_FUNCTIONS)},
default=DEFAULT_CONF_FUNCTIONS_STR,
): TemplateSelector(),
vol.Optional(
CONF_ATTACH_USERNAME,
description={
"suggested_value": options.get(CONF_ATTACH_USERNAME)
},
default=DEFAULT_ATTACH_USERNAME,
): BooleanSelector(),
}
def openai_config_option_schema(self, options: MappingProxyType[str, Any]) -> dict:
"""Return a schema for OpenAI completion options."""
if not options:
options = DEFAULT_OPTIONS

return {
vol.Optional(
CONF_PROMPT,
description={"suggested_value": options[CONF_PROMPT]},
default=DEFAULT_PROMPT,
): TemplateSelector(),
vol.Optional(
CONF_CHAT_MODEL,
description={
# New key in HA 2023.4
"suggested_value": options.get(CONF_CHAT_MODEL, DEFAULT_CHAT_MODEL)
},
default=DEFAULT_CHAT_MODEL,
): str,
vol.Optional(
CONF_MAX_TOKENS,
description={"suggested_value": options[CONF_MAX_TOKENS]},
default=DEFAULT_MAX_TOKENS,
): int,
vol.Optional(
CONF_TOP_P,
description={"suggested_value": options[CONF_TOP_P]},
default=DEFAULT_TOP_P,
): NumberSelector(NumberSelectorConfig(min=0, max=1, step=0.05)),
vol.Optional(
CONF_TEMPERATURE,
description={"suggested_value": options[CONF_TEMPERATURE]},
default=DEFAULT_TEMPERATURE,
): NumberSelector(NumberSelectorConfig(min=0, max=1, step=0.05)),
vol.Optional(
CONF_MAX_FUNCTION_CALLS_PER_CONVERSATION,
description={
"suggested_value": options[CONF_MAX_FUNCTION_CALLS_PER_CONVERSATION]
},
default=DEFAULT_MAX_FUNCTION_CALLS_PER_CONVERSATION,
): int,
vol.Optional(
CONF_FUNCTIONS,
description={"suggested_value": options.get(CONF_FUNCTIONS)},
default=DEFAULT_CONF_FUNCTIONS_STR,
): TemplateSelector(),
vol.Optional(
CONF_ATTACH_USERNAME,
description={"suggested_value": options.get(CONF_ATTACH_USERNAME)},
default=DEFAULT_ATTACH_USERNAME,
): BooleanSelector(),
vol.Optional(
CONF_MODEL_KEY,
description={"suggested_value": options.get(CONF_MODEL_KEY)},
default=get_default_model_key(
self.config_entry.data.get(CONF_BASE_URL)
),
): SelectSelector(
SelectSelectorConfig(
options=[
SelectOptionDict(value=key, label=key) for key in MODEL_KEYS
],
mode=SelectSelectorMode.DROPDOWN,
)
),
}
11 changes: 9 additions & 2 deletions custom_components/extended_openai_conversation/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@

DOMAIN = "extended_openai_conversation"
DEFAULT_NAME = "Extended OpenAI Conversation"
CONF_BASE_URL = "base_url"
DEFAULT_CONF_BASE_URL = "https://api.openai.com/v1"
CONF_API_VERSION = "api_version"
CONF_SKIP_AUTHENTICATION = "skip_authentication"
DEFAULT_SKIP_AUTHENTICATION = False

EVENT_AUTOMATION_REGISTERED = "automation_registered_via_extended_openai_conversation"

CONF_PROMPT = "prompt"
DEFAULT_PROMPT = """I want you to act as smart home manager of Home Assistant.
I will provide information of smart home along with a question, you will truthfully make correction or answer using information provided in one sentence in everyday language.
Expand Down Expand Up @@ -75,7 +82,7 @@
"function": {"type": "native", "name": "execute_service"},
}
]
CONF_BASE_URL = "base_url"
DEFAULT_CONF_BASE_URL = "https://api.openai.com/v1"
CONF_ATTACH_USERNAME = "attach_username"
DEFAULT_ATTACH_USERNAME = False
CONF_MODEL_KEY = "model_key"
MODEL_KEYS = ["model", "engine"]
Loading