Skip to content

Added embedding generation. Avoid creating an instance of EmbeddingFieldFactory manually#3080

Open
mnocon wants to merge 2 commits intoIBX-9846from
IBX-9846-code-samples
Open

Added embedding generation. Avoid creating an instance of EmbeddingFieldFactory manually#3080
mnocon wants to merge 2 commits intoIBX-9846from
IBX-9846-code-samples

Conversation

@mnocon
Copy link
Contributor

@mnocon mnocon commented Mar 11, 2026

No description provided.

@github-actions
Copy link

Preview of modified files

Preview of modified Markdown:

@github-actions
Copy link

code_samples/ change report

Before (on target branch)After (in current PR)

code_samples/api/public_php_api/src/Command/FindByTaxonomyEmbeddingCommand.php

docs/search/search_api.md@406:``` php
docs/search/search_api.md@407:// ...
docs/search/search_api.md@408:[[= include_file('code_samples/api/public_php_api/src/Command/FindByTaxonomyEmbeddingCommand.php', 10, 21) =]]
docs/search/search_api.md@409:// ...
docs/search/search_api.md@410:[[= include_file('code_samples/api/public_php_api/src/Command/FindByTaxonomyEmbeddingCommand.php', 26, 28) =]]
docs/search/search_api.md@411:// ...
docs/search/search_api.md@412:[[= include_file('code_samples/api/public_php_api/src/Command/FindByTaxonomyEmbeddingCommand.php', 33, 78) =]]
docs/search/search_api.md@413:```

001⫶// ...
002⫶use Ibexa\Contracts\Core\Repository\SearchService;
003⫶use Ibexa\Contracts\Core\Repository\Values\Content\EmbeddingQueryBuilder;
004⫶use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\ContentTypeIdentifier;
005⫶use Ibexa\Contracts\Core\Repository\Values\Content\Search\SearchHit;

code_samples/api/public_php_api/src/Command/FindByTaxonomyEmbeddingCommand.php

docs/search/search_api.md@406:``` php
docs/search/search_api.md@407:// ...
docs/search/search_api.md@408:[[= include_file('code_samples/api/public_php_api/src/Command/FindByTaxonomyEmbeddingCommand.php', 10, 21) =]]
docs/search/search_api.md@409:// ...
docs/search/search_api.md@410:[[= include_file('code_samples/api/public_php_api/src/Command/FindByTaxonomyEmbeddingCommand.php', 26, 28) =]]
docs/search/search_api.md@411:// ...
docs/search/search_api.md@412:[[= include_file('code_samples/api/public_php_api/src/Command/FindByTaxonomyEmbeddingCommand.php', 33, 78) =]]
docs/search/search_api.md@413:```

001⫶// ...
002⫶use Ibexa\Contracts\Core\Repository\SearchService;
003⫶use Ibexa\Contracts\Core\Repository\Values\Content\EmbeddingQueryBuilder;
004⫶use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\ContentTypeIdentifier;
005⫶use Ibexa\Contracts\Core\Repository\Values\Content\Search\SearchHit;
006⫶use Ibexa\Contracts\Taxonomy\Search\Query\Value\TaxonomyEmbedding;
007⫶use Symfony\Component\Console\Attribute\AsCommand;
008⫶use Symfony\Component\Console\Command\Command;
009⫶use Symfony\Component\Console\Input\InputInterface;
010⫶use Symfony\Component\Console\Output\OutputInterface;
011⫶use Symfony\Component\Console\Style\SymfonyStyle;
012⫶
006⫶use Ibexa\Contracts\Core\Search\Embedding\EmbeddingProviderResolverInterface;
007⫶use Ibexa\Contracts\Taxonomy\Search\Query\Value\TaxonomyEmbedding;
008⫶use Symfony\Component\Console\Attribute\AsCommand;
009⫶use Symfony\Component\Console\Command\Command;
010⫶use Symfony\Component\Console\Input\InputInterface;
011⫶use Symfony\Component\Console\Output\OutputInterface;
012⫶use Symfony\Component\Console\Style\SymfonyStyle;
013⫶
014⫶// ...
013⫶
014⫶// ...
015⫶{
016⫶ public function __construct(private readonly SearchService $searchService)
015⫶final class FindByTaxonomyEmbeddingCommand extends Command
016⫶{
017⫶
018⫶// ...
017⫶
018⫶// ...
019⫶        InputInterface $input,
020⫶ OutputInterface $output
021⫶ ): int {
022⫶ $io = new SymfonyStyle($input, $output);
023⫶
024⫶ // Example embedding vector.
025⫶ // In a real-life scenario, generate it with an embedding provider
026⫶ // and make sure its dimensions match the configured model.
027⫶ $vector = [
028⫶ 0.0123,
029⫶ -0.9876,
030⫶ 0.4567,
031⫶ 0.1111,
032⫶ ];
033⫶
034⫶ $query = EmbeddingQueryBuilder::create()
035⫶ ->withEmbedding(new TaxonomyEmbedding($vector))
036⫶ ->setFilter(new ContentTypeIdentifier('article'))
037⫶ ->setLimit(10)
038⫶ ->setOffset(0)
039⫶ ->setPerformCount(true)
040⫶ ->build();
041⫶
042⫶ $result = $this->searchService->findContent($query);
043⫶
044⫶ $io->success(sprintf('Found %d items.', $result->totalCount));
045⫶
046⫶ foreach ($result->searchHits as $searchHit) {
047⫶ assert($searchHit instanceof SearchHit);
048⫶
049⫶ /** @var \Ibexa\Contracts\Core\Repository\Values\Content\Content $content */
050⫶ $content = $searchHit->valueObject;
051⫶ $contentInfo = $content->versionInfo->contentInfo;
052⫶
053⫶ $io->writeln(sprintf(
054⫶ '%d: %s',
055⫶ $contentInfo->id,
056⫶ $contentInfo->name
057⫶ ));
058⫶ }
059⫶
060⫶ return self::SUCCESS;
061⫶ }
062⫶}
019⫶    }
020⫶
021⫶ protected function execute(
022⫶ InputInterface $input,
023⫶ OutputInterface $output
024⫶ ): int {
025⫶ $io = new SymfonyStyle($input, $output);
026⫶
027⫶ // Example embedding vector.
028⫶ // In a real-life scenario, generate it with an embedding provider as shown below
029⫶ // and make sure its dimensions match the configured model.
030⫶ $vector = [
031⫶ 0.0123,
032⫶ -0.9876,
033⫶ 0.4567,
034⫶ 0.1111,
035⫶ ];
036⫶
037⫶ $embeddingProvider = $this->embeddingProviderResolver->resolve();
038⫶ $embedding = $embeddingProvider->getEmbedding('example_content');
039⫶
040⫶ $query = EmbeddingQueryBuilder::create()
041⫶ ->withEmbedding(new TaxonomyEmbedding($embedding))
042⫶ ->setFilter(new ContentTypeIdentifier('article'))
043⫶ ->setLimit(10)
044⫶ ->setOffset(0)
045⫶ ->setPerformCount(true)
046⫶ ->build();
047⫶
048⫶ $result = $this->searchService->findContent($query);
049⫶
050⫶ $io->success(sprintf('Found %d items.', $result->totalCount));
051⫶
052⫶ foreach ($result->searchHits as $searchHit) {
053⫶ assert($searchHit instanceof SearchHit);
054⫶
055⫶ /** @var \Ibexa\Contracts\Core\Repository\Values\Content\Content $content */
056⫶ $content = $searchHit->valueObject;
057⫶ $contentInfo = $content->versionInfo->contentInfo;
058⫶
059⫶ $io->writeln(sprintf(
060⫶ '%d: %s',
061⫶ $contentInfo->id,
062⫶ $contentInfo->name
063⫶ ));


code_samples/api/public_php_api/src/Service/TaxonomyEmbeddingSearchService.php

docs/search/search_api.md@417:``` php
docs/search/search_api.md@418:// ...
docs/search/search_api.md@419:[[= include_file('code_samples/api/public_php_api/src/Service/TaxonomyEmbeddingSearchService.php', 10, 15) =]]
docs/search/search_api.md@420:// ...
docs/search/search_api.md@421:[[= include_file('code_samples/api/public_php_api/src/Service/TaxonomyEmbeddingSearchService.php', 16, 18) =]]
docs/search/search_api.md@422:// ...
docs/search/search_api.md@423:[[= include_file('code_samples/api/public_php_api/src/Service/TaxonomyEmbeddingSearchService.php', 25, 36) =]]
docs/search/search_api.md@424:```

001⫶// ...
002⫶use Ibexa\Contracts\Core\Repository\SearchService;
003⫶use Ibexa\Contracts\Core\Repository\Values\Content\Content;
004⫶use Ibexa\Contracts\Core\Repository\Values\Content\EmbeddingQueryBuilder;
005⫶use Ibexa\Contracts\Core\Repository\Values\Content\Search\SearchResult;
006⫶use Ibexa\Contracts\Taxonomy\Search\Query\Value\TaxonomyEmbedding;
007⫶
008⫶// ...


code_samples/api/public_php_api/src/Service/TaxonomyEmbeddingSearchService.php

docs/search/search_api.md@417:``` php
docs/search/search_api.md@418:// ...
docs/search/search_api.md@419:[[= include_file('code_samples/api/public_php_api/src/Service/TaxonomyEmbeddingSearchService.php', 10, 15) =]]
docs/search/search_api.md@420:// ...
docs/search/search_api.md@421:[[= include_file('code_samples/api/public_php_api/src/Service/TaxonomyEmbeddingSearchService.php', 16, 18) =]]
docs/search/search_api.md@422:// ...
docs/search/search_api.md@423:[[= include_file('code_samples/api/public_php_api/src/Service/TaxonomyEmbeddingSearchService.php', 25, 36) =]]
docs/search/search_api.md@424:```

001⫶// ...
002⫶use Ibexa\Contracts\Core\Repository\SearchService;
003⫶use Ibexa\Contracts\Core\Repository\Values\Content\Content;
004⫶use Ibexa\Contracts\Core\Repository\Values\Content\EmbeddingQueryBuilder;
005⫶use Ibexa\Contracts\Core\Repository\Values\Content\Search\SearchResult;
006⫶use Ibexa\Contracts\Taxonomy\Search\Query\Value\TaxonomyEmbedding;
007⫶
008⫶// ...
009⫶final class TaxonomyEmbeddingSearchService
009⫶final readonly class TaxonomyEmbeddingSearchService
010⫶{
011⫶
012⫶// ...
013⫶ * @return SearchResult<Content>
014⫶ */
015⫶ public function searchByEmbedding(array $vector): SearchResult
016⫶ {
017⫶ $query = EmbeddingQueryBuilder::create()
018⫶ ->withEmbedding(new TaxonomyEmbedding($vector))
019⫶ ->setLimit(10)
020⫶ ->setOffset(0)
021⫶ ->build();
022⫶
023⫶ return $this->searchService->findContent($query);


code_samples/api/public_php_api/src/embedding_fields.php

010⫶{
011⫶
012⫶// ...
013⫶ * @return SearchResult<Content>
014⫶ */
015⫶ public function searchByEmbedding(array $vector): SearchResult
016⫶ {
017⫶ $query = EmbeddingQueryBuilder::create()
018⫶ ->withEmbedding(new TaxonomyEmbedding($vector))
019⫶ ->setLimit(10)
020⫶ ->setOffset(0)
021⫶ ->build();
022⫶
023⫶ return $this->searchService->findContent($query);


code_samples/api/public_php_api/src/embedding_fields.php

docs/search/search_api.md@467:``` php
docs/search/search_api.md@468:[[= include_file('code_samples/api/public_php_api/src/embedding_fields.php') =]]
docs/search/search_api.md@469:```

001⫶<?php declare(strict_types=1);
002⫶
003⫶// Create an embedding field using the default embedding provider (type derived from configuration's field suffix)
004⫶
005⫶/** @var Ibexa\Contracts\Core\Search\FieldType\EmbeddingFieldFactory $factory */
006⫶$embeddingField = $factory->create();
007⫶echo $embeddingField->getType(); // for example, "ibexa_dense_vector_model_123"
008⫶
009⫶// Create a custom embedding field with a specific type
010⫶$customField = $factory->create('custom_embedding_type');
011⫶echo $customField->getType(); // "custom_embedding_type"


code_samples/back_office/limitation/src/Controller/CustomController.php

docs/permissions/custom_policies.md@253:```php
docs/permissions/custom_policies.md@254:[[= include_file('code_samples/back_office/limitation/src/Controller/CustomController.php') =]]
docs/permissions/custom_policies.md@255:```

001⫶<?php declare(strict_types=1);
002⫶
003⫶namespace App\Controller;
004⫶
005⫶use App\Security\Limitation\CustomLimitationValue;
006⫶use Ibexa\Contracts\AdminUi\Controller\Controller;
007⫶use Ibexa\Contracts\AdminUi\Permission\PermissionCheckerInterface;
008⫶use Ibexa\Contracts\Core\Repository\PermissionResolver;
009⫶use Ibexa\Contracts\User\Controller\AuthenticatedRememberedCheckTrait;
010⫶use Ibexa\Contracts\User\Controller\RestrictedControllerInterface;
011⫶use Ibexa\Core\MVC\Symfony\Security\Authorization\Attribute;
012⫶use Symfony\Component\HttpFoundation\Request;
013⫶use Symfony\Component\HttpFoundation\Response;
014⫶
015⫶class CustomController extends Controller implements RestrictedControllerInterface
016⫶{
017⫶ use AuthenticatedRememberedCheckTrait {
018⫶ AuthenticatedRememberedCheckTrait::performAccessCheck as public traitPerformAccessCheck;
019⫶ }
020⫶
021⫶ public function __construct(
022⫶ // ...,
023⫶ private readonly PermissionResolver $permissionResolver,
024⫶ private readonly PermissionCheckerInterface $permissionChecker
025⫶ ) {
026⫶ }
027⫶
028⫶ // Controller actions...
029⫶ public function customAction(Request $request): Response
030⫶ {
031⫶ // ...
032⫶ if ($this->getCustomLimitationValue()) {
033⫶ // Action only for user having the custom limitation checked
034⫶ }
035⫶
036⫶ return new Response('<html><body>...</body></html>');
037⫶ }
038⫶
039⫶ private function getCustomLimitationValue(): bool
040⫶ {
041⫶ $hasAccess = $this->permissionResolver->hasAccess('custom_module', 'custom_function_2');
042⫶
043⫶ if (is_bool($hasAccess)) {
044⫶ return $hasAccess;
045⫶ }
046⫶
047⫶ $customLimitationValues = $this->permissionChecker->getRestrictions(
048⫶ $hasAccess,
049⫶ CustomLimitationValue::class
050⫶ );
051⫶
052⫶ return $customLimitationValues['value'] ?? false;
053⫶ }
054⫶

code_samples/back_office/limitation/src/Controller/CustomController.php

docs/permissions/custom_policies.md@253:```php
docs/permissions/custom_policies.md@254:[[= include_file('code_samples/back_office/limitation/src/Controller/CustomController.php') =]]
docs/permissions/custom_policies.md@255:```

001⫶<?php declare(strict_types=1);
002⫶
003⫶namespace App\Controller;
004⫶
005⫶use App\Security\Limitation\CustomLimitationValue;
006⫶use Ibexa\Contracts\AdminUi\Controller\Controller;
007⫶use Ibexa\Contracts\AdminUi\Permission\PermissionCheckerInterface;
008⫶use Ibexa\Contracts\Core\Repository\PermissionResolver;
009⫶use Ibexa\Contracts\User\Controller\AuthenticatedRememberedCheckTrait;
010⫶use Ibexa\Contracts\User\Controller\RestrictedControllerInterface;
011⫶use Ibexa\Core\MVC\Symfony\Security\Authorization\Attribute;
012⫶use Symfony\Component\HttpFoundation\Request;
013⫶use Symfony\Component\HttpFoundation\Response;
014⫶
015⫶class CustomController extends Controller implements RestrictedControllerInterface
016⫶{
017⫶ use AuthenticatedRememberedCheckTrait {
018⫶ AuthenticatedRememberedCheckTrait::performAccessCheck as public traitPerformAccessCheck;
019⫶ }
020⫶
021⫶ public function __construct(
022⫶ // ...,
023⫶ private readonly PermissionResolver $permissionResolver,
024⫶ private readonly PermissionCheckerInterface $permissionChecker
025⫶ ) {
026⫶ }
027⫶
028⫶ // Controller actions...
029⫶ public function customAction(Request $request): Response
030⫶ {
031⫶ // ...
032⫶ if ($this->getCustomLimitationValue()) {
033⫶ // Action only for user having the custom limitation checked
034⫶ }
035⫶
036⫶ return new Response('<html><body>...</body></html>');
037⫶ }
038⫶
039⫶ private function getCustomLimitationValue(): bool
040⫶ {
041⫶ $hasAccess = $this->permissionResolver->hasAccess('custom_module', 'custom_function_2');
042⫶
043⫶ if (is_bool($hasAccess)) {
044⫶ return $hasAccess;
045⫶ }
046⫶
047⫶ $customLimitationValues = $this->permissionChecker->getRestrictions(
048⫶ $hasAccess,
049⫶ CustomLimitationValue::class
050⫶ );
051⫶
052⫶ return $customLimitationValues['value'] ?? false;
053⫶ }
054⫶
055⫶    public function performAccessCheck(): void
056⫶ {
057⫶ $this->traitPerformAccessCheck();
058⫶ $this->denyAccessUnlessGranted(new Attribute('custom_module', 'custom_function_2'));
059⫶ }
060⫶}
055⫶    #[\Override]
056⫶ public function performAccessCheck(): void
057⫶ {
058⫶ $this->traitPerformAccessCheck();
059⫶ $this->denyAccessUnlessGranted(new Attribute('custom_module', 'custom_function_2'));
060⫶ }
061⫶}


Download colorized diff

@sonarqubecloud
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant