Skip to content

Commit

Permalink
add support for function calling
Browse files Browse the repository at this point in the history
  • Loading branch information
bernard-ng committed Dec 27, 2024
1 parent c8d7412 commit 079b7af
Show file tree
Hide file tree
Showing 32 changed files with 374 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

declare(strict_types=1);

namespace Devscast\Lugha\Retrieval\Loader\Reader\Exception;
namespace Devscast\Lugha\Exception;

/**
* Class UnsupportedFileException.
Expand Down
14 changes: 14 additions & 0 deletions src/Exception/InvalidArgumentException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace Devscast\Lugha\Exception;

/**
* Class InvalidArgumentException.
*
* @author bernard-ng <[email protected]>
*/
final class InvalidArgumentException extends \InvalidArgumentException
{
}
14 changes: 14 additions & 0 deletions src/Exception/RuntimeException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace Devscast\Lugha\Exception;

/**
* Class RuntimeException.
*
* @author bernard-ng <[email protected]>
*/
final class RuntimeException extends \RuntimeException
{
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php

/*
* This file is part of the Lugha package.
* This file is part of the Lugha package.
*
* (c) Bernard Ngandu <[email protected]>
*
Expand All @@ -11,17 +11,17 @@

declare(strict_types=1);

namespace Devscast\Lugha\Provider\Service;
namespace Devscast\Lugha\Exception;

use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;

/**
* Class IntegrationException.
* Class ServiceIntegrationException.
*
* @author bernard-ng <[email protected]>
*/
class IntegrationException extends \Exception
class ServiceIntegrationException extends \Exception
{
public function __construct(string $message = '', int $code = 0, ?\Throwable $previous = null)
{
Expand Down
26 changes: 26 additions & 0 deletions src/Exception/UnformattedPromptTemplateException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php
/*
* This file is part of the Lugha package.
*
* (c) Bernard Ngandu <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Devscast\Lugha\Exception;

/**
* Class UnformattedPromptTemplateException.
*
* @author bernard-ng <[email protected]>
*/
final class UnformattedPromptTemplateException extends \RuntimeException
{
public function __construct()
{
parent::__construct('The prompt template has not been formatted yet');
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php

/*
* This file is part of the Lugha package.
* This file is part of the Lugha package.
*
* (c) Bernard Ngandu <[email protected]>
*
Expand All @@ -11,7 +11,7 @@

declare(strict_types=1);

namespace Devscast\Lugha\Retrieval\Loader\Reader\Exception;
namespace Devscast\Lugha\Exception;

/**
* Class UnsupportedFileException.
Expand All @@ -22,7 +22,7 @@ final class UnreadableFileException extends \RuntimeException
{
public function __construct(string $file, int $code = 0, \Throwable $previous = null)
{
$message = sprintf('Unable to read the content of %s', $file);
$message = sprintf('Unable to read or write the content of %s', $file);
parent::__construct($message, $code, $previous);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php

/*
* This file is part of the Lugha package.
* This file is part of the Lugha package.
*
* (c) Bernard Ngandu <[email protected]>
*
Expand All @@ -11,7 +11,7 @@

declare(strict_types=1);

namespace Devscast\Lugha\Retrieval\Loader\Reader\Exception;
namespace Devscast\Lugha\Exception;

/**
* Class UnsupportedFileException.
Expand Down
11 changes: 9 additions & 2 deletions src/Model/Completion/Prompt/PromptTemplate.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

namespace Devscast\Lugha\Model\Completion\Prompt;

use Devscast\Lugha\Exception\UnformattedPromptTemplateException;
use Devscast\Lugha\Exception\UnreadableFileException;
use Webmozart\Assert\Assert;

/**
Expand All @@ -37,11 +39,14 @@ public function __construct(string $template, array $values = [])
}
}

/**
* @throws UnformattedPromptTemplateException If the prompt has not been formatted yet
*/
#[\Override]
public function __toString(): string
{
if ($this->prompt === null) {
throw new \RuntimeException('The template has not been formatted yet');
throw new UnformattedPromptTemplateException();
}

return $this->prompt;
Expand All @@ -65,12 +70,14 @@ public static function from(string $template): self
* <code>
* $template = PromptTemplate::fromFile("/path/to/file.txt");
* </code>
*
* @throws UnreadableFileException If the file cannot be read
*/
public static function fromFile(string $path): self
{
$content = file_get_contents($path);
if ($content === false) {
throw new \RuntimeException("Could not read the file at path: {$path}");
throw new UnreadableFileException($path);
}

return new self($content, []);
Expand Down
80 changes: 80 additions & 0 deletions src/Model/Completion/Tools/FunctionBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

/*
* This file is part of the Lugha package.
*
* (c) Bernard Ngandu <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Devscast\Lugha\Model\Completion\Tools;

use Devscast\Lugha\Exception\InvalidArgumentException;
use Devscast\Lugha\Exception\RuntimeException;
use Devscast\Lugha\Provider\Provider;

/**
* Class FunctionBuilder.
*
* @author bernard-ng <[email protected]>
*/
abstract class FunctionBuilder
{
/**
* @param class-string<object>|object $function
*/
public static function build(object|string $function, Provider $provider = Provider::OPENAI): array
{
$functionInfo = self::getFunctionInfo($function);

return match ($provider) {
Provider::OPENAI => self::buildOpenAICompatible($functionInfo),
default => throw new InvalidArgumentException('Unsupported provider.')
};
}

private static function buildOpenAICompatible(FunctionInfo $functionInfo): array
{
return [
'type' => 'function',
'function' => [
'name' => $functionInfo->name,
'description' => $functionInfo->description,
'parameters' => [
'type' => 'object',
'required' => $functionInfo->getRequiredParameters(),
'properties' => array_map(
fn (Parameter $parameter): array => $parameter->build(),
$functionInfo->parameters
),
],
],
];
}

/**
* @param class-string<object>|object $function
*/
private static function getFunctionInfo(object|string $function): FunctionInfo
{
try {
$class = new \ReflectionClass($function);
$attributes = $class->getAttributes(FunctionInfo::class);

if (! isset($attributes[0])) {
$functionFqcn = is_string($function) ? $function : $function::class;
throw new InvalidArgumentException(
sprintf('%s does not have the required %s attribute.', $functionFqcn, FunctionInfo::class)
);
}

return $attributes[0]->newInstance();
} catch (\ReflectionException $e) {
throw new RuntimeException($e->getMessage(), previous: $e);
}
}
}
46 changes: 46 additions & 0 deletions src/Model/Completion/Tools/FunctionInfo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

/*
* This file is part of the Lugha package.
*
* (c) Bernard Ngandu <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Devscast\Lugha\Model\Completion\Tools;

use Webmozart\Assert\Assert;

/**
* Class FunctionInfo.
*
* @author bernard-ng <[email protected]>
*/
#[\Attribute(\Attribute::TARGET_CLASS)]
readonly class FunctionInfo
{
/**
* @param array<Parameter> $parameters
*/
public function __construct(
public string $name,
public string $description,
public array $parameters
) {
Assert::notEmpty($name);
Assert::notEmpty($description);
Assert::allIsInstanceOf($parameters, Parameter::class);
}

public function getRequiredParameters(): array
{
return array_map(
fn (Parameter $parameter) => $parameter->name,
array_filter($this->parameters, fn (Parameter $parameter) => $parameter->required)
);
}
}
48 changes: 48 additions & 0 deletions src/Model/Completion/Tools/Parameter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

/*
* This file is part of the Lugha package.
*
* (c) Bernard Ngandu <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Devscast\Lugha\Model\Completion\Tools;

use Webmozart\Assert\Assert;

/**
* Class Parameter.
*
* @author bernard-ng <[email protected]>
*/
final readonly class Parameter
{
public const array SUPPORTED_TYPES = ['string', 'integer', 'number', 'boolean', 'array'];

public function __construct(
public string $name,
public string $type,
public ?string $description = null,
public ?array $enum = null,
public bool $required = false
) {
Assert::notEmpty($name);
Assert::oneOf($type, self::SUPPORTED_TYPES);
Assert::notEmpty($description);
}

public function build(): array
{
return array_filter([
'name' => $this->name,
'type' => $this->type,
'enum' => $this->enum,
'description' => $this->description,
], fn ($value) => $value !== null);
}
}
6 changes: 3 additions & 3 deletions src/Provider/Service/Client/GoogleClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

namespace Devscast\Lugha\Provider\Service\Client;

use Devscast\Lugha\Exception\ServiceIntegrationException;
use Devscast\Lugha\Model\Completion\Chat\History;
use Devscast\Lugha\Model\Completion\CompletionConfig;
use Devscast\Lugha\Model\Embedding\EmbeddingConfig;
Expand All @@ -22,7 +23,6 @@
use Devscast\Lugha\Provider\Service\Client;
use Devscast\Lugha\Provider\Service\HasCompletionSupport;
use Devscast\Lugha\Provider\Service\HasEmbeddingSupport;
use Devscast\Lugha\Provider\Service\IntegrationException;
use Webmozart\Assert\Assert;

/**
Expand Down Expand Up @@ -64,7 +64,7 @@ public function embeddings(string $prompt, EmbeddingConfig $config): EmbeddingRe
providerResponse: $this->config->providerResponse ? $response : [],
);
} catch (\Throwable $e) {
throw new IntegrationException('Unable to generate embeddings.', previous: $e);
throw new ServiceIntegrationException('Unable to generate embeddings.', previous: $e);
}
}

Expand Down Expand Up @@ -105,7 +105,7 @@ public function completion(string|History $input, CompletionConfig $config): Com
providerResponse: $this->config->providerResponse ? $response : [],
);
} catch (\Throwable $e) {
throw new IntegrationException('Unable to generate completion.', previous: $e);
throw new ServiceIntegrationException('Unable to generate completion.', previous: $e);
}
}

Expand Down
Loading

0 comments on commit 079b7af

Please sign in to comment.