Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Ai Store Laravel Package

symfony/ai-store

Symfony AI Store provides a low-level abstraction for storing and retrieving documents in vector stores, with bridge packages for popular backends. Experimental and not covered by Symfony’s BC promise.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the Core Package (v0.14+):
    composer require symfony/ai-store:^0.14
    
  2. Choose a Bridge (e.g., PostgreSQL with pgvector):
    composer require symfony/ai-postgres-store
    
  3. Configure the Store in Laravel’s config/services.php:
    'ai_store' => [
        'default' => env('AI_STORE_DRIVER', 'postgres'),
        'stores' => [
            'postgres' => [
                'dsn' => env('DATABASE_URL'),
                'table' => 'vector_documents',
                'embedding_dimension' => 384, // Match your model's output
            ],
        ],
    ];
    
  4. Register the Store Factory in a Laravel service provider:
    use Symfony\Component\AI\Store\StoreFactory;
    
    public function register()
    {
        $this->app->singleton(StoreFactory::class, function ($app) {
            return new StoreFactory(
                $app['config']['services.ai_store.stores']
            );
        });
    }
    

First Use Case: Indexing Documents

use Symfony\Component\AI\Store\StoreFactory;
use Symfony\Component\AI\Store\TextDocument;

$storeFactory = app(StoreFactory::class);
$store = $storeFactory->getStore('postgres');

$documents = [
    new TextDocument('id1', 'Laravel is a PHP framework...', ['source' => 'docs']),
    new TextDocument('id2', 'Symfony AI provides vector stores...', ['source' => 'docs']),
];

$store->add($documents); // Batch add

// v0.14+: sanity-check what's actually stored
$total = $store->count(); // int

First Use Case: Querying Vectors

use Symfony\Component\AI\Store\Query\VectorQuery;

$query = new VectorQuery(
    vector: $embedding, // Any VectorInterface (v0.14+), pre-computed upstream
    limit: 3,
    distance: 'cosine'
);

$results = $store->query($query);
foreach ($results as $result) {
    echo $result->getText(); // Retrieve document text
}

Where to Look First

  • StoreInterface — the contract every bridge implements (add, query, delete, count).
  • VectorDocumentInterface (new in v0.14) — the document contract, of which TextDocument is the built-in implementation.
  • VectorInterface (new in v0.14) — anything implementing this can be passed as a query vector.
  • Indexer / Loader — the batch ingestion path, now configurable via indexer options.

Implementation Patterns

Core Workflow: RAG Pipeline

  1. Vectorize Text (using symfony/ai-platform):
    $vectorizer = app(Symfony\Component\AI\Vectorizer::class);
    $embedding = $vectorizer->vectorize('Your query text');
    
  2. Query the Store:
    $query = new VectorQuery($embedding, limit: 5);
    $results = $store->query($query);
    
  3. Process Results (e.g., pass to an LLM):
    $context = implode("\n---\n", array_map(fn($r) => $r->getText(), $results));
    

Integration with Laravel

Service Container Binding

// app/Providers/AIServiceProvider.php
public function register()
{
    $this->app->bind(\Symfony\Component\AI\Store\StoreInterface::class, function ($app) {
        return $app->make(StoreFactory::class)->getStore(config('ai_store.default'));
    });
}

Artisan Command for Indexing

// app/Console/Commands/IndexDocuments.php
use Symfony\Component\AI\Store\Indexer\Indexer;
use Symfony\Component\AI\Store\Loader\JsonFileLoader;

protected function handle()
{
    $loader = new JsonFileLoader('path/to/documents.json');
    $indexer = new Indexer($this->store, $loader);
    $indexer->index(); // Batch process documents
}

Health / Metrics Endpoint (v0.14+)

Because every store now exposes count(), you can build a cheap status endpoint or scheduled check without running a vector query:

Route::get('/ai/store/status', function (\Symfony\Component\AI\Store\StoreInterface $store) {
    return response()->json([
        'driver'   => config('ai_store.default'),
        'documents' => $store->count(),
    ]);
});

Pair it with a scheduled command that logs count() deltas to detect a stalled indexing job.

Event-Driven Extensions

// Listen to query events (e.g., log queries)
use Symfony\Component\AI\Store\Event\PreQueryEvent;

public function boot()
{
    event(new PreQueryEvent($query, $store));
}

Batch Processing Patterns

  1. Chunking Large Documents:
    use Symfony\Component\AI\Store\Transformer\TextSplitTransformer;
    
    $transformer = new TextSplitTransformer(
        chunkSize: 1000,
        overlap: 200
    );
    $chunks = $transformer->transform($document);
    
  2. Parallel Indexing (Laravel Queues):
    // Dispatch a job for each document batch
    IndexDocumentsJob::dispatch($documents)->onQueue('ai-indexing');
    
  3. Passing Options Through to the Loader (v0.14+): Indexer options are now forwarded to the loader, so you can keep source-specific config at the call site instead of subclassing:
    $indexer = new Indexer($store, $loader);
    $indexer->index([
        'batch_size' => 50,
        'skip_header' => true,   // interpreted by your loader
    ]);
    
    class CsvLoader implements LoaderInterface
    {
        public function load(array $options = []): iterable
        {
            $skip = $options['skip_header'] ?? false;
            // ...
        }
    }
    

Custom Documents with VectorDocumentInterface (v0.14+)

Type against VectorDocumentInterface rather than TextDocument so you can carry rich metadata or a pre-computed vector without fighting the concrete class:

use Symfony\Component\AI\Store\VectorDocumentInterface;

final class ArticleDocument implements VectorDocumentInterface
{
    public function __construct(
        private string $id,
        private string $text,
        private array $metadata = [],
        private ?array $vector = null,
    ) {}

    public function getId(): string { return $this->id; }
    public function getText(): string { return $this->text; }
    public function getMetadata(): array { return $this->metadata; }
    public function getVector(): ?array { return $this->vector; }
}
public function add(VectorDocumentInterface ...$documents): void;
// or, on the consuming side:
$store->add(new ArticleDocument('a1', $body, ['tenant' => $tenantId]));

Custom Vector Types in Queries (v0.14+)

VectorQuery now accepts any VectorInterface, so an embedding object returned by a platform adapter can be dropped in directly rather than being converted to a plain array:

use Symfony\Component\AI\Store\Query\VectorQuery;

$query = new VectorQuery($platformEmbedding, limit: 5); // VectorInterface
// Still fine: new VectorQuery([0.12, -0.44, ...], limit: 5);

Hybrid Search (Keyword + Vector)

use Symfony\Component\AI\Store\Query\HybridQuery;

$query = new HybridQuery(
    vector: $embedding,
    text: 'Laravel framework',
    limit: 3
);

$results = $store->query($query);

Gotchas and Tips

Common Pitfalls

  1. Vector Dimension Mismatch:

    • Ensure embedding_dimension in config matches your model’s output (e.g., 384 for sentence-transformers/all-MiniLM-L6-v2).
    • Fix: Update the store’s schema or re-vectorize documents.
  2. Memory Leaks with Lazy Iterators:

    • Avoid holding references to lazy iterators (e.g., $store->query() results) in long-lived objects.
    • Tip: Use iterator_to_array() if you need to materialize results immediately.
  3. Bridge-Specific Quirks:

    • PostgreSQL: Requires pgvector extension. Run:
      CREATE EXTENSION vector;
      
    • Redis: Uses symfony/ai-redis-store but may need Redis 7+ for vector search.
    • SQLite: Limited to ~100MB databases; not for production-scale data.
  4. count() Semantics Are Bridge-Dependent (v0.14+):

    • The interface guarantees an int, but what is counted differs: documents vs. chunks vs. rows. If you chunked a document into 12 pieces, most bridges report 12, not 1.
    • Confirm behaviour per bridge before wiring count() into user-facing dashboards or billing logic.
  5. Version Pinning:

    • The package is still pre-1.0 and iterating fast (VectorDocumentInterface and VectorInterface landed in 0.14, changing type hints in custom implementations). Pin the minor:
      "symfony/ai-store": "^0.14.0"
      
    • When upgrading, grep your codebase for TextDocument type hints — those are the places where VectorDocumentInterface is now the better contract.
  6. Loader Option Contract Is Informal:

    • v0.14 passes indexer options straight to the loader, but each loader decides which keys it understands. Unknown keys are typically ignored silently — validate your own options to avoid a typo'd batch_sise doing nothing.

Debugging Tips

  1. Enable Logging:
    $store = $storeFactory->getStore('postgres');
    $store->setLogger($this->app->make(Psr\Log\LoggerInterface::class));
    
  2. Inspect Queries:
    • Use PreQueryEvent to log raw queries before execution:
      event(new PreQueryEvent($query, $store));
      
  3. Validate Vectors:
    • Check for NaN or infinite values in embeddings:
      $vector = $query->getVector();
      if (array_key_exists(0, array_filter($vector, fn($v) => !is_finite($v)))) {
          throw new \RuntimeException('Invalid vector detected');
      }
      
    • With v0.14's VectorInterface support, normalise at the boundary: cast/normalise to array inside your own adapter so validation logic lives in one place rather than per-call-site.

Performance Optimization

  1. Batch Size Tuning:

    • Start with 100 documents per batch. Monitor memory usage and adjust.
    • Example:
      $indexer->setBatchSize(50); // For memory-constrained environments
      
  2. Indexing Strategies:

    • Cold Start: Use ResetInterface to clear the store before bulk indexing:
      if ($store instanceof \Symfony\Component\AI\Store\ResetInterface) {
          $store->reset();
      }
      
    • Incremental Updates: Track document IDs to avoid re-indexing unchanged content.
    • Verify with count(): After a bulk run, compare $store->count() against the number of documents loaded. A mismatch almost always means duplicate IDs collapsing or chunks silently dropped.
  3. Query Optimization:

    • Limit distance calculations for large datasets:
      $query->setDistance('cosine'); // Faster than 'euclidean' for high-dimensional vectors
      
    • Use filter in VectorQuery to narrow results:
      $query->setFilter(['source' => 'docs']);
      

Extension Points

  1. Custom Bridges:

    • Implement StoreInterface for unsupported databases (e.g., MongoDB Atlas):
      class MongoDbStore implements StoreInterface
      {
          public function add(iterable $documents): void
          {
              // Custom MongoDB logic
          }
      
          public function count(): int
          {
              // v0.14+: must be implemented
              return $this->collection->countDocuments();
          }
          // ...
      }
      
    • Register with StoreFactory:
      $factory->addStore('mongodb', new MongoDbStore($client));
      
  2. Custom Transformers:

    • Extend TransformerInterface for domain-specific preprocessing:
      class DomainSpecificTransformer implements TransformerInterface
      {
          public function transform(VectorDocumentInterface $document): iterable
          {
              // Custom logic (e.g., extract entities before chunking)
          }
      }
      
  3. Custom Loaders:

    • Implement LoaderInterface and read options passed from the indexer (v0.14+) instead of hard-coding paths or credentials.
  4. Event Subscribers:

    • Enhance queries dynamically:
      use Symfony\Component\AI\Store\Event\PreQueryEvent;
      
      $subscriber = new class implements EventSubscriberInterface {
          public function onPreQuery(PreQueryEvent $event)
          {
              $event->getQuery()->setLimit(min($event->getQuery()->getLimit(), 10));
          }
      };
      

Laravel-Specific Tips

  1. Queue the Ingest, Cache the Answers:
    • Put indexing on a dedicated queue (ai-indexing) so embedding rate limits don't starve user-facing jobs.
    • Cache RAG answers keyed by a hash of the normalised query vector + filter set; vector search is cheap relative to the LLM call, but caching both still pays off for repeated questions.
    $key = 'rag:' . md5(json_encode([$vector, $filters, $limit]));
    return Cache::remember($key, now()->addHour(), fn() => $store->query($query));
    
  2. Bind the Default Store as a Singleton:
    • Register StoreInterface (see above) but resolve the concrete store from StoreFactory lazily — bridges hold connection objects you don't want duplicated per request.
  3. Health Check via count():
    • A count() call is a cheap liveness probe; add it to your health route or a scheduled ai:store:check command now that v0.14 makes it part of the contract.
  4. Keep Embedding Config in One Place:
    • embedding_dimension lives in config/services.php while the model lives in config/ai.php. Reference a single env var for the model name and derive the dimension, or a model swap will silently break every query.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log