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_api_token_here
CLOUDFLARE_VECTORIZE_INDEX=your_index_name
use Symfony\AI\CloudflareStore\CloudflareVectorizeStore;
use Symfony\Component\AI\Store\StoreInterface;
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 storeEmbedding(StoreInterface $store, array $embedding, string $id)
{
$store->upsert([$embedding], [$id]);
}
Batch Upsert for Efficiency:
$embeddings = [/* array of vectors */];
$ids = [/* corresponding IDs */];
$store->upsert($embeddings, $ids);
Semantic Search Queries:
$queryEmbedding = $embeddingService->embed("user query");
$results = $store->query($queryEmbedding, 5); // Top 5 matches
Metadata Filtering:
$results = $store->query($queryEmbedding, 5, [
'filter' => ['metadata_field' => 'value']
]);
Bulk Deletion:
$store->remove(['id1', 'id2', 'id3']);
Queue Jobs for Async Upserts:
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
class UpsertEmbeddingsJob implements ShouldQueue
{
use Queueable;
public function handle(StoreInterface $store)
{
$store->upsert($this->embeddings, $this->ids);
}
}
Caching Layer:
$cachedResults = Cache::remember("query_{$queryHash}", now()->addHours(1), function () use ($store, $queryEmbedding) {
return $store->query($queryEmbedding, 5);
});
Event-Driven Updates:
event(new EmbeddingStored($embedding, $id));
// Listen for updates and trigger re-indexing
Mocking Cloudflare API:
$mockStore = $this->createMock(StoreInterface::class);
$mockStore->method('upsert')->willReturn(true);
$mockStore->method('query')->willReturn([/* mock results */]);
$this->app->instance(StoreInterface::class, $mockStore);
Unit Testing Queries:
public function testQueryReturnsTopMatches()
{
$store = $this->app->make(StoreInterface::class);
$results = $store->query($testEmbedding, 3);
$this->assertCount(3, $results);
$this->assertEquals('expected_id', $results[0]['id']);
}
API Rate Limits:
use Symfony\Component\AI\Exception\RateLimitExceededException;
try {
$store->query($embedding);
} catch (RateLimitExceededException $e) {
sleep(2 ** $attempt); // Exponential backoff
retry();
}
ID Collisions:
$id = Str::uuid()->toString();
Metadata Filtering Quirks:
$store->query($embedding, 5, [
'filter' => ['category' => ['in' => ['books', 'movies']]]
]);
Vector Dimension Mismatch:
$expectedDim = 768; // Example: text-embedding-ada-002
if (count($embedding) !== $expectedDim) {
throw new \InvalidArgumentException("Vector dimension mismatch");
}
Enable API Logging:
// In CloudflareVectorizeStore constructor
$client = new \Symfony\Component\AI\Client(
$apiToken,
['debug' => true] // Enable debug logging
);
Validate NDJSON Payloads:
symfony/var-dumper to inspect payloads:
dump($store->getLastPayload()); // If exposed via a method
Check Index Configuration:
curl "https://api.cloudflare.com/client/v4/accounts/{account_id}/vectorize/indexes/{index_name}" \
-H "Authorization: Bearer $API_TOKEN"
Custom Distance Metrics:
cosine metric by extending CloudflareVectorizeStore:
class CustomCloudflareStore extends CloudflareVectorizeStore
{
protected function getDistanceMetric(): string
{
return 'euclidean';
}
}
Hybrid Search:
$hybridResults = collect($scoutResults)
->map(fn ($item) => [
'id' => $item->id,
'score' => $item->score + $vectorScore
])
->sortByDesc('score');
Local Fallback:
class FallbackCloudflareStore implements StoreInterface
{
public function __construct(
private StoreInterface $cloudflareStore,
private StoreInterface $localStore
) {}
public function query($embedding, int $limit, array $options = []): array
{
try {
return $this->cloudflareStore->query($embedding, $limit, $options);
} catch (\Exception $e) {
return $this->localStore->query($embedding, $limit, $options);
}
}
}
Environment Variables:
.env variables are prefixed consistently:
# Correct
CLOUDFLARE_VECTORIZE_INDEX=my_index
CLOUDFLARE_API_TOKEN=my_token
# Avoid (unless documented otherwise)
VECTORIZE_INDEX=my_index
Index Creation:
curl -X POST "https://api.cloudflare.com/client/v4/accounts/{account_id}/vectorize/indexes" \
-H "Authorization: Bearer $API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"my_index","dimension":768,"metric":"cosine"}'
PHP Version Warnings:
symfony/ai compatibility is maintained (may require polyfills).Batch Size Tuning:
Query Optimization:
limit and filter to reduce payload size:
$store->query($embedding, 10, ['filter' => ['category' => 'books']]);
Connection Pooling:
Client instance across requests to avoid connection overhead.How can I help you explore Laravel packages today?