TL;DR
LangChain is een van de meest gebruikte Python-bibliotheek om te communiceren met LLM's in minder dan een jaar, maar LangChain was vooral een bibliotheek voor POC's omdat het niet de mogelijkheid had om complexe en schaalbare toepassingen.
Alles veranderde in augustus 2023 toen ze LangChain uitdrukkingstaal (LCEL), een nieuwe syntaxis die de kloof overbrugt tussen POC naar productie. Dit artikel leidt u door de ins en outs van LCEL en laat u zien hoe het de creëren van aangepaste kettingen en waarom u het moet leren als u bouwt aan LLM aanvragen!
Prompts, LLM en kettingen, laten we ons geheugen opfrissen
Voordat we in de LCEL syntax duiken, denk ik dat het nuttig is om ons geheugen op te frissen over LangChain concepten zoals LLM en Prompt of zelfs een Chain.
LLM: In langchain is llm een abstractie rond het model dat gebruikt wordt om de aanvullingen te maken, zoals openai gpt3.5, claude, enz...
Prompt: Dit is de invoer van het LLM object, dat de LLM vragen zal stellen en de doelstellingen zal geven.
Ketting: Dit verwijst naar een opeenvolging van oproepen naar een LLM, of een willekeurige data verwerkingsstap.

Nu de definities uit de weg zijn, stel dat we een bedrijf willen oprichten! We hebben een coole en pakkende naam nodig en een bedrijfsmodel om geld te verdienen!
Voorbeeld - Bedrijfsnaam & Businessmodel met oude ketens
uit langchain.chains importeert LLMChain
uit langchain.prompts importeer PromptTemplate
van langchain_community.llms importeer OpenAI
USER_INPUT = "kleurrijke sokken"."
llm = OpenAI(temperatuur=0)
prompt_template_product = "Wat is een goede naam voor een bedrijf dat ?" maakt."
bedrijfsnaam_keten = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_product))
bedrijfsnaam_uitvoer = bedrijfsnaam_keten(USER_INPUT)
prompt_template_business = "Geef me het beste idee voor een bedrijfsmodel voor mijn bedrijf genaamd: "
business_model_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_business))
business_model_output = business_model_chain(company_name_output["text"])
print(bedrijfsnaam_uitvoer)
print(business_model_output)
>>>
>>>
Dit is vrij gemakkelijk te volgen, we zien een beetje redundantie, maar het is beheersbaar.
Laten we wat maatwerk toevoegen door de gevallen af te handelen waarin de gebruiker onze ketting niet gebruikt zoals verwacht.
Misschien voert de gebruiker iets in dat helemaal niets te maken heeft met het doel van onze keten? In dat geval willen we dat detecteren en op de juiste manier reageren.
Voorbeeld - Aanpassing & Routing met oude ketens
uit langchain.chains importeert LLMChain
uit langchain.prompts importeer PromptTemplate
van langchain_community.llms importeer OpenAI
import ast
USER_INPUT = "Harrison Chase"."
llm = OpenAI(temperatuur=0)
# -- Dezelfde code als hierboven
prompt_template_product = "Wat is een goede naam voor een bedrijf dat ?" maakt."
bedrijfsnaam_keten = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_product))
prompt_template_business = "Geef me het beste idee voor een bedrijfsmodel voor mijn bedrijf genaamd: "
business_model_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_business))
# -- Nieuwe code
prompt_template_is_product = (
"Uw doel is om uit te zoeken of de invoer van de gebruiker een plausibele productnaam is"
"Vragen, begroetingen, lange zinnen, beroemdheden of andere niet-relevante inputs worden niet als producten beschouwdn"
"invoer: n"
"Antwoord alleen met 'Waar' of 'Onwaar' en niets meer."
)
prompt_template_cannot_respond = (
"U kunt niet reageren op de gebruikersinvoer: n"
"Vraag de gebruiker om de naam van een product in te voeren zodat u er een bedrijf van kunt maken.n"
)
cannot_respond_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_cannot_respond))
bedrijfsnaam_keten = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_product))
business_model_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_business))
is_a_product_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_is_product))
# Als we bool gebruiken op een niet lege str zal het True zijn, dus hebben we `literal_eval` nodig.
is_a_product = ast.literal_eval(is_a_product_chain(USER_INPUT)["text"])
if_a_product:
bedrijfsnaam_uitvoer = bedrijfsnaam_keten(USER_INPUT)
business_model_output = business_model_chain(company_name_output["text"])
print(business_model_output)
anders:
print(cannot_respond_chain(USER_INPUT))
Dit wordt iets moeilijker te begrijpen, laten we het samenvatten:
Er beginnen zich meerdere problemen voor te doen:
Wat is LangChain Expression Language (LCEL)?
LCEL is een uniforme interface en syntaxis om samenstelbare productieklare ketens te schrijven, er moet veel uitgepakt worden om te begrijpen wat het betekent.
We zullen eerst proberen de nieuwe syntaxis te begrijpen door de ketting van eerder te herschrijven.
Voorbeeld - Bedrijfsnaam & Bedrijfsmodel met LCEL
uit langchain_core.runnables importeer RunnablePassthrough
uit langchain.prompts importeer PromptTemplate
van langchain_community.llms importeer OpenAI
USER_INPUT = "kleurrijke sokken"."
llm = OpenAI(temperatuur=0)
prompt_template_product = "Wat is een goede naam voor een bedrijf dat ?" maakt."
prompt_template_business = "Geef me het beste idee voor een bedrijfsmodel voor mijn bedrijf genaamd: "
ketting = (
PromptTemplate.from_template(prompt_template_product)
| llm
|
| PromptTemplate.from_template(prompt_template_business)
| llm
)
business_model_output = keten.invoke()
Veel ongebruikelijke code, in slechts een paar regels :
Maar is het echt beter samen te stellen om op deze manier ketens te maken?
Laten we de proef op de som nemen door de is_a_product_chain() en de vertakking toe te voegen als de invoer van de gebruiker niet correct is. We kunnen zelfs de keten typen met Python Typing, laten we dit doen als een goede oefening.
Voorbeeld - Aanpassing & Routing met LCEL
van typing importeer Dict
uit langchain_core.runnables importeer RunnablePassthrough, RunnableBranch
uit langchain.prompts importeer PromptTemplate
uit langchain_core.output_parsers importeert StrOutputParser
van langchain.output_parsers importeert BooleanOutputParser
van langchain_community.llms importeer OpenAI
USER_INPUT = "Harrrison Chase"."
llm = OpenAI(temperatuur=0)
prompt_template_product = "Wat is een goede naam voor een bedrijf dat ?" maakt."
prompt_template_cannot_respond = (
"U kunt niet reageren op de gebruikersinvoer: n"
"Vraag de gebruiker om de naam van een product in te voeren zodat u er een bedrijf van kunt maken.n"
)
prompt_template_business = "Geef me het beste idee voor een bedrijfsmodel voor mijn bedrijf genaamd: "
prompt_template_is_product = (
"Uw doel is om uit te zoeken of de invoer van de gebruiker een plausibele productnaam is"
"Vragen, begroetingen, lange zinnen, beroemdheden of andere niet-relevante inputs worden niet als producten beschouwdn"
"invoer: n"
"Antwoord alleen met 'Waar' of 'Onwaar' en niets meer."
)
antwoord_gebruiker_keten = (
PromptTemplate.from_template(prompt_template_product)
| llm
|
| PromptTemplate.from_template(prompt_template_business)
| llm
).with_types(input_type=Dict[str, str], output_type=str)
is_product_chain = (
PromptTemplate.from_template(prompt_template_is_product)
| llm
| BooleanOutputParser(true_val='True', false_val='False')
).with_types(input_type=Dict[str, str], output_type=bool)
cannot_respond_chain = (
PromptTemplate.from_template(prompt_template_cannot_respond) | llm
).with_types(input_type=Dict[str, str], output_type=str)
volledige_keten = RunnableBranch(
(is_product_chain, answer_user_chain),
kan_niet_reageren_keten
).with_types(input_type=Dict[str, str], output_type=str)
print(full_chain.invoke())
Laten we de verschillen eens op een rijtje zetten:
Waarom is LCEL beter voor industrialisatie?
Als ik dit artikel tot op dit punt aan het lezen was, en iemand zou mij vragen of ik overtuigd was van LCEL, dan zou ik waarschijnlijk nee zeggen. De syntaxis is te verschillend, en ik kan mijn code waarschijnlijk in functies indelen om bijna exact dezelfde code te krijgen. Maar ik ben hier en schrijf dit artikel, dus er moet iets meer zijn.
Out of the box aanroepen, streamen en batchen
Door LCEL te gebruiken heeft uw ketting automatisch:
mijn_keten = prompt | llm
# ---oproepen-- #
resultaat_met_invoke = mijn_keten.invoke(“hallo wereld!”)
# ---partij-- #
resultaat_met_batch = mijn_keten.batch([“hallo”, “wereld”, “!”])
# ---stroom-- #
voor chunk in my_chain.stream(“hello world!”):
print(chunk, flush=True, end=””)
Wanneer u itereert, kunt u de invoke methode gebruiken om het ontwikkelingsproces te vergemakkelijken. Maar wanneer u de uitvoer van uw keten in een UI toont, wilt u het antwoord streamen. U kunt nu de stream-methode gebruiken zonder iets te herschrijven.
Out of the box async methoden
Meestal zullen de frontend en backend van uw applicatie gescheiden zijn, wat betekent dat de frontend een verzoek doet aan de backend. Als u meerdere gebruikers hebt, moet u misschien meerdere verzoeken tegelijkertijd op uw backend afhandelen.
Aangezien het grootste deel van de code in LangChain gewoon wacht tussen API-aanroepen, kunnen we asynchrone code gebruiken om de schaalbaarheid van de API te verbeteren. Als u wilt begrijpen waarom dit belangrijk is, raad ik u aan om de gelijktijdig hamburgers verhaal van de FastAPI documentatie.
U hoeft zich geen zorgen te maken over de implementatie, omdat async methoden al beschikbaar zijn als u LCEL gebruikt:
.ainvoke() / .abatch() / .astreamAsynchrone versies van invoke, batch en stream.
Ik raad ook aan om de Waarom LCEL pagina uit LangChain documentatie gebruiken met voorbeelden voor elke sync / async methode.
Langchain bereikte deze “out of the box” functies door het creëren van een uniforme interface genaamd “Runnable”. Om LCEL volledig te benutten, moeten we ons verdiepen in deze nieuwe Runnable interface.
De Runnable-interface
Elk object dat we tot nu toe hebben gebruikt in de LCEL syntaxis zijn Runnables. Het is een python-object dat wordt gemaakt door LangChain, dit object erft automatisch elke functie waar we het eerder over hebben gehad en nog veel meer. Door de LCEL syntaxis te gebruiken, stellen we bij elke stap een nieuwe Runnable samen, wat betekent dat het uiteindelijk gemaakte object ook een Runnable zal zijn. U kunt meer te weten komen over de interface in de officiële documentatie.
Alle objecten in de onderstaande code zijn ofwel Runnable of woordenboeken die automatisch worden geconverteerd naar een Runnable :
uit langchain_core.runnables importeer RunnablePassthrough, RunnableParallel
uit langchain.prompts importeer PromptTemplate
van langchain_community.llms importeer OpenAI
chain_number_one = (
PromptTemplate.from_template(prompt_template_product)
| llm
| # <- DIT ZAL VERANDEREN
| PromptTemplate.from_template(prompt_template_business)
| llm
)
keten_nummer_twee = (
PromptTemplate.from_template(prompt_template_product)
| llm
| RunnableParallel(company=RunnablePassthrough()) # <- DIT VERANDERDE
| PromptTemplate.from_template(prompt_template_business)
| llm
)
print(keten_nummer_één == keten_nummer_twee)
>>> Waar
Waarom gebruiken we RunnableParallel() en niet gewoon Runnable()?
Omdat elke Runnable binnen een RunnableParallel parallel wordt uitgevoerd. Dit betekent dat als u 3 onafhankelijke stappen in uw Runnable hebt, deze tegelijkertijd op verschillende threads van uw machine worden uitgevoerd, waardoor de snelheid van uw keten gratis wordt verbeterd!
Nadelen van LCEL
Ondanks de voordelen heeft LCEL ook enkele potentiële nadelen:
Conclusie
Concluderend kan ik zeggen dat LangChain Expression Language (LCEL) een krachtig hulpmiddel is dat een nieuw perspectief biedt voor het bouwen van Python toepassingen. Ondanks de onconventionele syntaxis, raad ik het gebruik van LCEL om de volgende redenen ten zeerste aan:
Verder gaan...
De runnable abstractie
In sommige gevallen denk ik dat het belangrijk is om de abstractie te begrijpen die LangChain heeft geïmplementeerd om de LCEL syntaxis te laten werken.
U kunt de basisfunctionaliteiten van Runnable eenvoudig als volgt opnieuw implementeren:
klasse Runnable:
def __init__(self, func):
zelf.func = func
def __or__(self, other):
def chained_func(*args, **kwargs):
# self.func staat links, other staat rechts
return other(self.func(*args, **kwargs))
return Runnable(chained_func)
def __call__(self, *args, **kwargs):
return self.func(*args, **kwargs)
def add_ten(x):
keer x + 10 terug
def deel_bij_twee(x):
keer x / 2 terug
runnable_add_ten = Runnable(add_ten)
runnable_divide_by_two = Runnable(divide_by_two)
keten = runnable_add_ten | runnable_divide_by_two
resultaat = ketting(8) # (8+10) / 2 = 9,0 zou het antwoord moeten zijn
print(resultaat)
>>> 9.0
Een Runnable is eenvoudigweg een python-object waarin de .__or__() methode is overschreven.
In de praktijk heeft LangChain veel functionaliteiten toegevoegd, zoals het converteren van woordenboeken naar Runnable, typingsmogelijkheden, configureerbaarheidsmogelijkheden en invoke-, batch-, stream- en async-methodes!
Dus waarom zou u LCEL niet eens proberen in uw volgende project?
Als u meer wilt weten, raad ik u ten zeerste aan om eens rond te kijken LangChain kookboek op LCEL.

BLOG






