mirror of
https://github.com/symfony/ai-generic-platform.git
synced 2026-03-23 23:42:22 +01:00
[Platform] Split bridges into dedicated packages
This commit is contained in:
12
README.md
Normal file
12
README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
Generic Platform
|
||||
================
|
||||
|
||||
Generic platform bridge for Symfony AI.
|
||||
|
||||
Resources
|
||||
---------
|
||||
|
||||
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
|
||||
* [Report issues](https://github.com/symfony/ai/issues) and
|
||||
[send Pull Requests](https://github.com/symfony/ai/pulls)
|
||||
in the [main Symfony AI repository](https://github.com/symfony/ai)
|
||||
95
Tests/Completions/ModelClientTest.php
Normal file
95
Tests/Completions/ModelClientTest.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\AI\Platform\Bridge\Generic\Tests\Completions;
|
||||
|
||||
use PHPUnit\Framework\Attributes\TestWith;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\AI\Platform\Bridge\Generic\Completions\ModelClient;
|
||||
use Symfony\AI\Platform\Bridge\Generic\CompletionsModel;
|
||||
use Symfony\Component\HttpClient\MockHttpClient;
|
||||
use Symfony\Component\HttpClient\Response\MockResponse;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface as HttpResponse;
|
||||
|
||||
/**
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
*/
|
||||
final class ModelClientTest extends TestCase
|
||||
{
|
||||
public function testItIsSupportingTheCorrectModel()
|
||||
{
|
||||
$modelClient = new ModelClient(new MockHttpClient(), 'http://localhost:8000');
|
||||
|
||||
$this->assertTrue($modelClient->supports(new CompletionsModel('gpt-4o')));
|
||||
}
|
||||
|
||||
public function testItIsExecutingTheCorrectRequest()
|
||||
{
|
||||
$resultCallback = static function (string $method, string $url, array $options): HttpResponse {
|
||||
self::assertSame('POST', $method);
|
||||
self::assertSame('http://localhost:8000/v1/chat/completions', $url);
|
||||
self::assertSame('Authorization: Bearer sk-valid-api-key', $options['normalized_headers']['authorization'][0]);
|
||||
self::assertSame('{"temperature":1,"model":"gpt-4o","messages":[{"role":"user","content":"test message"}]}', $options['body']);
|
||||
|
||||
return new MockResponse();
|
||||
};
|
||||
$httpClient = new MockHttpClient([$resultCallback]);
|
||||
$modelClient = new ModelClient($httpClient, 'http://localhost:8000', 'sk-valid-api-key');
|
||||
$modelClient->request(new CompletionsModel('gpt-4o'), ['model' => 'gpt-4o', 'messages' => [['role' => 'user', 'content' => 'test message']]], ['temperature' => 1]);
|
||||
}
|
||||
|
||||
public function testItIsExecutingTheCorrectRequestWithArrayPayload()
|
||||
{
|
||||
$resultCallback = static function (string $method, string $url, array $options): HttpResponse {
|
||||
self::assertSame('POST', $method);
|
||||
self::assertSame('http://localhost:8000/v1/chat/completions', $url);
|
||||
self::assertSame('Authorization: Bearer sk-valid-api-key', $options['normalized_headers']['authorization'][0]);
|
||||
self::assertSame('{"temperature":0.7,"model":"gpt-4o","messages":[{"role":"user","content":"Hello"}]}', $options['body']);
|
||||
|
||||
return new MockResponse();
|
||||
};
|
||||
$httpClient = new MockHttpClient([$resultCallback]);
|
||||
$modelClient = new ModelClient($httpClient, 'http://localhost:8000', 'sk-valid-api-key');
|
||||
$modelClient->request(new CompletionsModel('gpt-4o'), ['model' => 'gpt-4o', 'messages' => [['role' => 'user', 'content' => 'Hello']]], ['temperature' => 0.7]);
|
||||
}
|
||||
|
||||
#[TestWith(['https://api.inference.eu', 'https://api.inference.eu/v1/chat/completions'])]
|
||||
#[TestWith(['https://api.inference.com', 'https://api.inference.com/v1/chat/completions'])]
|
||||
public function testItUsesCorrectBaseUrl(string $baseUrl, string $expectedUrl)
|
||||
{
|
||||
$resultCallback = static function (string $method, string $url, array $options) use ($expectedUrl): HttpResponse {
|
||||
self::assertSame('POST', $method);
|
||||
self::assertSame($expectedUrl, $url);
|
||||
self::assertSame('Authorization: Bearer sk-valid-api-key', $options['normalized_headers']['authorization'][0]);
|
||||
|
||||
return new MockResponse();
|
||||
};
|
||||
$httpClient = new MockHttpClient([$resultCallback]);
|
||||
$modelClient = new ModelClient($httpClient, $baseUrl, 'sk-valid-api-key');
|
||||
$modelClient->request(new CompletionsModel('gpt-4o'), ['messages' => []]);
|
||||
}
|
||||
|
||||
#[TestWith(['/custom/path', 'https://api.inference.com/custom/path'])]
|
||||
#[TestWith(['/v1/alternative/endpoint', 'https://api.inference.com/v1/alternative/endpoint'])]
|
||||
public function testsItUsesCorrectPathIfProvided(string $path, string $expectedUrl)
|
||||
{
|
||||
$resultCallback = static function (string $method, string $url, array $options) use ($expectedUrl): HttpResponse {
|
||||
self::assertSame('POST', $method);
|
||||
self::assertSame($expectedUrl, $url);
|
||||
self::assertSame('Authorization: Bearer sk-valid-api-key', $options['normalized_headers']['authorization'][0]);
|
||||
|
||||
return new MockResponse();
|
||||
};
|
||||
$httpClient = new MockHttpClient([$resultCallback]);
|
||||
$modelClient = new ModelClient($httpClient, 'https://api.inference.com', 'sk-valid-api-key', $path);
|
||||
$modelClient->request(new CompletionsModel('gpt-4o'), ['messages' => []]);
|
||||
}
|
||||
}
|
||||
248
Tests/Completions/ResultConverterTest.php
Normal file
248
Tests/Completions/ResultConverterTest.php
Normal file
@@ -0,0 +1,248 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\AI\Platform\Bridge\Generic\Tests\Completions;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\AI\Platform\Bridge\Generic\Completions\ResultConverter;
|
||||
use Symfony\AI\Platform\Exception\AuthenticationException;
|
||||
use Symfony\AI\Platform\Exception\BadRequestException;
|
||||
use Symfony\AI\Platform\Exception\ContentFilterException;
|
||||
use Symfony\AI\Platform\Exception\RuntimeException;
|
||||
use Symfony\AI\Platform\Result\ChoiceResult;
|
||||
use Symfony\AI\Platform\Result\RawHttpResult;
|
||||
use Symfony\AI\Platform\Result\TextResult;
|
||||
use Symfony\AI\Platform\Result\ToolCallResult;
|
||||
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
class ResultConverterTest extends TestCase
|
||||
{
|
||||
public function testConvertTextResult()
|
||||
{
|
||||
$converter = new ResultConverter();
|
||||
$httpResponse = self::createMock(ResponseInterface::class);
|
||||
$httpResponse->method('toArray')->willReturn([
|
||||
'choices' => [
|
||||
[
|
||||
'message' => [
|
||||
'role' => 'assistant',
|
||||
'content' => 'Hello world',
|
||||
],
|
||||
'finish_reason' => 'stop',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$result = $converter->convert(new RawHttpResult($httpResponse));
|
||||
|
||||
$this->assertInstanceOf(TextResult::class, $result);
|
||||
$this->assertSame('Hello world', $result->getContent());
|
||||
}
|
||||
|
||||
public function testConvertToolCallResult()
|
||||
{
|
||||
$converter = new ResultConverter();
|
||||
$httpResponse = self::createMock(ResponseInterface::class);
|
||||
$httpResponse->method('toArray')->willReturn([
|
||||
'choices' => [
|
||||
[
|
||||
'message' => [
|
||||
'role' => 'assistant',
|
||||
'content' => null,
|
||||
'tool_calls' => [
|
||||
[
|
||||
'id' => 'call_123',
|
||||
'type' => 'function',
|
||||
'function' => [
|
||||
'name' => 'test_function',
|
||||
'arguments' => '{"arg1": "value1"}',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'finish_reason' => 'tool_calls',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$result = $converter->convert(new RawHttpResult($httpResponse));
|
||||
|
||||
$this->assertInstanceOf(ToolCallResult::class, $result);
|
||||
$toolCalls = $result->getContent();
|
||||
$this->assertCount(1, $toolCalls);
|
||||
$this->assertSame('call_123', $toolCalls[0]->getId());
|
||||
$this->assertSame('test_function', $toolCalls[0]->getName());
|
||||
$this->assertSame(['arg1' => 'value1'], $toolCalls[0]->getArguments());
|
||||
}
|
||||
|
||||
public function testConvertMultipleChoices()
|
||||
{
|
||||
$converter = new ResultConverter();
|
||||
$httpResponse = self::createMock(ResponseInterface::class);
|
||||
$httpResponse->method('toArray')->willReturn([
|
||||
'choices' => [
|
||||
[
|
||||
'message' => [
|
||||
'role' => 'assistant',
|
||||
'content' => 'Choice 1',
|
||||
],
|
||||
'finish_reason' => 'stop',
|
||||
],
|
||||
[
|
||||
'message' => [
|
||||
'role' => 'assistant',
|
||||
'content' => 'Choice 2',
|
||||
],
|
||||
'finish_reason' => 'stop',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$result = $converter->convert(new RawHttpResult($httpResponse));
|
||||
|
||||
$this->assertInstanceOf(ChoiceResult::class, $result);
|
||||
$choices = $result->getContent();
|
||||
$this->assertCount(2, $choices);
|
||||
$this->assertSame('Choice 1', $choices[0]->getContent());
|
||||
$this->assertSame('Choice 2', $choices[1]->getContent());
|
||||
}
|
||||
|
||||
public function testContentFilterException()
|
||||
{
|
||||
$converter = new ResultConverter();
|
||||
$httpResponse = self::createMock(ResponseInterface::class);
|
||||
|
||||
$httpResponse->expects($this->exactly(1))
|
||||
->method('toArray')
|
||||
->willReturnCallback(function ($throw = true) {
|
||||
if ($throw) {
|
||||
throw new class extends \Exception implements ClientExceptionInterface {
|
||||
public function getResponse(): ResponseInterface
|
||||
{
|
||||
throw new RuntimeException('Not implemented');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return [
|
||||
'error' => [
|
||||
'code' => 'content_filter',
|
||||
'message' => 'Content was filtered',
|
||||
],
|
||||
];
|
||||
});
|
||||
|
||||
$this->expectException(ContentFilterException::class);
|
||||
$this->expectExceptionMessage('Content was filtered');
|
||||
|
||||
$converter->convert(new RawHttpResult($httpResponse));
|
||||
}
|
||||
|
||||
public function testThrowsAuthenticationExceptionOnInvalidApiKey()
|
||||
{
|
||||
$converter = new ResultConverter();
|
||||
$httpResponse = self::createMock(ResponseInterface::class);
|
||||
$httpResponse->method('getStatusCode')->willReturn(401);
|
||||
$httpResponse->method('getContent')->willReturn(json_encode([
|
||||
'error' => [
|
||||
'message' => 'Invalid API key provided: sk-invalid',
|
||||
],
|
||||
]));
|
||||
|
||||
$this->expectException(AuthenticationException::class);
|
||||
$this->expectExceptionMessage('Invalid API key provided: sk-invalid');
|
||||
|
||||
$converter->convert(new RawHttpResult($httpResponse));
|
||||
}
|
||||
|
||||
public function testThrowsExceptionWhenNoChoices()
|
||||
{
|
||||
$converter = new ResultConverter();
|
||||
$httpResponse = self::createMock(ResponseInterface::class);
|
||||
$httpResponse->method('toArray')->willReturn([]);
|
||||
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage('Response does not contain choices');
|
||||
|
||||
$converter->convert(new RawHttpResult($httpResponse));
|
||||
}
|
||||
|
||||
public function testThrowsExceptionForUnsupportedFinishReason()
|
||||
{
|
||||
$converter = new ResultConverter();
|
||||
$httpResponse = self::createMock(ResponseInterface::class);
|
||||
$httpResponse->method('toArray')->willReturn([
|
||||
'choices' => [
|
||||
[
|
||||
'message' => [
|
||||
'role' => 'assistant',
|
||||
'content' => 'Test content',
|
||||
],
|
||||
'finish_reason' => 'unsupported_reason',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage('Unsupported finish reason "unsupported_reason"');
|
||||
|
||||
$converter->convert(new RawHttpResult($httpResponse));
|
||||
}
|
||||
|
||||
public function testThrowsBadRequestExceptionOnBadRequestResponse()
|
||||
{
|
||||
$converter = new ResultConverter();
|
||||
$httpResponse = self::createMock(ResponseInterface::class);
|
||||
$httpResponse->method('getStatusCode')->willReturn(400);
|
||||
$httpResponse->method('getContent')->willReturn(json_encode([
|
||||
'error' => [
|
||||
'message' => 'Bad Request: invalid parameters',
|
||||
],
|
||||
]));
|
||||
|
||||
$this->expectException(BadRequestException::class);
|
||||
$this->expectExceptionMessage('Bad Request: invalid parameters');
|
||||
|
||||
$converter->convert(new RawHttpResult($httpResponse));
|
||||
}
|
||||
|
||||
public function testThrowsBadRequestExceptionOnBadRequestResponseWithNoResponseBody()
|
||||
{
|
||||
$converter = new ResultConverter();
|
||||
$httpResponse = self::createMock(ResponseInterface::class);
|
||||
$httpResponse->method('getStatusCode')->willReturn(400);
|
||||
|
||||
$this->expectException(BadRequestException::class);
|
||||
$this->expectExceptionMessage('Bad Request');
|
||||
|
||||
$converter->convert(new RawHttpResult($httpResponse));
|
||||
}
|
||||
|
||||
public function testThrowsDetailedErrorException()
|
||||
{
|
||||
$converter = new ResultConverter();
|
||||
$httpResponse = self::createMock(ResponseInterface::class);
|
||||
$httpResponse->method('toArray')->willReturn([
|
||||
'error' => [
|
||||
'code' => 'invalid_request_error',
|
||||
'type' => 'invalid_request',
|
||||
'param' => 'model',
|
||||
'message' => 'The model `gpt-5` does not exist',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage('Error "invalid_request_error"-invalid_request (model): "The model `gpt-5` does not exist".');
|
||||
|
||||
$converter->convert(new RawHttpResult($httpResponse));
|
||||
}
|
||||
}
|
||||
118
Tests/Embeddings/ModelClientTest.php
Normal file
118
Tests/Embeddings/ModelClientTest.php
Normal file
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\AI\Platform\Bridge\Generic\Tests\Embeddings;
|
||||
|
||||
use PHPUnit\Framework\Attributes\TestWith;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\AI\Platform\Bridge\Generic\CompletionsModel;
|
||||
use Symfony\AI\Platform\Bridge\Generic\Embeddings\ModelClient;
|
||||
use Symfony\AI\Platform\Bridge\Generic\EmbeddingsModel;
|
||||
use Symfony\Component\HttpClient\MockHttpClient;
|
||||
use Symfony\Component\HttpClient\Response\MockResponse;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface as HttpResponse;
|
||||
|
||||
/**
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
*/
|
||||
final class ModelClientTest extends TestCase
|
||||
{
|
||||
public function testItIsSupportingTheCorrectModel()
|
||||
{
|
||||
$modelClient = new ModelClient(new MockHttpClient(), 'http://localhost:8000');
|
||||
|
||||
$this->assertTrue($modelClient->supports(new EmbeddingsModel('text-embedding-3-small')));
|
||||
}
|
||||
|
||||
public function testItIsNotSupportingTheIncorrectModel()
|
||||
{
|
||||
$modelClient = new ModelClient(new MockHttpClient(), 'http://localhost:8000');
|
||||
|
||||
$this->assertFalse($modelClient->supports(new CompletionsModel('gpt-4o-mini')));
|
||||
}
|
||||
|
||||
public function testItIsExecutingTheCorrectRequest()
|
||||
{
|
||||
$resultCallback = static function (string $method, string $url, array $options): HttpResponse {
|
||||
self::assertSame('POST', $method);
|
||||
self::assertSame('http://localhost:8000/v1/embeddings', $url);
|
||||
self::assertSame('Authorization: Bearer sk-api-key', $options['normalized_headers']['authorization'][0]);
|
||||
self::assertSame('{"model":"text-embedding-3-small","input":"test text"}', $options['body']);
|
||||
|
||||
return new MockResponse();
|
||||
};
|
||||
$httpClient = new MockHttpClient([$resultCallback]);
|
||||
$modelClient = new ModelClient($httpClient, 'http://localhost:8000', 'sk-api-key');
|
||||
$modelClient->request(new EmbeddingsModel('text-embedding-3-small'), 'test text');
|
||||
}
|
||||
|
||||
public function testItIsExecutingTheCorrectRequestWithCustomOptions()
|
||||
{
|
||||
$resultCallback = static function (string $method, string $url, array $options): HttpResponse {
|
||||
self::assertSame('POST', $method);
|
||||
self::assertSame('http://localhost:8000/v1/embeddings', $url);
|
||||
self::assertSame('Authorization: Bearer sk-api-key', $options['normalized_headers']['authorization'][0]);
|
||||
self::assertSame('{"dimensions":256,"model":"text-embedding-3-large","input":"test text"}', $options['body']);
|
||||
|
||||
return new MockResponse();
|
||||
};
|
||||
$httpClient = new MockHttpClient([$resultCallback]);
|
||||
$modelClient = new ModelClient($httpClient, 'http://localhost:8000', 'sk-api-key');
|
||||
$modelClient->request(new EmbeddingsModel('text-embedding-3-large'), 'test text', ['dimensions' => 256]);
|
||||
}
|
||||
|
||||
public function testItIsExecutingTheCorrectRequestWithArrayInput()
|
||||
{
|
||||
$resultCallback = static function (string $method, string $url, array $options): HttpResponse {
|
||||
self::assertSame('POST', $method);
|
||||
self::assertSame('http://localhost:8000/v1/embeddings', $url);
|
||||
self::assertSame('Authorization: Bearer sk-api-key', $options['normalized_headers']['authorization'][0]);
|
||||
self::assertSame('{"model":"text-embedding-3-small","input":["text1","text2","text3"]}', $options['body']);
|
||||
|
||||
return new MockResponse();
|
||||
};
|
||||
$httpClient = new MockHttpClient([$resultCallback]);
|
||||
$modelClient = new ModelClient($httpClient, 'http://localhost:8000', 'sk-api-key');
|
||||
$modelClient->request(new EmbeddingsModel('text-embedding-3-small'), ['text1', 'text2', 'text3']);
|
||||
}
|
||||
|
||||
#[TestWith(['https://api.inference.eu', 'https://api.inference.eu/v1/embeddings'])]
|
||||
#[TestWith(['https://api.inference.com', 'https://api.inference.com/v1/embeddings'])]
|
||||
public function testItUsesCorrectBaseUrl(string $baseUrl, string $expectedUrl)
|
||||
{
|
||||
$resultCallback = static function (string $method, string $url, array $options) use ($expectedUrl): HttpResponse {
|
||||
self::assertSame('POST', $method);
|
||||
self::assertSame($expectedUrl, $url);
|
||||
self::assertSame('Authorization: Bearer sk-api-key', $options['normalized_headers']['authorization'][0]);
|
||||
|
||||
return new MockResponse();
|
||||
};
|
||||
$httpClient = new MockHttpClient([$resultCallback]);
|
||||
$modelClient = new ModelClient($httpClient, $baseUrl, 'sk-api-key');
|
||||
$modelClient->request(new EmbeddingsModel('text-embedding-3-small'), 'test input');
|
||||
}
|
||||
|
||||
#[TestWith(['/custom/path', 'https://api.inference.com/custom/path'])]
|
||||
#[TestWith(['/v1/alternative/endpoint', 'https://api.inference.com/v1/alternative/endpoint'])]
|
||||
public function testsItUsesCorrectPathIfProvided(string $path, string $expectedUrl)
|
||||
{
|
||||
$resultCallback = static function (string $method, string $url, array $options) use ($expectedUrl): HttpResponse {
|
||||
self::assertSame('POST', $method);
|
||||
self::assertSame($expectedUrl, $url);
|
||||
self::assertSame('Authorization: Bearer sk-api-key', $options['normalized_headers']['authorization'][0]);
|
||||
|
||||
return new MockResponse();
|
||||
};
|
||||
$httpClient = new MockHttpClient([$resultCallback]);
|
||||
$modelClient = new ModelClient($httpClient, 'https://api.inference.com', 'sk-api-key', $path);
|
||||
$modelClient->request(new EmbeddingsModel('text-embedding-3-small'), 'test input');
|
||||
}
|
||||
}
|
||||
57
Tests/Embeddings/ResultConverterTest.php
Normal file
57
Tests/Embeddings/ResultConverterTest.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\AI\Platform\Bridge\Generic\Tests\Embeddings;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\AI\Platform\Bridge\Generic\Embeddings\ResultConverter;
|
||||
use Symfony\AI\Platform\Result\RawHttpResult;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
class ResultConverterTest extends TestCase
|
||||
{
|
||||
public function testItConvertsAResponseToAVectorResult()
|
||||
{
|
||||
$result = $this->createStub(ResponseInterface::class);
|
||||
$result
|
||||
->method('toArray')
|
||||
->willReturn(json_decode($this->getEmbeddingStub(), true));
|
||||
|
||||
$vectorResult = (new ResultConverter())->convert(new RawHttpResult($result));
|
||||
$convertedContent = $vectorResult->getContent();
|
||||
|
||||
$this->assertCount(2, $convertedContent);
|
||||
|
||||
$this->assertSame([0.3, 0.4, 0.4], $convertedContent[0]->getData());
|
||||
$this->assertSame([0.0, 0.0, 0.2], $convertedContent[1]->getData());
|
||||
}
|
||||
|
||||
private function getEmbeddingStub(): string
|
||||
{
|
||||
return <<<'JSON'
|
||||
{
|
||||
"object": "list",
|
||||
"data": [
|
||||
{
|
||||
"object": "embedding",
|
||||
"index": 0,
|
||||
"embedding": [0.3, 0.4, 0.4]
|
||||
},
|
||||
{
|
||||
"object": "embedding",
|
||||
"index": 1,
|
||||
"embedding": [0.0, 0.0, 0.2]
|
||||
}
|
||||
]
|
||||
}
|
||||
JSON;
|
||||
}
|
||||
}
|
||||
60
composer.json
Normal file
60
composer.json
Normal file
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"name": "symfony/ai-generic-platform",
|
||||
"description": "Generic platform bridge for Symfony AI",
|
||||
"license": "MIT",
|
||||
"type": "symfony-ai-platform",
|
||||
"keywords": [
|
||||
"ai",
|
||||
"bridge",
|
||||
"generic",
|
||||
"platform"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christopher Hertel",
|
||||
"email": "mail@christopher-hertel.de"
|
||||
},
|
||||
{
|
||||
"name": "Oskar Stark",
|
||||
"email": "oskarstark@googlemail.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=8.2",
|
||||
"symfony/ai-platform": "@dev",
|
||||
"symfony/http-client": "^7.3|^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^2.1",
|
||||
"phpstan/phpstan-strict-rules": "^2.0",
|
||||
"phpunit/phpunit": "^11.5.46"
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\AI\\Platform\\Bridge\\Generic\\": ""
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Symfony\\AI\\PHPStan\\": "../../../../../.phpstan/",
|
||||
"Symfony\\AI\\Platform\\Bridge\\Generic\\Tests\\": "Tests/"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "0.x-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/ai",
|
||||
"url": "https://github.com/symfony/ai"
|
||||
}
|
||||
}
|
||||
}
|
||||
23
phpstan.dist.neon
Normal file
23
phpstan.dist.neon
Normal file
@@ -0,0 +1,23 @@
|
||||
includes:
|
||||
- ../../../../../.phpstan/extension.neon
|
||||
|
||||
parameters:
|
||||
level: 6
|
||||
paths:
|
||||
- .
|
||||
- Tests/
|
||||
excludePaths:
|
||||
- vendor/
|
||||
treatPhpDocTypesAsCertain: false
|
||||
ignoreErrors:
|
||||
-
|
||||
message: "#^Method .*::test.*\\(\\) has no return type specified\\.$#"
|
||||
reportUnmatched: false
|
||||
-
|
||||
identifier: missingType.iterableValue
|
||||
path: Tests/*
|
||||
reportUnmatched: false
|
||||
-
|
||||
identifier: 'symfonyAi.forbidNativeException'
|
||||
path: Tests/*
|
||||
reportUnmatched: false
|
||||
32
phpunit.xml.dist
Normal file
32
phpunit.xml.dist
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
colors="true"
|
||||
bootstrap="vendor/autoload.php"
|
||||
failOnDeprecation="true"
|
||||
failOnRisky="true"
|
||||
failOnWarning="true"
|
||||
>
|
||||
<php>
|
||||
<ini name="error_reporting" value="-1" />
|
||||
</php>
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="Symfony AI Generic Platform Test Suite">
|
||||
<directory>./Tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<source ignoreSuppressionOfDeprecations="true">
|
||||
<include>
|
||||
<directory>./</directory>
|
||||
</include>
|
||||
<exclude>
|
||||
<directory>./Resources</directory>
|
||||
<directory>./Tests</directory>
|
||||
<directory>./vendor</directory>
|
||||
</exclude>
|
||||
</source>
|
||||
</phpunit>
|
||||
Reference in New Issue
Block a user