diff --git a/README.md b/README.md index 0a64a55..0b0c97f 100644 --- a/README.md +++ b/README.md @@ -99,8 +99,12 @@ acseo_typesense: from: - title - description - model_config: - model_name: ts/e5-small + model_config: + model_name: ts/e5-small # Typesense Cloud model (requires Typesense Cloud) + # For custom embedding services (Ollama, local models, etc.): + # model_name: 'openai/your-model-name' + # url: 'http://your-embedding-service:port' + # api_key: 'your-api-key' # Optional default_sorting_field: sortable_id # Default sorting field. Must be int32 or float symbols_to_index: ['+'] # Optional - You can add + to this list to make the word c++ indexable verbatim. users: @@ -327,7 +331,7 @@ $commonParams = new TypesenseQuery()->addParameter('query_by', 'name'); $response = $this->collectionClient->multisearch($searchRequests, $commonParams); ``` -## Cookbook +## Cookbook ---------------- * [Use Typesense to make an autocomplete field](doc/cookbook/autocomplete.md) diff --git a/src/Client/CollectionClient.php b/src/Client/CollectionClient.php index 19075ff..aebfdfe 100644 --- a/src/Client/CollectionClient.php +++ b/src/Client/CollectionClient.php @@ -58,7 +58,7 @@ public function list() return $this->client->collections->retrieve(); } - public function create($name, $fields, $defaultSortingField, array $tokenSeparators, array $symbolsToIndex, bool $enableNestedFields = false, ?array $embed = null) + public function create($name, $fields, $defaultSortingField, array $tokenSeparators, array $symbolsToIndex, bool $enableNestedFields = false) { if (!$this->client->isOperationnal()) { return null; @@ -72,10 +72,6 @@ public function create($name, $fields, $defaultSortingField, array $tokenSeparat 'symbols_to_index' => $symbolsToIndex, 'enable_nested_fields' => $enableNestedFields, ]; - - if ($embed) { - $options['embed'] = $embed; - } $this->client->collections->create($options); } diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 476c869..9224d9f 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -50,6 +50,8 @@ public function getConfigTreeBuilder(): TreeBuilder ->arrayNode('model_config') ->children() ->scalarNode('model_name')->isRequired()->end() + ->scalarNode('api_key')->cannotBeEmpty()->end() + ->scalarNode('url')->cannotBeEmpty()->end() ->end() ->end() ->end() diff --git a/src/Manager/CollectionManager.php b/src/Manager/CollectionManager.php index d7105b7..d242258 100644 --- a/src/Manager/CollectionManager.php +++ b/src/Manager/CollectionManager.php @@ -79,8 +79,7 @@ public function createCollection($collectionDefinitionName) $definition['default_sorting_field'], $tokenSeparators, $symbolsToIndex, - $definition['enable_nested_fields'] ?? false, - $definition['embed'] ?? null + $definition['enable_nested_fields'] ?? false ); } } diff --git a/tests/Unit/DependencyInjection/ACSEOTypesenseExtensionTest.php b/tests/Unit/DependencyInjection/ACSEOTypesenseExtensionTest.php index c111de4..525a17c 100644 --- a/tests/Unit/DependencyInjection/ACSEOTypesenseExtensionTest.php +++ b/tests/Unit/DependencyInjection/ACSEOTypesenseExtensionTest.php @@ -92,4 +92,39 @@ public function testFinderServiceDefinitionWithCollectionPrefix() $this->assertSame('acseo_prefix_books', $arguments['typesense_name']); $this->assertSame('books', $arguments['name']); } + + public function testEmbeddingConfigurationWithCustomService() + { + $containerBuilder = new ContainerBuilder(); + $containerBuilder->registerExtension($extension = new ACSEOTypesenseExtension()); + $containerBuilder->setParameter('kernel.debug', true); + + $loader = new YamlFileLoader($containerBuilder, new FileLocator(__DIR__.'/fixtures')); + $loader->load('acseo_typesense.yml'); + + $extensionConfig = $containerBuilder->getExtensionConfig($extension->getAlias()); + $extension->load($extensionConfig, $containerBuilder); + + $this->assertTrue($containerBuilder->hasDefinition('typesense.client')); + $this->assertTrue($containerBuilder->hasDefinition('typesense.finder.books')); + + // Verify that embed configuration is properly stored at field level + $managerDefinition = $containerBuilder->getDefinition('typesense.collection_manager'); + $collections = $managerDefinition->getArgument(2); + + $this->assertArrayHasKey('books', $collections); + $this->assertArrayHasKey('embeddings', $collections['books']['fields']); + + $embeddingsField = $collections['books']['fields']['embeddings']; + $this->assertArrayHasKey('embed', $embeddingsField); + $this->assertArrayHasKey('from', $embeddingsField['embed']); + $this->assertArrayHasKey('model_config', $embeddingsField['embed']); + $this->assertEquals(['title'], $embeddingsField['embed']['from']); + $this->assertEquals('openai/test-model', $embeddingsField['embed']['model_config']['model_name']); + $this->assertEquals('test-api-key', $embeddingsField['embed']['model_config']['api_key']); + $this->assertEquals('http://test-url:8080', $embeddingsField['embed']['model_config']['url']); + + // Verify that there is NO embed parameter at collection level + $this->assertArrayNotHasKey('embed', $collections['books']); + } } diff --git a/tests/Unit/DependencyInjection/fixtures/acseo_typesense.yml b/tests/Unit/DependencyInjection/fixtures/acseo_typesense.yml index 9bb4c69..b540b1e 100644 --- a/tests/Unit/DependencyInjection/fixtures/acseo_typesense.yml +++ b/tests/Unit/DependencyInjection/fixtures/acseo_typesense.yml @@ -35,10 +35,21 @@ acseo_typesense: genres: name: genres type: collection # Convert ArrayCollection to array of strings - publishedAt: + publishedAt: name: publishedAt type: datetime optional: true # Declare field as optional + embeddings: + name: embeddings + type: float[] + optional: true + embed: + from: + - title + model_config: + model_name: 'openai/test-model' + api_key: 'test-api-key' + url: 'http://test-url:8080' default_sorting_field: sortable_id # Default sorting field. Must be int32 or float finders: title_or_author: