0Pricing
AI Engineering Academy · Lekcja

Rozgałęzianie i równoległe łańcuchy

Uczestnicy zbudują konstrukcje RunnableParallel i RunnableBranch, aby uruchamiać wiele łańcuchów jednocześnie lub kierować dane wejściowe do różnych łańcuchów na podstawie dynamicznych warunków.

Rozgałęzianie i równoległe łańcuchy to bezpłatna lekcja AI Engineering Academy na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej AI Engineering Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs AI Engineering Academy zawiera 4 lekcji w sumie.

Dlaczego równoległość i rozgałęzianie mają znaczenie

Rzeczywiste potoki LLM często muszą wykonywać wiele czynności jednocześnie albo kierować żądania w różne miejsca zależnie od ich treści. Łańcuchy równoległe uruchamiają wiele gałęzi jednocześnie, zmniejszając opóźnienia, gdy zadania są niezależne. Łańcuchy rozgałęzione kierują dane wejściowe do różnych wyspecjalizowanych łańcuchów na podstawie dynamicznych warunków. LCEL natywnie obsługuje oba wzorce za pomocą RunnableParallel i RunnableBranch.

Podstawy RunnableParallel

RunnableParallel przyjmuje słownik, w którym każdy klucz jest powiązany z obiektem Runnable. Po wywołaniu uruchamia wszystkie gałęzie współbieżnie i zwraca słownik, w którym każdy klucz zawiera wynik swojej gałęzi. Jest to idealne rozwiązanie, gdy chcą Państwo wygenerować wiele wyników na podstawie tych samych danych wejściowych — na przykład jednocześnie utworzyć podsumowanie i wyodrębnić słowa kluczowe.

from langchain_core.runnables import RunnableParallel
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

model = ChatOpenAI(model='gpt-4o-mini')
parser = StrOutputParser()

parallel = RunnableParallel(
    summary=(
        ChatPromptTemplate.from_template('Summarize: {text}') | model | parser
    ),
    keywords=(
        ChatPromptTemplate.from_template('Extract keywords from: {text}') | model | parser
    )
)

result = parallel.invoke({'text': 'Long document text here...'})
print(result['summary'])
print(result['keywords'])

Równoległość ze skróconą składnią słownika

LCEL udostępnia wygodny skrót: przekazanie zwykłego dict jako kroku w łańcuchu potoku automatycznie opakowuje go w RunnableParallel. Dzięki temu równoległe gałęzie wyglądają naturalnie i nie wymagają jawnego tworzenia instancji klasy. Klucze słownika stają się kluczami wyniku, a wartości są gałęziami uruchamianymi współbieżnie.

from langchain_core.runnables import RunnablePassthrough

# Dict shorthand creates RunnableParallel automatically
chain = (
    RunnablePassthrough.assign(
        sentiment=(
            ChatPromptTemplate.from_template('Sentiment of: {review}')
            | model | parser
        ),
        aspects=(
            ChatPromptTemplate.from_template('List aspects mentioned in: {review}')
            | model | parser
        )
    )
)

result = chain.invoke({'review': 'Great battery but poor camera quality.'})
print(result['sentiment'])
print(result['aspects'])

Zrozumienie RunnableBranch

RunnableBranch kieruje dane wejściowe do różnych łańcuchów na podstawie warunku. Należy przekazać listę par (condition, runnable) oraz domyślny obiekt Runnable. Gałąź sprawdza warunki w podanej kolejności i uruchamia pierwszą pasującą gałąź. Umożliwia to routing oparty na intencji — zapytania do działu obsługi klienta można kierować do łańcucha wsparcia, a pytania techniczne do łańcucha dokumentacji.

from langchain_core.runnables import RunnableBranch

technical_chain = (
    ChatPromptTemplate.from_template('Technical answer: {query}') | model | parser
)
general_chain = (
    ChatPromptTemplate.from_template('General answer: {query}') | model | parser
)

branch = RunnableBranch(
    (lambda x: 'error' in x['query'].lower() or 'bug' in x['query'].lower(),
     technical_chain),
    general_chain  # default branch
)

result = branch.invoke({'query': 'I got a TypeError in my code'})
# Routes to technical_chain because 'error' is in the query

Routing semantyczny z klasyfikacją LLM

Bardziej elastyczny wzorzec routingu wykorzystuje wywołanie klasyfikującego LLM do określenia, której gałęzi użyć. Router najpierw wywołuje niewielki model w celu sklasyfikowania intencji danych wejściowych, a następnie na podstawie klasyfikacji kieruje je do odpowiedniego wyspecjalizowanego łańcucha. Rozwiązanie to obsługuje niuanse pomijane przez dopasowywanie słów kluczowych, ale wymaga jednego dodatkowego wywołania API.

from langchain_core.output_parsers import StrOutputParser

# Step 1: classify intent
classify_prompt = ChatPromptTemplate.from_template(
    'Classify this query as exactly one of: billing, technical, general.\nQuery: {query}'
)
classifier = classify_prompt | model | StrOutputParser()

# Step 2: route based on classification
def route(classification_result: dict):
    topic = classification_result['topic'].strip().lower()
    if topic == 'billing':
        return billing_chain
    elif topic == 'technical':
        return technical_chain
    return general_chain

full_chain = (
    RunnablePassthrough.assign(topic=lambda x: classifier.invoke(x))
    | RunnableLambda(route)
)

Równoległy RAG: wiele retrieverów

W zaawansowanych systemach RAG można jednocześnie pobierać dane z wielu źródeł i scalać wyniki. RunnableParallel umożliwia jednoczesne wysyłanie zapytań do bazy produktów, repozytorium FAQ i indeksu dokumentacji. Następnie krok scalania łączy najlepsze wyniki przed przekazaniem kontekstu do LLM, zapewniając modelowi bogatszą bazę informacji.

from langchain_core.runnables import RunnableParallel, RunnablePassthrough

# Assume these retrievers are already set up
faq_retriever = faq_vectorstore.as_retriever(search_kwargs={'k': 3})
doc_retriever = doc_vectorstore.as_retriever(search_kwargs={'k': 3})

retrieval = RunnableParallel(
    faq_results=faq_retriever,
    doc_results=doc_retriever
)

def merge_docs(retrieved: dict) -> str:
    all_docs = retrieved['faq_results'] + retrieved['doc_results']
    return '\n\n'.join(d.page_content for d in all_docs)

pipeline = (
    retrieval
    | RunnableLambda(merge_docs)
    | ChatPromptTemplate.from_template('Context: {context}\nAnswer: {question}')
    | model | parser
)

Łańcuchy warunkowe z itemgetter

Gdy trzeba wybrać konkretny klucz z równoległego wyniku albo przekazać do następnego kroku tylko część kontekstu, Pythonowy operator.itemgetter sprawdza się jako lekki selektor Runnable. Jest to przydatne po kroku równoległym, gdy różne gałęzie zwracają różne klucze i trzeba wyodrębnić tylko ten właściwy dla kolejnego etapu przetwarzania.

from operator import itemgetter
from langchain_core.runnables import RunnablePassthrough

# After a parallel step, extract just the summary
chain = (
    RunnableParallel(
        summary=summary_chain,
        sentiment=sentiment_chain
    )
    | itemgetter('summary')  # pass only the summary onward
    | translate_chain
)

# itemgetter works because dict.__getitem__ is a valid transform

Mierzenie przyspieszenia równoległego

Najważniejszą zaletą RunnableParallel jest skrócenie czasu rzeczywistego wykonania. Trzy sekwencyjne wywołania LLM trwające po 2 sekundy zajęłyby łącznie 6 sekund. Wykonane równolegle zakończą się po około 2 sekundach — tyle, ile trwa najwolniejsza gałąź. Wywołania równoległe jednocześnie zwiększają jednak zużycie tokenów, dlatego należy kontrolować limity szybkości. W razie potrzeby proszę użyć max_concurrency w batch() albo ustawić limity szybkości dla poszczególnych kluczy.

import time

# Measure sequential time
start = time.time()
result1 = chain_a.invoke(input_data)
result2 = chain_b.invoke(input_data)
result3 = chain_c.invoke(input_data)
seq_time = time.time() - start
print(f'Sequential: {seq_time:.2f}s')

# Measure parallel time
start = time.time()
results = RunnableParallel(a=chain_a, b=chain_b, c=chain_c).invoke(input_data)
par_time = time.time() - start
print(f'Parallel: {par_time:.2f}s')
print(f'Speedup: {seq_time/par_time:.1f}x')

Asynchroniczne łańcuchy równoległe

Jeśli w aplikacji asynchronicznej potrzebne jest rzeczywiście nieblokujące wykonanie równoległe, proszę użyć RunnableParallel.ainvoke(). Wewnętrznie LCEL korzysta z asyncio.gather(), aby uruchamiać gałęzie współbieżnie w pętli zdarzeń. Jest to szczególnie ważne w usługach FastAPI, gdzie każdy program obsługi żądania jest korutyną — użycie interfejsu asynchronicznego zapobiega blokowaniu pętli zdarzeń nawet przy wielu jednoczesnych wywołaniach LLM.

import asyncio

async def analyze_document(text: str) -> dict:
    parallel = RunnableParallel(
        summary=summary_chain,
        keywords=keyword_chain,
        sentiment=sentiment_chain
    )
    # All three chains run concurrently with asyncio.gather internally
    result = await parallel.ainvoke({'text': text})
    return result

# In FastAPI:
from fastapi import FastAPI
app = FastAPI()

@app.post('/analyze')
async def analyze(request: dict):
    return await analyze_document(request['text'])

Zagnieżdżanie łańcuchów równoległych i sekwencyjnych

Złożone potoki często łączą kroki sekwencyjne i równoległe. Można zagnieżdżać RunnableParallel w potoku sekwencyjnym i odwrotnie. Przykładowo można najpierw sklasyfikować intencję (sekwencyjnie), następnie równolegle wykonać wyszukiwanie i formatowanie kontekstu, a na końcu wygenerować odpowiedź (sekwencyjnie). LangChain prawidłowo obsługuje zagnieżdżenia, dzięki czemu zaawansowane potoki pozostają czytelne i nie wymagają plątaniny callbacków.

# Full pipeline: classify → parallel retrieval → generate
pipeline = (
    RunnablePassthrough.assign(
        intent=classify_chain  # sequential: classify first
    )
    | RunnablePassthrough.assign(
        context=RunnableParallel(  # parallel: retrieve from both sources
            faq=faq_retriever,
            docs=doc_retriever
        )
    )
    | format_context_chain  # sequential: format merged context
    | generate_answer_chain  # sequential: call LLM
)

Obsługa błędów w gałęziach

Gdy jedna z gałęzi RunnableParallel zakończy się błędem, całe wywołanie równoległe domyślnie zgłasza wyjątek. Proszę użyć .with_fallbacks() na elementach wykonywalnych poszczególnych gałęzi, aby elegancko obsługiwać błędy na poziomie gałęzi. W przypadku RunnableBranch każdą gałąź można opakować w try-except wewnątrz RunnableLambda albo użyć domyślnego łańcucha zastępczego do obsługi błędów routingu.

from langchain_core.runnables import RunnableParallel

# Wrap each branch with a fallback
safe_summary = summary_chain.with_fallbacks([
    RunnableLambda(lambda x: 'Summary unavailable')
])
safe_sentiment = sentiment_chain.with_fallbacks([
    RunnableLambda(lambda x: 'Sentiment unavailable')
])

robust_parallel = RunnableParallel(
    summary=safe_summary,
    sentiment=safe_sentiment
)

# Now one branch failing won't kill the entire parallel call

Szybki test

Proszę sprawdzić swoją wiedzę na temat rozgałęzień i łańcuchów równoległych w LCEL.

Podsumowanie lekcji

W tej lekcji poznali Państwo: RunnableParallel uruchamia wiele łańcuchów współbieżnie i zwraca słownik wyników, zmniejszając opóźnienia niezależnych wywołań LLM; RunnableBranch kieruje dane wejściowe do różnych wyspecjalizowanych łańcuchów na podstawie warunków lub klasyfikacji wykonanej przez LLM; a zagnieżdżanie kroków równoległych i sekwencyjnych pozwala budować zaawansowane, wielościeżkowe potoki, które pozostają czytelne. W następnej części omówimy strumieniowanie danych wyjściowych w LangChain.

Często zadawane pytania

Czy lekcja „Rozgałęzianie i równoległe łańcuchy” jest bezpłatna?

Tak — pełny tekst „Rozgałęzianie i równoległe łańcuchy” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu AI Engineering Academy, przejdź na CoddyKit PRO. Kurs AI Engineering Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Rozgałęzianie i równoległe łańcuchy”?

Uczestnicy zbudują konstrukcje RunnableParallel i RunnableBranch, aby uruchamiać wiele łańcuchów jednocześnie lub kierować dane wejściowe do różnych łańcuchów na podstawie dynamicznych warunków. Ćwiczysz AI Engineering Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć AI Engineering Academy?

Nie wymagamy żadnego doświadczenia. AI Engineering Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.

Ile czasu zajmuje lekcja „Rozgałęzianie i równoległe łańcuchy”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji AI Engineering Academy?

Tak. Każda lekcja AI Engineering Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Architektura LangChain i podstawowe abstrakcje
  2. Tworzenie łańcuchów za pomocą LCEL
  3. Rozgałęzianie i równoległe łańcuchy
  4. Strumieniowanie danych wyjściowych w LangChain
← Powrót do AI Engineering Academy