diff --git a/examples/azure/audio-transcript.php b/examples/azure/audio-transcript.php index 6bbae7c6..7f386ba3 100644 --- a/examples/azure/audio-transcript.php +++ b/examples/azure/audio-transcript.php @@ -34,4 +34,4 @@ $response = $platform->request($model, $file); -echo $response->getContent().\PHP_EOL; +echo $response->asText().\PHP_EOL; diff --git a/examples/azure/embeddings.php b/examples/azure/embeddings.php index 505607d7..9abb7e19 100644 --- a/examples/azure/embeddings.php +++ b/examples/azure/embeddings.php @@ -11,7 +11,6 @@ use Symfony\AI\Platform\Bridge\Azure\OpenAI\PlatformFactory; use Symfony\AI\Platform\Bridge\OpenAI\Embeddings; -use Symfony\AI\Platform\Response\VectorResponse; use Symfony\Component\Dotenv\Dotenv; require_once dirname(__DIR__).'/vendor/autoload.php'; @@ -37,6 +36,4 @@ country was very peaceful and prosperous. The people lived happily ever after. TEXT); -assert($response instanceof VectorResponse); - -echo 'Dimensions: '.$response->getContent()[0]->getDimensions().\PHP_EOL; +echo 'Dimensions: '.$response->asVectors()[0]->getDimensions().\PHP_EOL; diff --git a/examples/huggingface/audio-classification.php b/examples/huggingface/audio-classification.php index 7de53777..9c4a6a0e 100644 --- a/examples/huggingface/audio-classification.php +++ b/examples/huggingface/audio-classification.php @@ -31,4 +31,4 @@ 'task' => Task::AUDIO_CLASSIFICATION, ]); -dump($response->getContent()); +dump($response->asObject()); diff --git a/examples/huggingface/automatic-speech-recognition.php b/examples/huggingface/automatic-speech-recognition.php index b6a88422..5ef25201 100644 --- a/examples/huggingface/automatic-speech-recognition.php +++ b/examples/huggingface/automatic-speech-recognition.php @@ -31,4 +31,4 @@ 'task' => Task::AUTOMATIC_SPEECH_RECOGNITION, ]); -echo $response->getContent().\PHP_EOL; +echo $response->asText().\PHP_EOL; diff --git a/examples/huggingface/chat-completion.php b/examples/huggingface/chat-completion.php index fbd0d9f9..1e23f3db 100644 --- a/examples/huggingface/chat-completion.php +++ b/examples/huggingface/chat-completion.php @@ -32,4 +32,4 @@ 'task' => Task::CHAT_COMPLETION, ]); -echo $response->getContent().\PHP_EOL; +echo $response->asText().\PHP_EOL; diff --git a/examples/huggingface/feature-extraction.php b/examples/huggingface/feature-extraction.php index cf9b9cee..8c8476ad 100644 --- a/examples/huggingface/feature-extraction.php +++ b/examples/huggingface/feature-extraction.php @@ -12,7 +12,6 @@ use Symfony\AI\Platform\Bridge\HuggingFace\PlatformFactory; use Symfony\AI\Platform\Bridge\HuggingFace\Task; use Symfony\AI\Platform\Model; -use Symfony\AI\Platform\Response\VectorResponse; use Symfony\Component\Dotenv\Dotenv; require_once dirname(__DIR__).'/vendor/autoload.php'; @@ -30,6 +29,4 @@ 'task' => Task::FEATURE_EXTRACTION, ]); -assert($response instanceof VectorResponse); - -echo 'Dimensions: '.$response->getContent()[0]->getDimensions().\PHP_EOL; +echo 'Dimensions: '.$response->asVectors()[0]->getDimensions().\PHP_EOL; diff --git a/examples/huggingface/fill-mask.php b/examples/huggingface/fill-mask.php index 0175c7b2..ec11f9cb 100644 --- a/examples/huggingface/fill-mask.php +++ b/examples/huggingface/fill-mask.php @@ -29,4 +29,4 @@ 'task' => Task::FILL_MASK, ]); -dump($response->getContent()); +dump($response->asObject()); diff --git a/examples/huggingface/image-classification.php b/examples/huggingface/image-classification.php index c5ae6109..9adcb63b 100644 --- a/examples/huggingface/image-classification.php +++ b/examples/huggingface/image-classification.php @@ -31,4 +31,4 @@ 'task' => Task::IMAGE_CLASSIFICATION, ]); -dump($response->getContent()); +dump($response->asObject()); diff --git a/examples/huggingface/image-segmentation.php b/examples/huggingface/image-segmentation.php index 0e4803f6..598b22e0 100644 --- a/examples/huggingface/image-segmentation.php +++ b/examples/huggingface/image-segmentation.php @@ -31,4 +31,4 @@ 'task' => Task::IMAGE_SEGMENTATION, ]); -dump($response->getContent()); +dump($response->asObject()); diff --git a/examples/huggingface/image-to-text.php b/examples/huggingface/image-to-text.php index cb999c4d..6f4c4bc8 100644 --- a/examples/huggingface/image-to-text.php +++ b/examples/huggingface/image-to-text.php @@ -31,4 +31,4 @@ 'task' => Task::IMAGE_TO_TEXT, ]); -echo $response->getContent().\PHP_EOL; +echo $response->asText().\PHP_EOL; diff --git a/examples/huggingface/object-detection.php b/examples/huggingface/object-detection.php index 0d875423..259dd9c4 100644 --- a/examples/huggingface/object-detection.php +++ b/examples/huggingface/object-detection.php @@ -31,4 +31,4 @@ 'task' => Task::OBJECT_DETECTION, ]); -dump($response->getContent()); +dump($response->asObject()); diff --git a/examples/huggingface/question-answering.php b/examples/huggingface/question-answering.php index ebec272c..f5000a51 100644 --- a/examples/huggingface/question-answering.php +++ b/examples/huggingface/question-answering.php @@ -34,4 +34,4 @@ 'task' => Task::QUESTION_ANSWERING, ]); -dump($response->getContent()); +dump($response->asObject()); diff --git a/examples/huggingface/sentence-similarity.php b/examples/huggingface/sentence-similarity.php index a04a8f63..66f50cc8 100644 --- a/examples/huggingface/sentence-similarity.php +++ b/examples/huggingface/sentence-similarity.php @@ -38,4 +38,4 @@ 'task' => Task::SENTENCE_SIMILARITY, ]); -dump($response->getContent()); +dump($response->asObject()); diff --git a/examples/huggingface/summarization.php b/examples/huggingface/summarization.php index 02df7cd8..a5b5521d 100644 --- a/examples/huggingface/summarization.php +++ b/examples/huggingface/summarization.php @@ -39,4 +39,4 @@ 'task' => Task::SUMMARIZATION, ]); -echo $response->getContent().\PHP_EOL; +echo $response->asText().\PHP_EOL; diff --git a/examples/huggingface/table-question-answering.php b/examples/huggingface/table-question-answering.php index 7e51474a..88aae451 100644 --- a/examples/huggingface/table-question-answering.php +++ b/examples/huggingface/table-question-answering.php @@ -37,4 +37,4 @@ 'task' => Task::TABLE_QUESTION_ANSWERING, ]); -dump($response->getContent()); +dump($response->asObject()); diff --git a/examples/huggingface/text-classification.php b/examples/huggingface/text-classification.php index d07c0de3..dda68045 100644 --- a/examples/huggingface/text-classification.php +++ b/examples/huggingface/text-classification.php @@ -29,4 +29,4 @@ 'task' => Task::TEXT_CLASSIFICATION, ]); -dump($response->getContent()); +dump($response->asObject()); diff --git a/examples/huggingface/text-generation.php b/examples/huggingface/text-generation.php index 381e9d72..31ba9a48 100644 --- a/examples/huggingface/text-generation.php +++ b/examples/huggingface/text-generation.php @@ -29,4 +29,4 @@ 'task' => Task::TEXT_GENERATION, ]); -echo $response->getContent().\PHP_EOL; +echo $response->asText().\PHP_EOL; diff --git a/examples/huggingface/text-to-image.php b/examples/huggingface/text-to-image.php index 2e05d67e..67d4e73d 100644 --- a/examples/huggingface/text-to-image.php +++ b/examples/huggingface/text-to-image.php @@ -12,7 +12,6 @@ use Symfony\AI\Platform\Bridge\HuggingFace\PlatformFactory; use Symfony\AI\Platform\Bridge\HuggingFace\Task; use Symfony\AI\Platform\Model; -use Symfony\AI\Platform\Response\BinaryResponse; use Symfony\Component\Dotenv\Dotenv; require_once dirname(__DIR__).'/vendor/autoload.php'; @@ -30,6 +29,4 @@ 'task' => Task::TEXT_TO_IMAGE, ]); -assert($response instanceof BinaryResponse); - -echo $response->toBase64().\PHP_EOL; +echo $response->asBase64().\PHP_EOL; diff --git a/examples/huggingface/token-classification.php b/examples/huggingface/token-classification.php index 0862bfe7..ba8d8aa6 100644 --- a/examples/huggingface/token-classification.php +++ b/examples/huggingface/token-classification.php @@ -29,4 +29,4 @@ 'task' => Task::TOKEN_CLASSIFICATION, ]); -dump($response->getContent()); +dump($response->asObject()); diff --git a/examples/huggingface/translation.php b/examples/huggingface/translation.php index 93850359..0af586bb 100644 --- a/examples/huggingface/translation.php +++ b/examples/huggingface/translation.php @@ -31,4 +31,4 @@ 'tgt_lang' => 'en', ]); -echo $response->getContent().\PHP_EOL; +echo $response->asText().\PHP_EOL; diff --git a/examples/huggingface/zero-shot-classification.php b/examples/huggingface/zero-shot-classification.php index a36cd8dd..8d7f5bc5 100644 --- a/examples/huggingface/zero-shot-classification.php +++ b/examples/huggingface/zero-shot-classification.php @@ -31,4 +31,4 @@ 'candidate_labels' => ['refund', 'legal', 'faq'], ]); -dump($response->getContent()); +dump($response->asObject()); diff --git a/examples/misc/parallel-chat-gpt.php b/examples/misc/parallel-chat-gpt.php index 4374377f..b9badfe3 100644 --- a/examples/misc/parallel-chat-gpt.php +++ b/examples/misc/parallel-chat-gpt.php @@ -41,5 +41,5 @@ echo 'Waiting for the responses ...'.\PHP_EOL; foreach ($responses as $response) { - echo 'Next Letter: '.$response->getContent().\PHP_EOL; + echo 'Next Letter: '.$response->asText().\PHP_EOL; } diff --git a/examples/misc/parallel-embeddings.php b/examples/misc/parallel-embeddings.php index 8905e039..1feb433b 100644 --- a/examples/misc/parallel-embeddings.php +++ b/examples/misc/parallel-embeddings.php @@ -11,7 +11,6 @@ use Symfony\AI\Platform\Bridge\OpenAI\Embeddings; use Symfony\AI\Platform\Bridge\OpenAI\PlatformFactory; -use Symfony\AI\Platform\Response\VectorResponse; use Symfony\Component\Dotenv\Dotenv; require_once dirname(__DIR__).'/vendor/autoload.php'; @@ -36,6 +35,5 @@ echo 'Waiting for the responses ...'.\PHP_EOL; foreach ($responses as $response) { - assert($response instanceof VectorResponse); - echo 'Dimensions: '.$response->getContent()[0]->getDimensions().\PHP_EOL; + echo 'Dimensions: '.$response->asVectors()[0]->getDimensions().\PHP_EOL; } diff --git a/examples/mistral/embeddings.php b/examples/mistral/embeddings.php index 216e42fb..3354e440 100644 --- a/examples/mistral/embeddings.php +++ b/examples/mistral/embeddings.php @@ -11,7 +11,6 @@ use Symfony\AI\Platform\Bridge\Mistral\Embeddings; use Symfony\AI\Platform\Bridge\Mistral\PlatformFactory; -use Symfony\AI\Platform\Response\VectorResponse; use Symfony\Component\Dotenv\Dotenv; require_once dirname(__DIR__).'/vendor/autoload.php'; @@ -32,6 +31,4 @@ salt. The goal was to prevent deficiencies and promote better health in the population. TEXT); -assert($response instanceof VectorResponse); - -echo 'Dimensions: '.$response->getContent()[0]->getDimensions().\PHP_EOL; +echo 'Dimensions: '.$response->asVectors()[0]->getDimensions().\PHP_EOL; diff --git a/examples/openai/audio-transcript.php b/examples/openai/audio-transcript.php index 21638522..528b6aa9 100644 --- a/examples/openai/audio-transcript.php +++ b/examples/openai/audio-transcript.php @@ -28,4 +28,4 @@ $response = $platform->request($model, $file); -echo $response->getContent().\PHP_EOL; +echo $response->asText().\PHP_EOL; diff --git a/examples/openai/embeddings.php b/examples/openai/embeddings.php index 8d19e12d..cb360292 100644 --- a/examples/openai/embeddings.php +++ b/examples/openai/embeddings.php @@ -11,7 +11,6 @@ use Symfony\AI\Platform\Bridge\OpenAI\Embeddings; use Symfony\AI\Platform\Bridge\OpenAI\PlatformFactory; -use Symfony\AI\Platform\Response\VectorResponse; use Symfony\Component\Dotenv\Dotenv; require_once dirname(__DIR__).'/vendor/autoload.php'; @@ -31,6 +30,4 @@ country was very peaceful and prosperous. The people lived happily ever after. TEXT); -assert($response instanceof VectorResponse); - -echo 'Dimensions: '.$response->getContent()[0]->getDimensions().\PHP_EOL; +echo 'Dimensions: '.$response->asVectors()[0]->getDimensions().\PHP_EOL; diff --git a/examples/openai/image-output-dall-e-2.php b/examples/openai/image-output-dall-e-2.php index acdac0b8..3ea50027 100644 --- a/examples/openai/image-output-dall-e-2.php +++ b/examples/openai/image-output-dall-e-2.php @@ -32,6 +32,6 @@ ], ); -foreach ($response->getContent() as $index => $image) { +foreach ($response->getResponse()->getContent() as $index => $image) { echo 'Image '.$index.': '.$image->url.\PHP_EOL; } diff --git a/examples/openai/image-output-dall-e-3.php b/examples/openai/image-output-dall-e-3.php index 6747834b..9a7584b1 100644 --- a/examples/openai/image-output-dall-e-3.php +++ b/examples/openai/image-output-dall-e-3.php @@ -30,7 +30,7 @@ options: [ 'response_format' => 'url', // Generate response as URL ], -); +)->getResponse(); assert($response instanceof ImageResponse); diff --git a/examples/transformers/text-generation.php b/examples/transformers/text-generation.php index 00faa5ca..acdfa59b 100644 --- a/examples/transformers/text-generation.php +++ b/examples/transformers/text-generation.php @@ -32,4 +32,4 @@ 'task' => Task::Text2TextGeneration, ]); -echo $response->getContent().\PHP_EOL; +echo $response->asText().\PHP_EOL; diff --git a/examples/voyage/embeddings.php b/examples/voyage/embeddings.php index 621b9c77..c4e8ef82 100644 --- a/examples/voyage/embeddings.php +++ b/examples/voyage/embeddings.php @@ -11,7 +11,6 @@ use Symfony\AI\Platform\Bridge\Voyage\PlatformFactory; use Symfony\AI\Platform\Bridge\Voyage\Voyage; -use Symfony\AI\Platform\Response\VectorResponse; use Symfony\Component\Dotenv\Dotenv; require_once dirname(__DIR__).'/vendor/autoload.php'; @@ -31,6 +30,4 @@ country was very peaceful and prosperous. The people lived happily ever after. TEXT); -assert($response instanceof VectorResponse); - -echo 'Dimensions: '.$response->getContent()[0]->getDimensions().\PHP_EOL; +echo 'Dimensions: '.$response->asVectors()[0]->getDimensions().\PHP_EOL; diff --git a/src/agent/src/Agent.php b/src/agent/src/Agent.php index 1e9eb156..a0703963 100644 --- a/src/agent/src/Agent.php +++ b/src/agent/src/Agent.php @@ -20,7 +20,6 @@ use Symfony\AI\Platform\Message\MessageBagInterface; use Symfony\AI\Platform\Model; use Symfony\AI\Platform\PlatformInterface; -use Symfony\AI\Platform\Response\AsyncResponse; use Symfony\AI\Platform\Response\ResponseInterface; use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface; @@ -76,11 +75,7 @@ public function call(MessageBagInterface $messages, array $options = []): Respon } try { - $response = $this->platform->request($model, $messages, $options); - - if ($response instanceof AsyncResponse) { - $response = $response->unwrap(); - } + $response = $this->platform->request($model, $messages, $options)->getResponse(); } catch (ClientExceptionInterface $e) { $message = $e->getMessage(); $content = $e->getResponse()->toArray(false); diff --git a/src/agent/src/Toolbox/Tool/SimilaritySearch.php b/src/agent/src/Toolbox/Tool/SimilaritySearch.php index 05b0f2da..7dc6a0d9 100644 --- a/src/agent/src/Toolbox/Tool/SimilaritySearch.php +++ b/src/agent/src/Toolbox/Tool/SimilaritySearch.php @@ -14,7 +14,6 @@ use Symfony\AI\Agent\Toolbox\Attribute\AsTool; use Symfony\AI\Platform\Model; use Symfony\AI\Platform\PlatformInterface; -use Symfony\AI\Platform\Vector\Vector; use Symfony\AI\Store\Document\VectorDocument; use Symfony\AI\Store\VectorStoreInterface; @@ -41,8 +40,7 @@ public function __construct( */ public function __invoke(string $searchTerm): string { - /** @var Vector[] $vectors */ - $vectors = $this->platform->request($this->model, $searchTerm)->getContent(); + $vectors = $this->platform->request($this->model, $searchTerm)->asVectors(); $this->usedDocuments = $this->vectorStore->query($vectors[0]); if (0 === \count($this->usedDocuments)) { diff --git a/src/ai-bundle/src/Profiler/TraceablePlatform.php b/src/ai-bundle/src/Profiler/TraceablePlatform.php index 87ccf265..f8315566 100644 --- a/src/ai-bundle/src/Profiler/TraceablePlatform.php +++ b/src/ai-bundle/src/Profiler/TraceablePlatform.php @@ -14,7 +14,7 @@ use Symfony\AI\Platform\Message\Content\File; use Symfony\AI\Platform\Model; use Symfony\AI\Platform\PlatformInterface; -use Symfony\AI\Platform\Response\ResponseInterface; +use Symfony\AI\Platform\Response\ResponsePromise; /** * @author Christopher Hertel @@ -23,7 +23,7 @@ * model: Model, * input: array|string|object, * options: array, - * response: ResponseInterface, + * response: ResponsePromise, * } */ final class TraceablePlatform implements PlatformInterface @@ -38,7 +38,7 @@ public function __construct( ) { } - public function request(Model $model, array|string|object $input, array $options = []): ResponseInterface + public function request(Model $model, array|string|object $input, array $options = []): ResponsePromise { $response = $this->platform->request($model, $input, $options); @@ -50,7 +50,7 @@ public function request(Model $model, array|string|object $input, array $options 'model' => $model, 'input' => \is_object($input) ? clone $input : $input, 'options' => $options, - 'response' => $response->getContent(), + 'response' => $response, ]; return $response; diff --git a/src/platform/doc/index.rst b/src/platform/doc/index.rst index a527a9d9..68d54a59 100644 --- a/src/platform/doc/index.rst +++ b/src/platform/doc/index.rst @@ -246,7 +246,7 @@ The standalone usage results in an ``Vector`` instance:: $embeddings = new Embeddings($platform, Embeddings::TEXT_3_SMALL); - $vectors = $platform->request($embeddings, $textInput)->getContent(); + $vectors = $platform->request($embeddings, $textInput)->asVectors(); dump($vectors[0]->getData()); // returns something like: [0.123, -0.456, 0.789, ...] @@ -276,7 +276,7 @@ which can be useful to speed up the processing:: } foreach ($responses as $response) { - echo $response->getContent().PHP_EOL; + echo $response->asText().PHP_EOL; } .. note:: diff --git a/src/platform/src/Bridge/Bedrock/Anthropic/ClaudeHandler.php b/src/platform/src/Bridge/Bedrock/Anthropic/ClaudeHandler.php index 08977da5..5342c38e 100644 --- a/src/platform/src/Bridge/Bedrock/Anthropic/ClaudeHandler.php +++ b/src/platform/src/Bridge/Bedrock/Anthropic/ClaudeHandler.php @@ -18,7 +18,6 @@ use Symfony\AI\Platform\Bridge\Bedrock\BedrockModelClient; use Symfony\AI\Platform\Exception\RuntimeException; use Symfony\AI\Platform\Model; -use Symfony\AI\Platform\Response\ResponseInterface as LlmResponse; use Symfony\AI\Platform\Response\TextResponse; use Symfony\AI\Platform\Response\ToolCall; use Symfony\AI\Platform\Response\ToolCallResponse; @@ -39,7 +38,7 @@ public function supports(Model $model): bool return $model instanceof Claude; } - public function request(Model $model, array|string $payload, array $options = []): LlmResponse + public function request(Model $model, array|string $payload, array $options = []): InvokeModelResponse { unset($payload['model']); @@ -57,12 +56,10 @@ public function request(Model $model, array|string $payload, array $options = [] 'body' => json_encode(array_merge($options, $payload), \JSON_THROW_ON_ERROR), ]; - $invokeModelResponse = $this->bedrockRuntimeClient->invokeModel(new InvokeModelRequest($request)); - - return $this->convert($invokeModelResponse); + return $this->bedrockRuntimeClient->invokeModel(new InvokeModelRequest($request)); } - public function convert(InvokeModelResponse $bedrockResponse): LlmResponse + public function convert(InvokeModelResponse $bedrockResponse): ToolCallResponse|TextResponse { $data = json_decode($bedrockResponse->getBody(), true, 512, \JSON_THROW_ON_ERROR); diff --git a/src/platform/src/Bridge/Bedrock/BedrockModelClient.php b/src/platform/src/Bridge/Bedrock/BedrockModelClient.php index 25fe1121..a9a7daaa 100644 --- a/src/platform/src/Bridge/Bedrock/BedrockModelClient.php +++ b/src/platform/src/Bridge/Bedrock/BedrockModelClient.php @@ -11,8 +11,9 @@ namespace Symfony\AI\Platform\Bridge\Bedrock; +use AsyncAws\BedrockRuntime\Result\InvokeModelResponse; use Symfony\AI\Platform\Model; -use Symfony\AI\Platform\Response\ResponseInterface as LlmResponse; +use Symfony\AI\Platform\Response\ResponseInterface; /** * @author Björn Altmann @@ -25,5 +26,7 @@ public function supports(Model $model): bool; * @param array|string $payload * @param array $options */ - public function request(Model $model, array|string $payload, array $options = []): LlmResponse; + public function request(Model $model, array|string $payload, array $options = []): InvokeModelResponse; + + public function convert(InvokeModelResponse $bedrockResponse): ResponseInterface; } diff --git a/src/platform/src/Bridge/Bedrock/Meta/LlamaModelClient.php b/src/platform/src/Bridge/Bedrock/Meta/LlamaModelClient.php index 4b6e31a5..0db79e2d 100644 --- a/src/platform/src/Bridge/Bedrock/Meta/LlamaModelClient.php +++ b/src/platform/src/Bridge/Bedrock/Meta/LlamaModelClient.php @@ -17,7 +17,6 @@ use Symfony\AI\Platform\Bridge\Bedrock\BedrockModelClient; use Symfony\AI\Platform\Bridge\Meta\Llama; use Symfony\AI\Platform\Model; -use Symfony\AI\Platform\Response\ResponseInterface as LlmResponse; use Symfony\AI\Platform\Response\TextResponse; /** @@ -35,18 +34,16 @@ public function supports(Model $model): bool return $model instanceof Llama; } - public function request(Model $model, array|string $payload, array $options = []): LlmResponse + public function request(Model $model, array|string $payload, array $options = []): InvokeModelResponse { - $response = $this->bedrockRuntimeClient->invokeModel(new InvokeModelRequest([ + return $this->bedrockRuntimeClient->invokeModel(new InvokeModelRequest([ 'modelId' => $this->getModelId($model), 'contentType' => 'application/json', 'body' => json_encode($payload, \JSON_THROW_ON_ERROR), ])); - - return $this->convert($response); } - public function convert(InvokeModelResponse $bedrockResponse): LlmResponse + public function convert(InvokeModelResponse $bedrockResponse): TextResponse { $responseBody = json_decode($bedrockResponse->getBody(), true, 512, \JSON_THROW_ON_ERROR); diff --git a/src/platform/src/Bridge/Bedrock/Nova/NovaHandler.php b/src/platform/src/Bridge/Bedrock/Nova/NovaHandler.php index 9e7624c3..e5b71fdb 100644 --- a/src/platform/src/Bridge/Bedrock/Nova/NovaHandler.php +++ b/src/platform/src/Bridge/Bedrock/Nova/NovaHandler.php @@ -17,7 +17,6 @@ use Symfony\AI\Platform\Bridge\Bedrock\BedrockModelClient; use Symfony\AI\Platform\Exception\RuntimeException; use Symfony\AI\Platform\Model; -use Symfony\AI\Platform\Response\ResponseInterface as LlmResponse; use Symfony\AI\Platform\Response\TextResponse; use Symfony\AI\Platform\Response\ToolCall; use Symfony\AI\Platform\Response\ToolCallResponse; @@ -37,7 +36,7 @@ public function supports(Model $model): bool return $model instanceof Nova; } - public function request(Model $model, array|string $payload, array $options = []): LlmResponse + public function request(Model $model, array|string $payload, array $options = []): InvokeModelResponse { $modelOptions = []; if (isset($options['tools'])) { @@ -58,12 +57,10 @@ public function request(Model $model, array|string $payload, array $options = [] 'body' => json_encode(array_merge($payload, $modelOptions), \JSON_THROW_ON_ERROR), ]; - $invokeModelResponse = $this->bedrockRuntimeClient->invokeModel(new InvokeModelRequest($request)); - - return $this->convert($invokeModelResponse); + return $this->bedrockRuntimeClient->invokeModel(new InvokeModelRequest($request)); } - public function convert(InvokeModelResponse $bedrockResponse): LlmResponse + public function convert(InvokeModelResponse $bedrockResponse): ToolCallResponse|TextResponse { $data = json_decode($bedrockResponse->getBody(), true, 512, \JSON_THROW_ON_ERROR); diff --git a/src/platform/src/Bridge/Bedrock/Platform.php b/src/platform/src/Bridge/Bedrock/Platform.php index 23f23dde..7f53d86a 100644 --- a/src/platform/src/Bridge/Bedrock/Platform.php +++ b/src/platform/src/Bridge/Bedrock/Platform.php @@ -18,7 +18,7 @@ use Symfony\AI\Platform\Exception\RuntimeException; use Symfony\AI\Platform\Model; use Symfony\AI\Platform\PlatformInterface; -use Symfony\AI\Platform\Response\ResponseInterface; +use Symfony\AI\Platform\Response\ResponsePromise; /** * @author Björn Altmann @@ -56,7 +56,7 @@ public function __construct( $this->modelClients = $modelClients instanceof \Traversable ? iterator_to_array($modelClients) : $modelClients; } - public function request(Model $model, array|string|object $input, array $options = []): ResponseInterface + public function request(Model $model, array|string|object $input, array $options = []): ResponsePromise { $payload = $this->contract->createRequestPayload($model, $input); $options = array_merge($model->getOptions(), $options); @@ -72,11 +72,17 @@ public function request(Model $model, array|string|object $input, array $options * @param array|string $payload * @param array $options */ - private function doRequest(Model $model, array|string $payload, array $options = []): ResponseInterface + private function doRequest(Model $model, array|string $payload, array $options = []): ResponsePromise { foreach ($this->modelClients as $modelClient) { if ($modelClient->supports($model)) { - return $modelClient->request($model, $payload, $options); + $response = $modelClient->request($model, $payload, $options); + + return new ResponsePromise( + $modelClient->convert(...), + new RawBedrockResponse($response), + $options, + ); } } diff --git a/src/platform/src/Bridge/Bedrock/RawBedrockResponse.php b/src/platform/src/Bridge/Bedrock/RawBedrockResponse.php new file mode 100644 index 00000000..b86b6a5f --- /dev/null +++ b/src/platform/src/Bridge/Bedrock/RawBedrockResponse.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\AI\Platform\Bridge\Bedrock; + +use AsyncAws\BedrockRuntime\Result\InvokeModelResponse; +use Symfony\AI\Platform\Response\RawResponseInterface; + +/** + * @author Christopher Hertel + */ +final readonly class RawBedrockResponse implements RawResponseInterface +{ + public function __construct( + private InvokeModelResponse $invokeModelResponse, + ) { + } + + public function getRawData(): array + { + return json_decode($this->invokeModelResponse->getBody(), true, 512, \JSON_THROW_ON_ERROR); + } + + public function getRawObject(): InvokeModelResponse + { + return $this->invokeModelResponse; + } +} diff --git a/src/platform/src/Bridge/OpenAI/TokenOutputProcessor.php b/src/platform/src/Bridge/OpenAI/TokenOutputProcessor.php index 5df6da37..d3561fcc 100644 --- a/src/platform/src/Bridge/OpenAI/TokenOutputProcessor.php +++ b/src/platform/src/Bridge/OpenAI/TokenOutputProcessor.php @@ -14,6 +14,7 @@ use Symfony\AI\Agent\Output; use Symfony\AI\Agent\OutputProcessorInterface; use Symfony\AI\Platform\Response\StreamResponse; +use Symfony\Contracts\HttpClient\ResponseInterface; /** * @author Denis Zunke @@ -27,8 +28,8 @@ public function processOutput(Output $output): void return; } - $rawResponse = $output->response->getRawResponse(); - if (null === $rawResponse) { + $rawResponse = $output->response->getRawResponse()?->getRawObject(); + if (!$rawResponse instanceof ResponseInterface) { return; } diff --git a/src/platform/src/Bridge/TransformersPHP/PipelineExecution.php b/src/platform/src/Bridge/TransformersPHP/PipelineExecution.php new file mode 100644 index 00000000..eab0b6e9 --- /dev/null +++ b/src/platform/src/Bridge/TransformersPHP/PipelineExecution.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\AI\Platform\Bridge\TransformersPHP; + +use Codewithkyrian\Transformers\Pipelines\Pipeline; + +/** + * @author Christopher Hertel + */ +final class PipelineExecution +{ + /** + * @var array|null + */ + private ?array $result = null; + + /** + * @param array|string|object $input + */ + public function __construct( + private readonly Pipeline $pipeline, + private readonly object|array|string $input, + ) { + } + + public function getPipeline(): Pipeline + { + return $this->pipeline; + } + + /** + * @return array + */ + public function getResult(): array + { + if (null === $this->result) { + $this->result = ($this->pipeline)($this->input); + } + + return $this->result; + } +} diff --git a/src/platform/src/Bridge/TransformersPHP/Platform.php b/src/platform/src/Bridge/TransformersPHP/Platform.php index 166afc9a..cba2380c 100644 --- a/src/platform/src/Bridge/TransformersPHP/Platform.php +++ b/src/platform/src/Bridge/TransformersPHP/Platform.php @@ -17,6 +17,7 @@ use Symfony\AI\Platform\PlatformInterface; use Symfony\AI\Platform\Response\ObjectResponse; use Symfony\AI\Platform\Response\ResponseInterface; +use Symfony\AI\Platform\Response\ResponsePromise; use Symfony\AI\Platform\Response\TextResponse; use function Codewithkyrian\Transformers\Pipelines\pipeline; @@ -26,14 +27,14 @@ */ final class Platform implements PlatformInterface { - public function request(Model $model, object|array|string $input, array $options = []): ResponseInterface + public function request(Model $model, object|array|string $input, array $options = []): ResponsePromise { if (null === $task = $options['task'] ?? null) { throw new InvalidArgumentException('The task option is required.'); } $pipeline = pipeline( - $options['task'], + $task, $model->getName(), $options['quantized'] ?? true, $options['config'] ?? null, @@ -41,10 +42,19 @@ public function request(Model $model, object|array|string $input, array $options $options['revision'] ?? 'main', $options['modelFilename'] ?? null, ); + $execution = new PipelineExecution($pipeline, $input); - $data = $pipeline($input); + return new ResponsePromise($this->convertResponse(...), new RawPipelineResponse($execution), $options); + } + + /** + * @param array $options + */ + private function convertResponse(PipelineExecution $pipelineExecution, array $options): ResponseInterface + { + $data = $pipelineExecution->getResult(); - return match ($task) { + return match ($options['task']) { Task::Text2TextGeneration => new TextResponse($data[0]['generated_text']), default => new ObjectResponse($data), }; diff --git a/src/platform/src/Bridge/TransformersPHP/RawPipelineResponse.php b/src/platform/src/Bridge/TransformersPHP/RawPipelineResponse.php new file mode 100644 index 00000000..0a6199e4 --- /dev/null +++ b/src/platform/src/Bridge/TransformersPHP/RawPipelineResponse.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\AI\Platform\Bridge\TransformersPHP; + +use Symfony\AI\Platform\Response\RawResponseInterface; + +/** + * @author Christopher Hertel + */ +final readonly class RawPipelineResponse implements RawResponseInterface +{ + public function __construct( + private PipelineExecution $pipelineExecution, + ) { + } + + public function getRawData(): array + { + return $this->pipelineExecution->getResult(); + } + + public function getRawObject(): PipelineExecution + { + return $this->pipelineExecution; + } +} diff --git a/src/platform/src/Exception/UnexpectedResponseTypeException.php b/src/platform/src/Exception/UnexpectedResponseTypeException.php new file mode 100644 index 00000000..d3829454 --- /dev/null +++ b/src/platform/src/Exception/UnexpectedResponseTypeException.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\AI\Platform\Exception; + +class UnexpectedResponseTypeException extends RuntimeException +{ + public function __construct(string $expectedType, string $actualType) + { + parent::__construct(\sprintf( + 'Unexpected response type: expected "%s", got "%s".', + $expectedType, + $actualType + )); + } +} diff --git a/src/platform/src/Platform.php b/src/platform/src/Platform.php index b45b9234..9213ef4c 100644 --- a/src/platform/src/Platform.php +++ b/src/platform/src/Platform.php @@ -12,9 +12,9 @@ namespace Symfony\AI\Platform; use Symfony\AI\Platform\Exception\RuntimeException; -use Symfony\AI\Platform\Response\AsyncResponse; -use Symfony\AI\Platform\Response\ResponseInterface; -use Symfony\Contracts\HttpClient\ResponseInterface as HttpResponse; +use Symfony\AI\Platform\Response\RawHttpResponse; +use Symfony\AI\Platform\Response\ResponsePromise; +use Symfony\Contracts\HttpClient\ResponseInterface; /** * @author Christopher Hertel @@ -45,7 +45,7 @@ public function __construct( $this->responseConverter = $responseConverter instanceof \Traversable ? iterator_to_array($responseConverter) : $responseConverter; } - public function request(Model $model, array|string|object $input, array $options = []): ResponseInterface + public function request(Model $model, array|string|object $input, array $options = []): ResponsePromise { $payload = $this->contract->createRequestPayload($model, $input); $options = array_merge($model->getOptions(), $options); @@ -63,7 +63,7 @@ public function request(Model $model, array|string|object $input, array $options * @param array $payload * @param array $options */ - private function doRequest(Model $model, array|string $payload, array $options = []): HttpResponse + private function doRequest(Model $model, array|string $payload, array $options = []): ResponseInterface { foreach ($this->modelClients as $modelClient) { if ($modelClient->supports($model)) { @@ -77,11 +77,15 @@ private function doRequest(Model $model, array|string $payload, array $options = /** * @param array $options */ - private function convertResponse(Model $model, HttpResponse $response, array $options): ResponseInterface + private function convertResponse(Model $model, ResponseInterface $response, array $options): ResponsePromise { foreach ($this->responseConverter as $responseConverter) { if ($responseConverter->supports($model)) { - return new AsyncResponse($responseConverter, $response, $options); + return new ResponsePromise( + fn (ResponseInterface $response, array $options) => $responseConverter->convert($response, $options), + new RawHttpResponse($response), + $options, + ); } } diff --git a/src/platform/src/PlatformInterface.php b/src/platform/src/PlatformInterface.php index ba020861..c51967d3 100644 --- a/src/platform/src/PlatformInterface.php +++ b/src/platform/src/PlatformInterface.php @@ -11,7 +11,7 @@ namespace Symfony\AI\Platform; -use Symfony\AI\Platform\Response\ResponseInterface; +use Symfony\AI\Platform\Response\ResponsePromise; /** * @author Christopher Hertel @@ -22,5 +22,5 @@ interface PlatformInterface * @param array|string|object $input * @param array $options */ - public function request(Model $model, array|string|object $input, array $options = []): ResponseInterface; + public function request(Model $model, array|string|object $input, array $options = []): ResponsePromise; } diff --git a/src/platform/src/Response/AsyncResponse.php b/src/platform/src/Response/AsyncResponse.php deleted file mode 100644 index eb9aef10..00000000 --- a/src/platform/src/Response/AsyncResponse.php +++ /dev/null @@ -1,83 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\AI\Platform\Response; - -use Symfony\AI\Platform\Response\Exception\RawResponseAlreadySetException; -use Symfony\AI\Platform\Response\Metadata\MetadataAwareTrait; -use Symfony\AI\Platform\ResponseConverterInterface; -use Symfony\Contracts\HttpClient\ResponseInterface as HttpResponse; - -/** - * @author Christopher Hertel - */ -final class AsyncResponse implements ResponseInterface -{ - use MetadataAwareTrait; - - private bool $isConverted = false; - private ResponseInterface $convertedResponse; - - /** - * @param array $options - */ - public function __construct( - private readonly ResponseConverterInterface $responseConverter, - private readonly HttpResponse $response, - private readonly array $options = [], - ) { - } - - public function getContent(): string|iterable|object|null - { - return $this->unwrap()->getContent(); - } - - public function getRawResponse(): HttpResponse - { - return $this->response; - } - - public function setRawResponse(HttpResponse $rawResponse): void - { - // Empty by design as the raw response is already set in the constructor and must only be set once - throw new RawResponseAlreadySetException(); - } - - public function unwrap(): ResponseInterface - { - if (!$this->isConverted) { - $this->convertedResponse = $this->responseConverter->convert($this->response, $this->options); - - if (null === $this->convertedResponse->getRawResponse()) { - // Fallback to set the raw response when it was not handled by the response converter itself - $this->convertedResponse->setRawResponse($this->response); - } - - $this->isConverted = true; - } - - return $this->convertedResponse; - } - - /** - * @param array $arguments - */ - public function __call(string $name, array $arguments): mixed - { - return $this->unwrap()->{$name}(...$arguments); - } - - public function __get(string $name): mixed - { - return $this->unwrap()->{$name}; - } -} diff --git a/src/platform/src/Response/BaseResponse.php b/src/platform/src/Response/BaseResponse.php index 78013e91..0dec61e6 100644 --- a/src/platform/src/Response/BaseResponse.php +++ b/src/platform/src/Response/BaseResponse.php @@ -14,6 +14,8 @@ use Symfony\AI\Platform\Response\Metadata\MetadataAwareTrait; /** + * Base response of converted response classes. + * * @author Denis Zunke */ abstract class BaseResponse implements ResponseInterface diff --git a/src/platform/src/Response/RawHttpResponse.php b/src/platform/src/Response/RawHttpResponse.php new file mode 100644 index 00000000..dbb53573 --- /dev/null +++ b/src/platform/src/Response/RawHttpResponse.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\AI\Platform\Response; + +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Christopher Hertel response->toArray(false); + } + + public function getRawObject(): ResponseInterface + { + return $this->response; + } +} diff --git a/src/platform/src/Response/RawResponseAwareTrait.php b/src/platform/src/Response/RawResponseAwareTrait.php index 029ab77b..15e20bc9 100644 --- a/src/platform/src/Response/RawResponseAwareTrait.php +++ b/src/platform/src/Response/RawResponseAwareTrait.php @@ -12,25 +12,24 @@ namespace Symfony\AI\Platform\Response; use Symfony\AI\Platform\Response\Exception\RawResponseAlreadySetException; -use Symfony\Contracts\HttpClient\ResponseInterface as SymfonyHttpResponse; /** * @author Denis Zunke */ trait RawResponseAwareTrait { - protected ?SymfonyHttpResponse $rawResponse = null; + protected ?RawResponseInterface $rawResponse = null; - public function setRawResponse(SymfonyHttpResponse $rawResponse): void + public function setRawResponse(RawResponseInterface $rawResponse): void { - if (null !== $this->rawResponse) { + if (isset($this->rawResponse)) { throw new RawResponseAlreadySetException(); } $this->rawResponse = $rawResponse; } - public function getRawResponse(): ?SymfonyHttpResponse + public function getRawResponse(): ?RawResponseInterface { return $this->rawResponse; } diff --git a/src/platform/src/Response/RawResponseInterface.php b/src/platform/src/Response/RawResponseInterface.php new file mode 100644 index 00000000..f9b83656 --- /dev/null +++ b/src/platform/src/Response/RawResponseInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\AI\Platform\Response; + +/** + * Base class for raw model responses. + * + * @author Christopher Hertel + */ +interface RawResponseInterface +{ + /** + * Returns an array representation of the raw response data. + * + * @return array + */ + public function getRawData(): array; + + public function getRawObject(): object; +} diff --git a/src/platform/src/Response/ResponseInterface.php b/src/platform/src/Response/ResponseInterface.php index c8738240..e039a9a5 100644 --- a/src/platform/src/Response/ResponseInterface.php +++ b/src/platform/src/Response/ResponseInterface.php @@ -13,7 +13,6 @@ use Symfony\AI\Platform\Response\Exception\RawResponseAlreadySetException; use Symfony\AI\Platform\Response\Metadata\Metadata; -use Symfony\Contracts\HttpClient\ResponseInterface as SymfonyHttpResponse; /** * @author Christopher Hertel @@ -28,10 +27,10 @@ public function getContent(): string|iterable|object|null; public function getMetadata(): Metadata; - public function getRawResponse(): ?SymfonyHttpResponse; + public function getRawResponse(): ?RawResponseInterface; /** * @throws RawResponseAlreadySetException if the response is tried to be set more than once */ - public function setRawResponse(SymfonyHttpResponse $rawResponse): void; + public function setRawResponse(RawResponseInterface $rawResponse): void; } diff --git a/src/platform/src/Response/ResponsePromise.php b/src/platform/src/Response/ResponsePromise.php new file mode 100644 index 00000000..ccf4c823 --- /dev/null +++ b/src/platform/src/Response/ResponsePromise.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\AI\Platform\Response; + +use Symfony\AI\Platform\Exception\UnexpectedResponseTypeException; +use Symfony\AI\Platform\Vector\Vector; + +/** + * @author Christopher Hertel + */ +final class ResponsePromise +{ + private bool $isConverted = false; + private ResponseInterface $convertedResponse; + + /** + * @param array $options + */ + public function __construct( + private readonly \Closure $responseConverter, + private readonly RawResponseInterface $response, + private readonly array $options = [], + ) { + } + + public function getResponse(): ResponseInterface + { + return $this->await(); + } + + public function getRawResponse(): RawResponseInterface + { + return $this->response; + } + + public function await(): ResponseInterface + { + if (!$this->isConverted) { + $this->convertedResponse = ($this->responseConverter)($this->response->getRawObject(), $this->options); + + if (null === $this->convertedResponse->getRawResponse()) { + // Fallback to set the raw response when it was not handled by the response converter itself + $this->convertedResponse->setRawResponse($this->response); + } + + $this->isConverted = true; + } + + return $this->convertedResponse; + } + + public function asText(): string + { + return $this->as(TextResponse::class)->getContent(); + } + + public function asObject(): object + { + return $this->as(ObjectResponse::class)->getContent(); + } + + public function asBinary(): string + { + return $this->as(BinaryResponse::class)->getContent(); + } + + public function asBase64(): string + { + $response = $this->as(BinaryResponse::class); + + \assert($response instanceof BinaryResponse); + + return $response->toDataUri(); + } + + /** + * @return Vector[] + */ + public function asVectors(): array + { + return $this->as(VectorResponse::class)->getContent(); + } + + public function asStream(): \Generator + { + yield from $this->as(StreamResponse::class)->getContent(); + } + + /** + * @return ToolCall[] + */ + public function asToolCalls(): array + { + return $this->as(ToolCallResponse::class)->getContent(); + } + + /** + * @param class-string $type + */ + private function as(string $type): ResponseInterface + { + $response = $this->getResponse(); + + if (!$response instanceof $type) { + throw new UnexpectedResponseTypeException($type, $response::class); + } + + return $response; + } +} diff --git a/src/platform/tests/Bridge/OpenAI/TokenOutputProcessorTest.php b/src/platform/tests/Bridge/OpenAI/TokenOutputProcessorTest.php index f2ea7595..d2a48c36 100644 --- a/src/platform/tests/Bridge/OpenAI/TokenOutputProcessorTest.php +++ b/src/platform/tests/Bridge/OpenAI/TokenOutputProcessorTest.php @@ -21,6 +21,7 @@ use Symfony\AI\Platform\Message\MessageBagInterface; use Symfony\AI\Platform\Model; use Symfony\AI\Platform\Response\Metadata\Metadata; +use Symfony\AI\Platform\Response\RawHttpResponse; use Symfony\AI\Platform\Response\ResponseInterface; use Symfony\AI\Platform\Response\StreamResponse; use Symfony\AI\Platform\Response\TextResponse; @@ -132,7 +133,7 @@ public function itHandlesMissingUsageFields(): void self::assertNull($metadata->get('total_tokens')); } - private function createRawResponse(array $data = []): SymfonyHttpResponse + private function createRawResponse(array $data = []): RawHttpResponse { $rawResponse = self::createStub(SymfonyHttpResponse::class); $rawResponse->method('getHeaders')->willReturn([ @@ -140,7 +141,7 @@ private function createRawResponse(array $data = []): SymfonyHttpResponse ]); $rawResponse->method('toArray')->willReturn($data); - return $rawResponse; + return new RawHttpResponse($rawResponse); } private function createOutput(ResponseInterface $response): Output diff --git a/src/platform/tests/Response/BaseResponseTest.php b/src/platform/tests/Response/BaseResponseTest.php index cd4afdbe..1d700a08 100644 --- a/src/platform/tests/Response/BaseResponseTest.php +++ b/src/platform/tests/Response/BaseResponseTest.php @@ -22,7 +22,7 @@ use Symfony\AI\Platform\Response\Metadata\Metadata; use Symfony\AI\Platform\Response\Metadata\MetadataAwareTrait; use Symfony\AI\Platform\Response\RawResponseAwareTrait; -use Symfony\Contracts\HttpClient\ResponseInterface as SymfonyHttpResponse; +use Symfony\AI\Platform\Response\RawResponseInterface; #[CoversClass(BaseResponse::class)] #[UsesTrait(MetadataAwareTrait::class)] @@ -50,7 +50,7 @@ public function itCanHandleMetadata(): void public function itCanBeEnrichedWithARawResponse(): void { $response = $this->createResponse(); - $rawResponse = self::createMock(SymfonyHttpResponse::class); + $rawResponse = $this->createRawResponse(); $response->setRawResponse($rawResponse); self::assertSame($rawResponse, $response->getRawResponse()); @@ -62,7 +62,7 @@ public function itThrowsAnExceptionWhenSettingARawResponseTwice(): void self::expectException(RawResponseAlreadySetException::class); $response = $this->createResponse(); - $rawResponse = self::createMock(SymfonyHttpResponse::class); + $rawResponse = $this->createRawResponse(); $response->setRawResponse($rawResponse); $response->setRawResponse($rawResponse); @@ -77,4 +77,19 @@ public function getContent(): string } }; } + + public function createRawResponse(): RawResponseInterface + { + return new class implements RawResponseInterface { + public function getRawData(): array + { + return ['key' => 'value']; + } + + public function getRawObject(): object + { + return new \stdClass(); + } + }; + } } diff --git a/src/platform/tests/Response/RawResponseAwareTraitTest.php b/src/platform/tests/Response/RawResponseAwareTraitTest.php index 03ad37df..2f90332e 100644 --- a/src/platform/tests/Response/RawResponseAwareTraitTest.php +++ b/src/platform/tests/Response/RawResponseAwareTraitTest.php @@ -17,6 +17,7 @@ use PHPUnit\Framework\Attributes\UsesClass; use PHPUnit\Framework\TestCase; use Symfony\AI\Platform\Response\Exception\RawResponseAlreadySetException; +use Symfony\AI\Platform\Response\RawHttpResponse; use Symfony\AI\Platform\Response\RawResponseAwareTrait; use Symfony\Contracts\HttpClient\ResponseInterface as SymfonyHttpResponse; @@ -31,8 +32,8 @@ public function itCanBeEnrichedWithARawResponse(): void $response = $this->createTestClass(); $rawResponse = self::createMock(SymfonyHttpResponse::class); - $response->setRawResponse($rawResponse); - self::assertSame($rawResponse, $response->getRawResponse()); + $response->setRawResponse(new RawHttpResponse($rawResponse)); + self::assertSame($rawResponse, $response->getRawResponse()?->getRawObject()); } #[Test] @@ -43,8 +44,8 @@ public function itThrowsAnExceptionWhenSettingARawResponseTwice(): void $response = $this->createTestClass(); $rawResponse = self::createMock(SymfonyHttpResponse::class); - $response->setRawResponse($rawResponse); - $response->setRawResponse($rawResponse); + $response->setRawResponse(new RawHttpResponse($rawResponse)); + $response->setRawResponse(new RawHttpResponse($rawResponse)); } private function createTestClass(): object diff --git a/src/platform/tests/Response/AsyncResponseTest.php b/src/platform/tests/Response/ResponsePromiseTest.php similarity index 71% rename from src/platform/tests/Response/AsyncResponseTest.php rename to src/platform/tests/Response/ResponsePromiseTest.php index 2c906ecd..9705858d 100644 --- a/src/platform/tests/Response/AsyncResponseTest.php +++ b/src/platform/tests/Response/ResponsePromiseTest.php @@ -16,21 +16,23 @@ use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\Attributes\UsesClass; use PHPUnit\Framework\TestCase; -use Symfony\AI\Platform\Response\AsyncResponse; use Symfony\AI\Platform\Response\BaseResponse; use Symfony\AI\Platform\Response\Exception\RawResponseAlreadySetException; use Symfony\AI\Platform\Response\Metadata\Metadata; +use Symfony\AI\Platform\Response\RawHttpResponse; +use Symfony\AI\Platform\Response\RawResponseInterface; use Symfony\AI\Platform\Response\ResponseInterface; +use Symfony\AI\Platform\Response\ResponsePromise; use Symfony\AI\Platform\Response\TextResponse; use Symfony\AI\Platform\ResponseConverterInterface; use Symfony\Contracts\HttpClient\ResponseInterface as SymfonyHttpResponse; -#[CoversClass(AsyncResponse::class)] +#[CoversClass(ResponsePromise::class)] #[UsesClass(Metadata::class)] #[UsesClass(TextResponse::class)] #[UsesClass(RawResponseAlreadySetException::class)] #[Small] -final class AsyncResponseTest extends TestCase +final class ResponsePromiseTest extends TestCase { #[Test] public function itUnwrapsTheResponseWhenGettingContent(): void @@ -44,9 +46,9 @@ public function itUnwrapsTheResponseWhenGettingContent(): void ->with($httpResponse, []) ->willReturn($textResponse); - $asyncResponse = new AsyncResponse($responseConverter, $httpResponse); + $responsePromise = new ResponsePromise($responseConverter->convert(...), new RawHttpResponse($httpResponse)); - self::assertSame('test content', $asyncResponse->getContent()); + self::assertSame('test content', $responsePromise->getResponse()->getContent()); } #[Test] @@ -61,12 +63,12 @@ public function itConvertsTheResponseOnlyOnce(): void ->with($httpResponse, []) ->willReturn($textResponse); - $asyncResponse = new AsyncResponse($responseConverter, $httpResponse); + $responsePromise = new ResponsePromise($responseConverter->convert(...), new RawHttpResponse($httpResponse)); // Call unwrap multiple times, but the converter should only be called once - $asyncResponse->unwrap(); - $asyncResponse->unwrap(); - $asyncResponse->getContent(); + $responsePromise->await(); + $responsePromise->await(); + $responsePromise->getResponse(); } #[Test] @@ -75,21 +77,9 @@ public function itGetsRawResponseDirectly(): void $httpResponse = $this->createStub(SymfonyHttpResponse::class); $responseConverter = $this->createStub(ResponseConverterInterface::class); - $asyncResponse = new AsyncResponse($responseConverter, $httpResponse); + $responsePromise = new ResponsePromise($responseConverter->convert(...), new RawHttpResponse($httpResponse)); - self::assertSame($httpResponse, $asyncResponse->getRawResponse()); - } - - #[Test] - public function itThrowsExceptionWhenSettingRawResponse(): void - { - self::expectException(RawResponseAlreadySetException::class); - - $httpResponse = $this->createStub(SymfonyHttpResponse::class); - $responseConverter = $this->createStub(ResponseConverterInterface::class); - - $asyncResponse = new AsyncResponse($responseConverter, $httpResponse); - $asyncResponse->setRawResponse($httpResponse); + self::assertSame($httpResponse, $responsePromise->getRawResponse()->getRawObject()); } #[Test] @@ -102,11 +92,11 @@ public function itSetsRawResponseOnUnwrappedResponseWhenNeeded(): void $responseConverter = $this->createStub(ResponseConverterInterface::class); $responseConverter->method('convert')->willReturn($unwrappedResponse); - $asyncResponse = new AsyncResponse($responseConverter, $httpResponse); - $asyncResponse->unwrap(); + $responsePromise = new ResponsePromise($responseConverter->convert(...), new RawHttpResponse($httpResponse)); + $responsePromise->await(); // The raw response in the model response is now set and not null anymore - self::assertSame($httpResponse, $unwrappedResponse->getRawResponse()); + self::assertSame($httpResponse, $unwrappedResponse->getRawResponse()->getRawObject()); } #[Test] @@ -120,11 +110,11 @@ public function itDoesNotSetRawResponseOnUnwrappedResponseWhenAlreadySet(): void $responseConverter = $this->createStub(ResponseConverterInterface::class); $responseConverter->method('convert')->willReturn($unwrappedResponse); - $asyncResponse = new AsyncResponse($responseConverter, $originHttpResponse); - $asyncResponse->unwrap(); + $responsePromise = new ResponsePromise($responseConverter->convert(...), new RawHttpResponse($originHttpResponse)); + $responsePromise->await(); // It is still the same raw response as set initially and so not overwritten - self::assertSame($anotherHttpResponse, $unwrappedResponse->getRawResponse()); + self::assertSame($anotherHttpResponse, $unwrappedResponse->getRawResponse()->getRawObject()); } /** @@ -132,10 +122,12 @@ public function itDoesNotSetRawResponseOnUnwrappedResponseWhenAlreadySet(): void * mock creation "Type Traversable|object|array|string|null contains both object and a class type" * in PHPUnit MockClass. */ - private function createResponse(?SymfonyHttpResponse $rawResponse): ResponseInterface + private function createResponse(?SymfonyHttpResponse $httpResponse): ResponseInterface { + $rawResponse = null !== $httpResponse ? new RawHttpResponse($httpResponse) : null; + return new class($rawResponse) extends BaseResponse { - public function __construct(protected ?SymfonyHttpResponse $rawResponse) + public function __construct(protected ?RawResponseInterface $rawResponse) { } @@ -143,11 +135,6 @@ public function getContent(): string { return 'test content'; } - - public function getRawResponse(): ?SymfonyHttpResponse - { - return $this->rawResponse; - } }; } @@ -163,7 +150,7 @@ public function itPassesOptionsToConverter(): void ->with($httpResponse, $options) ->willReturn($this->createResponse(null)); - $asyncResponse = new AsyncResponse($responseConverter, $httpResponse, $options); - $asyncResponse->unwrap(); + $responsePromise = new ResponsePromise($responseConverter->convert(...), new RawHttpResponse($httpResponse), $options); + $responsePromise->await(); } } diff --git a/src/store/src/Document/Vectorizer.php b/src/store/src/Document/Vectorizer.php index 03ad8621..12a8ea85 100644 --- a/src/store/src/Document/Vectorizer.php +++ b/src/store/src/Document/Vectorizer.php @@ -37,7 +37,7 @@ public function vectorizeDocuments(array $documents): array if ($this->model->supports(Capability::INPUT_MULTIPLE)) { $response = $this->platform->request($this->model, array_map(fn (TextDocument $document) => $document->content, $documents)); - $vectors = $response->getContent(); + $vectors = $response->asVectors(); } else { $responses = []; foreach ($documents as $document) { @@ -46,7 +46,7 @@ public function vectorizeDocuments(array $documents): array $vectors = []; foreach ($responses as $response) { - $vectors = array_merge($vectors, $response->getContent()); + $vectors = array_merge($vectors, $response->asVectors()); } } diff --git a/src/store/tests/IndexerTest.php b/src/store/tests/IndexerTest.php index 74cfadec..52537e76 100644 --- a/src/store/tests/IndexerTest.php +++ b/src/store/tests/IndexerTest.php @@ -20,7 +20,7 @@ use Symfony\AI\Platform\Bridge\OpenAI\Embeddings; use Symfony\AI\Platform\Message\ToolCallMessage; use Symfony\AI\Platform\Platform; -use Symfony\AI\Platform\Response\AsyncResponse; +use Symfony\AI\Platform\Response\ResponsePromise; use Symfony\AI\Platform\Response\ToolCall; use Symfony\AI\Platform\Response\VectorResponse; use Symfony\AI\Platform\Vector\Vector; @@ -42,7 +42,7 @@ #[UsesClass(ToolCall::class)] #[UsesClass(Embeddings::class)] #[UsesClass(Platform::class)] -#[UsesClass(AsyncResponse::class)] +#[UsesClass(ResponsePromise::class)] #[UsesClass(VectorResponse::class)] final class IndexerTest extends TestCase {