136 lines
7.1 KiB
PHP
136 lines
7.1 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
return [
|
||
// Uses same queue family as vision jobs by default; keeps embedding work async and non-blocking.
|
||
'queue' => env('RECOMMENDATIONS_QUEUE', env('VISION_QUEUE', 'default')),
|
||
|
||
// ─── Phase 1 "For You" feed scoring weights ───────────────────────────────
|
||
// Influences the PHP reranking pass after Meilisearch candidate retrieval.
|
||
// Tweak here without code changes.
|
||
'weights' => [
|
||
// Tag overlap score weight (0–1 normalized overlap fraction)
|
||
'tag_overlap' => (float) env('RECO_W_TAG_OVERLAP', 0.40),
|
||
// Creator affinity score weight (1.0 if followed, 0 otherwise)
|
||
'creator_affinity' => (float) env('RECO_W_CREATOR_AFFINITY', 0.25),
|
||
// Popularity boost (log-normalised views/downloads)
|
||
'popularity' => (float) env('RECO_W_POPULARITY', 0.20),
|
||
// Freshness boost (exponential decay over 30 days)
|
||
'freshness' => (float) env('RECO_W_FRESHNESS', 0.15),
|
||
],
|
||
|
||
// ─── User preference signal weights ──────────────────────────────────────
|
||
// How much each user action contributes to building the reco profile.
|
||
'signal_weights' => [
|
||
'award' => (float) env('RECO_SIG_AWARD', 5.0),
|
||
'favorite' => (float) env('RECO_SIG_FAVORITE', 3.0),
|
||
'reaction' => (float) env('RECO_SIG_REACTION', 2.0),
|
||
'view' => (float) env('RECO_SIG_VIEW', 1.0),
|
||
'follow' => (float) env('RECO_SIG_FOLLOW', 2.0),
|
||
],
|
||
|
||
// ─── Candidate generation ──────────────────────────────────────────────────
|
||
// How many Meilisearch candidates to fetch before PHP reranking.
|
||
'candidate_pool_size' => (int) env('RECO_CANDIDATE_POOL', 200),
|
||
|
||
// ─── Diversity controls ────────────────────────────────────────────────────
|
||
// Maximum artworks per creator allowed in a single page of results.
|
||
'max_per_creator' => (int) env('RECO_MAX_PER_CREATOR', 3),
|
||
// Minimum distinct tag count in first 20 feed results.
|
||
'min_unique_tags' => (int) env('RECO_MIN_UNIQUE_TAGS', 5),
|
||
|
||
// ─── TTLs (seconds) ────────────────────────────────────────────────────────
|
||
'ttl' => [
|
||
// User reco profile cache (tag/creator affinity data)
|
||
'user_reco_profile' => (int) env('RECO_TTL_PROFILE', 6 * 3600),
|
||
// For You paginated results cache
|
||
'for_you_feed' => (int) env('RECO_TTL_FOR_YOU', 5 * 60),
|
||
// Similar artworks per artwork
|
||
'similar_artworks' => (int) env('RECO_TTL_SIMILAR', 30 * 60),
|
||
// Suggested creators per user
|
||
'creator_suggestions' => (int) env('RECO_TTL_CREATORS', 30 * 60),
|
||
// Suggested tags per user
|
||
'tag_suggestions' => (int) env('RECO_TTL_TAGS', 60 * 60),
|
||
],
|
||
|
||
// ─── Profile limits ────────────────────────────────────────────────────────
|
||
'profile' => [
|
||
'top_tags_limit' => (int) env('RECO_PROFILE_TAGS', 20),
|
||
'top_categories_limit' => (int) env('RECO_PROFILE_CATS', 5),
|
||
'strong_creators_limit' => (int) env('RECO_PROFILE_CREATORS', 50),
|
||
],
|
||
|
||
'embedding' => [
|
||
'enabled' => env('RECOMMENDATIONS_EMBEDDING_ENABLED', true),
|
||
'model' => env('RECOMMENDATIONS_EMBEDDING_MODEL', 'clip'),
|
||
'model_version' => env('RECOMMENDATIONS_EMBEDDING_MODEL_VERSION', 'v1'),
|
||
'algo_version' => env('RECOMMENDATIONS_ALGO_VERSION', 'clip-cosine-v1'),
|
||
|
||
// Preferred CLIP endpoint for embeddings. The service also accepts an embedding payload from the analyze endpoint response.
|
||
'endpoint' => env('CLIP_EMBED_ENDPOINT', '/embed'),
|
||
'timeout_seconds' => (int) env('CLIP_EMBED_TIMEOUT_SECONDS', 8),
|
||
'connect_timeout_seconds' => (int) env('CLIP_EMBED_CONNECT_TIMEOUT_SECONDS', 2),
|
||
'retries' => (int) env('CLIP_EMBED_HTTP_RETRIES', 1),
|
||
'retry_delay_ms' => (int) env('CLIP_EMBED_HTTP_RETRY_DELAY_MS', 200),
|
||
|
||
// Guardrails for malformed service responses.
|
||
'min_dim' => (int) env('RECOMMENDATIONS_MIN_DIM', 64),
|
||
'max_dim' => (int) env('RECOMMENDATIONS_MAX_DIM', 4096),
|
||
],
|
||
|
||
// Backfill chunk size for resumable queue fan-out.
|
||
'backfill_batch_size' => (int) env('RECOMMENDATIONS_BACKFILL_BATCH', 200),
|
||
|
||
// A/B support for recommendation ranking variants.
|
||
'ab' => [
|
||
'algo_versions' => array_values(array_filter(array_map(
|
||
static fn (string $value): string => trim($value),
|
||
explode(',', (string) env('RECOMMENDATIONS_AB_ALGO_VERSIONS', env('RECOMMENDATIONS_ALGO_VERSION', 'clip-cosine-v1')))
|
||
))),
|
||
],
|
||
|
||
// ─── Similar Artworks (hybrid recommender) ─────────────────────────────────
|
||
'similarity' => [
|
||
'model_version' => env('SIMILARITY_MODEL_VERSION', 'sim_v1'),
|
||
|
||
// Vector DB integration (behind feature flag)
|
||
'vector_enabled' => (bool) env('SIMILARITY_VECTOR_ENABLED', false),
|
||
'vector_adapter' => env('SIMILARITY_VECTOR_ADAPTER', 'pgvector'), // pgvector | pinecone
|
||
|
||
// Hybrid blend weights (spec §5.4)
|
||
'weights_with_vector' => [
|
||
'visual' => (float) env('SIM_W_VISUAL', 0.45),
|
||
'tag' => (float) env('SIM_W_TAG_VEC', 0.25),
|
||
'behavior' => (float) env('SIM_W_BEH_VEC', 0.20),
|
||
'category' => (float) env('SIM_W_CAT_VEC', 0.10),
|
||
],
|
||
'weights_without_vector' => [
|
||
'tag' => (float) env('SIM_W_TAG', 0.55),
|
||
'behavior' => (float) env('SIM_W_BEH', 0.35),
|
||
'category' => (float) env('SIM_W_CAT', 0.10),
|
||
],
|
||
|
||
// Diversity caps (spec §6)
|
||
'max_per_author' => (int) env('SIM_MAX_PER_AUTHOR', 2),
|
||
'result_limit' => (int) env('SIM_RESULT_LIMIT', 30),
|
||
'candidate_pool' => (int) env('SIM_CANDIDATE_POOL', 100),
|
||
'min_categories_top12' => (int) env('SIM_MIN_CATS_TOP12', 2),
|
||
|
||
// Behavior pair building
|
||
'user_favourites_cap' => (int) env('SIM_USER_FAV_CAP', 50),
|
||
|
||
// Cache TTL for precomputed lists (sec)
|
||
'cache_ttl' => (int) env('SIM_CACHE_TTL', 6 * 3600),
|
||
|
||
// Pinecone adapter settings
|
||
'pinecone' => [
|
||
'api_key' => env('PINECONE_API_KEY'),
|
||
'index_host' => env('PINECONE_INDEX_HOST'),
|
||
'index_name' => env('PINECONE_INDEX_NAME', 'skinbase-artworks'),
|
||
'namespace' => env('PINECONE_NAMESPACE', ''),
|
||
'top_k' => (int) env('PINECONE_TOP_K', 100),
|
||
],
|
||
],
|
||
];
|