symfony/ai-cloudflare-store
Integrates Cloudflare Vectorize as a vector store for Symfony AI Store. Supports indexing and querying embeddings plus upserts and deletions via the Vectorize APIs, making it easy to connect Symfony AI apps to Cloudflare’s managed vector database.
composer require symfony/ai symfony/ai-cloudflare-store
.env:
CLOUDFLARE_API_TOKEN=your_cloudflare_api_token
CLOUDFLARE_VECTORIZE_INDEX=your_vectorize_index_name
CloudflareVectorizeServiceProvider) and register the store:
use Symfony\Component\AI\Store\StoreInterface;
use Symfony\AI\CloudflareStore\CloudflareVectorizeStore;
public function register()
{
$this->app->singleton(StoreInterface::class, function ($app) {
return new CloudflareVectorizeStore(
$app->make(\Symfony\Component\AI\Client::class),
config('services.cloudflare.vectorize_index')
);
});
}
use Symfony\Component\AI\Store\StoreInterface;
public function __construct(private StoreInterface $store) {}
public function indexDocument(array $embedding, string $id)
{
$this->store->upsert([$embedding], [$id]);
}
public function searchSimilarDocuments(array $queryEmbedding, int $limit = 5)
{
$results = $this->store->query($queryEmbedding, $limit);
return $results->getVectors();
}
symfony/ai with HuggingFace) to generate embeddings for documents:
$embedding = $aiClient->getEmbedding('Your document text');
$this->store->upsert([$embedding], ['doc_id_123']);
$queryEmbedding = $aiClient->getEmbedding('User search query');
$similarDocs = $this->store->query($queryEmbedding, 3);
$this->store->upsert($embeddings, $chunkIds);
$contextEmbedding = $aiClient->getEmbedding('User question');
$contextChunks = $this->store->query($contextEmbedding, 5);
Offload vector operations to avoid blocking requests:
use Illuminate\Support\Facades\Queue;
Queue::push(function () {
$this->store->upsert($embeddings, $ids);
});
Use Cloudflare Vectorize for semantic search and Scout for keyword search:
// Semantic search via Cloudflare
$semanticResults = $this->store->query($queryEmbedding, 10);
// Keyword search via Scout
$keywordResults = YourModel::search($query)->get();
Symfony AI Dependency:
symfony/ai, which may not be natively compatible with Laravel. Ensure you’re using a recent Laravel version (10+) and handle potential namespace conflicts.symfony/ai as a standalone dependency and explicitly bind services to avoid conflicts.Cloudflare API Rate Limits:
Illuminate\Support\Facades\Retry for resilient API calls:
Retry::retry(3, function () {
$this->store->query($embedding);
}, function ($e) {
return $e instanceof \Symfony\Component\AI\Exception\RateLimitExceededException;
});
Vector ID Collisions:
upsert() are unique. Cloudflare Vectorize may silently overwrite duplicates.Metadata Filtering:
query() method, but complex filters may require custom NDJSON payloads.Cost Monitoring:
Enable Debug Logging: Configure the Symfony AI client to log API requests:
$aiClient = new \Symfony\Component\AI\Client(
config('services.openai.key'),
new \Symfony\Component\AI\HttpClient\Psr18Client(
new \GuzzleHttp\Client(['debug' => true])
)
);
Use Laravel’s logging to inspect Cloudflare API responses:
Log::debug('Cloudflare API Response', ['response' => $response]);
Validate NDJSON Payloads:
Cloudflare Vectorize expects NDJSON for upsert and query. Validate payloads before sending:
$payload = json_encode([$embedding], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
Log::debug('NDJSON Payload', ['payload' => $payload]);
Handle API Errors: Catch specific exceptions from the Symfony AI client:
try {
$this->store->query($embedding);
} catch (\Symfony\Component\AI\Exception\CloudflareException $e) {
Log::error('Cloudflare Error: ' . $e->getMessage());
// Implement fallback logic (e.g., cache, retry)
}
Custom Filtering:
Extend the query() method to support custom filters by modifying the NDJSON payload:
$this->store->query($embedding, 5, [
'filter' => json_encode(['metadata' => ['category' => 'tech']])
]);
Batch Operations: For large datasets, implement batch upserts using Cloudflare’s bulk API:
$batchSize = 100;
foreach (array_chunk($embeddings, $batchSize) as $batch) {
$this->store->upsert($batch, array_chunk($ids, $batchSize));
}
Local Fallback: Implement a fallback to a local store (e.g., Redis) if Cloudflare is unavailable:
public function query(array $embedding, int $limit = 5)
{
try {
return $this->store->query($embedding, $limit);
} catch (\Exception $e) {
Log::error('Cloudflare fallback triggered', ['error' => $e]);
return $this->localStore->query($embedding, $limit);
}
}
Monitoring Metrics: Track vector store performance and costs using Laravel’s monitoring tools:
$this->store->query($embedding, 5, [], function ($response) {
$this->trackMetric('vectorize.query.time', $response->getElapsedTime());
});
Environment Variables:
Ensure CLOUDFLARE_API_TOKEN and CLOUDFLARE_VECTORIZE_INDEX are set in .env. The package may not throw clear errors if these are missing.
if (!config('services.cloudflare.api_token')) {
throw new \RuntimeException('Cloudflare API token not configured.');
}
Index Creation: The package assumes the Vectorize index already exists. Create it manually via the Cloudflare Dashboard or API if needed.
public function createVectorizeIndex()
{
$client = new \Cloudflare\Vectorize\Client(config('services.cloudflare.api_token'));
$client->createIndex(config('services.cloudflare.vectorize_index'), [
'dimension' => 768, // Adjust based on your embeddings
'metric' => 'cosine'
]);
}
Dimension Mismatch: Ensure your embeddings match the index’s dimension. Mismatches will cause silent failures.
$expectedDimension = 768; // Configured in your index
How can I help you explore Laravel packages today?