Studio: make grid checkbox rectangular and commit table changes
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Recommendations\VectorSimilarity;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* pgvector adapter — uses the artwork_embeddings table with cosine similarity.
|
||||
*
|
||||
* Requires PostgreSQL with the pgvector extension installed.
|
||||
* Schema: artwork_embeddings (artwork_id PK, model, dims, embedding vector(N), ...)
|
||||
*
|
||||
* Spec §9 Option A.
|
||||
*/
|
||||
final class PgvectorAdapter implements VectorAdapterInterface
|
||||
{
|
||||
public function querySimilar(int $artworkId, int $topK = 100): array
|
||||
{
|
||||
// Fetch reference embedding
|
||||
$ref = DB::table('artwork_embeddings')
|
||||
->where('artwork_id', $artworkId)
|
||||
->select('embedding_json')
|
||||
->first();
|
||||
|
||||
if (! $ref || ! $ref->embedding_json) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$embedding = json_decode($ref->embedding_json, true);
|
||||
if (! is_array($embedding) || $embedding === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// pgvector cosine distance operator: <=>
|
||||
// Score = 1 - distance (higher = more similar)
|
||||
$vecLiteral = '[' . implode(',', array_map('floatval', $embedding)) . ']';
|
||||
|
||||
try {
|
||||
$rows = DB::select(
|
||||
"SELECT artwork_id, 1 - (embedding_json::vector <=> ?::vector) AS score
|
||||
FROM artwork_embeddings
|
||||
WHERE artwork_id != ?
|
||||
ORDER BY embedding_json::vector <=> ?::vector
|
||||
LIMIT ?",
|
||||
[$vecLiteral, $artworkId, $vecLiteral, $topK]
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning("[PgvectorAdapter] Query failed: {$e->getMessage()}");
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_map(fn ($row) => [
|
||||
'artwork_id' => (int) $row->artwork_id,
|
||||
'score' => (float) $row->score,
|
||||
], $rows);
|
||||
}
|
||||
|
||||
public function upsertEmbedding(int $artworkId, array $embedding, array $metadata = []): void
|
||||
{
|
||||
$json = json_encode($embedding);
|
||||
|
||||
DB::table('artwork_embeddings')->updateOrInsert(
|
||||
['artwork_id' => $artworkId],
|
||||
[
|
||||
'embedding_json' => $json,
|
||||
'model' => $metadata['model'] ?? 'clip',
|
||||
'model_version' => $metadata['model_version'] ?? 'v1',
|
||||
'dim' => count($embedding),
|
||||
'is_normalized' => $metadata['is_normalized'] ?? true,
|
||||
'generated_at' => now(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
public function deleteEmbedding(int $artworkId): void
|
||||
{
|
||||
DB::table('artwork_embeddings')
|
||||
->where('artwork_id', $artworkId)
|
||||
->delete();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user