Photo Analysis Ingest
TODO: майбутній процес, який аналізує фото профілю, перетворює його на текст і записує в Supabase як частину довгої пам’яті.
Проблема
Зараз у tips-flow профіль може містити photoUrl, і AssistantService має вміти аналізувати фото через vision. Але база даних не отримує стабільний текстовий опис фото. Через це:
- фото не є повноцінною частиною vector memory;
- retrieval не може підняти фото-контекст за змістом;
- LLM може бачити URL або image input тільки в конкретному workflow;
- повторні генерації можуть робити зайві vision calls;
- важко перевірити, що саме система знала про фото.
Потрібен окремий ingest-flow:
photoUrl -> download -> vision analysis -> normalized text -> embedding -> client_memory_chunksЦіль
Зберігати не screenshot і не сире зображення, а текстовий результат аналізу фото. Саме цей текст має потрапляти в long-term memory і retrieval.
Де запускати
Є два можливих місця.
| Варіант | Як працює | Плюси | Мінуси |
|---|---|---|---|
| Daily sync | Якщо photo hash змінився, одразу аналізувати фото і писати chunk | Дані готові до generate, менше latency під час відповіді | Daily sync стане важчим |
| Lazy before retrieval | Якщо photo chunk відсутній або застарів, аналізувати перед MemoryService.search() | Не робить зайвої роботи, якщо фото не потрібно | Перший generate буде довшим |
Рекомендований старт: daily sync після photo hash diff. Так простіше контролювати idempotency і не змішувати vision latency з основною генерацією відповіді.
Майбутні кроки
DailySyncServiceвизначає, що photo URL змінився.PhotoAnalysisServiceотримує{ dialogKey, ruId, tuId, role, photoUrl }.- Сервіс завантажує фото через безпечний image fetch.
- Vision model повертає structured JSON.
- Сервіс нормалізує JSON у короткий текст для memory.
MemoryServiceрахує embedding для цього тексту.- Row пишеться в
client_memory_chunksзsource='photo'. tips_daily_entity_snapshotsотримує raw/audit payload.- Під час generate
MemoryService.search()може повернути photo chunk уselectedChunks. AssistantServiceотримує текстовий photo context у prompt.
Очікуваний output vision model
{
"summary": "Warm studio portrait with a smiling expression and elegant dark dress.",
"visible_style": ["elegant", "feminine", "polished"],
"setting": "indoor studio",
"safe_conversation_hooks": [
"compliment her elegant style",
"ask about the occasion",
"mention her warm smile"
],
"do_not_infer": [
"identity",
"health",
"race",
"religion",
"political views"
]
}У memory має йти не весь JSON, а короткий, читабельний і безпечний текст.
Очікуваний memory chunk
| Поле | Значення |
|---|---|
source | photo |
text | нормалізований опис фото |
source_id | photo:<role>:<photoHash> |
dialog_key | sha256 пари RU←>TU |
client_id | RU id |
woman_id | TU id |
metadata.photoUrl | URL |
metadata.role | ru або tu |
metadata.analysisModel | vision model |
metadata.safeHooks | hooks |
Безпека
Photo analysis має бути обмежений видимими і безпечними деталями. Заборонено:
- ідентифікувати людину;
- робити висновки про чутливі ознаки;
- додавати сексуалізований опис;
- вигадувати біографію;
- оцінювати привабливість числом;
- писати токсичні або принизливі формулювання.
Як це змінить generate
Після реалізації LLM input буде виглядати так:
Recent dialog + notes + profile + selected memory chunks:
1. [note; score=0.91] ...
2. [photo; score=0.77] Photo analysis for TU profile: warm studio portrait...
3. [conversation; score=0.73] ...Тобто AssistantService отримає не картинку, а вже підготовлений memory text. Vision call буде потрібен тільки тоді, коли фото нове або analysis відсутній.
TODO
- Створити
PhotoAnalysisService. - Вирішити, чи аналізувати RU photo, TU photo або обидва.
- Додати content hash фото або почати з hash URL.
- Додати idempotent upsert для
source='photo'. - Зберігати structured audit у
tips_daily_entity_snapshots. - Оновити source weights після тестів: поточний
photo=0.05може бути слабким або достатнім. - Додати metrics: скільки photo analysis calls, latency, errors, cache hit rate.