From fd16d485e6b7a0138e8934fff06c038b17efbe3f Mon Sep 17 00:00:00 2001 From: Vladimir Klimes Date: Thu, 8 Jun 2023 20:10:17 +0200 Subject: [PATCH 1/6] ability to pass your own PromptTemplate to VectorStoreIndex --- src/Chains/VectorDbQa/VectorDBQA.php | 10 ++++++++-- src/Indexes/VectorStoreIndexWrapper.php | 13 +++++++++++-- tests/Chains/VectorDBQA/VectorDBQATest.php | 8 ++------ 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/Chains/VectorDbQa/VectorDBQA.php b/src/Chains/VectorDbQa/VectorDBQA.php index 4c8abe5..5c4d30f 100644 --- a/src/Chains/VectorDbQa/VectorDBQA.php +++ b/src/Chains/VectorDbQa/VectorDBQA.php @@ -122,6 +122,8 @@ public static function fromLLM( public static function fromChainType( BaseLLM $llm, string $chainType = 'stuff', + ?BasePromptTemplate $promptTemplate = null, + ?string $documentVariableName = 'context', ?array $chainType_kwargs = null, array $kwargs = [] ): VectorDBQA { @@ -129,6 +131,8 @@ public static function fromChainType( $combineDocuments_chain = self::loadQAChain( $llm, $chainType, + $promptTemplate, + $documentVariableName, null, null, $chainType_kwargs @@ -152,6 +156,8 @@ public static function fromChainType( public static function loadQAChain( BaseLanguageModel $llm, string $chainType = 'stuff', + ?BasePromptTemplate $promptTemplate = null, + ?string $documentVariableName = 'context', ?bool $verbose = null, ?BaseCallbackManager $callbackManager = null, ?array $kwargs = [] @@ -159,8 +165,8 @@ public static function loadQAChain( return match ($chainType) { 'stuff' => self::loadStuffChain( $llm, - null, - 'context', + $promptTemplate, + $documentVariableName, $verbose, $callbackManager, $kwargs diff --git a/src/Indexes/VectorStoreIndexWrapper.php b/src/Indexes/VectorStoreIndexWrapper.php index 456bd15..0ab7711 100644 --- a/src/Indexes/VectorStoreIndexWrapper.php +++ b/src/Indexes/VectorStoreIndexWrapper.php @@ -3,6 +3,8 @@ namespace Kambo\Langchain\Indexes; use Kambo\Langchain\LLMs\BaseLLM; +use Kambo\Langchain\Prompts\BasePromptTemplate; +use Kambo\Langchain\Prompts\PromptTemplate; use Kambo\Langchain\VectorStores\VectorStore; use Kambo\Langchain\LLMs\OpenAI; use Kambo\Langchain\Chains\VectorDbQa\VectorDBQA; @@ -27,12 +29,19 @@ public function __construct(public VectorStore $vectorStore) * * @return string */ - public function query(string $question, ?BaseLLM $llm = null, array $additionalParams = []): string - { + public function query( + string $question, + ?BaseLLM $llm = null, + ?BasePromptTemplate $promptTemplate = null, + ?string $documentVariableName = 'context', + array $additionalParams = [] + ): string { $llm = $llm ?? new OpenAI(['temperature' => 0]); $chain = VectorDBQA::fromChainType( $llm, 'stuff', + $promptTemplate, + $documentVariableName, null, array_merge(['vectorstore' => $this->vectorStore], $additionalParams) ); diff --git a/tests/Chains/VectorDBQA/VectorDBQATest.php b/tests/Chains/VectorDBQA/VectorDBQATest.php index 785513e..9cac4e2 100644 --- a/tests/Chains/VectorDBQA/VectorDBQATest.php +++ b/tests/Chains/VectorDBQA/VectorDBQATest.php @@ -88,9 +88,7 @@ public function testRun(): void $chain = VectorDBQA::fromChainType( $openAI, - 'stuff', - null, - [ + kwargs: [ 'vectorstore' => new SimpleStupidVectorStore($embeddings) ] ); @@ -105,9 +103,7 @@ public function testToArray(): void $chain = VectorDBQA::fromChainType( $openAI, - 'stuff', - null, - [ + kwargs: [ 'vectorstore' => new SimpleStupidVectorStore($embeddings) ] ); From 246c8048f453315dc22e5946bd06bc97ee7a5bd2 Mon Sep 17 00:00:00 2001 From: Vladimir Klimes Date: Fri, 9 Jun 2023 14:13:03 +0200 Subject: [PATCH 2/6] a version of SimpleStupidVectorStore which stores acquired vectors in PSR-6 caching implementation if provided --- .../CachedSimpleStupidVectorStore.php | 62 +++++++++++++++++++ src/VectorStores/SimpleStupidVectorStore.php | 8 +-- 2 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 src/VectorStores/CachedSimpleStupidVectorStore.php diff --git a/src/VectorStores/CachedSimpleStupidVectorStore.php b/src/VectorStores/CachedSimpleStupidVectorStore.php new file mode 100644 index 0000000..8c2b98d --- /dev/null +++ b/src/VectorStores/CachedSimpleStupidVectorStore.php @@ -0,0 +1,62 @@ +cacheItemPool = $options['cacheItemPool'] ?? null; + } + + public function addTexts(iterable $texts, ?array $metadata = null, array $additionalArguments = []): array + { + $textsHash = md5(serialize($texts)); + + if ($this->cacheItemPool instanceof CacheItemPoolInterface) { + $cachedItem = $this->cacheItemPool->getItem($textsHash); + + if ($cachedItem->isHit()) { + $embeddings = $cachedItem->get(); + } else { + $embeddings = $this->embedding->embedDocuments($texts); + $this->cacheItemPool->save($cachedItem->set($embeddings)); + } + + return parent::addTexts($texts, $metadata, array_merge($additionalArguments, ['embeddings' => $embeddings])); + } + + return parent::addTexts($texts, $metadata, $additionalArguments); + } + + public function similaritySearch(string $query, int $k = 4, array $additionalArguments = []): array + { + $queryHash = md5(serialize($query)); + + if ($this->cacheItemPool instanceof CacheItemPoolInterface) { + $cachedItem = $this->cacheItemPool->getItem($queryHash); + + if ($cachedItem->isHit()) { + $embeddings = $cachedItem->get(); + } else { + $embeddings = $this->embedding->embedQuery($query); + $this->cacheItemPool->save($cachedItem->set($embeddings)); + } + + parent::similaritySearch($query, $k, array_merge($additionalArguments, ['embeddings' => $embeddings])); + } + + return parent::similaritySearch($query, $k, $additionalArguments); + } +} diff --git a/src/VectorStores/SimpleStupidVectorStore.php b/src/VectorStores/SimpleStupidVectorStore.php index 1084a75..f7d42ef 100644 --- a/src/VectorStores/SimpleStupidVectorStore.php +++ b/src/VectorStores/SimpleStupidVectorStore.php @@ -13,7 +13,7 @@ class SimpleStupidVectorStore extends VectorStore { private const LANGCHAIN_DEFAULT_COLLECTION_NAME = 'langchain'; private ?SSVStorage $storage; - private $collection; + protected $collection; public function __construct( private Embeddings $embedding, @@ -35,7 +35,7 @@ public function __construct( public function addTexts(iterable $texts, ?array $metadata = null, array $additionalArguments = []): array { - $embeddings = $this->embedding->embedDocuments($texts); + $embeddings = $additionalArguments['embeddings'] ?? $this->embedding->embedDocuments($texts); $uuids = []; for ($i = 0; $i < count($texts); $i++) { @@ -50,7 +50,7 @@ public function addTexts(iterable $texts, ?array $metadata = null, array $additi public function similaritySearch(string $query, int $k = 4, array $additionalArguments = []): array { - $embeddings = $this->embedding->embedQuery($query); + $embeddings = $additionalArguments['embeddings'] ?? $this->embedding->embedQuery($query); $data = $this->collection->similaritySearchWithScore($embeddings, $k); $documents = []; @@ -67,7 +67,7 @@ public static function fromTexts( ?array $metadata = null, array $additionalArguments = [] ): VectorStore { - $self = new self($embedding, null, $additionalArguments); + $self = new static($embedding, null, $additionalArguments); $self->addTexts($texts, $metadata); return $self; From 1b0b9c2fd220f2902160c5257d88683f5341b649 Mon Sep 17 00:00:00 2001 From: Vladimir Klimes Date: Fri, 9 Jun 2023 14:31:37 +0200 Subject: [PATCH 3/6] StringLoader for various plain string providers --- src/DocumentLoaders/StringLoader.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/DocumentLoaders/StringLoader.php diff --git a/src/DocumentLoaders/StringLoader.php b/src/DocumentLoaders/StringLoader.php new file mode 100644 index 0000000..a908417 --- /dev/null +++ b/src/DocumentLoaders/StringLoader.php @@ -0,0 +1,27 @@ +text, metadata: $metadata)]; + } +} From 3b85042c8383e484bdb3df78f4abfdee5ee61839 Mon Sep 17 00:00:00 2001 From: Vladimir Klimes Date: Wed, 14 Jun 2023 14:16:56 +0200 Subject: [PATCH 4/6] phpunit tests for both new classes - they actually work similar to their siblings, so I used some test inheritance --- ...ractDocumentFromFixturesLoaderTestCase.php | 52 +++++++ tests/DocumentLoaders/StringLoaderTest.php | 26 ++++ tests/DocumentLoaders/TextLoaderTest.php | 41 +----- ...ctMockHandlerDrivenVectorStoreTestCase.php | 129 ++++++++++++++++++ .../CachedSimpleStupidVectorStoreTest.php | 15 ++ .../SimpleStupidVectorStoreTest.php | 121 +--------------- 6 files changed, 231 insertions(+), 153 deletions(-) create mode 100644 tests/DocumentLoaders/AbstractDocumentFromFixturesLoaderTestCase.php create mode 100644 tests/DocumentLoaders/StringLoaderTest.php create mode 100644 tests/VectorStores/AbstractMockHandlerDrivenVectorStoreTestCase.php create mode 100644 tests/VectorStores/CachedSimpleStupidVectorStoreTest.php diff --git a/tests/DocumentLoaders/AbstractDocumentFromFixturesLoaderTestCase.php b/tests/DocumentLoaders/AbstractDocumentFromFixturesLoaderTestCase.php new file mode 100644 index 0000000..c4392aa --- /dev/null +++ b/tests/DocumentLoaders/AbstractDocumentFromFixturesLoaderTestCase.php @@ -0,0 +1,52 @@ +assertEquals( + 'Obchodní řetězec Billa v pondělí v Česku spustil pilotní verzi svého e-shopu, ' + . 'dostupný je zatím v Praze, v Brně a v jejich blízkém okolí.' . PHP_EOL, + $documentLoader->load()[0]->pageContent + ); + } + + public function testLoadAndSplitDefault() + { + $documentLoader = static::getDocumentLoader(); + $this->assertEquals( + 'Obchodní řetězec Billa v pondělí v Česku spustil pilotní verzi svého e-shopu, ' + . 'dostupný je zatím v Praze, v Brně a v jejich blízkém okolí.', + $documentLoader->loadAndSplit()[0]->pageContent + ); + } + + public function testLoadAndSplit() + { + $textSplitter = new RecursiveCharacterTextSplitter(['chunk_size' => 60, 'chunk_overlap' => 1]); + $documentLoader = static::getDocumentLoader(); + + $documents = $documentLoader->loadAndSplit($textSplitter); + $this->assertCount(3, $documents); + $this->assertEquals( + 'Obchodní řetězec Billa v pondělí v Česku spustil', + $documents[0]->pageContent + ); + + $this->assertEquals( + 'pilotní verzi svého e-shopu, dostupný je zatím v Praze,', + $documents[1]->pageContent + ); + } +} diff --git a/tests/DocumentLoaders/StringLoaderTest.php b/tests/DocumentLoaders/StringLoaderTest.php new file mode 100644 index 0000000..ae78fc3 --- /dev/null +++ b/tests/DocumentLoaders/StringLoaderTest.php @@ -0,0 +1,26 @@ +text = file_get_contents($filePath->getRealPath()); + } + + protected function getDocumentLoader(): BaseLoader + { + return new StringLoader($this->text); + } +} diff --git a/tests/DocumentLoaders/TextLoaderTest.php b/tests/DocumentLoaders/TextLoaderTest.php index 6bceca5..9c4c8dd 100644 --- a/tests/DocumentLoaders/TextLoaderTest.php +++ b/tests/DocumentLoaders/TextLoaderTest.php @@ -2,50 +2,17 @@ namespace Kambo\Langchain\Tests\DocumentLoaders; +use Kambo\Langchain\DocumentLoaders\BaseLoader; use Kambo\Langchain\DocumentLoaders\TextLoader; -use Kambo\Langchain\TextSplitter\RecursiveCharacterTextSplitter; use PHPUnit\Framework\TestCase; use const DIRECTORY_SEPARATOR; use const PHP_EOL; -class TextLoaderTest extends TestCase +class TextLoaderTest extends AbstractDocumentFromFixturesLoaderTestCase { - public function testLoad() + protected function getDocumentLoader(): BaseLoader { - $textLoader = new TextLoader(__DIR__ . DIRECTORY_SEPARATOR . 'fixtures.txt'); - $this->assertEquals( - 'Obchodní řetězec Billa v pondělí v Česku spustil pilotní verzi svého e-shopu, ' - . 'dostupný je zatím v Praze, v Brně a v jejich blízkém okolí.' . PHP_EOL, - $textLoader->load()[0]->pageContent - ); - } - - public function testLoadAndSplitDefault() - { - $textLoader = new TextLoader(__DIR__ . DIRECTORY_SEPARATOR . 'fixtures.txt'); - $this->assertEquals( - 'Obchodní řetězec Billa v pondělí v Česku spustil pilotní verzi svého e-shopu, ' - . 'dostupný je zatím v Praze, v Brně a v jejich blízkém okolí.', - $textLoader->loadAndSplit()[0]->pageContent - ); - } - - public function testLoadAndSplit() - { - $textSplitter = new RecursiveCharacterTextSplitter(['chunk_size' => 60, 'chunk_overlap' => 1]); - $textLoader = new TextLoader(__DIR__ . DIRECTORY_SEPARATOR . 'fixtures.txt'); - - $documents = $textLoader->loadAndSplit($textSplitter); - $this->assertCount(3, $documents); - $this->assertEquals( - 'Obchodní řetězec Billa v pondělí v Česku spustil', - $documents[0]->pageContent - ); - - $this->assertEquals( - 'pilotní verzi svého e-shopu, dostupný je zatím v Praze,', - $documents[1]->pageContent - ); + return new TextLoader(__DIR__ . DIRECTORY_SEPARATOR . 'fixtures.txt'); } } diff --git a/tests/VectorStores/AbstractMockHandlerDrivenVectorStoreTestCase.php b/tests/VectorStores/AbstractMockHandlerDrivenVectorStoreTestCase.php new file mode 100644 index 0000000..f39073a --- /dev/null +++ b/tests/VectorStores/AbstractMockHandlerDrivenVectorStoreTestCase.php @@ -0,0 +1,129 @@ +mockOpenAIEmbeddingsWithResponses( + [ + self::prepareResponse( + [ + 'object' => 'list', + 'data' => [ + [ + 'object' => 'embedding', + 'index' => 0, + 'embedding' => + [ + -0.015587599, + -0.03145355, + -0.010950541, + -0.014322372, + -0.0121335285, + -0.0009655265, + -0.025747374, + 0.0009908311, + -0.017751137, + -0.010210384, + 0.0010643724, + ], + ], + ], + 'usage' => [ + 'prompt_tokens' => 1468, + 'total_tokens' => 1468, + ], + ] + ), + self::prepareResponse( + [ + 'object' => 'list', + 'data' => [ + [ + 'object' => 'embedding', + 'index' => 0, + 'embedding' => + [ + -0.015587599, + -0.03145355, + -0.010950541, + -0.014322372, + -0.0121335285, + -0.0009655265, + -0.025747374, + 0.0009908311, + -0.017751137, + -0.010210384, + 0.0010643724, + ], + ], + ], + 'usage' => [ + 'prompt_tokens' => 1468, + 'total_tokens' => 1468, + ], + ] + ) + ] + ); + + $SSVS = static::getVectorStore($openAI); + $SSVS->addTexts(['foo bar baz'], []); + + $this->assertEquals( + [ + new Document('foo bar baz'), + ], + $SSVS->similaritySearch('foo bar baz', 1) + ); + } + + private static function prepareResponse(array $response): Response + { + return new Response(200, ['Content-Type' => 'application/json'], json_encode($response)); + } + + private static function mockOpenAIEmbeddingsWithResponses(array $responses, array $options = []): OpenAIEmbeddings + { + $mock = new MockHandler($responses); + + $client = self::client($mock); + return new OpenAIEmbeddings(array_merge(['openai_api_key' => 'test'], $options), $client); + } + + private static function client(MockHandler $mockHandler): Client + { + $apiKey = ApiKey::from('test'); + $baseUri = BaseUri::from('api.openai.com/v1'); + $headers = Headers::withAuthorization($apiKey); + + $handlerStack = HandlerStack::create($mockHandler); + $client = new GuzzleClient(['handler' => $handlerStack]); + + $transporter = new HttpTransporter($client, $baseUri, $headers); + + return new Client($transporter); + } +} diff --git a/tests/VectorStores/CachedSimpleStupidVectorStoreTest.php b/tests/VectorStores/CachedSimpleStupidVectorStoreTest.php new file mode 100644 index 0000000..7f0d654 --- /dev/null +++ b/tests/VectorStores/CachedSimpleStupidVectorStoreTest.php @@ -0,0 +1,15 @@ +mockOpenAIEmbeddingsWithResponses( - [ - self::prepareResponse( - [ - 'object' => 'list', - 'data' => [ - [ - 'object' => 'embedding', - 'index' => 0, - 'embedding' => - [ - -0.015587599, - -0.03145355, - -0.010950541, - -0.014322372, - -0.0121335285, - -0.0009655265, - -0.025747374, - 0.0009908311, - -0.017751137, - -0.010210384, - 0.0010643724, - ], - ], - ], - 'usage' => [ - 'prompt_tokens' => 1468, - 'total_tokens' => 1468, - ], - ] - ), - self::prepareResponse( - [ - 'object' => 'list', - 'data' => [ - [ - 'object' => 'embedding', - 'index' => 0, - 'embedding' => - [ - -0.015587599, - -0.03145355, - -0.010950541, - -0.014322372, - -0.0121335285, - -0.0009655265, - -0.025747374, - 0.0009908311, - -0.017751137, - -0.010210384, - 0.0010643724, - ], - ], - ], - 'usage' => [ - 'prompt_tokens' => 1468, - 'total_tokens' => 1468, - ], - ] - ) - ] - ); - - $SSVS = new SimpleStupidVectorStore($openAI); - $SSVS->addTexts(['foo bar baz'], []); - - $this->assertEquals( - [ - new Document('foo bar baz'), - ], - $SSVS->similaritySearch('foo bar baz', 1) - ); - } - - private static function prepareResponse(array $response): Response - { - return new Response(200, ['Content-Type' => 'application/json'], json_encode($response)); - } - - private static function mockOpenAIEmbeddingsWithResponses(array $responses, array $options = []): OpenAIEmbeddings - { - $mock = new MockHandler($responses); - - $client = self::client($mock); - return new OpenAIEmbeddings(array_merge(['openai_api_key' => 'test'], $options), $client); - } - - private static function client(MockHandler $mockHandler): Client + function getVectorStore(Embeddings $embedding): VectorStore { - $apiKey = ApiKey::from('test'); - $baseUri = BaseUri::from('api.openai.com/v1'); - $headers = Headers::withAuthorization($apiKey); - - $handlerStack = HandlerStack::create($mockHandler); - $client = new GuzzleClient(['handler' => $handlerStack]); - - $transporter = new HttpTransporter($client, $baseUri, $headers); - - return new Client($transporter); + return new SimpleStupidVectorStore($embedding); } } From 706d820c3728f502100903d532269663bed4e6eb Mon Sep 17 00:00:00 2001 From: Vladimir Klimes Date: Tue, 20 Jun 2023 09:02:26 +0200 Subject: [PATCH 5/6] ability to modify both search type and k-number of returned documents by QA chain --- src/Chains/VectorDbQa/VectorDBQA.php | 10 ++++++---- src/VectorStores/VectorStore.php | 3 +++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Chains/VectorDbQa/VectorDBQA.php b/src/Chains/VectorDbQa/VectorDBQA.php index 5c4d30f..f692dbc 100644 --- a/src/Chains/VectorDbQa/VectorDBQA.php +++ b/src/Chains/VectorDbQa/VectorDBQA.php @@ -32,14 +32,16 @@ class VectorDBQA extends Chain public string $outputKey = 'result'; public bool $returnSourceDocuments = false; public array $searchKwargs; - public string $searchType = 'similarity'; + public string $searchType = VectorStore::SIMILARITY_SEARCH; public function __construct($params) { parent::__construct(null, null, null, $params); $this->vectorstore = $params['vectorstore']; + $this->k = $params['k'] ?? $this->k; $this->combineDocumentsChain = $params['combine_documents_chain']; $this->searchKwargs = $params['search_kwargs'] ?? []; + $this->searchType = $params['search_type'] ?? $this->searchType; $this->validateSearchType(); } @@ -74,7 +76,7 @@ public function outputKeys(): array */ private function validateSearchType(): void { - if (!in_array($this->searchType, ['similarity', 'mmr'])) { + if (!in_array($this->searchType, [VectorStore::SIMILARITY_SEARCH, VectorStore::MAX_MARGINAL_RELEVANCE_SEARCH])) { throw new Exception('search_type of ' . $this->searchType . ' not allowed.'); } } @@ -260,9 +262,9 @@ protected function call(array $inputs): array { $question = $inputs[$this->inputKey]; - if ($this->searchType === 'similarity') { + if ($this->searchType === VectorStore::SIMILARITY_SEARCH) { $docs = $this->vectorstore->similaritySearch($question, $this->k, $this->searchKwargs); - } elseif ($this->searchType === 'mmr') { + } elseif ($this->searchType === VectorStore::MAX_MARGINAL_RELEVANCE_SEARCH) { $docs = $this->vectorstore->maxMarginalRelevanceSearch($question, $this->k, $this->searchKwargs); } else { throw new Exception('search_type of ' . $this->searchType . ' not allowed.'); diff --git a/src/VectorStores/VectorStore.php b/src/VectorStores/VectorStore.php index 92b3662..bc9331d 100644 --- a/src/VectorStores/VectorStore.php +++ b/src/VectorStores/VectorStore.php @@ -12,6 +12,9 @@ */ abstract class VectorStore { + const SIMILARITY_SEARCH = 'similarity'; + const MAX_MARGINAL_RELEVANCE_SEARCH = 'mmr'; + /** * @param iterable $texts Iterable of strings to add to the vectorstore. * @param array|null $metadata Optional list of metadatas associated with the texts. From 0a434564b6e264b5a572c1162e21a2492e969518 Mon Sep 17 00:00:00 2001 From: Vladimir Klimes Date: Fri, 21 Mar 2025 13:11:30 +0100 Subject: [PATCH 6/6] pass prompt as string if possible as Ollama OpenAI API implementation won't process arrays there --- composer.json | 11 +++++++---- src/LLMs/BaseOpenAI.php | 6 +++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 34f8af4..8f79472 100644 --- a/composer.json +++ b/composer.json @@ -16,10 +16,12 @@ ], "require": { "php": "^8.1", - "openai-php/client": "^0.3.5", + "nyholm/psr7": "^1.8", + "openai-php/client": "^0.16", "psr/simple-cache": "^3.0", "ramsey/uuid": "^4.7", - "stechstudio/backoff": "^1.2" + "stechstudio/backoff": "^1.2", + "symfony/http-client": "^7.2" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.13", @@ -45,8 +47,9 @@ "config": { "sort-packages": true, "allow-plugins": { - "phpstan/extension-installer": true, - "dealerdirect/phpcodesniffer-composer-installer": true + "dealerdirect/phpcodesniffer-composer-installer": true, + "php-http/discovery": true, + "phpstan/extension-installer": true } }, "minimum-stability": "dev", diff --git a/src/LLMs/BaseOpenAI.php b/src/LLMs/BaseOpenAI.php index 1607e1c..6c36d80 100644 --- a/src/LLMs/BaseOpenAI.php +++ b/src/LLMs/BaseOpenAI.php @@ -164,7 +164,11 @@ public function generateResult(array $prompts, ?array $stop = null): LLMResult // Includes prompt, completion, and total tokens used. $keys = ['completion_tokens', 'prompt_tokens', 'total_tokens']; foreach ($subPrompts as $_prompts) { - $response = $this->completionWithRetry($this->client, ['prompt' => $_prompts], ...$params); + $response = $this->completionWithRetry( + $this->client, + ['prompt' => sizeof($_prompts) == 1 ? array_shift($_prompts) : $_prompts], + ...$params + ); $choices = array_merge($choices, $response['choices']); $this->updateTokenUsage($keys, $response, $tokenUsage); }