Зворотний цикл зворотного зв'язку для SVG графіків у JavaScript
Агенти Claude з індексами Llama
Вступ
Це частина 3 з трьох частин серії (див. посилання). Частина 2 була присвячена викликам презентації великих обсягів даних для моделей, знову ж таки, моделями є Anthropic Claude-3-opus та OpenAI-gpt-4o. Основа цих викликів — це контекстне вікно моделей. Два нових агенти були додані до LangGraph StateGraph для вирішення цих проблем: DataProcessor та DataAnalyzer. Ці агенти обробляли сирі дані про глобальний температурний градієнт за 121 рік, які містили 1452 точки даних, щоб побудувати графік у вигляді бульбашкової діаграми. Агент Coder зміг виконати це завдання без жодних вхідних даних — лише за допомогою аналізу даних, наданих агентом DataAnalyzer. Частина 2 завершилась викликом: Агенту Coder потрібно створити гістограму з 1000 цін без доступу до реальних даних, але сирі дані у вигляді CSV файлу є вхідними даними для процесу.
Як цього досягти?
Першим кроком є агент Retriever, який використовує інструмент ChromaDB RAG для векторизації 1000 цін, що дає 15 «частин» збережених даних. Наступний крок здійснюється за допомогою csvDataTool агента Retriever для фільтрації сирих даних. Ці кроки однакові з Частиною 2, але тепер обробка даних стала набагато складнішою. Агенту Coder потрібно надати інформацію про частоту та діапазон цін, розміри бінів та кількість бінів, які будуть відображені на гістограмі. Як і в Частині 2, кожну частину даних отримують зі сховища, вбудовують у векторний магазин LlamaIndex та виконують запит за допомогою двигуна запитів LlamaIndex. Запит у цьому випадку виглядає так:
Запит, поданий до векторного сховища LlamaIndex, яке містить 1000 неупорядкованих цін.
Як можна побачити на скріншоті нижче, на прикладі одного з 15 частин, запитувальний двигун правильно виконує інструкції. Кожну частину даних обробляють через процес агрегації в TypeScript, де основний масив записів оновлюється значеннями з вхідної частини. Остаточний результат перетворюється в рядок і додається до другого векторного сховища ChromaDB для використання агентом DataProcessor. 15 вхідних частин тепер зводяться до 5 частин агрегованих даних, збережених у ChromaDB.
Від 1000 неупорядкованих цін до агрегації цін та зберігання у векторному сховищі
Після того, як упорядковані та агреговані дані збережено в ChromaDB, ми покрили фільтрацію даних, яка була темою Частини 2, хоча і з менш складними вимогами до даних. Тепер завдання полягає в тому, щоб агенти DataProcessor та DataAnalyzer надали діапазони цін та їх частоти, а також інші атрибути, такі як розміри бінів та інтервали бінів для гістограми.
Аналіз даних та вимоги
Запити для агентів DataProcessor та DataAnalyzer залишаються такими ж, як у Частині 2. Запити мають залишатись однаковими для будь-якого типу графіків. Особливості в вимогах, наданих як вхідні дані в StateGraph, є важкими для формулювання. Перший варіант містив згадки про «розміри бінів» та «інтервали бінів», а також про «діапазон» та «частоту» цін. Ідея полягала в тому, щоб мати одне вимога для агента з обробки даних та для агента кодування. Однак це спричинило плутанину для моделей (Claude та OpenAI). Тому тепер є вимога для даних та вимога для кодування як змінні стану.
Ось вимога до даних:
Вимога до даних, надана агенту DataProcessor як змінна стану.
Інша проблема з вимогами до даних полягає у різному процесуванні кожної з 5 частин, що призвело до різних інтервалів для даних (наприклад, для однієї частини «0–55» був інтервал, а для іншої «0–100»), ця варіація викликала проблеми для DataAnalyzer. Тому, як можна побачити з вимоги до даних на наведеному вище скріншоті, діапазон встановлено на 23–1000, з інтервалами по 100, що відповідає тому, як були надані всі аналізи частин, як видно на скріншоті нижче:
Кожну з 5 частин точно аналізує агент DataProcessor, який запитує більше даних, поки не досягне загальної кількості частин і не повідомить: “moredata: no” (більше запитів даних виділено червоним на наведеному вище скріншоті). Агент DataAnalyzer бере 5 частин з «пам’яті» та надає точний розподіл даних, придатний для того, щоб Coder створив гістограму. Аналіз агента DataAnalyzer зберігається у змінній стану _dataFindings, з якої Coder створює гістограму:
Друге відображення вимог від Claude з використанням результатів аналізу даних агентом DataAnalyzer
Завдання було виконано за допомогою запиту Coder: state.dataFindings та state.codeRequirement. Ось вимога від Coder:
Вимога від Coder
Тепер розглянемо запит Coder нижче. На даний момент зверніть увагу, як Claude взяв фразу “The current phase is Review Phase” зі свого запиту і помістив її на вершину графіка. Coder має «початкову фазу» та «фазу огляду», де інформація про його відображення передається йому назад. У випадку Claude початкова фаза була некоректною (зліва на малюнку), в той час як OpenAI мав лише один варіант коду (праворуч на малюнку — хоча й з іншими проблемами, які будуть обговорені в висновках).
Ідея про те, щоб один агент надавав зворотний зв'язок іншому агенту, була важливою здатністю, яку слід оцінити. Від першого неправильного варіанту Claude до другого правильного після огляду, зворотний зв'язок, здається, спрацював. Той факт, що система усвідомлювала, що вона знаходиться в «фазі огляду», що має state.answer для виправлення коду, а також state.html попередньої спроби для допомоги, разом з оригінальним state.codeRequirement — все це свідчить про певний рівень міркувань та можливість того, що між агентами відбувається діалог удосконалення. Цей процес удосконалення буде темою решти статті. Перед тим, як продовжити, швидко підсумуємо загальний робочий процес agentic у фреймворку LangGraph.
Робочий процес
Діаграма потоку нижче базується на тій, що в Частині 2, за винятком процесу агрегації даних, який вже був обговорений, та обробки SVG, що буде розглянуто пізніше. Тепер розглянемо інших агентів: Coder, SVGAnalyzer та Evaluator. Ці агенти забезпечують цикл зворотного зв'язку.
Від цього моменту ми розглянемо, що відбувається після того, як керування переходить від DataAnalyzer до Coder. Як видно з діаграми потоку, створений HTML код зберігається на диску. Коротко, інструмент, який зберігає HTML, є DynamicTool у ланцюжку інструментів Coder — див. Частину 2 для деталей про ці інструменти. Цей інструмент також запускає зовнішню фонову службу для запуску WebDriver від Sellenium, який відображає HTML у браузері. Фонова служба отримує SVG елементи у запитах до WebDriver. Ці елементи об’єднуються в файл, що містить псевдо-відображення SVG елементів. Тепер до справи вступає агент SVGAnalyzer.
Його завдання полягає в тому, щоб сформулювати набір запитань щодо вимог до коду. Ці запитання передаються агенту svgToolAgent. Це особливий агент, оскільки він не викликає модель, натомість файл SVG елементів передається в вектор LlamaIndex, і сховище запитується за допомогою запитань від SVGAnalyzer. Процес стає циклічним, поки Evaluator не буде задоволений відображенням HTML.
Фреймворк LangGraph
Див. Частину 2 для глибшого обговорення LangGraph та спрямованої циклічної деревоподібної структури, яку він надає для взаємодії агентів. Ми вже бачили циклічний процес у повторному виклику агента DataProcessor. У поточній конфігурації агентів: один — Coder — оцінюється іншим, Evaluator, який надає зворотний зв'язок, використовуючи аналіз третього агента, SVGAnalyzer.
Схема, що показує деталі трьох агентів у циклі. Див. Частину 2 для схеми агентів обробки даних.
Вищезгадана схема представляє потоки всередину та ззовні LangGraph StateGraph (в центрі), що підтримує стан протягом усього процесу. Вона також зображує ланцюжки агентів, які передають відповіді назад до StateGraph після виклику кожного агента, де частини стану передаються як параметри виклику. Потік є циклічним, поки Evaluator не поставить оцінку "так" для коду Coder на основі коду d3 js.
Запити
Запити спроектовані таким чином, щоб бути незалежними від конкретного виду графіку. Крім того, запити повинні працювати для обох моделей — як це було продемонстровано двома гістограмами, які вони створили. Як і в Частині 2, правильно налаштувати запити складно. Ось запит Coder:
Запит Coder
Основна думка, яку слід донести до моделі, полягає в двох фазах розробки коду: початковій фазі, яка слідує за вимогами до коду та dataFindings агента DataAnalyzer; та фазі огляду, що відбувається, коли є помилка в коді або код не відповідає вимогам. Цей огляд коду передається Coder як змінна стану answer, тобто відповідь на запитання, поставлені до запитувального механізму агента SVGAnalyzer, а також попередня версія його HTML. Coder інтерпретує відповідь і знову відображає код для аналізу. У випадку Claude, Evaluator поставив оцінку "no" для початкового варіанту коду після огляду answer від SVGAnalyzer, тим самим знову запустивши фазу огляду. OpenAI gpt-4o також отримав оцінку "no", але це ми обговоримо в висновках. Запит агента Evaluator:
Запит вимагає від Evaluator оцінити answer і визначити, чи задовольняє відображений графік вимоги, і дати оцінку "yes" або "no" відповідно. Оцінка визначає, чи завершиться процес чи ні. І, нарешті, це запит агента SVGAnalyzer:
Запит вимагає від SVGAnalyzer проаналізувати state.codeRequirement та state.dataFindings і надати набір критеріїв, які повинні бути виконані для того, щоб вимога була задоволена. Критерії сформульовані як запитання, які будуть передані запитувальному механізму LlamaIndex з використанням псевдо SVG елементів як джерела документа. Цей процес буде розглянуто далі.
Результати запитів і відповідей
Псевдо SVG елементи будуть коротко описані для розуміння документа, який запитує LlamaIndex. Нижче наведено фрагмент цього документа з поясненнями двох псевдо SVG елементів: «відміток» на осі y та «прямокутників», що визначають стовпці в гістограмі.
Ця інформація векторизується в LlamaIndex і потім запитується за допомогою набору запитань від SVGAnalyzer, які додаються до пояснювальної примітки, щоб LlamaIndex мав уявлення про те, що йому потрібно зробити.
Запит, переданий до запитувального механізму LlamaIndex, складається з пояснення, яке знаходиться нижче, і з вхідного запиту, що містить набір запитань від SVGAnalyzer, доданих як окремий рядок з роздільниками "*" для підкреслення розриву:
Інформація та запит, які LlamaIndex використовує для запиту векторного сховища SVG елементів
Запитувальний механізм LlamaIndex запитується для перевірки псевдо SVG елементів, використовуючи запитання, сформульовані SVGAnalyzer. Ось його запитання:
SVGToolAgent отримує відповідь від LlamaIndex:
Відповідь виявляє основну проблему в першому варіанті коду Claude, а саме те, що немає стовпців, що представляють гістограму, оскільки їх висоти встановлені на "NaN" (тобто JavaScript для "Not a Number"). Також описуються інші недоліки коду. Ця відповідь встановлюється в стані, і Evaluator запускається. Evaluator дає оцінку "no". Керування повертається до Coder, другий варіант коду — успішний, як показано вище, оцінка "yes" встановлюється, і робочий процес завершується.
Заключні зауваження
Чи змусила зворотна зв'язок Coder виправити код, чи це просто вдача? Принаймні, він не повторив той самий результат, другий варіант виявляє несподівану ступінь вдосконалення порівняно з першим. Отже, можливо, це було самокоригування, що, якби це відбулося, було б важливою здатністю моделей. Однак gpt-4o від OpenAI не продемонстрував цієї здатності в момент, коли він викликав агента кодування. Хоча його результат є правильним, він потрапив у безкінечний цикл. Тим не менш, OpenAI запустив усіх агентів, за винятком тесту Anthropic, що запустив Coder, який дав результати, які ми бачили, включаючи завершення процесу за допомогою OpenAI, що виконує Evaluator в тому ж запуску — для відомості, gpt-4o значно дешевший у запуску, ніж Claude-opus-3 від Anthropic, але, ймовірно, це краща модель. Загалом, gpt-4o від OpenAI дуже добре керував агентами, особливо агентами обробки даних, незважаючи на те, що, за всіма ознаками, він набагато поступається більш пізній моделі gpt-o1, яка, своєю чергою, значно поступається gpt-o3 (цікаво, що немає o2 — ймовірно, через авторські права). Повідомлення зрозуміле: тип обробки, представлений тут, тільки покращуватиметься.
Автоматична зворотна зв'язка виглядає перспективно, але однозначно остаточна оцінка або запити на поліпшення вимагатимуть втручання ззовні. Ось тут і зіграє важливу роль людина в циклі (Human-in-the-loop, HITL). HITL буде розглянуто в Частині 4. Ми розширимо графік з Частини 1: графік заробітної плати в США за 20 років по 19 галузям. В Частині 1 можна було представити лише 3 роки та 3 сектори на лінійному графіку через обмеження контекстного вікна. Частина 4 знову перевірить DataProcessor та DataAnalyzer в наданні інформації для кодування 19 галузей за 20 років у лінійному графіку. RAG будуть незамінними. Можливість HITL буде частиною інтерфейсу, що дозволяє користувачам запитувати зміни графіків. Інтерфейс також дозволить користувачам вводити вимоги та джерела даних.
Джерела
Частина 1: Розробка коду за допомогою багатосторонніх агентів LLM
Частина 2: Розробка коду за допомогою багатосторонніх агентів LLM, робота з RAG
Перекладено з: LLM Multi-Agent Code Development with Self-Refinement, Part 3