|
| 1 | +<?php |
| 2 | + |
| 3 | +/* |
| 4 | + * This file is part of the Symfony package. |
| 5 | + * |
| 6 | + * (c) Fabien Potencier <fabien@symfony.com> |
| 7 | + * |
| 8 | + * For the full copyright and license information, please view the LICENSE |
| 9 | + * file that was distributed with this source code. |
| 10 | + */ |
| 11 | + |
| 12 | +use Symfony\AI\Fixtures\Movies; |
| 13 | +use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory; |
| 14 | +use Symfony\AI\Store\Bridge\Meilisearch\Store; |
| 15 | +use Symfony\AI\Store\Document\Loader\InMemoryLoader; |
| 16 | +use Symfony\AI\Store\Document\Metadata; |
| 17 | +use Symfony\AI\Store\Document\TextDocument; |
| 18 | +use Symfony\AI\Store\Document\Vectorizer; |
| 19 | +use Symfony\AI\Store\Indexer; |
| 20 | +use Symfony\Component\Uid\Uuid; |
| 21 | + |
| 22 | +require_once dirname(__DIR__).'/bootstrap.php'; |
| 23 | + |
| 24 | +echo "=== Meilisearch Hybrid Search Demo ===\n\n"; |
| 25 | +echo "This example demonstrates how to configure the semantic ratio to balance\n"; |
| 26 | +echo "between semantic (vector) search and full-text search in Meilisearch.\n\n"; |
| 27 | + |
| 28 | +// Initialize the store with a balanced hybrid search (50/50) |
| 29 | +$store = new Store( |
| 30 | + httpClient: http_client(), |
| 31 | + endpointUrl: env('MEILISEARCH_HOST'), |
| 32 | + apiKey: env('MEILISEARCH_API_KEY'), |
| 33 | + indexName: 'movies_hybrid', |
| 34 | + semanticRatio: 0.5, // Balanced hybrid search by default |
| 35 | +); |
| 36 | + |
| 37 | +// Create embeddings and documents |
| 38 | +$documents = []; |
| 39 | +foreach (Movies::all() as $i => $movie) { |
| 40 | + $documents[] = new TextDocument( |
| 41 | + id: Uuid::v4(), |
| 42 | + content: 'Title: '.$movie['title'].\PHP_EOL.'Director: '.$movie['director'].\PHP_EOL.'Description: '.$movie['description'], |
| 43 | + metadata: new Metadata($movie), |
| 44 | + ); |
| 45 | +} |
| 46 | + |
| 47 | +// Initialize the index |
| 48 | +$store->setup(); |
| 49 | + |
| 50 | +// Create embeddings for documents |
| 51 | +$platform = PlatformFactory::create(env('OPENAI_API_KEY'), http_client()); |
| 52 | +$vectorizer = new Vectorizer($platform, 'text-embedding-3-small', logger()); |
| 53 | +$indexer = new Indexer(new InMemoryLoader($documents), $vectorizer, $store, logger: logger()); |
| 54 | +$indexer->index($documents); |
| 55 | + |
| 56 | +// Create a query embedding |
| 57 | +$queryText = 'futuristic technology and artificial intelligence'; |
| 58 | +echo "Query: \"$queryText\"\n\n"; |
| 59 | +$queryEmbedding = $vectorizer->vectorize($queryText); |
| 60 | + |
| 61 | +// Test different semantic ratios to compare results |
| 62 | +$ratios = [ |
| 63 | + ['ratio' => 0.0, 'description' => '100% Full-text search (keyword matching)'], |
| 64 | + ['ratio' => 0.5, 'description' => 'Balanced hybrid (50% semantic + 50% full-text)'], |
| 65 | + ['ratio' => 1.0, 'description' => '100% Semantic search (vector similarity)'], |
| 66 | +]; |
| 67 | + |
| 68 | +foreach ($ratios as $config) { |
| 69 | + echo "--- {$config['description']} ---\n"; |
| 70 | + |
| 71 | + // Override the semantic ratio for this specific query |
| 72 | + $results = $store->query($queryEmbedding, [ |
| 73 | + 'semanticRatio' => $config['ratio'], |
| 74 | + 'q' => 'technology', // Full-text search keyword |
| 75 | + ]); |
| 76 | + |
| 77 | + echo "Top 3 results:\n"; |
| 78 | + foreach (array_slice($results, 0, 3) as $i => $result) { |
| 79 | + $metadata = $result->metadata->getArrayCopy(); |
| 80 | + echo sprintf( |
| 81 | + " %d. %s (Score: %.4f)\n", |
| 82 | + $i + 1, |
| 83 | + $metadata['title'] ?? 'Unknown', |
| 84 | + $result->score ?? 0.0 |
| 85 | + ); |
| 86 | + } |
| 87 | + echo "\n"; |
| 88 | +} |
| 89 | + |
| 90 | +echo "--- Custom query with pure semantic search ---\n"; |
| 91 | +echo "Query: Movies about space exploration\n"; |
| 92 | +$spaceEmbedding = $vectorizer->vectorize('space exploration and cosmic adventures'); |
| 93 | +$results = $store->query($spaceEmbedding, [ |
| 94 | + 'semanticRatio' => 1.0, // Pure semantic search |
| 95 | +]); |
| 96 | + |
| 97 | +echo "Top 3 results:\n"; |
| 98 | +foreach (array_slice($results, 0, 3) as $i => $result) { |
| 99 | + $metadata = $result->metadata->getArrayCopy(); |
| 100 | + echo sprintf( |
| 101 | + " %d. %s (Score: %.4f)\n", |
| 102 | + $i + 1, |
| 103 | + $metadata['title'] ?? 'Unknown', |
| 104 | + $result->score ?? 0.0 |
| 105 | + ); |
| 106 | +} |
| 107 | +echo "\n"; |
| 108 | + |
| 109 | +// Cleanup |
| 110 | +$store->drop(); |
| 111 | + |
| 112 | +echo "=== Summary ===\n"; |
| 113 | +echo "- semanticRatio = 0.0: Best for exact keyword matches\n"; |
| 114 | +echo "- semanticRatio = 0.5: Balanced approach combining both methods\n"; |
| 115 | +echo "- semanticRatio = 1.0: Best for conceptual similarity searches\n"; |
| 116 | +echo "\nYou can set the default ratio when instantiating the Store,\n"; |
| 117 | +echo "and override it per query using the 'semanticRatio' option.\n"; |
0 commit comments