Package: sageai
Folder-based functions for ChatGPT's function calling with Pydantic support 🚀SageAI lets you connect custom Python functions to ChatGPT. It organizes these functions in folders
and allows you to call them with natural language.Table of ContentsKey FeaturesRequirementsInstallationDesignSetupConventionAPISageAI InitializeSageAI MethodsVector DBTestingUnit TestsIntegration TestsOutput EqualityCLIExamplesRoadmapContributingKey FeaturesFunction organization through folder-centric functions.Strong typing for functions using Pydantic.Built-in Qdrant vector database with in-memory support for function storage and retrieval, with the option to
integrate your own.Easily test each function with an associatedtest.jsonfile, supporting both unit and integration tests.Built with CI/CD in mind, ensuring synchronicity between your vector db and the functions directory across all
environments using theindexmethod.Lightweight implementation with only three dependencies:openaipydanticqdrant-clientRequirementspython >=3.9, <3.12
pydantic >=1.6, <=1.10.12
openai >=0.27.0
qdrant-client >=1.4.0Installation# pip$pipinstallsageai# poetry$poetryaddsageaiDesignSageAI is built around the concept of afunctionsdirectory, which contains all of your functions. Each function is
defined in a Python filefunction.py, and is associated with an optionaltest.jsonfile for testing.The format of thefunction.pyfile must contain two things in order for SageAI to work:The function itselfTheFunctionobjectInput and output types may be defined using Pydantic models, and are automatically validated by SageAI. They can also be
defined outside thefunction.pyfile, and imported into the file.Here is a simplified example of how SageAI might handle a function that fetches the current weather for a given
location.# functions/get_current_weather/function.pyfromenumimportEnumfromtypingimportOptionalfrompydanticimportBaseModel,Fieldfromsageai.types.functionimportFunctionclassUnitTypes(str,Enum):CELSIUS="Celsius"FAHRENHEIT="Fahrenheit"classFunctionInput(BaseModel):location:str=Field(...,description="The city, e.g. San Francisco")unit:Optional[UnitTypes]=Field(UnitTypes.CELSIUS,description="The unit of temperature.")classFunctionOutput(BaseModel):weather:strdef__eq__(self,other):ifnotisinstance(other,FunctionOutput):returnFalsereturnself.weather==other.weatherdefget_current_weather(params:FunctionInput)->FunctionOutput:weather=(f"The weather in{params.location}is currently 22 degrees{params.unit.value}.")returnFunctionOutput(weather=weather)function=Function(function=get_current_weather,description="Get the current weather in a given location.",)We'll break down the above example into its components below.SetupCreate afunctionsdirectory in the root directory, and add your functions as described inDesign.Then initializeSageAI.fromsageaiimportSageAIsage=SageAI(openai_key="")Then index the vector database.sage.index()That's it! You're now set up and ready to interact with SageAI through natural language queries. 🚀message="What's the weather like in Toronto right now?"response=sage.chat(messages=[dict(role="user",content=message)],model="gpt-3.5-turbo-0613",top_n=5,)# response:# {#   'name': 'get_current_weather',#   'args': {'location': 'Toronto'},#   'result': {'weather': 'The weather in Toronto is currently 22 degrees Celsius.'}# }ConventionSageAI follows a convention over configuration approach to make it easy to define functions.Ensure that yourfunction.pyfile contains the following:Afunctionobject that is an instance ofFunction.A function that is the actual function that will be called by ChatGPT.The functionmusthave typed input and output Pydantic models.Each field in the input modelmusthave a description.Minimal example:defmy_function(params:PydanticInput)->PydanticOutput:returnPydanticOutput(...)function=Function(function=my_function,description="My function description.",)APISageAI InitializeTheSageAIconstructor accepts the following parameters:ParameterDescriptionDefaultsopenai_keyThe API key for OpenAI.Requiredfunctions_directoryDirectory containing functions./functionsvectordbAn implementation of theAbstractVectorDBfor vector database operations.DefaultVectorDBServicelog_levelDesired log level for the operations.ERRORSageAI Methods1.chatInitiate a chat using OpenAI's API and the provided parameters.Parameters:ParameterDescriptionDefaults-Accepts the same parameters as OpenAI'schat endpoint-top_nThe number of top functions to consider from the vector database.RequiredReturns:dict(name="function_name",args={"arg1":"value1","arg2":"value2"},result={"out1":"value1","out2":"value2"},# Optionalerror="",# Optional)Eitherresultorerrorwill be present in the response, but not both.2.get_top_n_functionsGet the topnfunctions from the vector database based on a query.Parameters:ParameterDescriptionDefaultsqueryThe query to search against.Requiredtop_nThe number of functions to return.RequiredReturns:A dict of function names toFunctiondefinitions.3.run_functionExecute a function based on its name and provided arguments.Parameters:ParameterDescriptionDefaultsnameName of the function.RequiredargsArguments to pass to the function.RequiredReturns:The function result as a dict.4.call_openaiCalls the OpenAI API with the provided parameters.Parameters:ParameterDescriptionDefaultsopenai_argsAccepts the same parameters as OpenAI'schat endpointRequiredtop_functionsList of dicts that is a representation of your functions.RequiredReturns:A tuple of the function name and the function args.5.indexIndex the vector database based on the functions directory.
This method is useful to update the vectordb when new functions are added or existing ones are updated.Want more control?Thechatfunction usesget_top_n_functions,run_function, andcall_openaiinternally.
However, we also expose these methods incase you wish to use them directly to implement your ownchatlogic.Vector DBSageAI comes with a built-in in-memory vector database, Qdrant, which is used to store and retrieve functions.If you wish to use your own vector database, you can implement theAbstractVectorDBclass and pass it into theSageAIconstructor.See theadvanced examplefor an example of how to integrate your own vector database.TestingAs for the optionaltest.jsonfile in each function, follow this structure:[{"message":"What's the weather like in Toronto right now?","input":{"location":"Toronto","unit":"Celsius"},"output":{"weather":"The weather in Toronto is currently 22 degrees Celsius."}}]Each object in the array represents a test case.Themessagefield is the natural language message that will be sent
to ChatGPT, and theinputfield is the expected input that will be passed to the function.Theoutputfield is the
expected output of the function.SageAI offers unit and integration tests.Unit TestsUnit tests do not call the vector database nor ChatGPT, andwill notcost you money.Unit tests are used to ensure your functions directory is valid, and it tests the function in isolation.It tests whether:thefunctionsdirectory exists.each function has afunction.pyfile.eachfunction.pyfile has aFunctionobject.and more!It also tests whether the input and output types are valid, and whether the function returns the expected output based
on
the input alone by callingfunc(test_case["input"]) == test_case["output"].Integration TestsIntegration tests will call the vector database and ChatGPT, andwillcost you money.Integration tests are used to test the function by calling ChatGPT and the vector database.They test whether the vector database is able to retrieve the function, and whether ChatGPT can call the function
with the given input and return the expected output.Because ChatGPT's responses can vary, integration tests may return different results each time.
It's important to use integration tests as a tool to ensure ChatGPT is able to call the right function with the right
input, and not as a definitive test to measure the test rate of your functions.Output EqualityYou can customize how to determine equality between the expected and actual output by overriding the__eq__method in the output model.classFunctionOutput(BaseModel):weather:strtemperature:intdef__eq__(self,other):ifnotisinstance(other,FunctionOutput):returnFalsereturnself.weather==other.weatherIn the case above, we only care about theweatherfield, and not thetemperaturefield. Therefore, we only compare
theweatherfield in the__eq__method.This is especially useful when you are returning an object from a database, for example, and you only care to test
against a subset of the fields (for example, theidfield).CLI# To run unit and integration tests for all functions:poetryrunsageai-tests--apikey=openapikey--directory=path/to/functions# To run unit tests only for all functions:poetryrunsageai-tests--apikey=openapikey--directory=path/to/functions--unit# To run integration tests only for all functions:poetryrunsageai-tests--apikey=openapikey--directory=path/to/functions--integration# To run unit and integration tests for a specific function:poetryrunsageai-tests--apikey=openapikey--directory=path/to/functions/get_current_weatherParameterDescriptionDefaults--apikeyOpenAI API key.Required--directoryDirectory of the functions or of the specific function to run/functions--unitOnly run unit testsfalse--integrationOnly run integration testsfalseExamplesBasic- Get started with a simple SageAI function.Advanced- Dive deeper with more intricate functionalities and use-cases.RoadmapAdd tests and code coverageSupport multiple function callsSupport streamingSupport asyncioSupport Pydantic V2Write Chainlit exampleWrite fullstack exampleContributingInterested in contributing to SageAI? Please see ourCONTRIBUTING.mdfor guidelines, coding
standards, and other details.