GenAI Course · Module 8 · Session 29

Dynamic Prompting with LangChain

⏱ 12 min read🎯 Intermediate🖼 2 diagrams🧪 1 live demo🐍 Python code

Writing one prompt by hand is easy. Building an app that fills prompts with live data, calls a model, cleans the reply and passes it to the next step is harder. LangChain gives you small building blocks that snap together with a simple | pipe, like Lego.

In this session

  • Prompt templates and output parsers
  • LCEL: joining steps with the | operator
  • Runnables: invoke, batch and stream
  • Sequential chains and parallel chains

01Prompt templates

A prompt template is a prompt with blanks to fill at run time. It keeps your prompt text in one place and makes it reusable.

template.py
# pip install langchain-core langchain-openai
from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a helpful banking tutor."),
    ("user", "Explain {topic} in one line."),
])
print(prompt.invoke({"topic": "UPI"}).to_messages())
[SystemMessage(content='You are a helpful banking tutor.', …), HumanMessage(content='Explain UPI in one line.', …)]
📮

Like a printed bank form. The text is fixed; you only fill in the blanks (name, account number). One form, used thousands of times.

02Output parsers

A chat model returns a message object. An output parser turns it into what your code needs:

03LCEL: the pipe operator

The LangChain Expression Language joins steps with |. The output of each step becomes the input of the next:

chain = prompt | llm | StrOutputParser()
first_chain.py
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
chain = prompt | llm | StrOutputParser()

print(chain.invoke({"topic": "UPI"}))
No API key? Practise for free. Swap in a fake model that returns fixed replies: from langchain_core.language_models.fake_chat_models import FakeListChatModel then llm = FakeListChatModel(responses=["UPI lets you send money instantly using a phone number or QR code."]). The chain works exactly the same.

04Runnables: one interface for everything

Prompts, models, parsers and whole chains are all Runnables, so they share the same methods:

MethodWhat it doesUse it for
invoke(x)Run once on one inputA single request
batch([x1, x2, …])Run on many inputs, in parallelProcessing a file of 1,000 tickets
stream(x)Yield the answer piece by pieceChat UIs that show text as it is typed
ainvoke, abatch, astreamAsync versionsWeb servers (FastAPI)
runnables.py
answers = chain.batch([{"topic": "UPI"}, {"topic": "NEFT"}, {"topic": "IMPS"}])

for piece in chain.stream({"topic": "RTGS"}):
    print(piece, end="", flush=True)

You can also turn any Python function into a Runnable with RunnableLambda, and pass input through unchanged with RunnablePassthrough.

05Sequential and parallel chains

Sequential: one step feeds the next

Example: summarise a customer complaint in English, then translate the summary into Hindi for the branch team.

The output of the first chain becomes the input of the second.
sequential.py
summarise = ChatPromptTemplate.from_template("Summarise this complaint in one line:\n{complaint}") | llm | StrOutputParser()
translate = ChatPromptTemplate.from_template("Translate to Hindi:\n{summary}") | llm | StrOutputParser()

pipeline = {"summary": summarise} | translate     # the dict maps the first output into {summary}
print(pipeline.invoke({"complaint": "I was charged twice for my electricity bill payment on 3 Oct…"}))

Parallel: several jobs on the same input

RunnableParallel runs branches at the same time and returns a dict, which saves waiting time.

Demo · Sequential vs parallel timing

Three model calls (sentiment, keywords, summary), each taking about 1.2 seconds.

parallel.py
from langchain_core.runnables import RunnableParallel, RunnablePassthrough

sentiment = ChatPromptTemplate.from_template("Sentiment (positive/negative/neutral) of: {text}") | llm | StrOutputParser()
keywords  = ChatPromptTemplate.from_template("3 keywords from: {text}") | llm | StrOutputParser()

analyse = RunnableParallel(sentiment=sentiment, keywords=keywords, original=RunnablePassthrough())
print(analyse.invoke({"text": "Got my refund after a delay, thanks!"}))
{'sentiment': 'positive', 'keywords': 'refund, delay', 'original': {'text': 'Got my refund after a delay, thanks!'}}

Output above from running with a fake model on langchain-core; a real model’s wording will differ.

Go deeper: the LangChain complete notes cover models, LCEL, structured output, tools, create_agent, memory and middleware, with key-free runnable code.

06Summary

07Quick quiz

1. In prompt | llm | parser, what does | do?

2. Which method shows the answer word by word in a chat UI?

3. What does StrOutputParser return?

4. You need sentiment and keywords for the same review, fast. Use…

5. To process 1,000 tickets with one chain, use…

08References

Created by Ajeetkumar