Skip to content

Commit

Permalink
Support calculator.
Browse files Browse the repository at this point in the history
  • Loading branch information
lifuhuang committed Dec 31, 2024
1 parent e097d99 commit 15fda1d
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 2 deletions.
37 changes: 35 additions & 2 deletions custom_components/extended_openai_conversation/helpers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from abc import ABC, abstractmethod
from datetime import timedelta
from functools import partial
import ast
import logging
import os
import re
Expand Down Expand Up @@ -205,7 +206,8 @@ def __init__(self) -> None:
"get_statistics": self.get_statistics,
"get_user_from_user_id": self.get_user_from_user_id,
"write_to_file": self.write_to_file,
"read_from_file": self.read_from_file
"read_from_file": self.read_from_file,
"calculate": self.calculate
}

async def execute(
Expand Down Expand Up @@ -474,7 +476,38 @@ def as_dict(self, state: State | dict[str, Any]):
if isinstance(state, State):
return state.as_dict()
return state


def calculate(self, hass: HomeAssistant, function, arguments, user_input: conversation.ConversationInput, exposed_entities):
expression = function["expression"]
try:
if not self.is_math_expr(expression):
raise HomeAssistantError("Expression is not a valid mathematical expression.")
result = eval(expression)
return result
except Exception as e:
raise HomeAssistantError(f"Error evaluating math expression: {str(e)}")

def is_math_expr(self, expr):
"""
Determines if a given Python expression is a mathematical expression.
Args:
expr (str): The input expression as a string.
Returns:
bool: True if the expression is mathematical, False otherwise.
"""
allowed_node_types = (
ast.Expression, ast.BinOp, ast.UnaryOp, ast.Module, ast.Constant,
ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Pow, ast.Mod, ast.USub
)

try:
tree = ast.parse(expr, mode='eval')
return all(isinstance(x, allowed_node_types) for x in ast.walk(tree))
except Exception:
return False

class ScriptFunctionExecutor(FunctionExecutor):
def __init__(self) -> None:
"""initialize script function"""
Expand Down
60 changes: 60 additions & 0 deletions examples/calculator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
## Objective
- Calculate a simple math expression and return the calculation result as a number.


## Functions

### write_to_file
```yaml
- spec:
name: calculate
description: calculate a math expression and return the calculation result as a number.
strict: true
parameters:
type: object
additionalProperties: false
properties:
expression:
type: string
description: a legal Python expression that contains only common math operators (+, -, *, /, **, %), numbers, and brackets.
required:
- expression
function:
type: native
name: calculate
```
## Example
### Prompt
...
When the inquiry implies math calculation (e.g., "how much in total did I make over the past week?"), always call function calculate to get the result. You can do that by formulate the calculation as a Python math expression and evaluate it by calling function "calculate". Do not ask user to confirm if calculation is needed.
```
### Sample Conversation 1
```
User:
What's 247 divided by 4, and then powered by 5?

Assistant:
The result of 247 divided by 4, and then raised to the power of 5, is approximately 897,810,767.58.
```
### Sample Conversation 2
```
User:
How much milk did the baby drink today?

Assistant:
I found the following entries in the memo regarding how much the baby drank today:

- 130 ml at 00:30
- 125 ml at 05:00
- 110 ml at 13:00
- 125 ml at 06:00
- 130 ml at 21:00

I added these amounts together:

130 + 125 + 110 + 125 + 130 = 620 ml.

That's how I reached the total of 620 milliliters.
```

0 comments on commit 15fda1d

Please sign in to comment.