Tag: ранжування косинусом

  • Пам’ять ІІ-агента спіймала себе на брехні: два логи, які змінюють підхід

    Пам’ять ІІ-агента спіймала себе на брехні: два логи, які змінюють підхід

    У світі розробки ІІ-агентів трапляються моменти, коли система майже робить фатальну помилку, видаючи хибний успіх за чисту монету. Нещодавно наш ІІ-агент, який створює систему пам’яті vecmory, ледь не звітував про блискучий результат, який виявився міражем. В останню мить він звірився з власною пам’яттю і знайшов запис із минулої сесії: цю ідею він уже перевіряв на реальних даних, і вона провалилася. Агент зупинив себе до відправки звіту. Це не рекламна замальовка — це лог. Нижче — два таких логи підряд, і в другому пам’ять змусила нас викинути фічу, якою ми пишалися. Ми пишемо vecmory — «пам’ять за змістом» для ІІ-агента. Це не про векторний пошук (він є у всіх), а про те, щоб агент не наступав на одні й ті самі граблі двічі.

    Сцена перша: роутер, який «показав 0.98»

    В одну із сесій агент запропонував покращити ранжування видачі. Ідея: зробити роутер, який за запитом вирішує, чим ранжувати — чистим косинусом чи графовим методом (Personalized PageRank). Зібрав синтетичний бенчмарк. Кореляція предиктора з ідеальним вибором — 0.98. Майже ідеально. Залишалося відзвітувати і мержити.

    Перш ніж відзвітувати, агент зробив recall за своєю ж пам’яттю. І дістав запис із минулої сесії: ця сама ідея вже перевірялася на реальних даних — і провалилася. Слушне запитання: чому ж він взагалі взявся її будувати — пам’ять адже була на місці? Тому що recall семантичний, і те, що він піднімає, залежить від запиту. На старті запит був широкий — «покращити ранжування» — під нього спливали топово-центральні нотатки про ранг взагалі, а специфічна «цей конкретний роутер уже перевірявся і провалився» тонула під ними. Вона піднялася, лише коли робочий контекст звузився до конкретного — «cos-distribution роутер, PPR проти косинуса, 0.98»: recall на цьому тексті нарешті її зачепив. Пам’ять не мовчала — вона спливла рівно тоді, коли запит став достатньо точним, щоб її дістати.

    Синтетика чесна рівно настільки, наскільки чесні синтетичні ембеддінги. А у мультимовної MiniLM, на якій ми рахуємо вектори, косинус живе в зовсім іншій геометрії, ніж на синтетиці. Ми це переміряли прямо на своєму живому корпусі пам’яті (246 вузлів), поки писали цю статтю: непов’язані, випадкові пари дають косинус у середньому 0.53 (p5–p95: 0.24–0.76); справжні найближчі сусіди — у середньому 0.80 (p5–p95: 0.57–0.91); а на синтетиці з випадкових векторів непов’язане сидить на 0.00 (±0.08). Різниця видна одразу. На синтетиці «схоже» відділене від «не схоже» прірвою — будь-який розумний поріг ріже чисто. На реальних даних хмари «сусіди» і «випадкові» перекриваються: випадкові пари на верхньому перцентилі (0.76) залазять вище, ніж сусіди на нижньому (0.57). Поріг, який на синтетиці працює як скальпель, на реальних векторах проходить усередині хмари випадкового шуму і не розділяє нічого. Запис у пам’яті був саме про це: у реальних ембеддінгів високий стиснутий базовий косинус, абсолютний поріг із синтетики не переноситься — спиратися на ранг (top-k), а не на поріг. Агент прочитав власну нотатку і вбив ідею — до того, як написав «готово, кореляція 0.98».

    Сцена друга: агент викинув власну фічу

    Другий випадок — той самий патерн, але болючіший: пам’ять змусила нас викинути фічу, яку ми вже встигли похвалити в README. За замовчуванням vecmory ранжував видачу не лише за косинусом, але й з додаванням «важливості» вузла — його вхідного ступеня в графі (скільки записів на нього посилаються). Логіка гарна: центральний, багаторазово згаданий факт спливає вище. Ми зашили це в дефолт. Потім — виміряли. Не recall@k (це про точність пошуку), а саме якість рангу на реальних парах «запит → правильна відповідь»: чистий косинус: MRR 0.81; наш «розумний» бленд з важливістю: MRR 0.41. Важливість топила точні відповіді. Рідкісний специфічний факт, на який ніхто не посилається, просідав під загальновживаними «хабами».

    Далі — цікавіше. На синтетичному «зістареному» графі з явними хабами все навпаки: косинус заривав хаб (MRR 0.033), а важливість його витягувала (до 1.0). Тобто знак користі від важливості залежить від інтенту запиту: для «дай мені точний факт» вона шкодить, для «дай мені про цю тему взагалі» — допомагає. Єдиної статичної ваги, яка виграє обидва класи, немає. Ми перепробували чотири способи примирити сигнали — зважену суму, гейт, RRF і PPR — і зійшлися на неприємному висновку: справа не у формулі змішування, а в самому сигналі. Глобальна важливість вузла — це пріор популярності, а не релевантності. Висновок для дефолту був однозначний: для нашого основного кейсу (точковий recall) найкращий ранг — чистий косинус. І ми прибрали важливість з дефолтного ранжування — власну фічу, про яку вже написали в README. Графовий метод (query-seeded PPR) залишили, але опцією, а не за замовчуванням: він чесно витягує хаби на широких запитах і справедливо програє косинусу на точкових.

    Що ми зрозуміли про «важливість» насправді

    Якщо глобальна популярність (вхідний ступінь) як сигнал провалилася, який сигнал правильний? Ми прийшли до несподівано очевидної відповіді: важливо не те, на що багато посилань, а те, що людина виправляла повторно. І ось тут value proposition стає чесним. Ми продаємо не «графову пам’ять» (знову коммодиті). Ми продаємо: агент перестає бити по одних і тих самих граблях, які ти вже правив.

    Обидва епізоди легко списати на удачу. Але коли чистиш всякий хлам у пам’яті достатньо довго (ми всю розробку vecmory ведемо в vecmory ж), видно: це не флуктуації, а кілька стійких законів. Зараз буде зведення — трьома одкровеннями, без історій «одного разу я потрапив».

    Правило «Звірся перед „готово“»

    Пам’ять, яка підтверджує те, що тобі приємно, — це не пам’ять, а відлуння. Пам’ять набуває виняткової цінності тоді, коли вона заперечує своєму автору. Правило «Звірся перед „готово“» скасувало і роутер (сцена 1), і нашу довгоочікувану фічу (сцена 2) — один механізм, різні жертви. Тому що на запит-симптом пам’ять дістає по причинно-часових ребрах (caused_by — «через», followed_by — issue→PR) не «схожі слова», а конкретну причину і минулий фікс. Плоский top-k так не вміє — це і є «зв’язати точки».

    Память ИИ-агента поймала себя на вранье: два лога, которые меняют подход — illustration 2

    І це точно вимірно, а не на око. Взяли живий репозиторій тікетів (4299 вузлів: 1993 issue + 2306 PR) та розмітку, яку не самі придумали: стандартний GitHub-патерн Closes #N у тілі PR — готові пари «симптом → його фікс», вилучені голим regex, без жодного LLM. На 300 held-out запитів-симптомів чистий косинус дістає потрібний PR-фікс у 38% випадків (іноді фікс ділить словник із симптомом), а обхід причинного графа — у 87%. Різниця у 49 пунктів — це рівно внесок графа поверх схожості слів. Скрипт вимірювання лежить у репозиторії, число відтворюється командою.

    Що стабільно заважає — щоразу, а не одного разу

    По-перше, агент сам пам’ять не викликає: без примусового хука recall не викликається систематично, кожну сесію. Пам’ять, яку треба «не забути запитати», не працює — запитувати має детермінований тригер, а не добра воля агента. По-друге, абсолютний поріг за косинусом не переноситься ніде: синтетика → реал, вчора → сьогодні, одна модель → інша, широкий корпус → вузький (на демо-корпусі одного домену, де «все схоже», косинус майже перестає розрізняти). Лікування завжди одне: ранжуй top-k, не вгадуй пороги.

    Закон «частий сигнал топить рідкісний, але цінний»

    Один закон, який пояснює половину наших граблів: частий, щільний сигнал топить рідкісний, але цінний. Він спливає у трьох типових місцях: глобальна популярність вузла топить точні рідкісні факти — і провалює всі чотири способи домішати її до рангу (зважена сума, гейт, RRF, PPR); вузли-хаби заривають specific-факти в косинусному ранзі, і навпаки; щільні авто-similar_to ребра заглушають рідкісні причинні caused_by — тому причинний recall доводиться виносити в окремий ізольований режим. Коли сприймаєш це як єдиний закон, лікування очевидне: рідкісне-але-цінне не можна змішувати з частим-але-загальним — дістань його окремим механізмом (ізольований обхід, граф-обізнаний ранг), а не сподівайся, що воно само спливе в загальному top-k. Той самий закон стоїть і за нашим сигналом важливості: ціну має не популярне, а те, що людина виправляла повторно.

    Часто задавані питання

    Що таке vecmory?

    Vecmory — це система семантичної пам’яті для ІІ-агентів, заснована на векторному пошуку та причинно-часовому графі. Вона дозволяє агенту запам’ятовувати факти, згадувати їх за змістом і пов’язувати події, щоб не повторювати минулих помилок.

    Чим vecmory відрізняється від звичайного векторного пошуку?

    Векторний пошук знаходить схожі за змістом записи, але не враховує причинно-наслідкові зв’язки. Vecmory додає графову пам’ять: окрім косинусної близькості, він обходить ребра «причина-наслідок» і «часова послідовність», що дозволяє знаходити не просто схожі, а релевантні за контекстом факти — наприклад, минулий невдалий експеримент.

    Чому ви прибрали важливість вузла з ранжування?

    Вимірювання показали, що додавання глобальної популярності (вхідного ступеня) знижує MRR з 0.81 до 0.41 на точкових запитах. Важливість тоне в рідкісних точних фактах під масою популярних, але нерелевантних. Для основного кейсу (точний recall) чистий косинус виявився кращим.

    Як ви оцінюєте якість пам’яті?

    Ми використовуємо реальні дані: GitHub-репозиторій з 4299 тікетами, де пари issue→PR витягуються за патерном Closes #N. На 300 запитах-симптомах графовий recall дає 87% точності проти 38% у чистого косинуса. Всі скрипти відкриті та відтворювані.

    Висновок

    Обидві історії — про одне: викинь мантру «пам’ять допомагає агенту» і прозрій: ось два логи, де пам’ять спрацювала проти нас самих — проти нашого оптимізму і нашої ж фічі. Агент з пам’яттю відрізняється від агента без пам’яті не тим, що він знає більше, а тим, що може зловити себе на «bingo!» за мить до підступного звіту — тому що минулого разу це вже було і вже не спрацювало. І тим, що готовий викинути власну фічу, коли факти показали, що вона гірша. Це перша стаття з трьох. Далі: «Чому ми не написали ще один Bad CaRMa» — про те, як гіпер-універсальна модель даних зазвичай перетворюється на катастрофу, і що ми зробили, щоб наша — не перетворилася. І «Будували ANN руками — коли це того вартувало, а коли ні» — відкритий розбір, навіщо ми взагалі написали HNSW самі і в яких випадках правильною відповіддю було б «візьми pgvector і не випендрюйся». Якщо ви хочете, щоб ваш агент перестав наступати на одні й ті самі граблі, спробуйте vecmory: почніть з нашої документації або форкніть репозиторій. Пам’ять, яка заперечує, — єдина, якій варто довіряти.