99 lines
3.7 KiB
PHP
99 lines
3.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Infrastructure\Chat;
|
|
|
|
use App\Domain\Chat\ChatProviderInterface;
|
|
use App\Domain\Chat\ChatResult;
|
|
use App\Domain\Chat\Choice;
|
|
use App\Domain\Chat\Message;
|
|
use App\Domain\Chat\MessageCollection;
|
|
use App\Domain\Chat\ToolCollection;
|
|
use App\Domain\Chat\ToolInterface;
|
|
use Exception;
|
|
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
|
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
|
|
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
|
|
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
|
|
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
|
|
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
|
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
|
use Symfony\Contracts\HttpClient\ResponseInterface;
|
|
|
|
final readonly class OpenAIChatProvider implements ChatProviderInterface
|
|
{
|
|
public function __construct(
|
|
private HttpClientInterface $httpClient,
|
|
|
|
#[Autowire('%env(OPENAI_API_KEY)%')]
|
|
private string $openAiApiKey,
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* @throws Exception When the OpenAI API returns an error
|
|
*/
|
|
public function chat(MessageCollection $messages, ToolCollection $tools, bool $forceToolCalls = false, bool $reasoning = true): ChatResult
|
|
{
|
|
$payload = [
|
|
'model' => $reasoning ? 'o4-mini' : 'gpt-4o-mini',
|
|
'messages' => array_map(function (Message $message) {
|
|
return $message->toArray();
|
|
}, $messages->toArray()),
|
|
];
|
|
|
|
if ($tools->count() > 0) {
|
|
$payload['tool_choice'] = $forceToolCalls ? 'required' : 'auto';
|
|
$payload['tools'] = [];
|
|
/** @var ToolInterface $tool */
|
|
foreach ($tools->toArray() as $tool) {
|
|
$payload['tools'][] = [
|
|
'type' => 'function',
|
|
'function' => [
|
|
'name' => $tool->getName(),
|
|
'description' => $tool->getDescription(),
|
|
'parameters' => [
|
|
'type' => 'object',
|
|
'properties' => $tool->getArguments(),
|
|
'required' => $tool->getRequiredArguments(),
|
|
],
|
|
],
|
|
];
|
|
}
|
|
}
|
|
|
|
try {
|
|
/** @var ResponseInterface $response */
|
|
$response = $this->httpClient->request('POST', "https://api.openai.com/v1/chat/completions", [
|
|
'headers' => [
|
|
'Content-Type' => 'application/json',
|
|
'Authorization' => 'Bearer ' . $this->openAiApiKey,
|
|
],
|
|
'json' => $payload,
|
|
'timeout' => 60 * 10,
|
|
'max_duration' => 60 * 10,
|
|
]);
|
|
|
|
$responseArray = $response->toArray(false);
|
|
|
|
if (isset($responseArray['choices']) && is_array($responseArray['choices']) && count($responseArray['choices']) > 0) {
|
|
$choices = [];
|
|
foreach ($responseArray['choices'] as $choice) {
|
|
$choices[] = Choice::fromArray($choice);
|
|
}
|
|
|
|
return new ChatResult(
|
|
choices: $choices,
|
|
messageHistory: $messages->toArray(),
|
|
);
|
|
}
|
|
|
|
throw new Exception('Error: ' . $response->getContent(false));
|
|
} catch (ClientExceptionInterface | DecodingExceptionInterface | RedirectionExceptionInterface |
|
|
ServerExceptionInterface | TransportExceptionInterface $e) {
|
|
throw new Exception('API Error: ' . $e->getMessage(), 0, $e);
|
|
}
|
|
}
|
|
}
|