Skip to content

Commit

Permalink
Merge pull request #9 from jekalmin/add-composite
Browse files Browse the repository at this point in the history
add composite function
  • Loading branch information
jekalmin authored Nov 2, 2023
2 parents 89db578 + bab487d commit 4a41db8
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 18 deletions.
38 changes: 37 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Since "gpt-3.5-turbo" model already knows how to call service of Home Assistant
4. In the bottom right corner, select the Add Integration button.
5. Follow the instructions on screen to complete the setup (API Key is required).
- [Generating an API Key](https://www.home-assistant.io/integrations/openai_conversation/#generate-an-api-key)
6. Go to Settings > Voice Assistants.
6. Go to Settings > [Voice Assistants](https://my.home-assistant.io/redirect/voice_assistants/).
7. Click to edit Assistant (named "Home Assistant" by default).
8. Select "Extended OpenAI Conversation" from "Conversation agent" tab.
<details>
Expand Down Expand Up @@ -79,6 +79,7 @@ Options include [OpenAI Conversation](https://www.home-assistant.io/integrations
- `template`: The value to be returned from function.
- `rest`: Getting data from REST API endpoint.
- `scrape`: Scraping information from website
- `composite`: A sequence of functions to execute.

Below is a default configuration of functions.

Expand Down Expand Up @@ -340,6 +341,41 @@ scrape:
<img width="300" alt="스크린샷 2023-10-31 오후 9 48 36" src="https://github.com/jekalmin/extended_openai_conversation/assets/2917984/f968e328-5163-4c41-a479-76a5406522c1">


### 6. composite
#### 6-1. Search Youtube Music
When using [ytube_music_player](https://github.com/KoljaWindeler/ytube_music_player), after `ytube_music_player.search` service is called, result is stored in attribute of `sensor.ytube_music_player_extra` entity.<br/>


```yaml
- spec:
name: search_music
description: Use this function to search music
parameters:
type: object
properties:
query:
type: string
description: The query
required:
- query
function:
type: composite
sequence:
- type: script
sequence:
- service: ytube_music_player.search
data:
entity_id: media_player.ytube_music_player
query: "{{ query }}"
- type: template
value_template: >-
media_content_type,media_content_id,title
{% for media in state_attr('sensor.ytube_music_player_extra', 'search') -%}
{{media.type}},{{media.id}},{{media.title}}
{% endfor%}
```

<img width="300" alt="스크린샷 2023-11-02 오후 8 40 36" src="https://github.com/jekalmin/extended_openai_conversation/assets/2917984/648efef8-40d1-45d2-b3f9-9bac4a36c517">

## Logging
In order to monitor logs of API requests and responses, add following config to `configuration.yaml` file
Expand Down
13 changes: 3 additions & 10 deletions custom_components/extended_openai_conversation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,14 @@
)

from .helpers import (
FUNCTION_EXECUTORS,
FunctionExecutor,
NativeFunctionExecutor,
ScriptFunctionExecutor,
TemplateFunctionExecutor,
RestFunctionExecutor,
ScrapeFunctionExecutor,
CompositeFunctionExecutor,
convert_to_template,
)

Expand All @@ -72,15 +74,6 @@
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)


FUNCTION_EXECUTORS: dict[str, FunctionExecutor] = {
"predefined": NativeFunctionExecutor(),
"native": NativeFunctionExecutor(),
"script": ScriptFunctionExecutor(),
"template": TemplateFunctionExecutor(),
"rest": RestFunctionExecutor(),
"scrape": ScrapeFunctionExecutor(),
}

# hass.data key for agent.
DATA_AGENT = "agent"

Expand Down Expand Up @@ -326,7 +319,7 @@ async def execute_function(
arguments = json.loads(message["function_call"]["arguments"])

result = await function_executor.execute(
self.hass, function, arguments, user_input, exposed_entities
self.hass, function["function"], arguments, user_input, exposed_entities
)

messages.append(
Expand Down
51 changes: 45 additions & 6 deletions custom_components/extended_openai_conversation/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ async def execute(
user_input: conversation.ConversationInput,
exposed_entities,
) -> str:
name = function["function"]["name"]
name = function["name"]
if name == "execute_service":
return await self.execute_service(
hass, function, arguments, user_input, exposed_entities
Expand Down Expand Up @@ -236,10 +236,10 @@ async def execute(
) -> str:
script = Script(
hass,
function["function"]["sequence"],
function["sequence"],
"extended_openai_conversation",
DOMAIN,
running_description=f"""[extended_openai_conversation] function {function.get("spec", {}).get("name")}""",
running_description="[extended_openai_conversation] function",
logger=_LOGGER,
)

Expand All @@ -261,7 +261,7 @@ async def execute(
user_input: conversation.ConversationInput,
exposed_entities,
) -> str:
return Template(function["function"]["value_template"], hass).async_render(
return Template(function["value_template"], hass).async_render(
arguments,
parse_result=False,
)
Expand All @@ -279,7 +279,7 @@ async def execute(
user_input: conversation.ConversationInput,
exposed_entities,
) -> str:
config = function["function"]
config = function
rest_data = _get_rest_data(hass, config, arguments)

await rest_data.async_update()
Expand All @@ -306,7 +306,7 @@ async def execute(
user_input: conversation.ConversationInput,
exposed_entities,
) -> str:
config = function["function"]
config = function
rest_data = _get_rest_data(hass, config, arguments)
coordinator = scrape.coordinator.ScrapeCoordinator(
hass,
Expand Down Expand Up @@ -376,3 +376,42 @@ def _extract_value(self, data: BeautifulSoup, sensor_config: dict[str, Any]) ->
value = None
_LOGGER.debug("Parsed value: %s", value)
return value


class CompositeFunctionExecutor(FunctionExecutor):
def __init__(self) -> None:
"""initialize composite function"""

async def execute(
self,
hass: HomeAssistant,
function,
arguments,
user_input: conversation.ConversationInput,
exposed_entities,
) -> str:
config = function
sequence = config["sequence"]

for executor_config in sequence:
function_executor = FUNCTION_EXECUTORS[executor_config["type"]]
result = await function_executor.execute(
hass, executor_config, arguments, user_input, exposed_entities
)

response_variable = executor_config.get("response_variable")
if response_variable:
arguments[response_variable] = str(result)

return result


FUNCTION_EXECUTORS: dict[str, FunctionExecutor] = {
"predefined": NativeFunctionExecutor(),
"native": NativeFunctionExecutor(),
"script": ScriptFunctionExecutor(),
"template": TemplateFunctionExecutor(),
"rest": RestFunctionExecutor(),
"scrape": ScrapeFunctionExecutor(),
"composite": CompositeFunctionExecutor(),
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@
"requirements": [
"openai==0.27.2"
],
"version": "0.0.5"
"version": "0.0.6"
}

0 comments on commit 4a41db8

Please sign in to comment.