AI Engineering Academy · Lección

Chains ramificadas y paralelas

Creará estructuras RunnableParallel y RunnableBranch para ejecutar varias chains simultáneamente o dirigir la entrada a distintas chains según condiciones dinámicas.

Lección 3 de 413 pasos

Chains ramificadas y paralelas es una lección gratuita de AI Engineering Academy en CoddyKit. Esta es la lección 3 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de AI Engineering Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de AI Engineering Academy incluye 4 lecciones en total.

Por qué son importantes el paralelismo y las ramificaciones

Los pipelines de LLM del mundo real a menudo necesitan hacer varias cosas a la vez o dirigir las solicitudes de forma diferente según su contenido. Las cadenas paralelas ejecutan varias ramas simultáneamente, lo que reduce la latencia cuando las tareas son independientes. Las cadenas con ramificaciones dirigen la entrada a distintas cadenas especializadas según condiciones dinámicas. LCEL admite ambos patrones de forma nativa con RunnableParallel y RunnableBranch.

Conceptos básicos de RunnableParallel

RunnableParallel recibe un diccionario en el que cada clave se asigna a un Runnable. Al invocarlo, ejecuta todas las ramas simultáneamente y devuelve un diccionario en el que cada clave contiene el resultado de su rama. Es ideal cuando desea generar varias salidas a partir de una misma entrada, por ejemplo, generar un resumen y extraer palabras clave al mismo tiempo.

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'])

Paralelismo con la abreviatura de dict

LCEL proporciona una abreviatura práctica: al pasar un dict normal como paso de una cadena de tuberías, se envuelve automáticamente en RunnableParallel. Esto hace que las ramas paralelas resulten naturales y evita instanciar explícitamente la clase. Las claves del dict se convierten en las claves del resultado y los valores son las ramas que se ejecutan simultáneamente.

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'])

Comprender RunnableBranch

RunnableBranch dirige la entrada a distintas cadenas según una condición. Debe proporcionar una lista de pares (condition, runnable) y un runnable predeterminado. La rama evalúa las condiciones en orden y ejecuta la primera rama que coincida. Esto permite enrutar según la intención: enviar las consultas de atención al cliente a una cadena de soporte y las preguntas técnicas a una cadena de documentación.

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

Enrutamiento semántico con clasificación mediante un LLM

Un patrón de enrutamiento más flexible utiliza una llamada de clasificación a un LLM para determinar qué rama debe utilizarse. Primero, el router llama a un modelo pequeño para clasificar la intención de la entrada y, después, utiliza esa clasificación para dirigirla a la cadena especializada adecuada. Esto permite gestionar casos sutiles que la coincidencia de palabras clave no detecta, a cambio de realizar una llamada adicional a la 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)
)

RAG paralelo: varios Retrievers

En los sistemas RAG avanzados, puede recuperar información simultáneamente de varias fuentes de datos y combinar los resultados. RunnableParallel le permite consultar al mismo tiempo una base de datos de productos, un almacén de preguntas frecuentes y un índice de documentación. Después, un paso de combinación reúne los mejores resultados antes de pasar el contexto al LLM, lo que proporciona al modelo una base de información más completa.

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
)

Cadenas condicionales con itemgetter

Cuando necesite seleccionar una clave específica de un resultado paralelo o pasar solo una parte del contexto al siguiente paso, operator.itemgetter de Python funciona como un selector Runnable ligero. Resulta útil después de un paso paralelo, cuando distintas ramas generan claves diferentes y necesita extraer únicamente la relevante para la siguiente etapa del procesamiento.

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

Medición de la aceleración del procesamiento paralelo

La principal ventaja de RunnableParallel es la reducción del tiempo de ejecución real. Tres llamadas secuenciales a un LLM que tardan 2 segundos cada una tomarían 6 segundos en total. En paralelo, se completan en aproximadamente 2 segundos: el tiempo que tarda la rama más lenta. Sin embargo, las llamadas paralelas multiplican el uso de tokens de forma simultánea, así que debe vigilar los límites de frecuencia. Use max_concurrency en batch() o establezca límites de frecuencia por clave si es necesario.

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')

Cadenas paralelas asíncronas

Para ejecutar tareas en paralelo de forma verdaderamente no bloqueante en aplicaciones asíncronas, use RunnableParallel.ainvoke(). Internamente, LCEL utiliza asyncio.gather() para ejecutar las ramas de forma concurrente en el bucle de eventos. Esto es especialmente importante en servicios de FastAPI, donde cada controlador de solicitudes es una corrutina: el uso de la interfaz asíncrona evita bloquear el bucle de eventos incluso con varias llamadas simultáneas a 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'])

Anidamiento de cadenas paralelas y secuenciales

Las canalizaciones complejas suelen combinar pasos secuenciales y paralelos. Puede anidar RunnableParallel dentro de una tubería secuencial y viceversa. Por ejemplo: clasificar la intención (secuencial), ejecutar en paralelo la recuperación y el formateo del contexto y, después, generar la respuesta final (secuencial). LangChain evalúa correctamente el anidamiento, lo que permite mantener legibles las canalizaciones sofisticadas sin una maraña de callbacks.

# 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
)

Gestión de errores en las ramas

Cuando falla una rama de RunnableParallel, toda la invocación paralela genera una excepción de forma predeterminada. Use .with_fallbacks() en los runnables de cada rama para gestionar correctamente los errores a nivel de rama. En el caso de RunnableBranch, incluya cada rama en un bloque try-except dentro de un RunnableLambda o use una cadena alternativa predeterminada para gestionar los errores de enrutamiento.

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

Comprobación rápida

Compruebe sus conocimientos sobre las cadenas ramificadas y paralelas en LCEL.

Resumen de la lección

En esta lección ha aprendido que RunnableParallel ejecuta varias cadenas de forma concurrente y devuelve un diccionario de resultados, lo que reduce la latencia de las llamadas independientes a LLM; RunnableBranch dirige la entrada a distintas cadenas especializadas según condiciones o una clasificación realizada por un LLM; y el anidamiento de pasos paralelos y secuenciales permite crear canalizaciones sofisticadas de múltiples rutas que siguen siendo legibles. A continuación, exploraremos la transmisión de resultados en LangChain.

Gratis para empezar

Aprende Python con un tutor de IA — gratis

Escribe y ejecuta código real en tu navegador, obtén ayuda instantánea de un tutor de IA disponible 24/7 y continúa donde lo dejaste en la web o en la aplicación.

Cursos
30
Lecciones
120

Preguntas frecuentes

¿La lección «Chains ramificadas y paralelas» es gratis?

Sí — el texto completo de «Chains ramificadas y paralelas» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de AI Engineering Academy, actualiza a CoddyKit PRO. El curso de AI Engineering Academy incluye 4 lecciones en total.

¿Qué aprenderé en «Chains ramificadas y paralelas»?

Creará estructuras RunnableParallel y RunnableBranch para ejecutar varias chains simultáneamente o dirigir la entrada a distintas chains según condiciones dinámicas. Practicas AI Engineering Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar AI Engineering Academy?

No se requiere experiencia previa. AI Engineering Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 3 de 4.

¿Cuánto tiempo toma la lección «Chains ramificadas y paralelas»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de AI Engineering Academy?

Sí. Cada lección de AI Engineering Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Arquitectura de LangChain y abstracciones principales
  2. Creación de chains con LCEL
  3. Chains ramificadas y paralelas
  4. Streaming de salida en LangChain
← Volver a AI Engineering Academy