photo-analysis-ingest

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 з основною генерацією відповіді.

Майбутні кроки

  1. DailySyncService визначає, що photo URL змінився.
  2. PhotoAnalysisService отримує { dialogKey, ruId, tuId, role, photoUrl }.
  3. Сервіс завантажує фото через безпечний image fetch.
  4. Vision model повертає structured JSON.
  5. Сервіс нормалізує JSON у короткий текст для memory.
  6. MemoryService рахує embedding для цього тексту.
  7. Row пишеться в client_memory_chunks з source='photo'.
  8. tips_daily_entity_snapshots отримує raw/audit payload.
  9. Під час generate MemoryService.search() може повернути photo chunk у selectedChunks.
  10. 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

ПолеЗначення
sourcephoto
textнормалізований опис фото
source_idphoto:<role>:<photoHash>
dialog_keysha256 пари RU>TU
client_idRU id
woman_idTU id
metadata.photoUrlURL
metadata.roleru або tu
metadata.analysisModelvision model
metadata.safeHookshooks

Безпека

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.

Зв’язки