Skip to content

Commit

Permalink
Merge pull request #65 from jekalmin/v0.0.10
Browse files Browse the repository at this point in the history
0.0.10
  • Loading branch information
jekalmin authored Dec 31, 2023
2 parents f858745 + 16240af commit d7ba5f8
Show file tree
Hide file tree
Showing 12 changed files with 232 additions and 90 deletions.
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

0 comments on commit d7ba5f8

Please sign in to comment.