Граф Связей Пользователя (User Fact Knowledge Graph)

Помимо классической истории диалогов, ForjBot Engine в процессе взаимодействия строит Динамический Граф Знаний о Пользователе (UserFact + FactRelation).

graph TD subgraph UserNode ["Пользователь (ID: 48291)"] U["User: Алексей\n(metric_score: 85)"] end subgraph FactNodes ["Узлы Графа Фактов (UserFact)"] F1["Имя: Алексей\n(category: personal | imp: 10)"] F2["Работа: Senior Developer\n(category: career | imp: 9)"] F3["Питомец: Кот Барсик\n(category: pets | imp: 8)"] F4["Хоби: Велоспорт & Триатлон\n(category: interests | imp: 7)"] F5["Город: Санкт-Петербург\n(category: location | imp: 8)"] end subgraph RelationsEdge ["Ребра и Связи (FactRelation)"] U -->|has_fact| F1 U -->|has_fact| F2 U -->|has_fact| F3 U -->|has_fact| F4 U -->|has_fact| F5 F1 -.->|owns / weight: 0.95| F3 F1 -.->|works_as / weight: 0.90| F2 F1 -.->|lives_in / weight: 0.85| F5 F2 -.->|relates_to / weight: 0.60| F4 end subgraph MemoryInject ["Инъекция в Системный Промпт"] GraphSearch["get_fact_graph(user_id)"] --> Format["Форматирование узлов и связей"] --> PromptBlock["{user_facts} + {retrieved_facts}"] end

Структура Данных Графа

1. Узлы Фактов (UserFact)

Каждый узел представляет извлеченный факт со следующими атрибутами:

  • category: Категория факта (personal, career, interests, pets, family, preferences, location).
  • key: Уникальный ключ факта для пользователя (job_title, pet_name, favorite_color, city).
  • value: Значение факта (“Разработчик Python”, “Кот Барсик”, “Санкт-Петербург”).
  • importance: Важность факта по шкале от 1 до 10 (влияет на приоритет подстановки в промпт).
  • confidence: Коэффициент уверенности модели (0.0 – 1.0).
  • source_message_id: Ссылка на оригинальное сообщение пользователя.

2. Связи и Ребра Графа (FactRelation)

Граф объединяет отдельные факты в смысловую сеть:

  • source_fact_id: ID исходного факта.
  • target_fact_id: ID целевого факта.
  • relation: Тип связи (owns, likes, works_at, family_with, located_in).
  • strength: Вес/прочность связи (0.0 – 1.0).

Извлечение и Запрос Графа (get_fact_graph)

При каждом вызове модель обращается к хранилищу через функцию get_fact_graph(session, user_id):

async def get_fact_graph(session: AsyncSession, user_id: int) -> tuple[list[UserFact], list[FactRelation]]:
    facts = await get_user_facts(session, user_id)
    fact_ids = {f.id for f in facts}
    if not fact_ids:
        return [], []
    result = await session.execute(
        select(FactRelation).where(
            FactRelation.source_fact_id.in_(fact_ids) & FactRelation.target_fact_id.in_(fact_ids)
        )
    )
    relations = list(result.scalars().all())
    return facts, relations

Авто-Обновление при Conflict (upsert_user_fact)

При изменении факта (например, пользователь сменил работу) система выполняет атомарный UPSERT:

  • Если факт с ключом (user_id, key) уже существует, обновляется value, importance и время updated_at.
  • Если узел новый — создается новая запись в графе.