Skip to content
This repository has been archived by the owner on Aug 13, 2024. It is now read-only.

Commit

Permalink
Assistant only interact with plain python interfaces (#63)
Browse files Browse the repository at this point in the history
  • Loading branch information
jordan-wu-97 authored Dec 20, 2023
1 parent d5d95d2 commit 9861bdf
Show file tree
Hide file tree
Showing 14 changed files with 267 additions and 179 deletions.
21 changes: 15 additions & 6 deletions docs/docs/extending-openassistants/create-a-custom-function.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ sidebar_position: 1

# Create a custom function

You can create your own custom functions in OpenAssistants by extending the base class `BaseFunction`.
You can create your own custom functions in OpenAssistants by extending the base interface class `IBaseFunction`.

Let's take a look at the included `PythonEvalFunction` to get a sense of how this works.

Expand All @@ -18,7 +18,7 @@ class PythonEvalFunction(BaseFunction):
python_code: str

async def execute(
self, deps: FunctionExecutionDependency
self, deps: FunctionExecutionDependency
) -> AsyncStreamVersion[Sequence[FunctionOutput]]:
# This is unsafe, make sure you trust python_code provided in the YAML
exec_locals: Dict[str, Any] = {}
Expand Down Expand Up @@ -55,8 +55,14 @@ class PythonEvalFunction(BaseFunction):

Note the following:

- Your class must have an `execute` function which runs asyncronously, which means it must be prepended with `async` where results are `yielded`, not `returned`
- `AsyncStreamVersion[Sequence[FunctionOutput]]`: this means that the `execute` function yields a list of `FunctionOutput` type. `FunctionOutput` is a generic type that encompasses `TextOutput`, `DataFrameOutput`, `VisualizationOutput` and `FollowUpsOutput`. Since it's a sequence, you can return more than one type in the same query response (i.e. return a table via `DataFrameOutput` and then a visualization of the data via `VisualizationOutput` in the same chat response). Note the output of function `main` in the yaml definition below for proper formatting
- Your class must have an `execute` function which runs asyncronously, which means it must be prepended with `async`
where results are `yielded`, not `returned`
- `AsyncStreamVersion[Sequence[FunctionOutput]]`: this means that the `execute` function yields a list
of `FunctionOutput` type. `FunctionOutput` is a generic type that
encompasses `TextOutput`, `DataFrameOutput`, `VisualizationOutput` and `FollowUpsOutput`. Since it's a sequence, you
can return more than one type in the same query response (i.e. return a table via `DataFrameOutput` and then a
visualization of the data via `VisualizationOutput` in the same chat response). Note the output of function `main` in
the yaml definition below for proper formatting

Is used in a YAML function definition as follows:

Expand Down Expand Up @@ -86,7 +92,10 @@ python_code: |
yield [{"type": "text", "text": f"Inquiry email about recent purchase sent to: {args.get('to')}"}]
```
As you can see, the `python_code` and `parameters` YAML properties are passed into the class constructor to make sure the provided code in the YAML
As you can see, the `python_code` and `parameters` YAML properties are passed into the class constructor to make sure
the provided code in the YAML
can be used by the function's implementation.

Check out the [example app](https://github.com/definitive-io/openassistants/blob/main/examples/fast-api-server/fast_api_server/main.py) to see how you can add custom functions to the assistant library.
Check out
the [example app](https://github.com/definitive-io/openassistants/blob/main/examples/fast-api-server/fast_api_server/main.py)
to see how you can add custom functions to the assistant library.
55 changes: 28 additions & 27 deletions packages/openassistants-fastapi/openassistants_fastapi/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from fastapi import APIRouter, HTTPException
from openassistants.core.assistant import Assistant
from openassistants.data_models.chat_messages import OpasMessage
from openassistants.functions.base import BaseFunction
from openassistants.utils.async_utils import last_value
from pydantic import BaseModel, Field

Expand Down Expand Up @@ -53,30 +52,32 @@ async def stream() -> AsyncStreamVersion[ChatResponse]:
else:
return await last_value(stream())

@v1alpha_router.get("/libraries/{assistant_id}/functions")
async def get_assistant_functions(
assistant_id: str,
) -> List[BaseFunction]:
if assistant_id not in route_assistants.assistants:
raise HTTPException(status_code=404, detail="assistant not found")

return await route_assistants.assistants[assistant_id].get_all_functions()

@v1alpha_router.get("/libraries/{assistant_id}/functions/{function_id}")
async def get_function_from_assistant(
assistant_id: str,
function_id: str,
) -> BaseFunction:
if assistant_id not in route_assistants.assistants:
raise HTTPException(status_code=404, detail="assistant not found")
function_libraries = route_assistants.assistants[
assistant_id
].function_libraries

for library in function_libraries:
if (func := await library.aread(function_id)) is not None:
return func

raise HTTPException(status_code=404, detail="function not found")

# TODO: we need a way to specify IBaseFunctions that can be serialized
# commented out for now since its not being used anyways
# @v1alpha_router.get("/libraries/{assistant_id}/functions")
# async def get_assistant_functions(
# assistant_id: str,
# ) -> List[IBaseFunction]:
# if assistant_id not in route_assistants.assistants:
# raise HTTPException(status_code=404, detail="assistant not found")
#
# return await route_assistants.assistants[assistant_id].get_all_functions()
#
# @v1alpha_router.get("/libraries/{assistant_id}/functions/{function_id}")
# async def get_function_from_assistant(
# assistant_id: str,
# function_id: str,
# ) -> IBaseFunction:
# if assistant_id not in route_assistants.assistants:
# raise HTTPException(status_code=404, detail="assistant not found")
# function_libraries = route_assistants.assistants[
# assistant_id
# ].function_libraries
#
# for library in function_libraries:
# if (func := await library.aread(function_id)) is not None:
# return func
#
# raise HTTPException(status_code=404, detail="function not found")
#
return v1alpha_router
12 changes: 8 additions & 4 deletions packages/openassistants/openassistants/contrib/index_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,25 @@
from openassistants.functions.base import (
BaseFunction,
FunctionExecutionDependency,
IBaseFunction,
)
from openassistants.functions.utils import AsyncStreamVersion


class IndexFunction(BaseFunction):
type: Literal["IndexFunction"] = "IndexFunction"
functions: Callable[[], Awaitable[List[BaseFunction]]]
functions: Callable[[], Awaitable[List[IBaseFunction]]]

async def execute(
self, deps: FunctionExecutionDependency
) -> AsyncStreamVersion[Sequence[FunctionOutput]]:
output = ""
for function in await self.functions():
if function.type == "IndexFunction":
if function.get_type() == "IndexFunction":
continue
output += f"""**{function.display_name}**
{function.description}\n\n"""
output += f"""**{function.get_display_name()}**
{function.get_description()}\n\n"""
yield [TextOutput(text=output)]

def get_parameters_json_schema(self) -> dict:
return {"type": "object", "properties": {}}
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,5 @@ async def execute(
f"Error while executing action function {self.id}. function raised: {e}"
) from e

async def get_parameters_json_schema(self) -> dict:
def get_parameters_json_schema(self) -> dict:
return self.parameters.json_schema
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from typing import Awaitable, Callable, Dict, Sequence
from typing import Awaitable, Callable, Mapping, Sequence

from openassistants.data_models.function_input import BaseJSONSchema
from openassistants.data_models.function_output import FunctionOutput
from openassistants.functions.base import (
BaseFunction,
EntityConfig,
FunctionExecutionDependency,
IEntityConfig,
)
from openassistants.functions.utils import AsyncStreamVersion

Expand All @@ -17,16 +17,16 @@ class PythonCallableFunction(BaseFunction):

parameters: BaseJSONSchema

get_entity_configs_callable: Callable[[], Awaitable[dict[str, EntityConfig]]]
get_entity_configs_callable: Callable[[], Awaitable[Mapping[str, IEntityConfig]]]

async def execute(
self, deps: FunctionExecutionDependency
) -> AsyncStreamVersion[Sequence[FunctionOutput]]:
async for output in self.execute_callable(deps):
yield output

async def get_parameters_json_schema(self) -> dict:
def get_parameters_json_schema(self) -> dict:
return self.parameters.json_schema

async def get_entity_configs(self) -> Dict[str, EntityConfig]:
async def get_entity_configs(self) -> Mapping[str, IEntityConfig]:
return await self.get_entity_configs_callable()
7 changes: 5 additions & 2 deletions packages/openassistants/openassistants/contrib/python_eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@

from openassistants.data_models.function_input import BaseJSONSchema
from openassistants.data_models.function_output import FunctionOutput
from openassistants.functions.base import BaseFunction, FunctionExecutionDependency
from openassistants.functions.base import (
BaseFunction,
FunctionExecutionDependency,
)
from openassistants.functions.utils import AsyncStreamVersion
from pydantic import TypeAdapter

Expand Down Expand Up @@ -45,5 +48,5 @@ async def execute(
f"Error while executing action function {self.id}. function raised: {e}"
) from e

async def get_parameters_json_schema(self) -> dict:
def get_parameters_json_schema(self) -> dict:
return self.parameters.json_schema
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
OpasFunctionMessage,
OpasMessage,
)
from openassistants.data_models.function_input import BaseJSONSchema, FunctionCall
from openassistants.data_models.function_input import (
BaseJSONSchema,
FunctionCall,
)
from openassistants.data_models.function_output import (
DataFrameOutput,
FollowUpsOutput,
Expand Down Expand Up @@ -201,5 +204,5 @@ async def execute(

yield results

async def get_parameters_json_schema(self) -> dict:
def get_parameters_json_schema(self) -> dict:
return self.parameters.json_schema
Loading

0 comments on commit 9861bdf

Please sign in to comment.