Comments (5)
@samratfkt I meant, how you would like to use the package in an ideal world.
@yguedidi Thank you for the link.
A feature to convert PHP functions into a proper request and then execute them when requested is on our todo list.
from client.
Hello guys!
If it helps, I'll like to share what I did for the functions registration and execution.
I used Reflection to parse, register and execute the functions.
This way, I can define the functions as classes and keep the code clean.
Implementation
The implementation is something like this:
(I removed some parts to make it more readable, since it's a Laravel project, and I'm using models and other stuff to manage the Assistants and Functions from an admin panel):
Example/Description.php
This is just an attribute helper class to define the tool descriptions (see below).
<?php declare(strict_types=1);
namespace Example;
use Attribute;
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PARAMETER)]
final class Description {
public function __construct(
public string $value,
) {}
}
Functions management
Example/HasTools.php
This trait basically has the following methods:
register(array $function_classes)
- Used to register the functions classes.call(string $function_name, array $parameters = [])
- Used to call a registered function.
Skip to next section before I overwhelm you with the implementation, then come back here 😅.
<?php declare(strict_types=1);
namespace Example;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use ReflectionClass;
use ReflectionEnum;
use ReflectionException;
use ReflectionParameter;
use RuntimeException;
trait HasFunctions {
private array $registered_functions = [];
/**
* @throws ReflectionException
*/
public function register(array $function_classes): void {
foreach ($function_classes as $class_name) {
if ( !class_exists($class_name)) {
continue;
}
$function_class = new ReflectionClass($class_name);
$function_name = Str::snake(basename(str_replace('\\', '/', $class_name)));
if (! $function_class->hasMethod('handle')) {
Log::warning(sprintf('Function class %s has no "handle" method', $function_class));
continue;
}
$tool_definition = [
'type' => 'function',
'function' => [ 'name' => $function_name ],
];
// set function description, if it has one
if ( !empty($descriptions = $function_class->getAttributes(Description::class))) {
$tool_definition['function']['description'] = implode(
separator: "\n",
array: array_map(static fn($td) => $td->newInstance()->value, $descriptions),
);
}
if ($function_class->getMethod('handle')->getNumberOfParameters() > 0) {
$tool_definition['function']['parameters'] = $this->parseFunctionParameters($function_class);
}
$this->registered_functions[ $class_name ] = $tool_definition;
}
}
/**
* @throws ReflectionException
*/
public function call(string $function_name, array $arguments = []): mixed {
if (null === $function_class = array_key_first(array_filter($this->registered_functions, static fn($registered_function) => $registered_function['function']['name'] === $function_name))) {
return null;
}
$function_class = new ReflectionClass($function_class);
$handle_method = $function_class->getMethod('handle');
$params = [];
foreach ($handle_method->getParameters() as $parameter) {
if ( !array_key_exists($parameter->name, $arguments) && !$parameter->isOptional() && !$parameter->isDefaultValueAvailable()) {
throw new RuntimeException(sprintf('Parameter %s is required', $parameter->name));
}
// check if parameter type is an Enum and add fetch a valid value
if (($parameter_type = $parameter->getType()) !== null && !$parameter_type->isBuiltin()) {
if (enum_exists($parameter_type->getName())) {
$params[$parameter->name] = $parameter_type->getName()::tryFrom($arguments[$parameter->name]) ?? $parameter->getDefaultValue();
continue;
}
}
$params[$parameter->name] = $arguments[$parameter->name] ?? $parameter->getDefaultValue();
}
return $handle_method->invoke(new $function_class->name, ...$params);
}
/**
* @throws ReflectionException
*/
private function parseFunctionParameters(ReflectionClass $tool): array {
$parameters = [ 'type' => 'object' ];
if (count($method_parameters = $tool->getMethod('handle')->getParameters()) > 0) {
$parameters['properties'] = [];
}
foreach ($method_parameters as $method_parameter) {
$property = [ 'type' => $this->getFunctionParameterType($method_parameter) ];
// set property description, if it has one
if (!empty($descriptions = $method_parameter->getAttributes(Description::class))) {
$property['description'] = implode(
separator: "\n",
array: array_map(static fn($pd) => $pd->newInstance()->value, $descriptions),
);
}
// register parameter to the required properties list if it's not optional
if ( !$method_parameter->isOptional()) {
$parameters['required'] ??= [];
$parameters['required'][] = $method_parameter->getName();
}
// check if parameter type is an Enum and add it's valid values to the property
if (($parameter_type = $method_parameter->getType()) !== null && !$parameter_type->isBuiltin()) {
if (enum_exists($parameter_type->getName())) {
$property['type'] = 'string';
$property['enum'] = array_column((new ReflectionEnum($parameter_type->getName()))->getConstants(), 'value');
}
}
$parameters['properties'][$method_parameter->getName()] = $property;
}
return $parameters;
}
private function getFunctionParameterType(ReflectionParameter $parameter): string {
if (null === $parameter_type = $parameter->getType()) {
return 'string';
}
if ( !$parameter_type->isBuiltin()) {
return $parameter_type->getName();
}
return match ($parameter_type->getName()) {
'bool' => 'boolean',
'int' => 'integer',
'float' => 'number',
default => 'string',
};
}
}
Example functions
Example\Functions\MakeLead.php
That allows you to define your functions as a simple class, and all the above process will take care of building the
definitions to register it for OpenAI's API (You may want to customize the Trait to your needs).
<?php declare(strict_types=1);
namespace Example\Functions;
use Example\Description;
#[Description('Having all the necessary information, create the lead for the Sales department')]
final class MakeLead {
public function handle(
#[Description('Customer\'s name')]
string $name,
#[Description('Customer\'s surname')]
string $surname,
#[Description('Customers\' email')]
string $email,
#[Description('Customer\'s phone number')]
string $phone,
#[Description('Chosen product by the customer for purchase')]
int $product_id,
): ?array {
// TODO: do your function logic gere
return null;
}
}
Example App
Here it's just an example (I didn't test it). You register the functions, and then call them when needed.
Again, there is a lot of code that I removed for brevity (and still is a lot of it 😅).
The main idea is to keep your code organized and readable.
Example\App.php
<?php declare(strict_types=1);
namespace Example;
use Example\HasFunctions;
use Example\Functions\MakeLead;
use Example\Functions\SearchProducts;
use Example\Functions\AnotherUsefulFunction;
class App {
use HasFunctions;
public function __construct() {
$this->register([
MakeLead::class,
SearchProducts::class,
AnotherUsefulFunction::class,
// ...
]);
}
public function run() {
$response = $client->chat()->create([
'model' => 'gpt-3.5-turbo-0613',
'messages' => [
// ... message history
[ 'role' => 'user', 'content' => 'Yes, I want to purchase one coffee machine' ],
],
'tools' => array_values($this->registered_functions),
]);
$choice = $response->choices[0];
if ($choice->finishReason === 'tool_calls') {
foreach ($choice->message->toolCalls as $tool_call) {
// execute the function call
$result = $this->call($tool_call->function->name, json_decode($tool_call->function->arguments));
// TODO: do something with the result ...
}
}
}
}
Hope that this helps you!
from client.
Hi @samratfkt
Can you give an example how the API should look like?
from client.
I don't think we can do it via api, but currently I am checking the function call and then using "call_user_func_array($functionName, $argumants)" function to processing the complete calling. So I think if you guys can add the whole function calling into it, it will be more helpful and easy to integrate.
from client.
Maybe https://github.com/theodo-group/LLPhant#function can help here
from client.
Related Issues (20)
- [Bug]: system_fingerprint is always null when using CreateResponse.fake HOT 1
- [Bug]: transcribe 3 times and did not succeed HOT 4
- [Bug]: Delete Thread Message Endpoint is now Admin-Only (assistants/threads API) HOT 1
- Error completions createStreamed : gpt-3.5-turbo-1106 HOT 2
- [Bug]: Can't download imageFile generated by code_interpreter HOT 1
- get reply of run HOT 2
- [Bug]: Undefined array key "message" (Azure with Vector Database) HOT 4
- Unable to retrieve messages from thread HOT 1
- [Bug]: OpenAI\Responses\Files\RetrieveResponse::__construct(): Argument #3 ($bytes) must be of type int, null given HOT 3
- Help: Unable to test fake streamed response HOT 1
- [Request] I want to get raw response headers. HOT 1
- [Bug]: Assistants API Run object missing usage field HOT 1
- [Request] I want to put MetaInformation in StreamResponse. HOT 2
- [Bug]: Call to undefined method OpenAI\Client::factory() in Laravel HOT 1
- [Bug]: Each element of the header is in array format HOT 1
- [Bug]: Validation error for Request calling createAndRun() HOT 1
- HBushIA
- [Bug]: Error handling for RetrieveJobResponse::from() HOT 1
- [Bug]: Undefined array key "code" $client->fineTuning()->listJobs() HOT 2
- [Bug]: Undefined array key "usage" with Vision endpoint on Azure HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from client.