Складанне каналаводоў LangChain з LCEL: лінейныя, паралельныя та розгалужаныя.
Навучыцеся підключаць запиты, моделі та парсеры да лінейных, багатоэтапных, паралельных та умовных каналаводоў LangChain за дапамогою аператара pipe, RunnableParallel та RunnableBranch.
Крыніцы: Chains in LangChain: Simple, Sequential, Parallel, and Conditional — Part 7
Рэальныя функцыі LLM практычна ніколі не заканчываюцца простым вызовам адного модэлю. Вы форматаваеце запит, вызываеце модэль, працоўваеце з рэзультатам, які ён вяртае, падаеце гэты рэзультат у іншы запит, іноды выконваеце калька запытоў паралельна, а часам выбіраеце іншы пат залежна ад вхідных дадзеных. Ручная наладка кожнага пераходу быстра ператвараецца на «клейкі» код, які важка чытаць і ўсё важча змяніць. У гэтым кансалтаты разбіраюцца чатыры шаблону складання ў LangChain, створаныя за дапамогою мовы выражэння LangChain (LCEL): простыя пайплайны, багатоэтапныя пайплайны, розгалужэння з RunnableParallel і маршрутызацыя з RunnableBranch. Кожны шаблон паказаны два разы — аднажды на Google Gemini і аднажды на OpenAI, ўсёлякі вы маглі бачыць, якія часткі змінююцца падчас пераходу да іншага прадаўця, а якія застаюцца незменнымі.
Што такое ланцюг на самай працоўцы
Ланцюг — это лінія обробкі, складаецца з компанентаў LangChain, у якой кожны крок прымае тое, што створыў паперадні крок. Тыповыя елементы такога ланцюга — шаблоны запитоў, моделі для чату або завершэння тэкста, а таксама парсеры выходных даных; пры тым можа браты участак будзь-што, што рэалізуе інтерфейс Runnable з LangChain. Найменшы корыстны ланцюг выглядае так:
Prompt → LLM → Output Parser → Output
Кожны этап выпаловае адну задачу. Шаблон запита бера вашы вхідныя зменныкі і ператварае іх у інструкцыі для моделі. Модель чытае этыя інструкцыі і стварае адпаведны адказ. Парсер выходных даных перакаштоввае гэты адказ у формат, які патрэбны вашай прыкладнай програме — напрыклад, у звычную строчку або пераканалізаваны об’ект. Рэзультат, які застаецца пасля працы парсера, ёсць рэзультатам усіго ланцюга.
Цянькавасць абстракцыі заключаецца у тым, што вам больш не трэба самім перадаваць даныя между гэтымі стадіямі. Вы адзін раз заявляеце порядак, і LangChain сама вызывае кожны компонент з правымі данымі. Канцэптуальна, вы складаеце маленькія блакіты ў адну большую единіцу:
Input
↓
Component 1
↓
Component 2
↓
Component 3
↓
Output
Гэта мало значыць для скрыпту з двума крокамі, але становіцца неабходным, калі прыкладка вырастае да дзесятка крокаў. Заявленае каналаванне дазволяе бачыць прайом даных у аднам месца, замест таго каб яны рассеяваліся між дапаможнымі функцыямі. Ментальная модель — цэлкава парада: вхід, обробка, выхід, а потым знову обробка гэтага выхіднага результата, пакуль не будзе атрымана заканчоўная цэнна.
Чаму композіцыя краща за ручнае з’ўязку
Калі кожная проміжная цэнна перадаецца явна, код становіцца хаотычным і нестабільным. Ланцюгі рашаюць тры практычныя проблемы.
Прайом даных без додатковага коду
Галоўная задача — канкрэтна, што рэзультат аднаго крока автаматычна стае аргументам наступнага. Возьмем зноў вядомую сэрію:
Prompt → LLM → Output Parser → Output
Якщо пісаць вручную, трэба будзе сформатаваць запит, вызваць модель з фарматаваным значэнням, выявіць текст з об’екта адпаведзі і передаць яго парсеру, зберагчы зменную для кожнага крока. З LCEL той самы процес спраўляецца за дапамогою адной выразы, створанай з аператара pipe:
chain = prompt | llm | parser
Аператар | працюе таму, што запыты, моделі і парсеры всі рэалізуюць адну і тую ж інтарфейс Runnable, таму кожны знае, як прыняць выход пярэдней складовай. Менш тымчасовых змян означае менш месцаў, дзе можна пасłaць некоректнае значэнне, а канвейер чытаецца з лева на права ў той самы порядак, у яком выкананы.
Логіка з калькамі застаеся чытабельной
Функцыі вырабаткі частаючы трэбуюць калькаў моделі з разнымі запросамі. Часта форма — гэта стварэнне чаго-небудзь на адной тэме, а пасля дадатковая обробка гэтага проміжнага рэзультата з дапамогай другога запросу:
Topic
↓
Prompt 1
↓
LLM
↓
Output Parser
↓
Prompt 2
↓
LLM
↓
Output Parser
↓
Final Output
Калі такі процес керуецца вручную, ён распрастаёцца ў меру дадавання новых крокаў. Калі ён выражаецца як ланцюг, усі етапы залічваюцца ў адной декларацыі, а правіло пра тое, што кожны етап падае даныя на наступны, выканана фрэймворкам, а не правіламі дисцыпліны.
Компаненты можна заменіць
Пакалі кожны етап ёсць незалежной компанентай, можна заменіць одну без вплыву на іншыя. Падазроўваю, што практычнае рашэнне зараз працуе на Gemini:
chain = prompt | gemini | parser
Пераход на OpenAI значыць зміну толькі компаненты моделі:
chain = prompt | openai | parser
Запросы і парсеры застаюцца такімі, якія ўжо є. Це корыстна, калі змянююцца трэбавання, калі команда параганяе паспрэдавальнікі за цэной чыста якасцю, або калі вы хочаце дышэўнейшую модель пад час розработкі і моцнейшую у працэйным режыме.
Прыямыя выгоды
- Менш коду для з’ѐеднання: рэзультаты автаматычна пераходзяць да наступнага крока.
- Структураваныя багатоэтапныя процесы: калькі запросаў, модэляў і парсераў знаходзяцца ў аднай заданай лініи обработкі.
- Прыемнае развіцце: компаненты можна перадаўжваць выкарыстоўваць чыста заменяць, укладаючы паспрэдавальніка модэля, без перадзялёвкі прабегу процеса.
Чатыры шаблону складання
Дапамагае групаванне пайплайнаў LangChain у чатыры типы. Строга кажучы, гэтыя не ўжо адзінлівыя класы ў сучасным LangChain; гэта шаблоны, якія ствараюцца з тых самых прымітываў Runnable. Назвы як і раней ёсць практычным слоўнікам:
- Простая ланцюгова структура: адна простая лінія ад запиту да парсера.
- Параднай ланцюговай структуры: калькі такіх ліній, з’едыненыя ад канца да канца.
- Паралельная ланцюговая структура: калькі незалежных падланцюгоў, якія адразу працуюць з тым самым вхідным дадзеннем.
- Умовная ланцюговая структура: тачка прынятні рашэння, якая выбирае, кой падланцуг будзе обрабоўваць вхідны дадзенні.
Ўсё гэта пакрывае ўсё, ад одноразовага запиту да рабочых процэсаў, якія розгалужуюцца і прымаюць рашэнні. Кожны з гэтых типаў практычна показаны нижэй на прыкладах з двума прадастальнікамі.
Простыя ланцюговыя структуры: адна простая перадача
Самая простая лінія обробкі з’єднуюе ўсі елементы ў прямай лініі. Рэзультат выходу кожнага элемента становіцься вхідным дадзеннем наступнага, і па цій лініі няма жадных розгалужэнняў чы правалявань:
Input
↓
Prompt Template
↓
LLM
↓
Output Parser
↓
Output
Практычны прыклад ўжоўся як адказвальны інструмент: па заданай тэме модель должна даць калькі простых, зрозумелых фактов, не заглубляючыся ў складныя тэхнічныя деталі. Ланцюг для цього ўсё як можна корачэй:
prompt → llm → parser
У простыях словах, значэнне topic падае ў шаблон запиту, сформатаваны запит падаецца модэлі, а адказ модэлі праходзіць через парсер выходных дадзенняў пры вярнэнні. Парсер тэхнічна ўзельцевы, але яго наявнасць дапамагае прадбачаць тип вярнуваных дадзенняў.
Конкрэтна, у прыкладзе як тэму задаёцца крікет, і прасіма падаць пяць цікавых фактов. Дадзенні пераходзяць так:
{'topic': 'cricket'}
↓
PromptTemplate
↓
LLM
↓
StrOutputParser
↓
Five interesting facts about cricket
Версія Gemini
Ёсць цэлы праект, які выкарыстоўвае Gemini ад Google через інтеграцыю langchain_google_genai:
from langchain_google_genai import GoogleGenerativeAI
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from dotenv import load_dotenv
load_dotenv()
llm = GoogleGenerativeAI(model="gemini-2.5-flash")
prompt = PromptTemplate(
template='Generate 5 interesting facts about {topic}',
input_variables=['topic']
)
parser = StrOutputParser()
chain = prompt | llm | parser
result = chain.invoke({'topic':'cricket'})
# print(result)
chain.get_graph().print_ascii()
У яму ствараецца модель GoogleGenerativeAI на базе gemini-2.5-flash, запрос з адной зменнай і StrOutputParser, пасля чаго ўсе гэта складаецца за дапамогою ланцоўкі prompt | llm | parser. У пасляпэўнай лініі выдруковываецца дыяграма ланцоўкі у формате ASCII, якая будзе розглянута далей. Заўважыце, што лінія print(result) у гэтым варыянце ўзначана як коментар, таму скрыпт паказвае толькі дыяграму, якщо толькі вы знова ёе не актывацыяце.
Разбір коду Gemini
У оператывах імпорту прыводзяцца чатыры элементы, якія патрабуе скрыпт:
from langchain_google_genai import GoogleGenerativeAI
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from dotenv import load_dotenv
GoogleGenerativeAI — гэта абгараўнік навакола моделі Gemini. PromptTemplate задае запит з полам {topic}, які неабходна запаўніць пад час выканання. StrOutputParser ператварае выход моделі на звычную строчку Python. load_dotenv чытае локальны файл .env і запісвае яго змест на аб’екты сераўнавання сяродовісы.
Выклік load_dotenv робіць ключ API і ўсе іншыя настройкі з файла .env доступнымі для процэса ўсё раней, чым будзе створены які-небудзь кліент:
load_dotenv()
Інтеграцыя з Gemini чытае свой ключ з сяродовісы, таму гэты выклік павінен адбыцца раней, чым будзе створена модель. Застаўленне ключоў у файле .env (і не падключэнне яго да системы керавання версіямі) — гэта стандартная практыка.
Пасля чаго сама модель стварыцца, прыкрепленае да версіі gemini-2.5-flash:
llm = GoogleGenerativeAI(model="gemini-2.5-flash")
Шаблон запиту адзначае як текст, так і зменныя, якія ён чакае:
prompt = PromptTemplate(
template='Generate 5 interesting facts about {topic}',
input_variables=['topic']
)
Пад час вызыву заместыкатель {topic} заполняецца з слоўніка вхідных дадзеных. Перадаючы гэты вхід:
{'topic': 'cricket'}
утвараецца следуючы конкрэтны запит:
Generate 5 interesting facts about cricket
Парсер ствараецца без жадных аргументаў:
parser = StrOutputParser()
Яго задача — аддаць адпаведзь модэлю у вигляде строкі. У разе модэля типу GoogleGenerativeAI, які вже вяртае текст, гэта майже як працэс без перерабаткаў. У разе чат-модэля, які вяртае об’ект паведамлення, парсер выкалывае тэкстовы ўместак. У будзь-якім случае наступны корыстнік атрымлеў простую строку str.
Потым прыходзіць лінія, якая визначае весь пайплайн:
chain = prompt | llm | parser
Оператор трубкі з’єднуе трыя складовыя так, што сформатаваны запит надаецца модэлю, а адказ модэля — парсеру:
PromptTemplate → Gemini 2.5 Flash → StrOutputParser
У канцы ланцюг запускаецца за дапамою словніку, які містіць тэму:
result = chain.invoke({'topic':'cricket'})
Этот адзін вызов запускае кожны этап па порядку, без проміжных зменных у вашам коде:
cricket
↓
PromptTemplate
↓
Gemini 2.5 Flash
↓
StrOutputParser
↓
Final Result
Што вяртаецца
Значэнне, якое вяртаецца, — это строка, якая містіць пяць фактов пра крікет, чыгун такога роду:
1. Cricket originated in England and has been played for several centuries.
2. A cricket team consists of 11 players.
3. Test cricket matches can last up to five days.
4. The Cricket World Cup is one of the major international cricket tournaments.
5. Cricket is especially popular in countries such as India, Australia, England, and New Zeakand.
Паколькі тэкст генеруецца, формулювання і конкрэтныя факты разлічаюцца з адной выконанні да іншага, а прыкладны адказ нават містіць прабелку (“Zeakand”). Вважаеце прыкладныя адказы ілюстраціямі формату, а не фіксаванымі рэзультатамі. Якщо вам патрэбны детерміністычныя форматавання, гэта заведома задача строгага парсера аб структураванага выходу, а не спадзявання на тое, што модэль будзе павтарацца.
Той самы пайплайн у OpenAI
Цяпер той самы прыбор для аналізу дадзеных, які работае на OpenAI. Асаблівы тое, насколькі мало змян:
from langchain_openai import ChatOpenAI
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from dotenv import load_dotenv
load_dotenv()
llm = ChatOpenAI(model="gpt-4o-mini")
prompt = PromptTemplate(
template='Generate 5 interesting facts about {topic}',
input_variables=['topic']
)
parser = StrOutputParser()
chain = prompt | llm | parser
result = chain.invoke({'topic':'cricket'})
print(result)
chain.get_graph().print_ascii()
Модель тепер — ChatOpenAI з gpt-4o-mini. Запрос, парсер і процес складання дадзеных застаюцца такімі ж, як і ранейш, а гэта версія выдрукуе як рэзультат, так і граф.
Едын новы элемент — аб’ект моделі:
llm = ChatOpenAI(model="gpt-4o-mini")
ChatOpenAI з бібліятэкі langchain_openai заменяе GoogleGenerativeAI. Цэта модель для чату, таму яна вяртае аб’ект паведамлення, а не суровы строк, і самэ гэта ўскладнівае збережэння парсера в ланцюгу обробкі.
Шаблон запита застаўся незмянным:
prompt = PromptTemplate(
template='Generate 5 interesting facts about {topic}',
input_variables=['topic']
)
Парсер таксама застаўся незмянным:
parser = StrOutputParser()
Процес складання дадзеных таксама застаўся незмянным:
chain = prompt | llm | parser
Структура застаецца той жа — трывяжная лінія, з OpenAI ў середзіне:
PromptTemplate → OpenAI → StrOutputParser
Выклік ланцуга та выдрукаванне рэзультата выглядае так:
result = chain.invoke({'topic':'cricket'})
print(result)
Выконанне на OpenAI таксама вяртае строку з пяцью фактамі пра крыкет, напрыклад:
1. Cricket is believed to have originated in England.
2. Each cricket team has 11 players.
3. A Test match can be played over a maximum of five days.
4. The Cricket World Cup is one of the most important international cricket tournaments.
5. Cricket has a particularly large following in India and several other countries.
Знову ж, точны выраз будзе разніцявацца ў залежнасці ад выконанняў та модэляў.
Выдрукаванне структуры ланцуга
Оба скрыпты завершаюцца вызовам, які прадстаўляе канвейер у вачынку текстовай дыяграмы:
chain.get_graph().print_ascii()
get_graph() вяртае представленне графа Runnable, а print_ascii() атрыбуе яго ў тэрмінале. Для ланцуга Gemini вы отрымаеце ўдзейсненне на кшталт гэтага:
PromptInput
*
*
PromptTemplate
*
*
GoogleGenerativeAI
*
*
StrOutputParser
*
*
StrOutputParserOutput
Узлы PromptInput і StrOutputParserOutput являюцца точкамі входу та выходу, выведзеннымі з схемы; трохі узлов межаў імі ёсць вашыя компаненты. Цэе простая перапаконтка, каб пераканацца, што ланцюг падключены так, як вы гэта супакоўваліся. У большасці сераў print_ascii() залежыць ад неабавязковага пакета grandalf, таму інсталюйце яго, якщо вызов выклікае памылку імпорту.
Зменіўся толькі модэль
Разместіце два пайплайны па боку ад другога — разліка будзе ў аднам узле. З Gemini:
Input
↓
PromptTemplate
↓
GoogleGenerativeAI
↓
StrOutputParser
↓
Output
З OpenAI:
Input
↓
PromptTemplate
↓
ChatOpenAI
↓
StrOutputParser
↓
Output
У коде вся разліка заключаецца у тым, які об’ект модэля вы ствараеце:
# Google Gemini
llm = GoogleGenerativeAI(model="gemini-2.5-flash")
# OpenAI
llm = ChatOpenAI(model="gpt-4o-mini")
Усе, што знаходзіцца навакола, можа застацься такім жа. Гэта практычная перавага викорыстання стандартизаваных компонентоў: выбор прадаўцы стае рашэнням пра налашчванні, а не перапісваўкай коду. У практыцы існуюць ўсё ж некалькі дробных разлікаў, на якія варта зважаць — такіх як моделі чату і завершэння запытання, назвы параметраў, а таксама спосаб, якім кожны прадаўца обробляе інструкцыі системы, але сама ланцюговая структура є переноснаю.
Полны шлях дадзенаў
У загальных рысах простая ланцюговая структура выглядае так:
Input
↓
PromptTemplate
↓
LLM
↓
StrOutputParser
↓
Output
За дапамогою інпуту з крыкету кожная проміжная значэння стае виднымі — ад словніка да выробленага запытання, ад первіснай адпаведзі моделі да фінальной строчкі:
{'topic': 'cricket'}
↓
PromptTemplate
↓
"Generate 5 interesting facts about cricket"
↓
LLM
↓
Raw LLM Response
↓
StrOutputParser
↓
Final String Output
Гэта і є вся ідея. Кожны компонент выканае адну задачу, а яго рэзультат стае інпутам для наступнага компонента, у рамках адной простай практыкі.
Паўзовыя ланцюгі: перадача рэзультата аднае моделі наступнай
Парадыгма або ланцужок парадыгм распрастраняе тую ж самую ідею па калькі стадзій. Спачатку ланцужок зазвычай складаецца з аднаго такога элемента:
Prompt → LLM → Output Parser
Ланцужок парадыгм можа мець калькі шаблонаў запитоў, калькі вызываў модэляў і калькі парсероў, якія ўсе з’едыненыя разам. Кантрольныя правілы не зменшуюцца: тое, што даўае текущая стадзія, яно і спрымлевае наступная стадзія. Яшчэ не існуе розгалужэнняў; стадзіі завжды выконвуюцца у там самым фіксаваным порядку. Версія з двума стадзіямі выглядае так:
Input
↓
Prompt Template 1
↓
LLM 1
↓
Output Parser
↓
Prompt Template 2
↓
LLM 2
↓
Output Parser
↓
Output
Прыклад: дакладны атлас, потым статыстыка
У прыкладным завадзе ёсць два этапы. Першы — стварыць дакладны атлас на паводзе таго чыяго-небудзь тэмы. Другі — сукупна пераказаць гэты атлас у статыстыку з пяці пунктаў. На другам этапе ніколі не бачыцца самай первінной тэмы; ён працуе толькі на атласе, які стварыў першы этап:
Input
↓
Explanation Prompt
↓
LLM
↓
StrOutputParser
↓
Detailed Report
↓
Summary Prompt
↓
LLM
↓
StrOutputParser
↓
5-Point Summary
Два шаблону запитоў виканаюць цю роботу. explaination_prompt (парафраза ў коде, таму залишыце яе, якщо копіюўце фрагменты) запрашае детальны звіт. summary_prompt берае гэты звіт як вхідны даны і запрашае пяць пунктавых узагальнэнняў.
Версія Gemini
Цэга ўвесь програм на Gemini:
from langchain_google_genai import GoogleGenerativeAI
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from dotenv import load_dotenv
load_dotenv()
llm = GoogleGenerativeAI(model="gemini-2.5-flash")
# Prompt 1 : Explanation
explaination_prompt = PromptTemplate(
template='Generate a detailed report on {topic}',
input_variables=['topic']
)
# Prompt 2 : Summary
summary_prompt = PromptTemplate(
template='Generate a 5 pointer summary from the following text \n {text}',
input_variables=['text']
)
parser = StrOutputParser()
chain = explaination_prompt | llm | parser | summary_prompt | llm | parser
result = chain.invoke({"topic": "Generative AI"})
# print(result)
chain.get_graph().print_ascii()
Одна інстанцыя GoogleGenerativeAI обслужвае оба этапы, а пайплайн — цэ это адна выраза з шасцю складовымі: два запиты, модель два разы і парсер таксама два разы.
Аналіз коду Gemini
Імпорты паспаўляюць простай ланцузі:
from langchain_google_genai import GoogleGenerativeAI
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from dotenv import load_dotenv
GoogleGenerativeAI для модэлі, PromptTemplate для двух повтарна викорыстоўваных шаблонаў запитоў, StrOutputParser для ператварэння кожнай адпаведзі на строку, і load_dotenv для налаштавання.
Зменныя сераўіса аптываюцца першымі, так сама як і раней:
load_dotenv()
Модэль ствараецца аднаразова:
llm = GoogleGenerativeAI(model="gemini-2.5-flash")
Павторышчанне аднаго об’екта модэлі для обох стадзій ўсё абсалютна нормальна. Кэшеры модэляў у LangChain не маюць стану ў зв’язку з адзінамі вызывамі, таму той самы экземпляр можа быть выкарыстоўваны столькі разоў, сколькі патрэбна ў ланцугу.
Першы запит прымае topic і прасіць падробны атлас:
explaination_prompt = PromptTemplate(
template='Generate a detailed report on {topic}',
input_variables=['topic']
)
З урахоўваннем гэтага вводу:
{"topic": "Generative AI"}
гэта выклікае:
Generate a detailed report on Generative AI
Самэў цікавы перадача дакумента адбываецца у другім запыті:
summary_prompt = PromptTemplate(
template='Generate a 5 pointer summary from the following text \n {text}',
input_variables=['text']
)
Ён чакае зменную пад назвай text, але ў словніку вводу няма нічога пад такой назвай. Значэнне прыходзіць з першай стадзіі: той атлас, які быў створаны праз першы вызыв модэлі, запоўняе гэта месца.
Это працюе за дапамою парсера, які знаходзится межы стадіямі:
parser = StrOutputParser()
Пасля першага вызову модэлі парсер стварае звычную строку. Калі PromptTemplate з роўна адной зменнай-вхідам прымае чыстую строку заместо слоўніка, LCEL супарабатвае гэтую строку з той адной зменнай. Так рэпорт падае ў {text} без якога-лібо явнага супарабатвання. Якшо другі запрос мяў две зменны, вам патрэбна будзе самім стварыць слоўнік, напрыклад, за дапамою маленькага слоўніка Runnables або RunnablePassthrough.assign.
Усё канцэрнай лінія ўскладненаўся ў адну лінію:
chain = explaination_prompt | llm | parser | summary_prompt | llm | parser
Чытанне злева напраўо паказвае порядак выканання:
Explanation Prompt
↓
Gemini
↓
StrOutputParser
↓
Summary Prompt
↓
Gemini
↓
StrOutputParser
↓
Final Output
Кожны компонент прымае выход пярэдньага компонента. Ніч гэта не пазначаецца ў вашам кодзе на проміжны рэпорт.
Выкананне ўскладнаваецца адной тэмай:
result = chain.invoke({"topic": "Generative AI"})
За гэтым вызовам обе стадіі выконваюцца адна за другой:
"Generative AI"
↓
Explanation Prompt
↓
Gemini
↓
StrOutputParser
↓
Detailed Report
↓
Summary Prompt
↓
Gemini
↓
StrOutputParser
↓
Final 5-Point Summary
Што вяртаецца
Фінальная цэнтрызаваная інформацыя — это п’ятыступенчатыя апূর্তнікі пра тэхналогію Generative AI, напрыклад:
1. Generative AI is a branch of artificial intelligence that creates new content.
2. It can generate text, images, audio, video, and code.
3. Foundation models are an important part of modern Generative AI systems.
4. Generative AI is used across areas such as education, software development, and customer support.
5. It is enabling applications that can automate and simplify many creative and knowledge-based tasks.
Важна деталь — гэтыя апূর্তнікі не ствараюцца безпосередна на адной з тэм. Gemini спачатку пішае цэлы аўтарапорт, і толькі гэты рапорт надаецца для стварання апূর্তніка. Такой дваэтапны падход зазвычай дае можлівасць другаму вызову працаваць з болей багатым матэрыялам для стиснення, але за цэну дадзейна додатковага вызова моделі, а таксама затрымкі і кантэйнераў, якія з’являюцца ўнаследак гэтага. Як і завжды, конкретныя формулюванні разлічаюцца з адной эксперыменты да іншай.
Той самы процес на OpenAI
Варіант OpenAI застаецца пры тым жа прадзеянні:
from langchain_openai import ChatOpenAI
from dotenv import load_dotenv
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from dotenv import load_dotenv
load_dotenv()
llm = ChatOpenAI()
explaination_prompt = PromptTemplate(
template="Generate a detailed report on {topic}",
input_variables=["topic"]
)
summary_prompt = PromptTemplate(
template="Generate a 5 pointer summary from the following text \n {text}",
input_variables=["text"],
)
parser = StrOutputParser()
chain = explaination_prompt | llm | parser | summary_prompt | llm | parser
result = chain.invoke({"topic": "Generative AI"})
print(result)
chain.get_graph().print_ascii()
Это тая жа дваэтапная структура: запит на адаптацыю, модэль, парсар, запіт на статысць, модэль, парсар. У скрыпту таксама дваразова імпортуецца load_dotenv, што нешкодна, але можа быць выправлена.
Ці раз модэль ствараецца без аргументаў:
llm = ChatOpenAI()
Калі няма аргумента model, ChatOpenAI вяртаецца да стандартнай модэлі бібліятэкі, якая зменяецца ў разы выхода новых версій langchain_openai. У рэальных проектах неабходна явна задаць назву модэлі, каб апгрэйд не таямніча змяніў ўзроць працы чы структуру.
Запіт на адаптацыю ідэнтычны версіі Gemini:
explaination_prompt = PromptTemplate(
template="Generate a detailed report on {topic}",
input_variables=["topic"]
)
Тое ж самае стосуецца і запіту на статысць, які зноў чакае адпаведныя даныя ў формате {text}:
summary_prompt = PromptTemplate(
template="Generate a 5 pointer summary from the following text \n {text}",
input_variables=["text"],
)
Сюды і праглядаецца перадоўгі патэрн: другі запіт автаматычна заполняецца на адпаведнасць выходу першага этапу.
Парсер застаўся тым самым StrOutputParser:
parser = StrOutputParser()
А складанне адбываецца символ за символам таксама:
chain = explaination_prompt | llm | parser | summary_prompt | llm | parser
З точкі зору тэчы, гэта выглядае так:
Prompt 1
↓
OpenAI
↓
StrOutputParser
↓
Prompt 2
↓
OpenAI
↓
StrOutputParser
↓
Final Output
Толькі вузол модэлю разніцяецца ад версіі Gemini.
Выклік зноў перадае тэму. Заазначыце, што паказваны фрагмент не мае заканчоўнай складзіны; у цэлым праекте є правильная лінія:
result = chain.invoke({"topic": "Generative AI"}
Потым ланцуг запускае оба этапы самастоятельна.
Што вяртаецца
Рэзультат зноў являе сабою пяціпунктны апытак заснованы на промежуточнай адзвітнасці:
1. Generative AI creates new content by learning patterns from existing data.
2. It is powered by large foundation models trained on diverse datasets.
3. Generative AI can produce text, images, audio, video, and code.
4. It is being used in many industries and applications.
5. It is becoming an important part of modern AI-powered software.
Деталі будуць разнымі праз розныя запускі.
Выдрук структуры ланцуга
Памочны прыстрой для графа таксама працуе з дужэйшымі ланцугамі:
chain.get_graph().print_ascii()
Для последовальнага ланцуга Gemini дыяграма паказваецца, як оба этапы розташаваны верткаясна:
PromptInput
*
*
PromptTemplate
*
*
GoogleGenerativeAI
*
*
StrOutputParser
*
*
PromptTemplate
*
*
GoogleGenerativeAI
*
*
StrOutputParser
*
*
StrOutputParserOutput
Узел модэлю практычыцца два разы, таму што той самы объект модэлю выкарыстоўваецца ў двух пунктах у канвэі, а граф відбівае пункты, а не унікальныя об’екты.
Порэйнаванне двух реалізацый
Обе версіі выконваюць тую жа последовную структуру; ўнікальная разліка — у прадаўцы. Канвэй Gemini:
Input
↓
Explanation Prompt
↓
GoogleGenerativeAI
↓
StrOutputParser
↓
Summary Prompt
↓
GoogleGenerativeAI
↓
StrOutputParser
↓
Output
Канвэй OpenAI:
Input
↓
Explanation Prompt
↓
ChatOpenAI
↓
StrOutputParser
↓
Summary Prompt
↓
ChatOpenAI
↓
StrOutputParser
↓
Output
Прыказы, парсер і распалажэнне ланцоўкі ўзаемна деляюцца. Лядзьве ўсё, што зміняецца — цэх будовы модэлю:
# Google Gemini
llm = GoogleGenerativeAI(model="gemini-2.5-flash")
# OpenAI
llm = ChatOpenAI()
Сама складаная ланцоўка застаецца ідэнтычной між рознымі прадаўцамі:
chain = explaination_prompt | llm | parser | summary_prompt | llm | parser
У абстрактным выразе, последовная ланцоўка выглядае так:
Prompt 1
↓
LLM 1
↓
Parser
↓
Prompt 2
↓
LLM 2
↓
Parser
↓
Output
Разліка з простай ланцоўкай заключаецца ў тым, што існуе калькі этапаў, і кожны наступны этап залежыць ад пярэднего.
Полны шлях дадзенняў
У загальнай форме последовальны ланцюг перадае даны так:
Input
↓
PromptTemplate 1
↓
LLM 1
↓
StrOutputParser
↓
Detailed Report
↓
PromptTemplate 2
↓
LLM 2
↓
StrOutputParser
↓
Final Output
Аналізаваецца з вхіднай інформацыяю генератывальнага ІІ, уключаючы сформаваныя запиты:
{"topic": "Generative AI"}
↓
Explanation Prompt
↓
"Generate a detailed
report on Generative AI"
↓
LLM
↓
StrOutputParser
↓
Detailed Report
↓
Summary Prompt
↓
"Generate a 5 pointer
summary from the following text"
↓
LLM
↓
StrOutputParser
↓
5-Point Summary
Першы вызов моделі ёсць проміжным крокам, а не адказам. Яго выход стае сырой матэрыялам для наступной операцыі. Можна продовжваць додаць ўзлёты, калі робочы процес выклеквае ўсё больш іх:
Prompt
↓
LLM
↓
Parser
↓
Prompt
↓
LLM
↓
Parser
↓
Prompt
↓
LLM
↓
Output
Як бы дзьвінг не стаў пайплайн, порядак застаецца фіксаваным, і кожны узлёт чакае на поперадні. Гэта таксама значыць, што затрымкі нараджаюцца: тры вызовы моделей па спадчынству коштаюць прыблізна столькі, сколькі сума ўсіх часоў адпаведных адказоў. Таксама варта дадаць захід проты неконтрольванага выходу проміжных рэзультатаў, адтак як дужа дзьвінгая звітка прабрацуецца безпосередна ў наступны запит і збільшыць яго кантэйнер токенав.
Паралельныя ланцюгі: розпад па аднам і тым жа вхідным даннем
Паралельныя ланцюгі працуюць калькамі, запускаючы колькісць падланцюгоў адночасна. У тым час як секвенцыйны ланцюг чакае, пакуль кожны етап не завершыцца, пераходзячы да наступнага, паралельны ланцюг запускае незалежныя падланцюгі разам:
┌──→ Sub-Chain 1 ──→ Output 1
│
Input ──────────────┼──→ Sub-Chain 2 ──→ Output 2
│
└──→ Sub-Chain 3 ──→ Output 3
↓
Combined Output
Паколькі падланцюгі ўзаемна незалежны, жадны з іх не бачыць рэзультатаў іншага. Механізм просты:
- Кожны падланцюг прыме той самы вхід і створыць свой выход.
- Усе падланцюгі запускаюцца адночасна.
- Паралельны етап чакае, пакуль усі падланцюгі не завершыць свою роботу.
- Рэзультаты ўсіх падланцюгоў збіраюцца ў адну сумарную значэнне.
Эта сумарная значэнне — это слоўнік. Кожны ключ — це назва, яку вы далі падланцюгу, а кожна значэнне — це рэзультат работы таго падланцюга:
{
"facts": "...",
"definition": "...",
"use_case": "..."
}
Прыклад: тры практычныя формы аднае тэмы
Калі є такая тэма, як Штучны расум, мета — стварыць тры рэзультаты з аднаго і тога ж вхіднага дання: пяць цікавых фактов, кантрольна дэфініцыя і прыклад практычнага выкарыстання. Ніхто з гэтых рэзультатов не залежыць ад іншых, таму ўжыванне іх адна за другой будзе толькі марнаваць час. У замен яны выконваюцца паралельна:
Input
↓
RunnableParallel
/ | \
/ | \
↓ ↓ ↓
Facts Definition Use Case
↓ ↓ ↓
LLM LLM LLM
↓ ↓ ↓
Parser Parser Parser
\ | /
\ | /
↓ ↓ ↓
Combined Result
Роботу выканаюць тры пад-алгорытмы: facts — для пяці фактов, definition — для кантрольнай дэфініцыі, а use_case — для практычнага прыкладу. Яны аб’еднваюцца за дапамою RunnableParallel. У першай реалізацыі для всіх трох пад-алгорытмов викорыстоўваецца Gemini; у другай — OpenAI і Anthropic, пры чым дадаўся етап аб’еднання рэзультатов.
Чаму выконваць пад-алгорытмы паралельна
Є два прычыны для вжывання такога падходу.
Першым факторам ёсць скорасць. Выкананне запытоў модэля залежыць ад часу передачы дадзеных па сеціі і часу ўтворэння рэзультата, таму незалежныя запыты значна выгадваюць ад одночаснага выканання. Уявіце сабе тры падзаданні, кожна з якіх выконваецца прыблізна за два секунды. Якщо выканаваць іх адна за другой, ўсе разам займе больш часу:
Chain 1 → 2 seconds
Chain 2 → 2 seconds
Chain 3 → 2 seconds
Total ≈ 6 seconds
Якщо выканаваць іх адночасна, загальны час будзе прыблізна такім жа, як у найпамедленнейшага падзадання:
Chain 1 ─┐
Chain 2 ─┼→ execute together → ≈ 2 seconds
Chain 3 ─┘
Рэальная еконамія часу залежыць ад лімітаў швайнароў і таго, насколькі равнамерна выкананыя падзаданні завершаюцца, але для незалежных задач павышэння эфектыўнасці зазвычай є значным.
Другой фактор — структурны: падзаданні раздыляюць адно вхідна даныя, але не патрабуюць рэзультатаў іншых. Напрыклад, з адного дакумента можна хацець статыс, спіс ключовых тэмаў і набор пытанняў для вучэння. Кожна з задач можа працаваць безпасоверхнева з дакументам:
Document
↓
┌───────┼───────┐
↓ ↓ ↓
Summary Topics Questions
Апавяданне не павінна быць частыней выдзеўвання тэмы чыста генеравання запытанняў. Калі задачы связаны такім чынам, прымусовая ўпорядакаванасць іх у алею прыносіць затрымкі і спаявае крокі, якія не маюць рэальнай залежнасці. Калі ж яны дзейсна залежыць адзін ад другога, паралелізм ў такі момент не падходзіць, а секвэнцыйны ланцуг яўляеся правдзівым атрыбутам такога падходу.
Версія Gemini
Ёсць трывекторны падход у Gemini:
from langchain_google_genai import GoogleGenerativeAI
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableParallel
from dotenv import load_dotenv
load_dotenv()
llm = GoogleGenerativeAI(model="gemini-2.5-flash")
parser = StrOutputParser()
# Prompt 1: Facts
fact_prompt = PromptTemplate(
input_variables=["topic"],
template="Give five interesting short facts about: {topic}"
)
# Prompt 2: Definition
definition_prompt = PromptTemplate(
input_variables=["topic"],
template="Give a concise definition of: {topic}"
)
# Prompt 3: Use Case
usecase_prompt = PromptTemplate(
input_variables=["topic"],
template="What is a real-world use case of: {topic}?"
)
# Run all chains in parallel
parallel_chain = RunnableParallel({
"facts": fact_prompt | llm | parser,
"definition": definition_prompt | llm | parser,
"use_case": usecase_prompt | llm | parser
})
# Invoke parallel chain
result = parallel_chain.invoke({"topic": "Artificial Intelligence"})
# print(type(result))
# print(result)
# print("\n📌 Facts:\n", result["facts"])
# print("\n📘 Definition:\n", result["definition"])
# print("\n🛠️ Use Case:\n", result["use_case"])
parallel_chain.get_graph().print_ascii()
Тры шаблоны запытанняў, адна модэль і адин парсер з’едынены ў тры маленькія ланцугі, якія RunnableParallel пасля чаго запускае адночасна на вхідным даным Artificial Intelligence. Калькі ліній print знаходзяцца у стане коментавання; ўвімкнуўшы іх, можна побачыць тип рэзультата і кожную з полей.
Аналіз коду Gemini
Імпорты дадаюць ўтварэнне адной новай складовай:
from langchain_google_genai import GoogleGenerativeAI
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableParallel
Парацельна з кэшаментам модэлю, PromptTemplate і StrOutputParser, скрыпт імпортуе RunnableParallel з langchain_core.runnables, які адпавядае за адгэннае запусканне падчэйнам.
Ствараецца адзін модэль Gemini 2.5 Flash:
llm = GoogleGenerativeAI(model="gemini-2.5-flash")
Усе тры падчэйны яго выкарыстоўваюць. Выкарыстоўванне аднаго об’екта модэлю між паралельнымі галузямі ў безпеке; кожны вызов яўляецца незалежным.
Таксама выкарыстоўваецца адзін парасэр:
parser = StrOutputParser()
У першай просьбе запрашваюцца пяць короткіх фактов:
fact_prompt = PromptTemplate(
input_variables=["topic"],
template="Give five interesting short facts about: {topic}"
)
У другай — канкрэтная дэфініцыя:
definition_prompt = PromptTemplate(
input_variables=["topic"],
template="Give a concise definition of: {topic}"
)
У трэцей — прыклад практычнага застосоўвання:
usecase_prompt = PromptTemplate(
input_variables=["topic"],
template="What is a real-world use case of: {topic}?"
)
Усе тры просьбы выкарыстоўваюць аднаковую назву зменной:
{topic}
Гэтыя спільныя назвы ўможлівяюць таму, што адна ентырпрыза вхідных дадзеных можа супляваць усе галузі. Самыя задачы застаюцца абсалютна незалежнымі.
Тепер цэнтральная частка — паралельны ланцюг:
parallel_chain = RunnableParallel({
"facts": fact_prompt | llm | parser,
"definition": definition_prompt | llm | parser,
"use_case": usecase_prompt | llm | parser
})
RunnableParallel прымеў дыкшанары, значэнняя каторага ёсць об’екты типу Runnable. Ключы гэтага дыкшанара стануць ключамі выходных рэзультатаў:
facts
definition
use_case
Кожна з значэнняў ёсць самастоятельным, завершаным маленькім ланцюгам:
fact_prompt → LLM → parser
definition_prompt → LLM → parser
usecase_prompt → LLM → parser
Паколькі ніхто з іх не завісіць ад другога, ўсе яны можу працаваць адразу. За дапамогою сінхроннага invoke LangChain запускае галузі ў пуле задач; за дапамогою ainvoke яны запускаюцца як канкурэнтныя асінхронныя задачы.
Ланцюг запускаецца аднае раз:
result = parallel_chain.invoke({"topic": "Artificial Intelligence"})
з адной ентырпрызай вхідных дадзеных:
{"topic": "Artificial Intelligence"}
Гэты дыкшанар перадаецца кожной галузі. Канцэптуальна:
Artificial Intelligence
↓
RunnableParallel
/ | \
↓ ↓ ↓
Facts Definition Use Case
↓ ↓ ↓
LLM LLM LLM
↓ ↓ ↓
Parser Parser Parser
\ | /
\ | /
Combined Result
Што вяртаецца
Рэзультатам яе слоўнік з адной ентры па кожный пад-ланцюг:
{
"facts": "1. Artificial Intelligence enables machines to perform tasks that normally require human intelligence.\n2. ...",
"definition": "Artificial Intelligence is the field of creating systems that can perform tasks requiring human-like intelligence.",
"use_case": "A real-world use case of AI is customer support chatbots that can understand and respond to user queries."
}
Тэкст у кожным полі будзе разным па кожнай рэжыментарной выконанні, але структура застойная, і самэ гэта ўсё, на чым залежыць ваш код:
result["facts"]
result["definition"]
result["use_case"]
Кожны ключ вяртае рэзультат адпаведнага галузі, таму код, які працуе пасля, можа выбраць толькі тую частку, якая йому патрэбна.
Сумешчанне прадаўцоў і з’едынэнне рэзультатаў
Другая реалізацыя паказвае ўсё больш два ідэі: разныя галузі можу выкарыстоўваць разных прадаўцоў, а паралельны крок сам можа быць аднай з стадзій большога последовага прайсупутніка. Хто тут адна галузь выкарыстоўвае OpenAI, а іншая — Anthropic, а пасляпэўны вызов OpenAI з’едынае оба рэзультаты ў адні дакумент:
from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic
from dotenv import load_dotenv
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain.schema.runnable import RunnableParallel
load_dotenv()
model1 = ChatOpenAI()
model2 = ChatAnthropic(model_name='claude-3-7-sonnet-20250219')
prompt1 = PromptTemplate(
template='Generate short and simple notes from the following text \n {text}',
input_variables=['text']
)
prompt2 = PromptTemplate(
template='Generate 5 short question answers from the following text \n {text}',
input_variables=['text']
)
prompt3 = PromptTemplate(
template='Merge the provided notes and quiz into a single document \n notes -> {notes} and quiz -> {quiz}',
input_variables=['notes', 'quiz']
)
parser = StrOutputParser()
parallel_chain = RunnableParallel({
'notes': prompt1 | model1 | parser,
'quiz': prompt2 | model2 | parser
})
merge_chain = prompt3 | model1 | parser
chain = parallel_chain | merge_chain
text = """
Support vector machines (SVMs) are a set of supervised learning methods used for classification, regression and outliers detection.
The advantages of support vector machines are:
Effective in high dimensional spaces.
Still effective in cases where number of dimensions is greater than the number of samples.
Uses a subset of training points in the decision function (called support vectors), so it is also memory efficient.
Versatile: different Kernel functions can be specified for the decision function. Common kernels are provided, but it is also possible to specify custom kernels.
The disadvantages of support vector machines include:
If the number of features is much greater than the number of samples, avoid over-fitting in choosing Kernel functions and regularization term is crucial.
SVMs do not directly provide probability estimates, these are calculated using an expensive five-fold cross-validation (see Scores and probabilities, below).
The support vector machines in scikit-learn support both dense (numpy.ndarray and convertible to that by numpy.asarray) and sparse (any scipy.sparse) sample vectors as input. However, to use an SVM to make predictions for sparse data, it must have been fit on such data. For optimal performance, use C-ordered numpy.ndarray (dense) or scipy.sparse.csr_matrix (sparse) with dtype=float64.
"""
result = chain.invoke({'text': text})
print(result)
chain.get_graph().print_ascii()
У вхідных данных є тэкст працы пра апаратах вектораў падтрымкі. Адна з галузей запісвае з яго нотаткі па вучэнню, іншая — квіз з пяць запытанняў, а ланцюг з’еднання спаўнае нотаткі і квіз у адны документ.
Два моменты ў гэтым описе выклікаюць адзін увагу. У яму імпортуецца RunnableParallel з langchain.schema.runnable, што ўжо стары пацек, які існаваў да роздзелення на langchain_core; у новыях версіях яго можна знайсці ў langchain_core.runnables, як у прыкладзе з Gemini, а стары пацек можа не існаваць у новыях версіях. Таксама задана версія claude-3-7-sonnet-20250219, якая ўжо застарела; прадастальнікі з часам выключаюць старыя версіі модэляў, таму перад паўтарным вжыццём трэба пераканацца ў актуальным списку модэляў Anthropic.
Ствараюцца два об’екты модэляў:
model1 = ChatOpenAI()
model2 = ChatAnthropic(model_name='claude-3-7-sonnet-20250219')
ChatOpenAI запісвае прыметкі, а пазней выкарыстоўвае ўзаеднанне. ChatAnthropic запісвае тэст. У RunnableParallel няма нічога, што вымагало бы, каб усе галузі выкарыстоўвалі той самы модель, што ёсць зручна, калі адны прадаўцы є кращыя або дешавейшыя для пэўнага падзадання.
Прыметка-прампт ператварае вхідны текст у короткія, простыя прыметкі:
prompt1 = PromptTemplate(
template='Generate short and simple notes from the following text \n {text}',
input_variables=['text']
)
Прампт-тэст стварае пяць короткіх пар запытання-адказ з таго ж тексту:
prompt2 = PromptTemplate(
template='Generate 5 short question answers from the following text \n {text}',
input_variables=['text']
)
Ніякое з заданняў не патрабуе выходных дадзеных другога, таму яны ёсць натуральнымі паралельнымі галузямі.
Трэці прампт разны. Ён запускаецца пасля розгалужэння і патрабуе оба рэзультата:
prompt3 = PromptTemplate(
template='Merge the provided notes and quiz into a single document \n notes -> {notes} and quiz -> {quiz}',
input_variables=['notes', 'quiz']
)
У яму прызначаюцца два зменныя-вхіднікі:
notes
quiz
Этыя названні не ўзначальныя. Яны должны падходзіць да ключоў паралельнага крока, таму што словнік, створаны праз розпад, як раз і ўсё, што отрымае гэты запит.
Паралельны крок визначае два галузі:
parallel_chain = RunnableParallel({
'notes': prompt1 | model1 | parser,
'quiz': prompt2 | model2 | parser
})
Його структура — это двухнаправлены розпад, які з’еднаецца ў словнік:
Input Text
↓
RunnableParallel
/ \
↓ ↓
Notes Chain Quiz Chain
↓ ↓
OpenAI Anthropic
↓ ↓
Parser Parser
\ /
↓ ↓
Dictionary
Канцэптуальна проміжная значення выглядае так:
{
"notes": "...",
"quiz": "..."
}
Ланцюг з’еднання — это звычайны просты ланцюг:
merge_chain = prompt3 | model1 | parser
Ён выкарыстоўвае словнік з паралельнага крока, запоўняе {notes} і {quiz}, і прасіць OpenAI аб’еднаны документ.
У канцы два етапы з’еднаюцца за допамогою ўжо аднаго каналу:
chain = parallel_chain | merge_chain
Рэзультат — гібрыдны прабег: паралельна на пачатку, последова на канцы:
Input Text
↓
RunnableParallel
/ \
↓ ↓
Notes Quiz
↓ ↓
OpenAI Anthropic
↓ ↓
Parser Parser
\ /
↓ ↓
Combined Dictionary
↓
Merge Prompt
↓
OpenAI
↓
Parser
↓
Final Output
Ключовая деталь працэйвання — гэтыя два роўні працуюць незалежна, але стадія з’еднання не можа пачацца, пакуль оба не завершыць свою роботу. Таму самы сповольнены роўень задае тэндэнцію для всей структуры. Якщо адны роўень не выконаеся, паралельны процес зупіняецца, і з’еднання так і не адбываецца; таму, калі гэта мае значэнне, трэба абгортаваць ненадзеяныя прадстаўнікі суроців за дапамой праказаў або альтэрнатываў.
Усю цячу запускаюць за дапамой наступнага тексту:
result = chain.invoke({'text': text})
Роўні з нотамі і тэстамі обрабляюць тэкст адночасна. Калі оба завершыць свою роботу, ўсе іх выходныя даны потрапляюць у ланцуг з’еднання, а рэзультат у вачынку які ўтвараецца — гэта тое, што вяртае функцыя invoke.
Адразуванне структуры ланцуга
Памагальны інструмент для графа таксама візуалізуе структуру розгалужэння:
parallel_chain.get_graph().print_ascii()
Для трывухроўневага ланцуга Gemini дыяграма паказана розпадзел і з’еднанне:
Parallel<...>Input
/ | \
/ | \
PromptTemplate PromptTemplate PromptTemplate
↓ ↓ ↓
LLM LLM LLM
↓ ↓ ↓
StrOutputParser StrOutputParser StrOutputParser
\ | /
\ | /
Parallel<...>Output
Узлы Parallel<...>Input і Parallel<...>Output пазначаюць месца, дзе вводныя даны распадаюцца, і месца, дзе рэзультаты збіраюцца знову ў словнік.
Порэшчанне двух версіяў
Обе версіі дэманструюць адную і тую ж ідею: незалежныя пад-ланцюгі, якія працуюць адночасна. Версія Gemini — цэлае розгалужэнне з трыма галузямі на адным модэле:
Input
↓
RunnableParallel
├── Facts → Gemini → Parser
├── Definition → Gemini → Parser
└── Use Case → Gemini → Parser
↓
Dictionary Output
Версія з сумешанымі прадаўцамі дзельцяеся на две галузі на розных прадаўцах, а потым дадае етап з’еднання:
Input
↓
RunnableParallel
├── Notes → OpenAI → Parser
└── Quiz → Anthropic → Parser
↓
Dictionary Output
↓
Merge Chain
↓
OpenAI
↓
Output
Пад спінамі обох стоіць тая ж сама структура:
RunnableParallel({
"chain_1": chain_1,
"chain_2": chain_2,
"chain_3": chain_3
})
Ключы называюць пад-ланцюгі; значэнняя — самыя пад-ланцюгі. Як спрыт, калі звычны слоўнік Runnable’а праказваецца ўнутрь выразу з трубкай, LCEL аўтаматычна ператварае яго на RunnableParallel, таму часта можна пабачыць слоўнік, напісаны без явныя класу.
Полная дарожніца дадзеных
У загальным выглядзе паралельны ланцюг дыстрыбуіруе адна вхідную дань і збірае слоўнік:
Input
↓
RunnableParallel
/ | \
↓ ↓ ↓
Sub-chain Sub-chain Sub-chain
↓ ↓ ↓
Output Output Output
\ | /
\ | /
↓ ↓ ↓
Dictionary
Для прыклада штучнага інтэлекту на Gemini:
{"topic": "Artificial Intelligence"}
↓
RunnableParallel
/ | \
↓ ↓ ↓
Facts Definition Use Case
↓ ↓ ↓
Gemini Gemini Gemini
↓ ↓ ↓
Parser Parser Parser
\ | /
\ | /
Dictionary
У канечным значэнні ёсць по аднаму ключу на кожную галужку:
{
"facts": "...",
"definition": "...",
"use_case": "..."
}
Гэта ўсё тое, што адрознівае яго ад последовныя ланцюга. У последовным ланцюгу кожны этап выкарыстоўвае выхід пярэднего этапу:
A → B → C → D
У паралельным ланцюгу незалежныя этапы працуюць паралельна на адной і той жа вхіднай дані, а ўсе яныя рэзультаты з’еднаюцца:
┌→ A ─┐
Input ─┼→ B ─┼→ Combined Output
└→ C ─┘
Для задач, які выкарыстоўваюць спольныя вхідныя даны, але не рэзультаты, трэба выкарыстоўваць паралелізм, а для задач, якія практычна будуюцца на поперадніх кроках, — последовную структуру. Сумешчанне гэтых двух падходаў, як у прыкладзе з нотаткамі і тэстамі, є распашчастым і абсалютна стандартным.
Умовныя ланцюгі: направленне вхідных дадзеных да правага падланцюга
Умовны ланцюг выбірае свой наступны крок пад час выконання. Последовны ланцюг завжды выконвае кожны етап у фіксаванай парады, а паралелізны ланцюг выконвае всі галузі адразу. Умовны ланцюг ацэнюе умову для вхідных дадзеных і выконвае толькі тую галузь, якая падходзіць:
Input
↓
Condition Check
↓
┌───────────────┐
│ │
Yes No
↓ ↓
Chain 1 Chain 2
↓ ↓
Output 1 Output 2
\ /
\ /
Final Output
У LangChain гэта выражаецца за дапамою RunnableBranch. Вы даёте яму спіс пар, у кожной з якых узгадваецца умова і адпавядаючы Runnable, плюс ў канцы — стандартны Runnable. Умовы пераглядаюцца па порядку, і першая з якіх вяртае True вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялічына, якая адначасова вялі
Input Text
↓
Condition Check
↓
Is word count > 50?
↓
┌───────┴───────┐
Yes No
↓ ↓
Summary Extend
Chain Chain
↓ ↓
LLM LLM
↓ ↓
Parser Parser
\ /
\ /
Final Output
Два запиты керуюць гілкамі: summary_prompt просіць кароткае падсумаванне, а extend_prompt прасіць модель развіць ідею. Невяленька функцыя is_long_text() лічыць слова, а RunnableBranch выкарыстоўвае яе рэзультат для выбору ланцоўкі. Як і у іншых патэранах, існуе версія для Gemini і версія для OpenAI.
У загальнай форме двухканальны умовны пайплайн выглядае так:
User Input
↓
Condition Check
↓
┌───────┴───────┐
Yes No
↓ ↓
Prompt 1 Prompt 2
↓ ↓
LLM LLM
↓ ↓
Parser Parser
\ /
└──────┬───────┘
↓
Final Output
Для будзь-якага заданага вхіднага даных запускаецца толькі адна гілка.
Аналіз конкрэтнага вхіднага даных
Прыкладны вхідны данны — адна-рэченневая вызначэння машыннага навчання, якае описвае яго як галузь ШІ, у якой статыстычныя методы дазволяюць комп’ютерам вучыцца на базе дадзеных.
Этае рэчэнне складаецца з 22 слоў, што значна меньш за порог у 50 слоў, таму умова вяртае False, і выбіраецца ланцоўка расшырэння:
Input Text
↓
Condition Check
↓
22 > 50?
↓
No
↓
Extend Chain
↓
Gemini 2.5 Flash
↓
StrOutputParser
↓
Extended Text
Як толькі увайшоў текст дужэй 50 слоў, умова станавіцца True, і даныя надаюцца на відпаведную ланцуговую обработку.
Калі корыстна умовная маршрутазацыя
Маршрутазацыя ўжыткавая тады, калі аднаковы тип даных патрабуе розныя способы обработкі залежна ад певных характэрыстыкаў:
- Дзейга: сконцэнтраваць длугі текст, распісаць кораткі текст.
- Настроены: надаць пазитыўныя і негатыўныя адзывы на розныя ланцуговыя варыянты адпаведзення.
- Тып запиту: направіць кожную категорыю запитоў на спецыялізаваны ланцуг.
Важным ўскладненням яе є тое, што не платзіце за веткі, якія вам не трэба. Выконваецца толькі выбраны шлях:
Input
↓
Condition
↓
Choose one branch
↓
Execute selected chain
↓
Output
Это робіць рабочы процес адаптабельным у спосабе, якога не можа быць фіксаваная последовасц, і ўшчэджае вызывы моделі паўстаноўна з выканання кожнай веткі і абмовы рэзультатаў.
Версія Gemini
Ця частка паказвае цэлую праграму маршрутацыі на Gemini:
from langchain_google_genai import GoogleGenerativeAI
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableBranch
from dotenv import load_dotenv
load_dotenv()
llm = GoogleGenerativeAI(model="gemini-2.5-flash")
parser = StrOutputParser()
# Prompt 1 : Summary
summary_prompt = PromptTemplate(
input_variables=["text"],
template="Summarize the following text briefly:\n\n{text}"
)
# Prompt 2 : Extend
extend_prompt = PromptTemplate(
input_variables=["text"],
template="Extend the idea in the following long text:\n\n{text}"
)
# Chain 1 : Summary
summary_chain = summary_prompt | llm | parser
# Chain 2 : Extend
extend_chain = extend_prompt | llm | parser
def is_long_text(input: dict) -> bool:
return len(input["text"].split()) > 50
conditional_chain = RunnableBranch(
(lambda x: is_long_text(x), summary_chain),
extend_chain
)
text_input = {
"text": "Machine learning is a field of artificial intelligence that uses statistical techniques to give computer systems the ability to learn from data."
}
result = conditional_chain.invoke(text_input)
# print("\nOutput:\n")
# print(result)
conditional_chain.get_graph().print_ascii()
У яй выдзеяны два маленькія ланцугі, summary_chain і extend_chain, а таксама предыкат для пільнення колькасці слоў, пасля чаго RunnableBranch можа выбіраць між гэтымі ланцугамі на адной засадзе гэтага предыката.
Аналіз коду Gemini
Модэль знова ўжо Gemini 2.5 Flash:
llm = GoogleGenerativeAI(model="gemini-2.5-flash")
Об’ект StrOutputParser ператварае кожны адпаведзь у страку:
parser = StrOutputParser()
У запытку на стварэнне апісу просіцца дадаць краткі апіс:
summary_prompt = PromptTemplate(
input_variables=["text"],
template="Summarize the following text briefly:\n\n{text}"
)
Запытка на розшырэнне выкорыстоўваецца на іншай дароге:
extend_prompt = PromptTemplate(
input_variables=["text"],
template="Extend the idea in the following long text:\n\n{text}"
)
Акцыянуцеся да словам: тут гаворыца пра "наступны дзялгі текст", але гэты варіант выконваецца толькі для короткіх вхідных дадзенняў. Гэта не спрычынае ніякіх проблем, але даёт модэлю хыбны сигнал. Перапісаўшы яго ў нечыць на кшталт "Раскажыце болей пра ідею, якая ў наступным тексте", можна здарыць невялікі правак у вашай версіі.
Кожны запрос сумашчаецца з модэлем і парсерам у свой сабэт ланцоў:
summary_chain = summary_prompt | llm | parser
extend_chain = extend_prompt | llm | parser
Гэта дае два незалежныя шляхі:
Summary Prompt → Gemini → Parser
Extend Prompt → Gemini → Parser
Умовны крок выбірае, які з іх выконваецца.
Умова — это звычная функцыя Python:
def is_long_text(input: dict) -> bool:
return len(input["text"].split()) > 50
Ўона дзеліць input["text"] на часткі за дапамою прабелаў і пісачыць ўжо ўскладнення. Їё функцыя проста:
True → if word count > 50
False → otherwise
Разлікванне за пробеламі є приблізным спосабом підрахунку колькасці слоў; знакі пунктуацыі залишаюцца пры слоўх, а терміны, з’еднаныя хіліем, лічуюцца як адна елемент. Гэта падходзіць для маршрутування за довжыной, але якщо вам справаўна важныя ліміты модэлю, трэба підраховваць токены, а не слоўх. Таксама зважце, што назва параметра input перакрывае вбудованы элемент Python; гэта працюе, але назва на кшталт data є болей чыстай.
Калі всі элементы ўставлены, RunnableBranch складае маршрутизатор:
conditional_chain = RunnableBranch(
(lambda x: is_long_text(x), summary_chain),
extend_chain
)
Першы аргумент — это тупл, які выглядае як правіла:
If is_long_text(x) is True
↓
Execute summary_chain
Паслядній аргумент, які не ўкладзены ў тупл, є значэнням за замовчаннем:
Otherwise
↓
Execute extend_chain
Обгортка лямбды не ўсунутая. RunnableBranch прыймае будзь-які функцыйнальны об’ект як умову, таму прынятне таксама ўжываць is_long_text безпосередна. У сукупности маршрутуванне выглядае так:
Input
↓
is_long_text()
/ \
True False
↓ ↓
summary_chain extend_chain
↓ ↓
Gemini Gemini
↓ ↓
Parser Parser
\ /
\ /
Output
Для ўрабаткі трэбаеся слоўнік з вхіднымі данымі:
result = conditional_chain.invoke(text_input)
Спачатку пераканальваецца умова, і толькі выбраныя галузь выпрацоўвае запыт да модэлю.
Што вяртаецца
З прыкладам вхідных даных з 22 словамі, прэдыкат вяртае:
is_long_text() → False
таму extend_chain адпрацоўвае вхідныя даны. Канцэптуальна:
Input Text
↓
Word Count Check
↓
False
↓
extend_chain
↓
Gemini 2.5 Flash
↓
StrOutputParser
↓
Extended Text
Якщо падаць текст дыяўальней 50 слоў, прэдыкат вяртае:
is_long_text() → True
чыя змінюе шлях на:
Input Text
↓
Word Count Check
↓
True
↓
summary_chain
↓
Gemini 2.5 Flash
↓
StrOutputParser
↓
Short Summary
Адна паіплайн, два можлівыя шляхі, якія выбіраюцца на адной з вхідных даных.
Той самы рутэр у OpenAI
Версія для OpenAI застаецца з той самай логікай рутавання, але змінюе толькі модэль:
from langchain_openai import ChatOpenAI
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableBranch
from dotenv import load_dotenv
load_dotenv()
llm = ChatOpenAI(model="gpt-4o-mini")
parser = StrOutputParser()
# Prompt 1 : Summary
summary_prompt = PromptTemplate(
input_variables=["text"],
template="Summarize the following text briefly:\n\n{text}"
)
# Prompt 2 : Extend
extend_prompt = PromptTemplate(
input_variables=["text"],
template="Extend the idea in the following long text:\n\n{text}"
)
# Chain 1 : Summary
summary_chain = summary_prompt | llm | parser
# Chain 2 : Extend
extend_chain = extend_prompt | llm | parser
def is_long_text(input: dict) -> bool:
return len(input["text"].split()) > 50
conditional_chain = RunnableBranch(
(lambda x: is_long_text(x), summary_chain),
extend_chain
)
text_input = {
"text": "Machine learning is a field of artificial intelligence that uses statistical techniques to give computer systems the ability to learn from data."
}
result = conditional_chain.invoke(text_input)
print(result)
conditional_chain.get_graph().print_ascii()
Две ланцюгі, адны прэдыкат, адна RunnableBranch — так сама, як і у Gemini.
Модэль – gpt-4o-mini:
llm = ChatOpenAI(model="gpt-4o-mini")
Два шаблону запитоў застаюцца незменнымі: адны для стварэння падсумку, другі — для розшырэння:
summary_prompt = PromptTemplate(
input_variables=["text"],
template="Summarize the following text briefly:\n\n{text}"
)
extend_prompt = PromptTemplate(
input_variables=["text"],
template="Extend the idea in the following long text:\n\n{text}"
)
Ланцюгі складаюцца так сама, як і раней:
summary_chain = summary_prompt | llm | parser
extend_chain = extend_prompt | llm | parser
Тепер оба шляхі праходзячы через OpenAI:
Summary Prompt → OpenAI → StrOutputParser
Extend Prompt → OpenAI → StrOutputParser
Прыямэль — это той самы пераказ на 50 слоў:
def is_long_text(input: dict) -> bool:
return len(input["text"].split()) > 50
Рутэр будуецца такім жа спосабам:
conditional_chain = RunnableBranch(
(lambda x: is_long_text(x), summary_chain),
extend_chain
)
Рэзультат True адправляе вхідны даны ў summary_chain; всё інша — у extend_chain.
Ланцюг запускаецца, а рэзультат выводзіцца:
result = conditional_chain.invoke(text_input)
print(result)
Для прыкладнага рэчення приямэль зноў дае такі ж результат:
is_long_text() → False
таму ланцюг розшырэння запускаецца і вяртае расширжаны апісанне вхідных даных, падобна да гэтага:
Machine learning enables computer systems to learn patterns
from data and improve their performance without being explicitly
programmed for every task. It is widely used in applications
such as recommendation systems, image recognition, and predictive
analytics.
Точны текст залежыць ад модэлю і будзе меняцца праз кожны запуск.
Адраптаванне структуры ланцуга
Той жа дапаможная прыемка для графа выкорыстоўваецца і для рутэраў:
conditional_chain.get_graph().print_ascii()
Выразны элемент адрасаванага дыяграмы — это вузол Branch. Це точка прыняцтва рашэння, дзе LangChain ацэнюе умовы і перадае вхідны даны аднаму з можлівых ланцугоў.
Як разлічаюцца два галузі
Кожны галузь мае свою адпаведальнасць. Галузь падсумовання:
summary_prompt
↓
Gemini
↓
StrOutputParser
↓
Short Summary
Галузь расшырэння:
extend_prompt
↓
Gemini
↓
StrOutputParser
↓
Extended Text
Прыемка выбірае между ямі:
Input
↓
is_long_text()
/ \
True False
↓ ↓
Summary Chain Extend Chain
Гэтае ўсё галоўная разліка з паралельным ланцугам: галузі не працуюць адночасова. Выконваецца толькі той шлях, якога умовы адпаведнае, што дазволяе зменшыць витраты і час адпаведна да фактычна патрэбнай роботы.
Рутэізацыя на адпаведнасці самога модэлю
Умовы не обмежваюцца толькі простымі критэрамі, такімі як калькуляцыя колькасці слоў. Шырока і болей эфектыва модэль — це калі першы ланцуг класифікуе вхідны дадзеныя, а пасля на ўснове гэтай класыфікацыі працюе далей. У прыкладзе з аналізам настрою вярнутыя адгукаты пазначаюцца як адна з двух значэнняў:
positive
negative
а RunnableBranch перадае іх ланцугу адпаведнай адпаведзі для гэтага пазначэння:
Feedback
↓
Sentiment Classifier
↓
┌───────────────┐
│ │
Positive Negative
│ │
↓ ↓
Positive Negative
Response Response
│ │
└───────┬───────┘
↓
Output
Надзеяны спосаб стварэння класыфікатора — калі модель вяртае структураваны выхід, які пераканаўляецца за дапамогою модэлі Pydantic, напрыклад, полье, якое можа мячыцца толькі значэнням positive або negative. Умовы розгалужэння пасля чаго пераканаюцца гэтым полем, а не парсуюць вольна напісаны текст, што запобегае неспадзянкам, калі модель вяртае "Positive." з великай буквы і крапкай. Заўжды трэба заставляць наявны розумны стандартны варіант розгалужэння, адтаку класыфікатор у канцы канцоў вярнуе ўсё, чаго вы не прыгадвалі.
Это паказвае, што якісць можа залежаць ад інфармацыі, якую выдала паказвальная ланцоўка раней, а не толькі ад первісных даных. Для болей сложнага маршрутавання дакументацыя LangChain таксама описвае маршрутаванне за дапамою спецыяльнай функцыі, якая абгортаецца ў RunnableLambda і вяртае ланцоўку для запуску; варта пераканацца, які падход рекамендуюць дакументы для вашай версіі.
Полны шлях даных
Загальныя правілы для умовных сцэнарыёў ёсць:
Input
↓
Condition Check
↓
┌───────┴───────┐
↓ ↓
Condition Condition
True False
↓ ↓
Chain 1 Chain 2
↓ ↓
LLM LLM
↓ ↓
Parser Parser
\ /
\ /
└─────┬───────┘
↓
Output
Для прыклада з дужынай тексту:
Machine Learning Text
↓
is_long_text()
↓
Word Count > 50?
/ \
Yes No
↓ ↓
Summary Extend
Chain Chain
↓ ↓
Gemini Gemini
↓ ↓
Parser Parser
\ /
\ /
Output
Короткія даны праходзяць адной дарогай:
Word Count ≤ 50
↓
Extend Chain
Дзялгія даны — іншай:
Word Count > 50
↓
Summary Chain
Якщо адступіць, то гэтыя чатыры формы знаходзяцца па боку ад боку:
Simple Chain
A → B → C
Sequential Chain
A → B → C → D → E
Parallel Chain
┌→ B ─┐
A ────┼→ C ─┼→ Output
└→ D ─┘
Conditional Chain
↓
A
↓
Condition
/ \
B C
Умовная ланцоўка надае працэсу LLM можлівасць прымяць рашэнні, запускаючы разную логіку залежна ад даных або ад значэння, вычысленага раней у практыцы обробкі.
Практычныя прытамулкі перад адправайом
Есць калькі прычын, якія варта памяцаць, калі пераходзіце з прыкладоў да рэальнага коду:
- Указваць назвы модэляў. Канстрактары без параметра модэля успадковуюць стандартныя настройкі, якія викорыстоўваецца ў інтеграцыі, а ідэнтыфікаторы з конкретным часам створэння згодна з часом будуць відмовленыя.
- Зберагаць парсэр. Модэлі чату вяртаюць об’екты паведамленняў;
StrOutputParserдае кожнам наступным кроку прыемлівы типstr. - Старанна ставіцца да перадачы данных. Чыстая строчка автаматычна запоўняе прапановку толькі тады, калі ў гэй прапановцы є роўна адна зменная. Для большага колькасці данных неабходна явна стварыць словнік.
- Спаўнасць ключоў з зменнымі. Пры розпадзе на частыні, якія пасля таго з’еднаюцца, паралельныя ключы павінны спаўнасці з назвамі зменных у прапановцы з’еднання.
langchain_core.runnables, а не з старых шляхоў langchain.schema.get_graph().print_ascii() (з установленым grandalf) — это просты спосаб убедзіцца, што складны ланцуг налаштаваны як трэба.Для болей шырокага апісання, якэ групавае гэтыя ланцугі разам з процесамі выкарыстоўвання дадзеных, інструментамі та агентамі, якія працуюць на локальных моделях, адзірніце LangChain 1.x у практыцы: ланцугі, RAG, інструменты та агенты локальна.
Ключовыя выводы
- Ланцюг — это заявлены пайплайн, у якога выход кожнага компонента падае на вхід наступнаго; аператар
|створае яго з будзь-якіх Runnables. - Простыя ланцюгі — це адна пряма перадача данных; последовныя ланцюгі складаюцца з калькі перадач, што дагучае затрэмцоўку на кожны ўзлок.
RunnableParallelзапускае незалежныя падланцюгі паралельна і вяртае словнік, ключы якога — назвы галузей, а наступныя ўзлокі можа выкарыстоўваць іх па назвам вяртаных значэнняў.RunnableBranchацэнюе умовы па порядку і запускае толькі першую падходячую галузь, а якщо ёй няма, то пераходзіць да стандартнага варыянта.- Умовы можу быць простымі пераканальваннямі дадзеных або рашэннямі, базаванымі на структураванай класыфікацыі паказанага раней модэлю.
- Паколькі модэль — це толькі адны компонент, змена межу Gemini, OpenAI чы Anthropic зазвычай выражаецца ў зміне адной лініі коду, тады як запросы, парсеры і структура застаюцца тымі ж.
Усе чатыры падчынныя модэлі ствараны на адной і той жа базе — інтэрфейсе Runnable, які ў першую чаргу дазволяе складаць запиты, модэлі, парсеры і власныя функцыі. Калі гэтыя форматы стануць знайомымі, наступны логічны крок — безпосередняя розумеўка самога інтэрфейсу.