{"id":26338935,"url":"https://github.com/assistantengine/open-functions-core","last_synced_at":"2025-03-16T03:15:47.428Z","repository":{"id":279277127,"uuid":"930290545","full_name":"AssistantEngine/open-functions-core","owner":"AssistantEngine","description":"This library provides a set of primitives that simplify LLM calling","archived":false,"fork":false,"pushed_at":"2025-03-04T15:33:29.000Z","size":52,"stargazers_count":0,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-04T15:33:38.502Z","etag":null,"topics":["llm","openai","toolcalling"],"latest_commit_sha":null,"homepage":"https://www.assistant-engine.com/","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AssistantEngine.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-02-10T11:52:34.000Z","updated_at":"2025-03-04T15:33:34.000Z","dependencies_parsed_at":"2025-02-24T19:16:39.870Z","dependency_job_id":"9bf22928-e48a-450f-a984-dc2dcae9e505","html_url":"https://github.com/AssistantEngine/open-functions-core","commit_stats":null,"previous_names":["assistantengine/open-functions-core"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AssistantEngine%2Fopen-functions-core","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AssistantEngine%2Fopen-functions-core/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AssistantEngine%2Fopen-functions-core/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AssistantEngine%2Fopen-functions-core/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AssistantEngine","download_url":"https://codeload.github.com/AssistantEngine/open-functions-core/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243818171,"owners_count":20352629,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["llm","openai","toolcalling"],"created_at":"2025-03-16T03:15:46.885Z","updated_at":"2025-03-16T03:15:47.412Z","avatar_url":"https://github.com/AssistantEngine.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n![Preview](media/preview.png)\n\n\u003c/div\u003e\n\n# OpenFunctions Core\n\nThis library provides a set of primitives that simplify LLM calling. It offers an easy way to define messages and message lists, create tool definitions, and execute tool calls. By abstracting these core functionalities, **OpenFunctions Core** helps you reduce boilerplate code and quickly integrate advanced tool-calling capabilities into your LLM-powered applications.\n\n## Key Features\n\n- ✅ **Structured Conversation Management** - Offers robust primitives for messages and message lists.\n- ✅ **Unified LLM Tool Interface** - **OpenFunctions** provide a standardized API for defining and invoking tools within LLM-powered applications.\n- ✅ **Function Registry \u0026 Meta-mode** - Manages multiple **OpenFunctions** and namespaces seamlessly, with support for dynamic activation and deactivation to keep tool definitions clear and concise.\n- ✅ **Extensible Architecture** - Designed to be highly customizable, allowing you to extend or replace components with your own implementations.\n- ✅ **Developer-Friendly Abstractions** - Reduces boilerplate code through intuitive helper classes for defining functions, parameters, and responses.\n- ✅ **Pre-built Integrations** - Includes ready-to-use implementations for popular platforms such as Slack, GitHub, Bitbucket, Trello, and Jira Service Desk.\n\n\n## Installation\nInstall the package via Composer:\n\n```bash\ncomposer require assistant-engine/open-functions-core\n```\n\n## Usage\n\nYou can use this library for the following challenges:\n\n```php\n// A common llm call\n$response = $client-\u003echat()-\u003ecreate([\n    'model'         =\u003e 'gpt-4o',\n    'messages'      =\u003e $messages, // 1. Building the messages array\n    'tools'         =\u003e $functionDefinitions, // 2. Collect the right function definitions\n]);\n\nif (isset($response-\u003echoices[0]-\u003emessage-\u003etoolCalls)) {\n    foreach ($response-\u003echoices[0]-\u003emessage-\u003etoolCalls as $toolCall) {\n        // 3. Executing the requested tool call\n    }\n}\n```\n\n## Messages\n\nBased on the OpenAI schema, the available message types are exposed as primitives. You can use these as building blocks to structure your conversation. The following example shows how to define an array of messages, add them to a MessageList, and convert that list to an array for use in an LLM call.\n\n```php\n\u003c?php\nuse AssistantEngine\\OpenFunctions\\Core\\Models\\Messages\\Content\\ToolCall;\nuse AssistantEngine\\OpenFunctions\\Core\\Models\\Messages\\DeveloperMessage;\nuse AssistantEngine\\OpenFunctions\\Core\\Models\\Messages\\ToolMessage;\nuse AssistantEngine\\OpenFunctions\\Core\\Models\\Messages\\UserMessage;\nuse AssistantEngine\\OpenFunctions\\Core\\Models\\Messages\\AssistantMessage;\nuse AssistantEngine\\OpenFunctions\\Core\\Models\\Messages\\MessageList;\n\n// Define an array of messages based on the OpenAI API schema.\n// These primitives can be used to structure the conversation context.\n$messages = [\n    new DeveloperMessage(\"You are a helpful assistant.\"),\n    new UserMessage(\"What's the weather like today in Paris?\"),\n    (new AssistantMessage())\n        -\u003eaddToolCall(new ToolCall(\"tool_call_1\", \"getWeather\", json_encode([\"cityName\" =\u003e \"Paris\"]))),\n    new ToolMessage(\"The weather in Paris is sunny with a temperature of 24°C.\", \"tool_call_1\"),\n    new AssistantMessage(\"The current weather in Paris is sunny with a high of 24°C.\"),\n    new UserMessage(\"Thanks!\")\n];\n\n// Create a MessageList and add the messages array\n$messageList = new MessageList();\n$messageList-\u003eaddMessages($messages);\n\n// Convert the MessageList to an array for use in an API call\n$conversationArray = $messageList-\u003etoArray();\n\n// These definitions can now be used as the tools parameter in your OpenAI client call.\n$response = $client-\u003echat()-\u003ecreate([\n    'model'    =\u003e 'gpt-4o',\n    'messages' =\u003e $conversationArray\n]);\n\n```\n\n## Tool Calling\n\nIn order to enable tool calling, the common challenge is to both define the function definitions and expose the methods to make tool calling possible. To address this, the concept of an **OpenFunction** is introduced. Inside an **OpenFunction**, you generate the function definitions and implement the methods that the LLM can invoke. The **AbstractOpenFunction** class provides convenience methods such as callMethod() to wrap the output in a standardized response and handle errors consistently.\n\n### Function Definitions\n\nEach class that extends the abstract open function must implement the generateFunctionDefinitions() method. This method is responsible for describing the functions that your tool exposes. To build these descriptions, you can use the provided helper classes:\n\n- **FunctionDefinition:** This class is used to create a structured schema for a function. It accepts a function name and a short description and can include details about parameters.\n- **Parameter:** This helper is used to define parameters for your function. It allows you to set the type (e.g., string, number, boolean) and additional details like description and whether the parameter is required.\n\nFor example, here’s a simple implementation:\n\n```php\n\u003c?php\nuse AssistantEngine\\OpenFunctions\\Core\\Contracts\\AbstractOpenFunction;\nuse AssistantEngine\\OpenFunctions\\Core\\Helpers\\FunctionDefinition;\nuse AssistantEngine\\OpenFunctions\\Core\\Helpers\\Parameter;\nuse AssistantEngine\\OpenFunctions\\Core\\Models\\Responses\\TextResponseItem;\n\nclass WeatherOpenFunction extends AbstractOpenFunction\n{\n    /**\n     * Generate function definitions.\n     *\n     * This method returns a schema defining the \"getWeather\" function.\n     * It requires a cityName parameter to fetch the current weather.\n     *\n     * @return array\n     */\n    public function generateFunctionDefinitions(): array\n    {\n        // Create a function definition for getWeather.\n        $functionDef = new FunctionDefinition('getWeather', 'Returns the current weather for a given city.');\n        \n        // Add a required parameter for the city name.\n        $functionDef-\u003eaddParameter(\n            Parameter::string('cityName')\n                -\u003edescription('The name of the city to get the weather for.')\n                -\u003erequired()\n        );\n        \n        // Return the function definition as an array.\n        return [$functionDef-\u003ecreateFunctionDescription()];\n    }\n}\n```\n\nOnce you have implemented your open functions (such as the WeatherOpenFunction), you can generate their function definitions and pass them to the OpenAI client as the tools parameter. For example:\n\n```php\n\u003c?php\nuse AssistantEngine\\OpenFunctions\\Core\\Examples\\WeatherOpenFunction;\n\n// Instantiate the WeatherOpenFunction.\n$weatherFunction = new WeatherOpenFunction();\n\n// Generate the function definitions. This creates a schema for functions like \"getWeather\" and \"getForecast\".\n$functionDefinitions = $weatherFunction-\u003egenerateFunctionDefinitions();\n\n// These definitions can now be used as the tools parameter in your OpenAI client call.\n$response = $client-\u003echat()-\u003ecreate([\n    'model'    =\u003e 'gpt-4o',\n    'messages' =\u003e $conversationArray,\n    'tools'    =\u003e $functionDefinitions,\n]);\n\n// Process the response and execute any tool calls as needed.\n```\n\n#### Open Function Registry\n\nIn some scenarios, you may want to use the same **OpenFunction** more than once with different configurations, or you might have different **OpenFunctions** that define methods with the same name. To handle these cases, the library provides an **OpenFunction Registry**, which itself is also an **OpenFunction**. This registry allows you to register each function under a unique namespace, ensuring that even if functions share the same underlying method name, they remain distinct.\n\nFor example, suppose you want one WeatherOpenFunction instance to operate in Celsius (the default) and another in Fahrenheit. You could register them as follows:\n\n```php\n\u003c?php\nuse AssistantEngine\\OpenFunctions\\Core\\Examples\\WeatherOpenFunction;\nuse AssistantEngine\\OpenFunctions\\Core\\Tools\\OpenFunctionRegistry;\n\n// Create an instance of the registry.\n$registry = new OpenFunctionRegistry();\n\n// Instantiate two WeatherOpenFunction instances.\n// For this example, imagine the WeatherOpenFunction can be configured to use different temperature units.\n// The first instance is set for Celsius (default), and the second for Fahrenheit.\n$weatherCelsius = new WeatherOpenFunction(\"celsius\"); // Configured to return temperatures in Celsius.\n$weatherFahrenheit = new WeatherOpenFunction(\"fahrenheit\"); // Imagine this instance is configured to return Fahrenheit.\n\n// Register the functions under different namespaces.\n// The registry automatically prefixes function names with the namespace (e.g., \"celsius_getWeather\", \"fahrenheit_getWeather\").\n$registry-\u003eregisterOpenFunction('celsius', 'Weather functions using Celsius.', $weatherCelsius);\n$registry-\u003eregisterOpenFunction('fahrenheit', 'Weather functions using Fahrenheit.', $weatherFahrenheit);\n\n// Retrieve all namespaced function definitions to pass to the OpenAI client.\n$toolDefinitions = $registry-\u003egenerateFunctionDefinitions();\n\n// Use these tool definitions in the client call.\n$response = $client-\u003echat()-\u003ecreate([\n    'model'    =\u003e 'gpt-4o',\n    'messages' =\u003e $conversationArray,\n    'tools'    =\u003e $toolDefinitions,\n]);\n\n// Later, when the client calls a function, the registry will use the namespaced function name \n// (e.g., \"celsius_getWeather\" or \"fahrenheit_getWeather\") to invoke the correct method.\n```\n\nIn this example, the registry ensures that even though both WeatherOpenFunction instances share the same method names (like getWeather), they are uniquely identified by their namespaces (celsius and fahrenheit). This separation allows you to call the appropriate function based on the desired temperature unit without any naming collisions.\n\n##### Meta Mode\n\nThe above example ensures that even if multiple functions are registered under similar method names, each one is uniquely namespaced and easily distinguishable. In some scenarios, however, the total number of methods you want to provide to the LLM may exceed the maximum number it can ingest in a single call. To address this, the **OpenFunction Registry** offers a meta-mode.\n\n```php\nuse AssistantEngine\\OpenFunctions\\Core\\Examples\\DeliveryOpenFunction;\nuse AssistantEngine\\OpenFunctions\\Core\\Tools\\OpenFunctionRegistry;\n\n$registry = new OpenFunctionRegistry(true, 'This is the registry where you can control active functions');\n\n$burger = new DeliveryOpenFunction([\n    'Classic Burger',\n    'Cheese Burger',\n    'Bacon Burger',\n    'Veggie Burger',\n    'Double Burger'\n]);\n\n$pizza = new DeliveryOpenFunction([\n    'Margherita',\n    'Pepperoni',\n    'Hawaiian',\n    'Veggie',\n    'BBQ Chicken',\n    'Meat Lovers'\n]);\n\n$sushi = new DeliveryOpenFunction([\n    'California Roll',\n    'Spicy Tuna Roll',\n    'Salmon Nigiri',\n    'Eel Avocado Roll',\n    'Rainbow Roll',\n    'Vegetable Roll'\n]);\n\n$registry-\u003eregisterOpenFunction(\n    'burger',\n    'This is a nice burger place.',\n    $burger\n);\n\n$registry-\u003eregisterOpenFunction(\n    'pizza',\n    'This is a nice pizza place',\n    $pizza\n);\n\n$registry-\u003eregisterOpenFunction(\n    'sushi',\n    'This is a nice sushi place',\n    $sushi\n);\n```\n\nIn **meta-mode**, only three core registry functions are initially registered to the LLM. These functions give the LLM the ability to activate and deactivate additional methods on the fly. This approach keeps the number of tool definitions sent to the LLM both limited and understandable. The following image demonstrates an activated meta-mode within the **[Filament Assistant Plugin](https://github.com/AssistantEngine/filament-assistant)**:\n\n![Meta Mode](media/meta-mode.png)\n\nThe three registry functions provided in meta-mode are:\n\n| **Method**       | **Description**                                                                                                 | **Parameters**                                                                                  |\n|------------------|-----------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------|\n| **listFunctions**    | Lists all available functions grouped by namespace, including their names, descriptions, and namespace details. | None                                                                                            |\n| **activateFunction** | Activates one or more functions from the registry. Duplicate activations are ignored.                           | **functionNames**: *array of string* (required) An array of valid function names to activate.   |\n| **deactivateFunction** | Deactivates one or more functions from the registry.                                                            | **functionNames**: *array of string* (required) An array of valid function names to deactivate. |\n\n##### Registry Presenter\n\nIt can be a good idea to inform the LLM about the different namespaces and give a little more context. In order to do this, you might want to add a dedicated developer message to the message list that explains the namespaces. To achieve this dynamically, the concept of a **Message List Extension** is implemented. Extensions implement a specific interface and are invoked when the message list is built, giving you the opportunity to modify or extend the messages.\n\n#### Message List Extensions\n\nSometimes it’s useful to add a message that explains context details to the LLM. For instance, you might want to inform the model about the namespaces registered by your tool. To achieve this dynamically, you can create an extension that implements the **MessageListExtensionInterface**. Once added to the message list, the extension is invoked automatically during the conversion process, allowing you to inject extra messages.\n\nFor example, the **RegistryPresenter** class implements this interface and in its **extend()** method it prepends a developer message that lists the namespaces:\n\n```php\n/**\n * Extend the message list by prepending a developer message with namespace details.\n *\n * @param MessageList $messageList\n * @return void\n */\npublic function extend(MessageList $messageList): void\n{\n    if (empty($this-\u003eregistry-\u003egetNamespaces())) {\n        return;\n    }\n    $messageList-\u003eprependMessages([$this-\u003egetNamespacesDeveloperMessage()]);\n}\n```\n\nTo add an extension to your message list, simply use the addExtension() method on your MessageList instance. Here’s how you can register the **RegistryPresenter** as an extension:\n\n```php\nuse AssistantEngine\\OpenFunctions\\Core\\Models\\Messages\\MessageList;\nuse AssistantEngine\\OpenFunctions\\Core\\Tools\\OpenFunctionRegistry;\nuse AssistantEngine\\OpenFunctions\\Core\\Presenter\\RegistryPresenter;\n\n// Instantiate your registry and register any open functions as needed.\n$registry = new OpenFunctionRegistry();\n// (Assume functions are registered here...)\n\n// Create a message list and add the registry as an extension.\n$messageList = new MessageList();\n$messageList-\u003eaddExtension(new RegistryPresenter($registry),);\n\n// When converting to an array, the registry extension prepends the developer message.\n$conversationArray = $messageList-\u003etoArray();\n```\n\n### Function Calling\n\nImplement all callable methods within your **OpenFunction** class. Each method should return a string, a text response, a binary response, or a list of responses. The callMethod in the abstract class ensures the output is consistently wrapped.\n\nFor example, in WeatherOpenFunction:\n\n```php\n\u003c?php\nuse AssistantEngine\\OpenFunctions\\Core\\Contracts\\AbstractOpenFunction;\nuse AssistantEngine\\OpenFunctions\\Core\\Helpers\\FunctionDefinition;\nuse AssistantEngine\\OpenFunctions\\Core\\Helpers\\Parameter;\nuse AssistantEngine\\OpenFunctions\\Core\\Models\\Responses\\TextResponseItem;\n\nclass WeatherOpenFunction extends AbstractOpenFunction\n{\n    /**\n     * Returns the current weather for the given city.\n     *\n     * @param string $cityName\n     * @return TextResponseItem\n     */\n    public function getWeather(string $cityName)\n    {\n        $weathers = ['sunny', 'rainy', 'cloudy', 'stormy', 'snowy', 'windy'];\n        $weather = $weathers[array_rand($weathers)];\n    \n        return new TextResponseItem(\"The weather in {$cityName} is {$weather}.\");\n    }\n    \n    // ...\n}\n```\n\nTo invoke the function:\n\n```php\n// Instantiate the WeatherOpenFunction.\n$weatherFunction = new WeatherOpenFunction();\n\n// Call the 'getWeather' method via callMethod.\n$response = $weatherFunction-\u003ecallMethod('getWeather', ['cityName' =\u003e 'New York']);\n\n// Output the response as an array.\nprint_r($response-\u003etoArray());\n```\n\nand if you used the registry\n\n```php\n\u003c?php\n// Execute the function call using the registry.\n$response = $registry-\u003ecallMethod('celsius_getWeather', ['cityName' =\u003e 'New York']);\n// Output the response as an array.\nprint_r($response-\u003etoArray());\n```\n\nso if you would use it within the llm loop it could look like something like this\n\n```php\n$response = $client-\u003echat()-\u003ecreate([\n    'model'         =\u003e 'gpt-4o',\n    'messages'      =\u003e $conversationArray, \n    'tools'         =\u003e $toolDefinitions,\n]);\n\nif (isset($response-\u003echoices[0]-\u003emessage-\u003etoolCalls)) {\n    foreach ($response-\u003echoices[0]-\u003emessage-\u003etoolCalls as $toolCall) {\n        $namespacedName = $toolCall['function']['name'] ?? null;\n        $argumentsJson = $toolCall['function']['arguments'] ?? '{}';\n    \n        $response = $registry-\u003ecallMethod($namespacedName, json_decode($argumentsJson, true));\n    }\n}\n```\n\n### Available Open Function Implementations\n\nIn addition to creating your own **OpenFunction**, there are several ready-to-use implementations available.\nHere’s a quick overview:\n\n- **[Memory](https://github.com/AssistantEngine/open-functions-memory)**:  Provides a standardized API for storing, updating, retrieving, and removing conversational memories.\n- **[Notion](https://github.com/AssistantEngine/open-functions-notion)**: Connects to your Notion workspace and enables functionalities such as listing databases, retrieving pages, and managing content blocks.\n- **[GitHub](https://github.com/AssistantEngine/open-functions-github)**: Integrates with GitHub to allow repository operations like listing branches, reading files, and committing changes.\n- **[Bitbucket](https://github.com/AssistantEngine/open-functions-bitbucket)**: Provides an interface similar to GitHub’s, enabling you to interact with Bitbucket repositories to list files, read file contents, and commit modifications.\n- **[Trello](https://github.com/AssistantEngine/open-functions-trello)**: Enables interactions with Trello boards, lists, and cards, facilitating project management directly within your assistant.\n- **[Slack](https://github.com/AssistantEngine/open-functions-slack)**: Seamlessly connects your assistant to Slack and perform actions like listing channels, posting messages, replying to threads, adding reactions, and retrieving channel history and user profiles.\n- **[Jira Service Desk](https://github.com/AssistantEngine/open-functions-jira-service-desk)**: Integrates with Jira Service Desk to interact with service requests—enabling you to create, update, and manage requests (cards), list queues, add comments, transition statuses, and manage priorities.\n\n## More Repositories\n\nWe’ve created more repositories to make AI integration even simpler and more powerful! Check them out:\n\n- **[Filament Assistant](https://github.com/AssistantEngine/filament-assistant)**: Add conversational AI capabilities directly into Laravel Filament.\n\n\u003e We are a young startup aiming to make it easy for developers to add AI to their applications. We welcome feedback, questions, comments, and contributions. Feel free to contact us at [contact@assistant-engine.com](mailto:contact@assistant-engine.com).\n\n\n## Consultancy \u0026 Support\n\nDo you need assistance integrating Filament Assistant into your Laravel Filament application, or help setting it up?  \nWe offer consultancy services to help you get the most out of our package, whether you’re just getting started or looking to optimize an existing setup.\n\nReach out to us at [contact@assistant-engine.com](mailto:contact@assistant-engine.com).\n\n## Contributing\n\nWe welcome contributions from the community! Feel free to submit pull requests, open issues, and help us improve the package.\n\n## License\n\nThis project is licensed under the MIT License. Please see [License File](LICENSE.md) for more information.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fassistantengine%2Fopen-functions-core","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fassistantengine%2Fopen-functions-core","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fassistantengine%2Fopen-functions-core/lists"}