Головна / Статті

Основи LangChain: що він вирішує та коли можна повністю його пропустити.

Дізнайтеся, що саме надає LangChain, чому використання інструментів з кількома постачальниками спонукає команди до його використання, які пакети потрібно встановити та коли краще обрати простий обгорток.

Видавець: REACTAPP.TOP7 хв читання1704 слів

Джерела: [Day 11/100] LangChain 101: Why It Exists, and When You Should (and Should Not) Use It

Агент із п’ятдесятьма рядками перестає бути п’ятдесятьма рядками

Додавання нового інструменту до невеликого ручно сконструйованого агента — це дешево. Ви пишете функцію, реєструєте її у словнику, описуєте у схемі, а сам цикл ніколи не змінюється. Ця частина роботи з агентом здається приємною, і це вводить людей у оману, змушуючи їх думати, що вся робота залишатиметься настільки ж простою.

Тоді з’являються справжні вимоги. Кінцева точка OpenAI час від часу повертає код 503, тому потрібні повторні спроби із затримкою. Іноді провайдер не працює протягом двадцяти хвилин, тож потрібен другий постачальник як резерв. Користувач, який чекає вісім секунд на завершення чотирьох послідовних викликів, припустить, що додаток завис, тому додається потокове передавання токенів. Служба нижчого рівня відхиляє відповідь, тому що одне з полів повернулося у вигляді рядка замість числа, тож тепер перевіряється структурований вихідний даний. А коли десять людей використовують його одночасно, блокуючі виклики вже не є прийнятними, тому все має стати асинхронним.

Усе це будується на базі базових бібліотек openai та requests, і кількість коду агента поступово зростає до приблизно п’ятисот рядків. Дуже мало з цього коду стосується самої роботи агента – це обробка JSON, експоненційне затримування спроб та перетворення між форматами повідомлень, які кожен постачальник розробив самостійно. Кожна команда змушена писати цей „інфраструктурний“ код, і нікому не подобається писати його знову.

Саме цей „інфраструктурний“ код і є продуктом LangChain. Нижче описано, що насправді є цією бібліотекою, проблеми, які вона призначена вирішувати, а також ситуації, коли її використання є помилкою.

Що є (і чого немає) у бібліотеці

LangChain не запускає вашого агента замість вас, не хостить жодних ресурсів та не покращує здатність моделі до логічних міркувань. Уявіть собі це як набір спільних інтерфейсів разом із готовими реалізаціями елементів додатку на основі LLM, які інакше довелося б писати самостійно.

Якщо ви вже створили базового агента, то більшість цих елементів ви вже розробили під іншими назвами. Список повідомлень, які ви передаєте, відповідає ChatMessageHistory. Схема інструменту перетворюється на Tool або на звичайну функцію, прикрашену анотацією @tool. Цикл while, який постійно викликає модель, поки та не припинить запитувати про інструменти, — це те, що раніше робив AgentExecutor. А прямий виклик client.chat.completions.create перетворюється на ChatModel. Тут немає нічого концептуально нового. Зміна полягає у тому, що ці компоненти тепер мають спільну структуру, яку вже використовують тисячі інших проектів, а спільні комбінації вже попередньо налаштовані.

Проблеми, для вирішення яких він був створений

Почніть з виклику інструменту, адже це чітко показує всю ситуацію. OpenAI додає елемент tool_calls до повідомлення асистента та очікує результат у вигляді повідомлення з позначкою role: „tool“. Anthropic надсилає блоки з контентом tool_use та очікує блоки tool_result у наступному кроці користувача. Gemini використовує елементи function_call та function_response. Ідея однакова, але три API мають три різні формати, які не підходять один до одного.

З одним постачальником це ніколи не стане проблемою. У день, коли вам знадобиться другий постачальник — можливо, тому що перший має проблеми з роботою або просто став дорожчим — вам доведеться писати та підтримувати три адаптери або скористатися кодом, який вже це обробляє. Саме тому і був створений LangChain: один інтерфейс ChatModel, де методи .invoke() та .bind_tools() функціонують однаково, незалежно від того, яка компанія виробляє токени. Перехід від ChatOpenAI до ChatAnthropic полягає лише у зміні імпорту, а не у переписуванні логіки обробки повідомлень.

Другою проблемою є складна структура. Передача запиту в модель, потім цієї моделі в парсер, а парсера — ще в одну модель означає необхідність наявності коду для з’єднання на кожному рівні, і цей код доводиться переписувати щоразу, коли змінюється структура потоку обробки даних. LangChain уніфікує ці рівні. Механізмом для цього є LCEL — мова виразів LangChain, яка заслуговує на окрему статтю.

Третьою проблемою є просте повторне використання. Агент, який виконує більше, ніж просто виклик моделі, потребує інструментів, які вже створили інші: завантажувачів для приблизно сорока форматів документів, клієнтів для десятка векторних баз даних, політик повторних спроб, механізмів стрімінгу, групування даних та відстеження процесів. Надійно підтримувана інтеграція майже завжди краща, ніж PDF-парсер, який ви написали у п’ятницю та тепер змушені виправляти його помилки.

Що ви отримуєте, не пишучи коду

Єдина інтерфейсна модель. ChatOpenAI, ChatAnthropic, ChatGoogleGenerativeAI та ChatOllama для локальних моделей усі надають методи .invoke(), .stream() та .bind_tools(). Ваш код більше не залежить від того, хто навчив модель, яка використовується під час виклику.

Композитні елементи будови. За допомогою оператора трубки LCEL ви пишете prompt | model | parser та отримуєте один об’єкт, який поводиться як кожен з його компонентів. Він може працювати у режимі потоку, обробляти дані пакетами та виконуватися асинхронно без додаткових зусиль.

Стійкість як виклик методу. Кожен об’єкт типу Runnable має методи .with_retry() та .with_fallbacks(), тож вам не потрібно вручну писати цикли повторних спроб чи механізми заміни постачальників.

Стрімінг та асинхронність скрізь. Кожен об’єкт типу Runnable має методи .stream(), .ainvoke(), .astream() та .batch(), навіть якщо сам об’єкт, який ви обгорнули, спочатку не підтримував їх.

Великий каталог інтеграцій. Завантажувачі документів, сховища векторів, інструменти пошуку, виконання коду, інструменти для баз даних. Багато з них підтримуються самими постачальниками, і всі вони використовують один і той самий інтерфейс Runnable.

Прямий шлях до LangGraph. Коли ваш агент потребує циклів, збереженого стану чи кроку людського схвалення, який неможливо описати за допомогою лінійної послідовності, ланцюг керування переміщується до LangGraph. Він побудований на тих самих основних абстракціях, тому те, що ви дізнаєтесь у LangChain, можна безпосередньо застосувати й тут.

Коли його пропускати

Більшість вступних матеріалів пропускають цю частину, а саме вона згодом допомагає уникнути найбільших проблем.

Один постачальник, один виклик, назавжди. Якщо вся сутність продукту полягає у „надсиланні цього запиту до GPT-4o та отриманні тексту“, простий власний обгорток буде легшим для читання та виправлення помилок, ніж залежність, яка включає сотні проміжних пакетів.

Вам потрібно бачити кожен запит. Абстракції зручні саме тому, що приховують деталі, але це приховування коштує вам часу, коли щось ламається. Простежування сліду виконання через шість рівнів обгорток типу Runnable перед тим, як знайти справжню помилку, — це втрата часу. Команди, які хочуть бачити кожну рядок між своїм кодом та HTTP-запитом, часто залишаються ближче до первинного SDK.

Шляхи з критично важливою затримкою. Кожен рівень додає певний надлишковий трафік. Поруч із викликом моделі, який займає 500 мс або більше, цей надлишок зазвичай є незначним. Але на шляхах з високою навантаженістю, де кожна мілісекунда має значення, це іноді не так.

Оновлення без фіксації версії. LangChain регулярно випускає зміни, які можуть порушити існуючу роботу. Проєкт, який використовує останню версію, зрештою зіткнеться зі зміною імен імпортів чи значень за замовчуванням. Забезпечте фіксацію версії, як і будь-якої іншої залежності, та оновлюйтеся тоді, коли вирішите це зробити, а не тоді, коли це зробить pip.

Усе це не означає, що ви ніколи не повинні використовувати LangChain. Це означає, що рішення слід приймати для кожного проєкту свідомо, а не через звичку.

Який пакет — який

LangChain складається з кількох пакетів, і варто знати, що робить кожен з них, перш ніж щось інсталювати.

  • langchain-core містить базові абстракції: Runnable, BaseChatModel, BaseTool, шаблони запитів та парсери результатів. У нього мало залежностей, і майже все інше будується на його основі.
  • langchain додає ланцюги, агентів та логіку отримання даних до основного функціоналу, не прив’язуючи вас до конкретного постачальника.
  • langchain-community збирає інтеграції від третіх сторін. Деякі з них є відмінними, а деякі — покинутими, оскільки будь-хто може допомогти з їх розвитком. Перед тим, як використовувати їх у продакшені, перевірте, коли востаннє вони оновлювалися.
  • Пакети-партнери, такі як langchain-openai, langchain-anthropic та langchain-google-genai, підтримуються разом із власним SDK кожного постачальника. Встановлюйте лише ті, які ви дійсно використовуєте.
  • langchain-classic — це місце, куди у версії v1 потрапили конструктори агентів до версії 1.0: AgentExecutor, create_tool_calling_agent та старий create_react_agent. Існуючий код продовжує працювати, але нові агенти не слід створювати тут.
  • langgraph — це окремий пакет, який будується на основі langchain-core та відповідає за керування процесами, що не є лінійними. З версії langchain v1 рекомендований інструмент для створення агентів langchain.agents.create_agent реалізований безпосередньо в ньому.

Щоб почати роботу, вам знадобляться лише три пакети:

uv add langchain langchain-openai python-dotenv

Заміна базового клієнта на ChatOpenAI

Не очікуйте нічого значного на цьому етапі. Мета — замінити звичайний клієнт OpenAI на обгортку чат-моделі від LangChain та перевірити, чи отримуєте ви ту саму відповідь.

from langchain_openai import ChatOpenAI
from dotenv import load_dotenv
load_dotenv()
llm = ChatOpenAI(model="gpt-4o", temperature=0)
response = llm.invoke("What is the weather in Paris?")
print(response.content)

llm.invoke(...) повертає вам об’єкт AIMessage, а не простий словник, але .content дає вам текст так само, як це робив .choices[0].message.content. Під спудом ChatOpenAI все одно використовує той самий кінцевий пункт OpenAI, який ви б викликали вручну, і сама модель зовсім не змінилась. Різниця полягає у тому, що тепер llm є об’єктом типу Runnable, тому методи .stream(), .batch() та .ainvoke() вже існують, і ви не писали жодного з них.

for chunk in llm.stream("Tell me one fact about Paris."):
  print(chunk.content, end="", flush=True)

Запустіть його, і токени надходитимуть по одному блоку за раз. Ви ніколи не писали обробника потокової передачі даних, і цей невеликий цикл фактично є основною причиною використання цієї бібліотеки.

Коротко про назви моделей: у прикладах використовується "gpt-4o" як замінник. Перелік постачальників змінюється швидше, ніж будь-який посібник може це відстежувати, тому кожен рядок у форматі model="..." слід розглядати як замінник, і під час запуску коду потрібно вставити ту модель, яку пропонує ваш постачальник. Механізм залишається незмінним.

Спробуйте це на власному агенті

  1. Додайте langchain та langchain-openai до вашого проекту за допомогою команди uv add.
  2. У кроці з моделлю вашого агента замініть прямий виклик OpenAI() на ChatOpenAI та не змінюйте решту циклу. Переконайтеся, що агент поводиться точно так само, як і раніше.
  3. Викличте llm.stream(...) для простого запитання без використання додаткових інструментів та друкуйте кожен фрагмент у міру його отримання. Тепер у вас є можливість стрімінгу для методу, який ви ніколи не реалізували.

Після цієї зміни ваш агент не стає розумнішим. Ви отримали основу, завдяки якій повторні спроби, альтернативні рішення, стрімінг та зміна постачальників коштують лише одного виклику методу, замість сотень рядків коду.