diff --git a/README.md b/README.md index 33c883e..fcbbe85 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,11 @@ Unlike built-in capabilities that may only list functions, this package makes th - [Function Payload Schema](#function-payload-schema) - [Function Results Schema](#function-results-schema) - [Prepare Prompt Function Payload](#prepare-prompt-function-payload) - - [Prepare Function Results Payload](#prepare-function-results-payload) - [Send Tool Use (Function Calling) Request to OpenRouter](#send-tool-use-function-calling-request-to-openrouter) - [Validate Function Signature](#validate-function-signature) + - [Call Function (Actual Function Call)](#call-function-actual-function-call) + - [Prepare Function Results Payload](#prepare-function-results-payload) + - [Send Function Results to OpenRouter](#send-function-results-to-openrouter) - [Using PromptAlchemistRequest Class](#using-promptalchemistrequest-class) - [💫 Contributing](#-contributing) - [📜 License](#-license) @@ -109,7 +111,8 @@ OPENROUTER_DEFAULT_MODEL=default_model ## ⚡ Quick Usage Guide This package is designed to be **flexible**, but for an **easy quick start**, follow the steps below: - Create a file named `functions.yml` under a directory of your choice. Modify config file accordingly for `functions_yml_path`. - (Check [Generate Function List](#generate-function-list) for further details). +
+(Check [Generate Function List](#generate-function-list) for further details). ```php 'functions_yml_path' => __DIR__ . '/../resources/functions.yml', // functions.yml is located under resources folder in this example. ``` @@ -189,7 +192,8 @@ LaravelPromptAlchemist::generateFunctionList($class, $functions, $fileName); ``` - Create yml file for **function payload schema** and modify `function_payload_schema_path` in **config** accordingly. - (Check [Function Payload Schema](#function-payload-schema) for further details). +
+(Check [Function Payload Schema](#function-payload-schema) for further details).
Sample `function_payload_schema.yml` as: @@ -237,7 +241,11 @@ $response = LaravelOpenRouter::chatRequest($chatData); ``` - **Validate** the response retrieved from **OpenRouter**. - Sample LLM returned functions: +
+(Check [Validate Function Signature](#validate-function-signature) for further details). +
+ +Sample **LLM returned functions**: ```php // $response = LaravelOpenRouter::chatRequest($chatData); $responseContentData = str_replace("\n", "", (Arr::get($response->choices[0], 'message.content'))); // Get content from the response. @@ -258,18 +266,22 @@ $llmReturnedFunction = [ // Sample LLM returned function $llmReturnedFunctionData = LaravelPromptAlchemist::formLlmReturnedFunctionData($llmReturnedFunction); ``` -Call `validateFunctionSignature` for function signature **validation** of `$llmReturnedFunctionData`: +Invoke `validateFunctionSignature` for function signature **validation** of `$llmReturnedFunctionData`: ```php $isValid = LaravelOpenRouter::validateFunctionSignature($llmReturnedFunctionData); ``` - And finally, call **functions returned from the LLM** which are necessary for answering the **prompt** (Since **function signature** is **validated**, it is now **safe** to call **LLM returned functions**).
+(Check [Call Function (Actual Function Call)](#call-function-actual-function-call) for further details). +
-(**Note:** You need to set **parameter values** to be able to call the function. **Required parameters** have to be set, otherwise **ErrorData** is returned.) +(**Note:** You need to set **parameter values** to be able to call the function. **Required parameters** have to be set, otherwise **ErrorData** is returned. +It is a **better practice** to set the parameter values as checking `validateFunctionSignature` since type of **provided values** also **gets validated** if set, but it can also be done in this step.)
-Set parameter values before calling function: +**Set parameter values** before calling function as below. +
```php // Create parameter values, you just need parameter name and its value in correct type (int, string, array, object ...) $parameters = [ @@ -288,17 +300,17 @@ $parameters = [ ]; if (true === $isValid) { - // Set parameter values + // Set parameter values. $llmReturnedFunctionData->setParameterValues($parameters); } ``` -Call the function as following: +**Call the function** as following: ```php $functionResultData = LaravelPromptAlchemist::callFunction($llmReturnedFunctionData); ``` -Sample function result (**$functionResultData**) DTO object (FunctionResultData): +Sample function result (**$functionResultData**) DTO object: ```php output: @@ -314,10 +326,11 @@ FunctionResultData([ ], ]) ``` -Where `function_name` is the **name of the function called** as the name suggested, and `result` is the **function call result** can be anything (void, array, object, bool etc. whatever your function return to.) +Where `function_name` is the **name of the function called** as the name suggested, and `result` is the **function call result** can be anything (void, array, object, bool etc. whatever your function returns to.) - Optionally, you can also send **function results** to LLM so that regarding `function_results_schema`, it will return the answer. - (Check [Prepare Function Results Payload](#prepare-function-results-payload) for more details) +
+(Check [Prepare Function Results Payload](#prepare-function-results-payload) for further details) ```php $prompt = 'Can tell me Mr. Boolean Bob credit score?'; $model = config('laravel-prompt-alchemist.env_variables.default_model'); // Check https://openrouter.ai/docs/models for supported models @@ -597,11 +610,11 @@ Also note that fields added to **FunctionData** DTO **overwrite** the existing p (Basically if a field is added to **FunctionData** DTO, it is taken into account; if a field is NOT added to FunctionData and exists in function declaration then this predefined description/field is added to function list). #### Define Function Signature Mapping -Based on the function list (functions.yml), align your naming practices for functions with the package format for integration. -It is better practice to set all possible fields for function signature (`FunctionSignatureMappingData`) for better performance since more info provided for LLM means better result. +Based on the **function list (functions.yml)**, align **your naming practices** for functions with the **package format for integration**. +It is better practice to set **all possible fields** for function signature (`FunctionSignatureMappingData`) for **better performance** since **more info** provided for LLM means **better result**.
-`function_signature_mapping` needs to be defined in the configuration file as following examples: +`function_signature_mapping` needs to be defined in the **config file** as following examples:
(As shown in examples below, use `'[]'` for the arrays. In package, it is replaced with the index key as e.g. `parameters[].name` becomes `parameters.{key}.name` which is `parameters.0.name` for the first array index, `parameters.1.name` for the second etc.) @@ -737,13 +750,13 @@ Regarding these 2 examples, you can define your `function_signature_mapping` in This section defines the instructions which gives **strict description** of **desired format answers** and how LLM provider will process provided prompt, schemas etc.
-You can use your own created/generated instructions for `prompt_function_instructions`, or use `generateInstructions` method. +You can use your **own created/generated instructions** for `prompt_function_instructions`, or use `generateInstructions` method.
-`generateInstructions` method simply generates customized instructions regarding your **function list** (`functions.yml`), **function payload schema** (`function_payload_schema.yml`) and **prompt for generating instructions** (`generate_prompt_function_instructions` in **config**). +`generateInstructions` method simply generates **customized instructions** regarding your **function list** (`functions.yml`), **function payload schema** (`function_payload_schema.yml`) and **prompt for generating instructions** (`generate_prompt_function_instructions` in **config**).
-(**Note:** `generate_prompt_function_instructions` is the prompt/instructions in **config** that describe how to generate specific/customized instructions with **functions** and **function_payload_schema**) +(**Note:** `generate_prompt_function_instructions` is the prompt/instructions in **config** that describe how to **generate specific/customized instructions** with **functions** and **function_payload_schema**)
Sample `generate_prompt_function_instructions` for `generateInstructions` call: @@ -791,11 +804,11 @@ You are an AI assistant that strictly follows instructions and provides response #### Define Schemas This section defines the desired schema samples that are used for formatting the **function payload** and **function results**. Basically schemas are for deciding the **response format of LLM**. -They are sent to LLM along with other info (prompt, functions - function results, instructions etc.) and in instructions LLM asked to give results as same format as in schema provided. -In this way, LLM response can be resolved in package so that it can be used for Tool Use (Function Calling). +They are sent to LLM along with other info (prompt, functions - function results, instructions etc.) and in instructions LLM asked to give results as **same format** as in schema provided. +In this way, LLM response can be resolved in package so that it can be used for **Tool Use (Function Calling)**. ##### Function Payload Schema -Schema that defines the structure of the function payload. +Schema that defines the **structure of the function payload**. - Create yml file for function payload schema with any preferred naming and directory, add it to config file under `schemas`. ```php 'schemas' => [ @@ -816,10 +829,10 @@ because LLM response will depend on this schema and response will be **validated ``` ##### Function Results Schema -Schema that defines the structure of the function results. This schema is for deciding the final response where function results are sent to LLM to form the response after Tool Use (Function Calling). +Schema that defines the **structure of the function results**. This schema is for deciding the **final response** where **function results** are sent to LLM to form the response after **Tool Use (Function Calling)**.
-This schema is not required if you will not be sending Tool Use (Function Calling) results to LLM. You might prefer using function results in your codebase to derive a response directly. +This schema is not required if you will not be sending **function call** results to LLM. You might prefer using **function results** in **your codebase** to derive a response directly. - Create yml file for function results schema with any preferred naming and directory, add it to config file under `schemas`. ```php 'schemas' => [ @@ -913,13 +926,159 @@ And this is the expected prepared payload response sample: ] ``` +#### Send Tool Use (Function Calling) Request to OpenRouter +Since this package is designed in a **flexible** way, you may use [Laravel OpenRouter](https://github.com/moe-mizrak/laravel-openrouter) (please check out **OpenRouter** github repository for more information) +which is used as the **default LLM provider** for this package, or you may use **any other LLM provider** with this package to send Tool Use (Function Calling) request. +
+This is the sample OpenRouter request: +```php +$prompt = 'Can tell me Mr. Boolean Bob credit score?'; +$model = config('laravel-prompt-alchemist.env_variables.default_model'); // Check https://openrouter.ai/docs/models for supported models +$content = LaravelPromptAlchemist::preparePromptFunctionPayload($prompt); + +$messageData = new MessageData([ + 'content' => json_encode($content), + 'role' => RoleType::USER, +]); + +$chatData = new ChatData([ + 'messages' => [ + $messageData, + ], + 'model' => $model, + 'temperature' => 0.1, // Set temperature low to get better result. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. +]); + +// Send OpenRouter request +$response = LaravelOpenRouter::chatRequest($chatData); +``` + +Sample **Laravel OpenRouter** response (**ResponseData** is returned): +```php +output: + +ResponseData([ + 'id' => 'gen-YFd68mMgTkrfHVvkdemwYxdGSfZA', + 'model' => 'mistralai/mistral-7b-instruct:free', + 'object' => 'chat.completion', + 'created' => 1719251736, + 'choices' => [ + 0 => [ + 'index' => 0, + 'message' => [ + 'role' => 'assistant', + 'content' => '["function_name":"getFinancialData", "parameters":[{"name":"userId","type":"int"},{"name":"startDate","type":"string"},{"name":"endDate","type":"string"}],"function_name":"categorizeTransactions", "parameters":[{"name":"transactions","type":"array"}],"function_name":"getTopCategories", "parameters":[{"name":"transactions","type":"array"}]]', + ], + 'finish_reason' => 'stop', + ] + ], + 'usage' => UsageData([ + 'prompt_tokens' => 1657, + 'completion_tokens' => 97, + 'total_tokens' => 1754, + ]) +]); +``` + +#### Validate Function Signature +Validates a function signature returned by the LLM. It returns **boolean** or **ErrorData** for **wrong** function signature with missing/wrong field. +You can retrieve LLM returned functions from **ResponseData** returned by [Send Tool Use (Function Calling) Request to OpenRouter](#send-tool-use-function-calling-request-to-openrouter). +
+Sample LLM returned functions: +```php +// $response = LaravelOpenRouter::chatRequest($chatData); as shown in [Send Tool Use (Function Calling) Request to OpenRouter] section +$responseContentData = str_replace("\n", "", (Arr::get($response->choices[0], 'message.content'))); // Get content from the response. +$llmReturnedFunctions = json_decode($responseContentData, true); // Functions returned from LLM. + +// Foreach $llmReturnedFunctions and get each function to validate: +$llmReturnedFunction = [ // Sample LLM returned function + "function_name" => "getFinancialData", + "parameters" => [ + [ "name" => "userId", "type" => "int"], + [ "name" => "startDate", "type" => "string"], + [ "name" => "endDate", "type" => "string"], + ], + 'class_name' => 'MoeMizrak\LaravelPromptAlchemist\Tests\Example' +]; + +// Form LLM returned function data (FunctionData). +$llmReturnedFunctionData = $this->request->formLlmReturnedFunctionData($llmReturnedFunction); + +// Add parameter values if exists, only name and value of the parameter are set. +$parameters = [ + new ParameterData([ + 'name' => 'userId', + 'value' => 99, + ]), + new ParameterData([ + 'name' => 'startDate', + 'value' => '2023-06-01', + ]), + new ParameterData([ + 'name' => 'endDate', + 'value' => '2023-07-01', + ]), +]; +// Set parameter values in $llmReturnedFunctionData DTO +$llmReturnedFunctionData->setParameterValues($parameters); +``` + +And this is how to **validate** a function signature returned from LLM (**formed llm returned function** as above, also **values are set** for parameters): +```php +$isValid = LaravelOpenRouter::validateFunctionSignature($llmReturnedFunctionData); // $isValid is bool or ErrorData +``` + +In case the LLM returned function signature is **invalid**, this is a sample **ErrorData** returned: +```php +output: + +ErrorData([ + 'code' => 400, + 'message' => 'Function invalidFunctionName does not exist in class MoeMizrak\LaravelPromptAlchemist\Tests\Example' +]); +``` + +#### Call Function (Actual Function Call) +This method make **actual function call** with provided function signatures along with values for parameters. +Since [Validate Function Signature](#validate-function-signature) is **performed before** this step, function signature is **safe** to make this function call (Also **parameter values are set** in [Validate Function Signature](#validate-function-signature) step). +Regardless of the function visibility (**private**, **protected**, **public** etc.), function can be called **directly**. +
+ +> This package makes **actual function calls** unlike built-in capabilities of LLMs that may only list functions. +> Basically with this package, you can get the **list of the functions** regarding your prompt from LLM, and then **make an actual function calls** to retrieve **function results**. + +This is how to `call function`: +```php +$functionResult = LaravelPromptAlchemist::callFunction($llmReturnedFunctionData); +``` + +Result is returned as `FunctionResultData` DTO object as a sample result given below: +```php +output: + +FunctionResultData([ + 'function_name' => 'getFinancialData', + 'result' => [ + 'totalAmount' => 122, + 'transactions' => [ + [ + 'amount' => 12, + 'date' => '2023-02-02', + 'description' => 'food', + ], + ] + ] +]), +``` +Where `function_name` is the **name of the function called**, `result` is whatever this **function returns** (array, bool, int, mixed, object, void etc.) + #### Prepare Function Results Payload -This method is responsible for creating a structured payload template based on a given prompt and a list of functions (functions.yml) which later will be sent to an LLM provider. -This method constructs an array containing **instructions**, the **prompt**, a **list of functions**, and a **function payload schema**. -The instructions detail how the prompt should be processed, ensuring the response strictly adheres to the provided format. +This method is responsible for creating a structured payload template based on a given prompt and a **function results** which later will be sent to an LLM provider. +This method constructs an array containing **instructions**, the **prompt**, a **function results**, and a **function results schema**. +The instructions detail how the prompt should be processed, ensuring the response strictly adheres to the provided format as **function results schema**.
-Prompt which will be used for Tool Use (Function Calling): +Prompt which will be used for **function results payload** preparation: ```php $prompt = 'Can tell me Mr. Boolean Bob credit score?'; @@ -951,7 +1110,7 @@ This is how you can call prepareFunctionResultsPayload method by using facade: ```php LaravelPromptAlchemist::prepareFunctionResultsPayload($prompt, $functionResults); ``` -And this is the expected prepared payload response sample: +And this is the expected prepared function results payload sample: ```php [ "prompt" => "Can tell me Mr. Boolean Bob credit score?", @@ -1024,15 +1183,39 @@ And this is the expected prepared payload response sample: ] ``` -#### Send Tool Use (Function Calling) Request to OpenRouter -Since this package is designed in a **flexible** way, you may use [Laravel OpenRouter](https://github.com/moe-mizrak/laravel-openrouter) (please check out **OpenRouter** github repository for more information) -which is used as the **default LLM provider** for this package, or you may use **any other LLM provider** with this package to send Tool Use (Function Calling) request. +#### Send Function Results to OpenRouter +Since this package is designed in a flexible way, you may use Laravel OpenRouter +(please check out OpenRouter github repository for more information) which is used as the default LLM provider for this package, +or you may use any other LLM provider with this package to send Tool Use (Function Calling) request.
-This is the sample OpenRouter request: +This is the sample OpenRouter request for **function results**: ```php $prompt = 'Can tell me Mr. Boolean Bob credit score?'; +$functionResults = [ + new FunctionResultData([ + 'function_name' => 'getFinancialData', + 'result' => [ + 'totalAmount' => 122, + 'transactions' => [ + [ + 'amount' => 12, + 'date' => '2023-02-02', + 'description' => 'food', + ], + ] + ] + ]), + new FunctionResultData([ + 'function_name' => 'getCreditScore', + 'result' => [ + 'creditScore' => 0.8, + 'summary' => 'reliable', + ] + ]), + ... +]; +$content = LaravelPromptAlchemist::prepareFunctionResultsPayload($prompt, $functionResults); $model = config('laravel-prompt-alchemist.env_variables.default_model'); // Check https://openrouter.ai/docs/models for supported models -$content = LaravelPromptAlchemist::preparePromptFunctionPayload($prompt); $messageData = new MessageData([ 'content' => json_encode($content), @@ -1051,7 +1234,7 @@ $chatData = new ChatData([ $response = LaravelOpenRouter::chatRequest($chatData); ``` -Sample **Laravel OpenRouter** response (**ResponseData** is returned): +Sample Laravel OpenRouter response (ResponseData is returned): ```php output: @@ -1059,62 +1242,25 @@ ResponseData([ 'id' => 'gen-YFd68mMgTkrfHVvkdemwYxdGSfZA', 'model' => 'mistralai/mistral-7b-instruct:free', 'object' => 'chat.completion', - 'created' => 1719251736, + 'created' => 1719440793, 'choices' => [ 0 => [ 'index' => 0, 'message' => [ 'role' => 'assistant', - 'content' => '["function_name":"getFinancialData", "parameters":[{"name":"userId","type":"int"},{"name":"startDate","type":"string"},{"name":"endDate","type":"string"}],"function_name":"categorizeTransactions", "parameters":[{"name":"transactions","type":"array"}],"function_name":"getTopCategories", "parameters":[{"name":"transactions","type":"array"}]]', + 'content' => '[{"name": "creditScore", "type": "float", "value": 0.8}, {"name": "summary", "type": "string", "value": "reliable"}]', ], 'finish_reason' => 'stop', ] ], 'usage' => UsageData([ - 'prompt_tokens' => 1657, - 'completion_tokens' => 97, - 'total_tokens' => 1754, + 'prompt_tokens' => 657, + 'completion_tokens' => 69, + 'total_tokens' => 726, ]) ]); ``` -#### Validate Function Signature -Validates a function signature returned by the LLM. It returns **boolean** or **ErrorData** for **wrong** function signature with missing/wrong field. -You can retrieve LLM returned functions from **ResponseData** returned by [Send Tool Use (Function Calling) Request to OpenRouter](#send-tool-use-function-calling-request-to-openrouter). -
-Sample LLM returned functions: -```php -// $response = LaravelOpenRouter::chatRequest($chatData); as shown in [Send Tool Use (Function Calling) Request to OpenRouter] section -$responseContentData = str_replace("\n", "", (Arr::get($response->choices[0], 'message.content'))); // Get content from the response. -$llmReturnedFunctions = json_decode($responseContentData, true); // Functions returned from LLM. - -// Foreach $llmReturnedFunctions and get each function to validate: -$llmReturnedFunction = [ // Sample LLM returned function - "function_name" => "getFinancialData", - "parameters" => [ - [ "name" => "userId", "type" => "int"], - [ "name" => "startDate", "type" => "string"], - [ "name" => "endDate", "type" => "string"], - ], - 'class_name' => 'MoeMizrak\LaravelPromptAlchemist\Tests\Example' -]; -``` - -And this is how to **validate** a function signature returned from LLM: -```php -LaravelOpenRouter::validateFunctionSignature($llmReturnedFunction); -``` - -In case the LLM returned function signature is **invalid**, this is a sample **ErrorData** returned: -```php -output: - -ErrorData([ - 'code' => 400, - 'message' => 'Function invalidFunctionName does not exist in class MoeMizrak\LaravelPromptAlchemist\Tests\Example' -]); -``` - ### Using PromptAlchemistRequest Class You can also inject the `PromptAlchemistRequest` class in the **constructor** of your class and use its methods directly. ```php @@ -1125,15 +1271,18 @@ While everything else stays same with the [Using Facade](#using-facade), with `P // generateFunctionList request. $this->promptAlchemistRequest->generateFunctionList($class, $functions, $fileName); -// Validate function signature returned by the LLM request. -$this->promptAlchemistRequest->validateFunctionSignature($llmReturnedFunction); - // Generate instructions request. $this->promptAlchemistRequest->generateInstructions(); // Prepare prompt function payload request. $this->promptAlchemistRequest->preparePromptFunctionPayload($prompt); +// Validate function signature returned by the LLM request. +$this->promptAlchemistRequest->validateFunctionSignature($llmReturnedFunctionData); // $isValid is bool or ErrorData + +// Forms LLM returned function into the FunctionData +$this->promptAlchemistRequest->formLlmReturnedFunctionData($llmReturnedFunction); + // Prepare function results payload request. $this->promptAlchemistRequest->prepareFunctionResultsPayload($prompt, $functionResults); ```