Ciao a tutti, lettori di agntbox! Nina qui, di nuovo con un’altra esplorazione nel mondo in continua evoluzione degli strumenti di IA. Oggi non stiamo solo esaminando uno strumento; stiamo parlando di qualcosa che sembra appena atterrato dal futuro, ma in un modo del tutto accessibile. Parliamo di LangChain Expressions Language (LCEL) e, in particolare, di come sta rendendo la mia vita come persona che costruisce e testa applicazioni IA molto più facile e strutturata.
Ricordo che solo un paio di anni fa, costruire qualcosa di più complesso di un prompt a turno singolo con un LLM sembrava come cercare di unire insieme una macchina di Rube Goldberg con il nastro adesivo. Passavi gli output da una funzione all’input di un’altra, gestendo manualmente gli stati di errore e pregando affinché i tuoi tipi di dati si allineassero. Funzionava, nella maggior parte dei casi, ma era ingombrante. È arrivato LangChain, offrendo un framework, che è stato un enorme passo avanti. Ma anche allora, concatenare i componenti insieme aveva ancora un po’ la sensazione di boilerplate, soprattutto quando volevi fare qualcosa di personalizzato o leggermente al di fuori delle catene pre-costruite.
Entra in scena LCEL. Quando ho iniziato a vedere chiacchiere al riguardo alla fine dello scorso anno, ammetto di essere stata un po’ scettica. Un’altra astrazione? Avevamo davvero bisogno di altri strati? Ma dopo aver trascorso alcune settimane costruttive a realizzare uno strumento interno piuttosto complesso per la sintesi e categorizzazione di contenuti per agntbox utilizzandolo, sono diventata una convertita. LCEL non è solo un’altra funzionalità; è un cambiamento fondamentale nel modo in cui comporre le applicazioni LLM all’interno dell’ecosistema LangChain. È come passare dal scrivere query SQL grezze per ogni interazione con il database a utilizzare un ORM davvero ben progettato – hai comunque il controllo, ma i modelli comuni sono solo… più fluidi.
LCEL: Più di una semplice concatenazione
Quindi, cos’è LCEL? Alla sua base, è un modo per comporre sequenze eseguibili in modo dichiarativo. Pensalo come un insieme di regole e primitive che ti permettono di concatenare LLM, template di prompt, parser e funzioni personalizzate in modo altamente flessibile ed efficiente. È progettato per essere:
- Componibile: Puoi combinare piccoli componenti indipendenti in quelli più grandi e complessi.
- Streaming: Supporta operazioni asincrone e streaming, il che è fantastico per applicazioni in tempo reale.
- Parallelo: Gestisce l’esecuzione concorrente dei componenti senza problemi.
- Ispezionabile: Puoi tracciare l’esecuzione delle tue catene, il che è un salvatore in fase di debug.
- Portatile: Una volta definita una sequenza eseguibile, può essere invocata in vari modi.
La parte “Expressions Language” potrebbe sembrare un po’ intimidatoria, ma riguarda davvero l’uso degli operatori nativi di Python (come | per piping) in un modo specifico per costruire queste sequenze. Si sente molto Pythonico una volta che prendi confidenza.
Il mio momento “Aha!”: Costruire un classificatore di contenuti
Lasciami raccontarti del progetto che mi ha davvero convinta su LCEL. Qui in agntbox, riceviamo molte proposte di articoli, e a volte, la categorizzazione iniziale non è del tutto corretta, o un articolo potrebbe toccare più argomenti. Il mio obiettivo era costruire uno strumento che potesse prendere una bozza di articolo, riassumerla e poi suggerire categorie primarie e secondarie da un elenco predefinito, insieme a un punteggio di fiducia. Prima di LCEL, probabilmente avrei scritto alcune funzioni diverse:
- Una funzione per ottenere il riassunto.
- Una funzione per ottenere le categorie basate sul riassunto.
- Gestione degli errori e parsing dei dati per ciascun passaggio.
Avrebbe funzionato, ma immagina di voler sostituire il modello di sintesi, o aggiungere un ulteriore passaggio per controllare il plagio prima della categorizzazione. Ogni modifica avrebbe significato scavare in una funzione specifica e potenzialmente rompere qualcos’altro.
Con LCEL, l’intero processo è sembrato come assemblare dei LEGO. Facciamo un passo indietro e vediamo una versione semplificata di come l’ho costruito.
Passo 1: I componenti di base
Per prima cosa, ho definito il mio LLM (sto usando gpt-4-turbo di OpenAI per questo, ma potresti facilmente cambiarlo). Poi, avevo bisogno di un prompt per la sintesi e un altro per la categorizzazione.
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser, JsonOutputParser
from langchain_core.pydantic_v1 import BaseModel, Field
# Il mio LLM
llm = ChatOpenAI(model="gpt-4-turbo", temperature=0)
# 1. Prompt di sintesi
summary_prompt = ChatPromptTemplate.from_template(
"Si prega di fornire un riepilogo conciso del seguente articolo per revisione interna. "
"Concentrati sui principali argomenti e le conclusioni chiave, puntando a circa 150-200 parole.\n\nArticolo: {article_content}"
)
# 2. Prompt di categorizzazione
# Definisci il formato di output previsto per la categorizzazione
class CategorySuggestions(BaseModel):
primary_category: str = Field(description="La categoria primaria più pertinente.")
secondary_category: str | None = Field(description="Una categoria secondaria pertinente, se applicabile.")
confidence_score: float = Field(description="Un punteggio di fiducia tra 0.0 e 1.0 per la categoria primaria.")
category_prompt = ChatPromptTemplate.from_messages([
("system", "Sei un esperto classificatore di contenuti. Dato un riassunto e un elenco di categorie disponibili, "
"suggerisci le migliori categorie primarie e secondarie. Fornisci anche un punteggio di fiducia per la categoria primaria. "
"Le categorie sono: AI Tools, Machine Learning, Data Science, Software Development, Cloud Computing, Cybersecurity, Robotics, Ethics in AI."),
("human", "Riassunto: {summary}\n\nSuggerisci categorie e un punteggio di fiducia in JSON formato.")
])
# Parser output
str_parser = StrOutputParser()
json_parser = JsonOutputParser(pydantic_object=CategorySuggestions)
Nota come sto già pensando a un output strutturato per il passaggio di categorizzazione utilizzando Pydantic e JsonOutputParser. Qui è dove LCEL brilla davvero – rende facile far rispettare l’integrità dei dati tra i passaggi.
Passo 2: Comporre la catena di sintesi
La prima parte del mio strumento è ottenere un riassunto. Con LCEL, questo è super chiaro:
summary_chain = (
summary_prompt
| llm
| str_parser
)
Questa sequenza legge quasi come in inglese: “Prendi il summary_prompt, passa il suo output al llm, e poi passa l’output del LLM attraverso il str_parser.” Semplice, giusto?
Passo 3: Comporre la catena di categorizzazione
La categorizzazione ha bisogno del riassunto come input, quindi è un po’ diversa. Volevo alimentare l’output della catena di sintesi nella catena di categorizzazione. LCEL fornisce modi per farlo utilizzando dizionari per gli input.
categorization_chain = (
{"summary": summary_chain} # Passa l'output di summary_chain come 'summary'
| category_prompt
| llm
| json_parser
)
Qui, {"summary": summary_chain} è un eseguibile che prende l’input dell’intero processo (il contenuto dell’articolo), lo passa alla summary_chain, e mappa il risultato a una chiave chiamata "summary". Questo dizionario è poi passato come input a category_prompt.
Passo 4: Mettere tutto insieme
Ora, volevo eseguire sia la sintesi che la categorizzazione. La categorizzazione dipende dal riassunto, quindi non è parallela. Ma cosa succede se volessi fare qualcos’altro in parallelo con la categorizzazione (ad esempio, controllare le parole chiave)? Per questo flusso particolare, è sequenziale, ma LCEL rende chiaro anche il branching complesso.
Per il mio classificatore di contenuti, ho deciso di mantenerlo semplice e di combinare i passaggi sequenzialmente, ma assicurandomi che l’output complessivo fosse un dizionario combinato di entrambi i risultati:
from langchain_core.runnables import RunnablePassthrough, RunnableParallel
# Un eseguibile che prende article_content e restituisce un dizionario con riassunto e categorizzazione
full_classifier_chain = (
RunnablePassthrough.assign(
summary=summary_chain
)
| RunnablePassthrough.assign(
categorization=categorization_chain
)
)
Analizziamo insieme:
RunnablePassthrough.assign(summary=summary_chain): Questo prende l’input iniziale (article_content) e lo passa attraverso. Esegue anchesummary_chaincon lo stesso input e assegna il suo risultato a una nuova chiave chiamata"summary". Quindi, l’output di questa prima fase è{"article_content": "...", "summary": "..."}.- Il
|poi passa questo dizionario al successivoRunnablePassthrough.assign. RunnablePassthrough.assign(categorization=categorization_chain): Questo prende il dizionario esistente (che ora haarticle_contentesummary), lo passa attraverso, e in modo cruciale, eseguecategorization_chain. Lacategorization_chainsi aspettasummarycome input, che trova nel dizionario passato a essa. Il suo output è poi assegnato alla chiave"categorization".
L’output finale di full_classifier_chain sarebbe un dizionario come: {"article_content": "...", "summary": "...", "categorization": {"primary_category": "...", ...}}.
Per usarlo:
article_draft = """
Le ultime innovazioni nei modelli di linguaggio di grandi dimensioni stanno spingendo i confini di ciò che è possibile nell'AI conversazionale. I ricercatori di XYZ Labs hanno recentemente presentato 'Synthetica-7', un modello capace di generare risposte altamente sfumate e consapevoli del contesto, anche in discussioni di lunga durata. A differenza delle iterazioni precedenti che avevano difficoltà a mantenere la coerenza nel corso di più scambi, Synthetica-7 impiega un meccanismo di attenzione innovativo che dà priorità alle dipendenze a lungo termine. Questa scoperta ha implicazioni significative per i chatbot del servizio clienti, i tutor educativi e persino gli assistenti alla scrittura creativa. Il modello è stato addestrato su un corpus massiccio di testi diversi, inclusi articoli scientifici, narrativa e manuali tecnici, permettendogli di adattare dinamicamente il suo tono e stile. Inoltre, XYZ Labs ha reso disponibile una versione più piccola e ottimizzata per la ricerca accademica, promuovendo un ambiente collaborativo per ulteriori innovazioni nel campo. Le considerazioni etiche riguardanti il bias dell'AI e l'abuso sono state anche un focus centrale durante il suo sviluppo, con test approfonditi condotti per mitigare potenziali output dannosi.
"""
# Invoca la catena
result = full_classifier_chain.invoke({"article_content": article_draft})
print(result["summary"])
print(result["categorization"])
Questa configurazione è incredibilmente potente. Se volessi aggiungere un passaggio per controllare il tono dell’articolo, potrei semplicemente aggiungere un altro RunnablePassthrough.assign con un nuovo prompt e una chiamata LLM. Se volessi passare da OpenAI a Cohere, sarebbe una modifica di una sola riga nella definizione di llm. La modularità è fantastica.
Oltre le Basi: Parallelismo e Fallback
LCEL non è solo per catene sequenziali semplici. Offre modi potenti per gestire scenari più complessi:
- Esecuzione Parallela: Usa
RunnableParallelper eseguire più componenti contemporaneamente e combinare i loro output in un dizionario. Questo è ottimo, ad esempio, per ottenere più prospettive da diversi LLM o eseguire una classificazione insieme a una sintesi in cui nessuno dipende dall’altro. - Fallback: Il metodo
.with_fallbacks()ti consente di definire altre esecuzioni da provare se quella principale fallisce. Questo è fondamentale per costruire applicazioni più resilienti, provando prima un modello più economico e veloce e ripiegando su uno più costoso e potente se il primo fallisce o fornisce un output a bassa fiducia. - Funzioni Personalizzate: Puoi facilmente integrare qualsiasi funzione Python nella tua catena LCEL utilizzando
RunnableLambda. Questo è come aggiungerei un passaggio di pre-elaborazione personalizzato o una validazione di post-elaborazione.
Ad esempio, se volessi ottenere il riassunto e anche controllare per parole chiave dall’articolo originale contemporaneamente, potrei fare:
from langchain_core.runnables import RunnableParallel
keyword_extraction_prompt = ChatPromptTemplate.from_template(
"Estrai 5-10 argomenti chiave o parole chiave dal seguente articolo. Elencali come valori separati da virgola.\n\nArticolo: {article_content}"
)
keyword_chain = (
keyword_extraction_prompt
| llm
| str_parser
| (lambda x: [kw.strip() for kw in x.split(',')]) # Parsing personalizzato per parole chiave
)
# Esegui sintesi e estrazione di parole chiave in parallelo
parallel_analysis = RunnableParallel(
summary=summary_chain,
keywords=keyword_chain
)
# Questo eseguirà sia 'summary_chain' che 'keyword_chain' contemporaneamente
# result_parallel = parallel_analysis.invoke({"article_content": article_draft})
# print(result_parallel)
Questo RunnableParallel rende i flussi di lavoro complessi intuitivi e performanti. Non devi aspettare che una chiamata LLM finisca prima di iniziarne un’altra se non dipendono l’una dall’altra.
Considerazioni Utili per i Tuoi Progetti AI
Quindi, dopo aver lavorato ampiamente con LCEL, ecco cosa ti consiglierei se stai costruendo applicazioni alimentate da LLM:
- Inizia Semplice, Costruisci Gradualmente: Non cercare di progettare l’intera applicazione con LCEL sin dal primo giorno. Inizia con una catena singola e chiara (come un prompt -> LLM -> parser). Man mano che acquisti fiducia, introduci elementi più complessi come l’esecuzione parallela o i fallback.
- Abbraccia l’Output Strutturato: Usa modelli Pydantic con
JsonOutputParsero metodi simili. Questo rende le tue catene molto più affidabili, poiché stai imponendo un contratto sull’output dell’LLM. Riduce drasticamente gli errori di parsing a valle. - Pensa in Runnables: Ogni componente in LCEL è un “runnable.” Questo include prompt, LLM, parser e persino funzioni Python personalizzate. Comprendere questo ti aiuta a vedere come tutto può essere integrato.
- Debug con Tracciamento: LangChain fornisce strumenti di tracciamento (come LangSmith) che si integrano perfettamente con LCEL. Usali! Essere in grado di visualizzare il percorso di esecuzione e gli input/output a ciascun passaggio è prezioso quando qualcosa va storto.
- Considera lo Streaming: Se stai costruendo applicazioni interattive (come chatbot), il supporto di LCEL per lo streaming dei risultati può migliorare significativamente l’esperienza utente mostrando risposte parziali man mano che vengono generate.
LCEL non è solo un aggiornamento minore; è un miglioramento fondamentale nel modo in cui costruire utilizzando LangChain. Rende la creazione di applicazioni LLM modulari e performanti meno simile a un hackathon e più come ingegneria del software strutturata. Se sei indeciso sull’approfondire LangChain o ti sei sentito sopraffatto dalle sue complessità precedenti, ora è sicuramente il momento di dare a LCEL un’attenta considerazione. Ha reso più fluido il mio flusso di lavoro di sviluppo e sono sicuro che può fare lo stesso per il tuo.
Questo è tutto per ora, gente! Buona costruzione e ci vediamo la prossima volta qui su agntbox.com.
🕒 Published: