Галоўная / Артыкулы

З адказвенага тексту да пайплайнаў: парсэры, LCEL, выканальныя элементы і память у LangChain

Як LangChain ператварае неапраны текст модэлю на структураваныя даны, складае крокі за дапамою LCEL і інтэрфейса Runnable, а таксама калічытае памяц для дыялогу сёння.

Выдавец: REACTAPP.TOP10 хв чытання2276 слоў

Крыніцы: LangChain Explained: Output Parsers, Chains, Runnables, and Memory

Выкліканне моделі мовы з добра створаным шаблонам запиту — це толькі першы крок у стварэнні рэальнага прыемніка. Модель як і раней вяртае неструктурованы строк, кожны выклік забывае все, што было раней, а ручна наладка запиту, моделі і пасляобработкі быстра ператвараецца на стандартны шаблон. У гідзе рассказана пра чатыро концэпціяў LangChain, якія рашаюць гэтыя проблемы: парсеры выходных даных, ланцюгі, створаныя з LCEL, інтерфейс Runnable, які дазволяе ўтвараць складныя структуры, і меморыя. Пасля чаго вы зможаце чытаць будзь-які LCEL-ланцюг, выбіраць правы парсер або Runnable для виконання задачі і знаваць, дзе знаходзіцца стан канверсацыі ў нынешнім LangChain.

У гідзе прыпускаецца, што вы вяселі, шта такое обгортка LLM і шаблон запиту ў LangChain, адколі все, што після чытаецца, будуе стварана на ўзмоўе з іх.

Парсеры выходных даных: ператворэнне тексту ў даны

Проблемы, якія рашаюць парсеры

Што бы вы не запиталі модель, адпаведна адказа прыходзіць у вигляде свабоднага тексту. Для простога чат-бота, які паказвае адказ на экране, гэтага дасяжна. Але як толькі ваш код павінен працаваць з адказам, напрыклад, запісваючы яго ў базу дадзеных, перадаючы іншай функцыі або вяртаючы яго з API у формате JSON, простая строка стае проблемай.

Парсер выходных даных знаходзіцца межа моделью і рэштой вашага програмы. Ён берае створаны текст і ператварае яго на ўсталеную структуру: список, слоўнік, об’ект JSON або экземпляр вашай сабецькай класы Python. Прыемны спосаб праілюстраваць гэта — як інтэрпретар: модель камунікуець у вигляде свабоднага прозы, а парсер ператварае гэтую прозу на формат, які ваш код можа выкарыстоўваць.

Чаму не трэба парсаваць вручную

  • Формат выходных данэў модель не гарантуе; адной і той жа запытка можа даць нейкія разлічныя структуры пад разнымі запускамі.
  • API-я, базы дадзэных і інтэрфейсы для корыстніка чакаюць дадзеныя, якія ўсёвыражаныя і прыведжамыя.
  • Разлічэнне страк ад случая да случая чыста за дапамою рэгулярных выразаў є нестойкім і зламваецца праз першую нечаканую разніцу.
  • Парсеры таксама можу працаваць з перакананнем, таму відсутняе поле чы рошчэраная значэнне фіксуюцца на самай першай стадыі, а не стаюць прамысловымі кашмарамі пазней у працэсе обробкі.
  • Семья вбудованых парсероў

    LangChain прыносіць кальколька парсероў, кожны з яых прызначаны для разных форматаў выходных дадзэных:

    • Парсер для выходу у вачынку (StrOutputParser): самы просты варыянт, які выкарыстоўваець чысты тэкст з адпаведнага выказву моделі. Яго можна нарабаты, калі трэба паказаць чыстую страку чы передаць яе да наступнага крока.
    • Парсер для ліста: раздзеляе выказву на Python-ліст, што є зручным, калі трэба атрымаць кальколька элементаў, такіх як ключавыя словы чы прыказы.
    • Парсер выходных дадзеных у формата JSON: ператварае адпаведны рэспанс у об’ект JSON і ёсць аднам з найчыльнейша выкарыстоўваных парсераў, калі патрэбны структураваныя дадзеныя.
    • Парсер выходных дадзеных у формата Pydantic: перакладвае адпаведны рэспанс прымусова у модель Pydantic, таму вядома верыфікацыя типу празаўсёды з структурой. Гэта правы выбар, калі дадзеныя павінны адпавядаць строгаму шымату.
    • Парсер даты і часу: стварае справжні об’ект datetime у Python замест таго, каб дата была запісана як текст.
    • Парсер Enum: обмежвае выходны результат аднай значэнню з узгаданага заранее набору і вяртае гэта значэнне.
    • Парсер XML: чытае выходны текст, запісаны як тагі XML, і ператварае яго у слоўнік.

    Структураваныя парсеры аб’яднаюць метод get_format_instructions(). Ён вяртае блакатекст, які можна дадзіць у запит, чым точна паведамляе модэлю, як утварыць свой адказ так, каб парсер могаў яго прачытаць. Парсер простага строкавог тэксту не мае нічога, што трэба было б прымусваць, таму ён являецца ежым членам гатовкі, дзе інструкцыі па формату не прыменяюцца.

    Парсеры проты натыўна структураванага выходу

    Большасць сучасных модэляў можа натыўна вяртаваць структураваныя даны, і LangChain адкрывае гэта через with_structured_output(). Калі гэта ўможлівана, часта гэта заменяе парсер, тым болей што прадаўцу модэля прыемліваеся схема, а не ваш прагнучы запит. Парсеры застаюцца неабходнымі, калі модэль не мае натыўна структураванага выходу, а таксама калі вам патрэбна дадатковая перакананне або трансфармацыя, якіх не гарантуе прадаўца.

    Ланцюгі і LCEL: з’яеднанне крокаў

    Што такое ланцюг

    Ланцюг — это серія крокаў, у якіх выходны даны кожнага крока становяцца вхіднымі для наступнага. У замест на тое, каб після написання трохцо адзінаковага коду для форматавання запиту, вызывання моделі та аналізу рэзультата, вы ўз’ѐднуеце іх у адну відновлюваную схему.

    Класычны прыклад складаецца з трохцо етапаў: шаблон запиту, потым модель, а пасля — парсер выходных даных. Вы задаеце необработанныя вхідныя даны, такія як тэма або запит, і ланцюг форматуе запит, вызывае модель та аналізуе рэзультат у ўсьміх кроках адразу.

    Што дае ланцюгі

    • Менш коду-шаблону. Спынкі між крокамі пішыцца адна раз і не павтараюцца ў кожным месцы вызывання.
    • Модульнасць. Шаблон запиту, модель та парсер — гэта незалежныя часткі, таму вы можете заменіць будзь-яку з іх, не трагнуўшы за іншыя.
  • Чытаемасць. Ланцуг адразу паказвае напрамак прытоку дадзейнаў, нават калі процес выканання є сложным.
  • Павторна выкарыстоўванне. Адзін раз заданы ланцуг можа быць выкліканы знову і знову з разнымі вхіднымі дадзеннямі.
  • Складанне з аператаром «трубка»

    Язык выражэння LangChain, LCEL, дапамагае ствараць ланцугі прыемна і лёгка. У яму для з’еднання компонентаў выкорыстоўваецца аператар |, падобна да таго, як вы спаўнюеце команды ў Unix-шэлі, замест таго, каб глыбока ўзаемна падключаць вызывы функцый.

    Две наступныя лініі ствараюць трывухэтапны ланцуг, описанный вышым, і запускаюць яго адназначна з вхідным дыкшанарыем. Заўважыце, што ключ дыкшанара topic павінен адпавядаць змэннай у шаблоне запиту.

    chain = prompt | model | output_parser
    result = chain.invoke({"topic": "black holes"})
    

    Даныя вводзяцца ў prompt, дзе ствараецца сформатаваны запит; ён падае да model, а адпаведна адказ модэля — да output_parser. Кожны компонент перадае свой рэзультат безпосередна наступнаму, а result храніць тое, што вяртае парсер.

    Кожны ланцуг LCEL таксама мае стандартны набор методаў выканання:

    • .invoke() запускае ланцуг аднае разы для аднаго вводу.
    • .batch() запускае яго для спісу вводоў за адны вызов.
    • .stream() выдае рэзультат поступова, праз адразу, калі ён ствараецца, замест таго, каб заставіць чакаць на цэлы адказ. Стрімінг даступае да вас токен за токенам, якщо кожны крок у ланцузе можа обрабатваць стрім; крок, якому патрэбны ўсе даныя з самага початку, будзе выконваліцца ў буферы.

    Гэты декларатыўны стыль ёсць важным фактарам таго, чаму LCEL здаецца складным у викорыстоўванні. Вы описваеце, што павінна адбыцца і ў якой спраўе, а не механізм таго, як кожны крок вызывае наступны.

    Runnables: інтэрфейс пад спадом

    Чаму все можна перадаваць через каналы

    У LCEL кожны компонент, будзь то запит, модель, парсер чы функцыя, яку вы самі напісалі, ёсць Runnable. Runnable — это загальны інтэрфейс, які можа реалізаваць будзь-який компонент, і самэ гэтае спакоўнае правіле ёсць тым, што дазволяе працаваць з символам |. Паколькі кожны элемент выканання аб’являе тыя ж методы, ўсі элементы можна з’єднваць незалежна ад таго, што яны робяць унутршняя.

    Самэй інтэрфейс якраз тое, чым адчуваецца аднообразнае взаімадзейства з ланцоўкай. Незалежна ад таго, чы рэчаўка вызывае модель, перакаштоввае данні чы запускае калькольванне калякоў водночас, вы керуеце яйным праз тыя ж методы .invoke(), .batch() і .stream(). Складаная ланцоўка сама ёсць Runnable, таму вы можете вярнавати ланцоўкі ўнутрь большых ланцоўак без якога-лібо спецыяльнага адхаджэння.

    Runnable-ы, якія вы будете вжываць найчэйш

    • RunnableSequence выкананаець крокі строго па порядку, перадаючы кожны выход наступнам кроку. Спаўненне рэчаўакоў за дапамогою | стварае для вас адны з гэтых у тылу. Тыповая практыка: спрашчэнне, потым модель, потым парсер.
  • RunnableParallel адмініструе калькольніка Runnables адразу на тым жа вхідным дадзенням і скупляе ўсе яныя рэзультаты ў аднам слоўніку, ключамі якога ёсць імена. Тыповая прыменэнне: адночасна падсумаванне тэксту і выявленне яго мовы, а не адна за другой.
  • RunnablePassthrough вяртае сваё вхідны дадзенне без змян. Ён пераважна выкорыстоўваецца ўнутры RunnableParallel, калі патрэбна аднаковае вхідны дадзенне разам з ператворанымі значэннямі. Тыповая прыменэнне: вяртанне первіснага запиту корыстувальца разам з створанай адпаведзю.
  • RunnableLambda абгортвае звычную функцію Python, каб яна магла стаць элементам у ланцузе. Тыповая прыменэнне: спецыяльная прадзейнаўка або паслядзейнаўка, такая як чыставанне тэксту, пісьба словаў або прыменэнне бізнес-правіл между элементамі ланцуза.
  • RunnableBranch дадзе можлівасць выкарыстоўваць логіку if/else для направлення вхідных дадзеных да разных элементаў, якія можна запусціць, залежна ад умовы. Тыпавое застаўленне: направляць тэхнічныя запытанні да адной ланцуговай структуры, а звычныя запытанні — да іншай.
  • Калі вы пабачыце, што моделі, парсеры і ланцуговыя структуры ўсе ёсць элементамі, якія можна запусціць і якія маюць аднаковы інтэрфейс, LangChain перестае выглядаць як набор спецыяльных рашэнняў. Це стае звычной композіцыяю: маленькія елементы з аднаковай структурой, якія спаўнаюцца ў большыя елементы з той самай структурой.

    Адзін паведамленне пра чрэзмерную композіцыю

    Паколькі хто-небудзь можа стаць об’ектам класу Runnable, бывае спакуса выражаць усю логіку прыемлена як вяроўнутыя ланцюгі. Для простых крокаў гэта добра работае, але глыбока вяроўнутыя галузі і лямбды можу стаць больш складнымі для дэбагавання, чым звычны Python. Разумнае правіла — выкорыстоўваць LCEL для пайплайна, які працуе з модэлем, а складныя потокі керавання залічыць у звычным кодзе, або перанесці іх у фрэймворк графа, калі ў яных є циклы і стан.

    Памяць: зберагчы контэкст між крокамі

    Што значыць памяць тут

    Кожны вызов модэля за замовчаннем є безстановым. Ён не мае жадных ведамасцей пра тое, калі вы запыталі ў пярэдні момент, як толькі ваша прыемлена зноў не надасе пярэдніяй пераказу. У LangChain памяць — це сэт механізмаў для зберагчыць контэкст размовы і ўзяць яго пазней, так што прыемлена можа продаважваць размову, а не спрыятаць кожны паведамленне як даўняе.

    Чаму патрэбна памяц для дыялогавых прыемнікаў

    • Без памяці чат-бот тыряе ўсё як толькі прыходзіць новая паведамленне: няма наступнасці, няма продажоў, няма спаканаванага контэксту.
    • Натуральны дыялог залежыць ад паканальных крокаў, напрыклад, ад ведамасці пра тое, што „яго“ у наступным паведамленні аднасаецца да чалавека, прызначанага два паведамленні раней.
    • Спаканаванный контэкст дае асистэнту вачуменне цягласнасі і дыялогавасці, замест таго каб ён адпавядаў на кожнае паведамленне ізольавана.
    • Это базовая патрабованне для ботаў падтрымкі кляёнтаў, персональных асистэнтоў і будзь-якіх іншых прыемнікаў з канальным ведамаўнем.

    Класычныя класы памяці

    Традыцыйны падход выкарыстоўваў класы памяці, якія знаходзіліся ўнутрь працюючага процэсу. Найпростейшы з іх зберагаў буфер усіх пераказоў; іншы скондэнсаваў старэйшыя паведамленні ў постаўныя падсумкі, каб захаваць токены. Пры кожным новым запыте зберажаны контэкст быў вводзіцься паўтарна ў запрошэнне.

    Дзе зараз знаходзіцца памяць

    Тыя класы памяці, якія былі выкарыстоўваны пад час адработкі, ўжо не выкарыстоўваюцца, адколі LangChain перанёс стан размовы на слой пастаноўкі дадзеных LangGraph. У гэтым модэле інструменты для зберагання стану размовы фіксуюць яго па кожнай сесіі (вызначаемай за дапамогою ниткі), тады як окольныя базы дадзеных зберагаюць далекасхідную память, якая должна застацца пры наступных сесіях. Канцэпцыя застаецца тая ж: паканальны контэкст неабходна зберагаць дзе-небудзь, каб яго можна было знова выкарыстоўваць. Змянілася толькі рэкамендаваная способа адбудовы, якая тепер є болей стойкай і лепш падходзіць для прыменення ў рэальных умовах, адколі стан можна пастанавляць у справжню базу дадзеных, а не він зникае пасля перазапуску процэса. Якщо вы хочаце пазнакоміцца з тым, як інструмент для зберагання стану фактычна яго зберагае, статыя «Inside LangGraph’s InMemorySaver» детальна распаводзіцься пра яго внутрошняе будова.

    Розумным спосабам навчання є спачатку зрозумець класычную ідею памяці, адколі яна пояснюе, чаму памяць мае значэнне і што включаецца ў запит, а пасля застосаваць падход, заснованы на стойкасці, для всьога, што прызначанае для практычнага викорыстоўвання. Адколі гэтая сфера быстра змінюецца, пераканайцеся ў актуальной документацыі LangChain і LangGraph ўсупершце ўжо па API-ях перад тым, як працаваць.

    Заключанне

    Разам з кэшарамі LLM і шаблонамі запытаў гэтыя концэпцыі складаюць основны набор засобаў LangChain. Кожны з іх сам по сабе є простым. Їхня цэннасць прояўляецца у сумесным викорыстоўванні: шаблон падае даны модэлю, парсер ператварае текст модэля ў даны, кожны элемент яўляецца елементам, які можа быць выкананы, і ўсе яны з’еднаныя ў ланцюг за дапамогою |, а шар стойкасці запамячае розмову между вызывамі.

    • Выкорыстоўвайце парсер выходных даных або натыўны структураваны выход, калі модэль гэта падтрымляе, кожны раз, калі код выкарыстоўвае адпаведны ўтварэння модэля.
  • Кожную LCEL-паіплайну трэба чытаць з левага на правы як RunnableSequence; калі прабэг не ўздоўж прямой лініи, выкарыстоўваюцца RunnableParallel, RunnablePassthrough, RunnableLambda і RunnableBranch.
  • Памятаце, што стрімінг ўсё толькі настолькі інкрементальны, насколькі сповольнейшы крок у стрімінгу.
  • Класы памяці з минулых версый трэба рассматрываць як тэму для вучэння, а для рэальных застосоўваń выкарыстоўваць чекпойнты і сховышчы LangGraph.
  • Калі гэтыя базовы элементы ўжо ёсць, наступнымі логічнымі тэмамі є генераванне з падтрымкай выкарыстання інфармацыі з баз дадзеных і агенты, якія таксама складаюцца з гэтых жа элементаў.