4.2 - 6 Custom Template Demo
Creating a Custom Template
Objective:
To learn how to create custom prompt templates using Pydantic models and template classes, enabling dynamic prompt generation with input validation and structured template management for scalable AI workflows.
Note:
Before running any demo, ensure that the requirements.txt file is installed. This file contains all the required dependencies for all demos and guided practices under Building LLM Applications.
If the dependencies were already installed earlier (after creating the virtual environment), there is no need to install them again. You can directly proceed with running the demo.
Refer to Lesson_01 Demo_01_Zero_Shot_Prompting.ipynb Step 1 for creating a virtual environment and installing the requirements.txt
Ensure you select the right kernel Python (myenv) while running the demos
Steps to perform:
Set up the environment
Define the prompt template
Create a custom prompt template class
Step 1: Set up the environment
Import required modules such as StringPromptTemplate from the langchain.prompts library and BaseModel and validator from Pydantic library.
from pydantic import BaseModel, validator, ValidationError
from typing import List
from openai import OpenAI
from pydantic.config import ConfigDict
client = OpenAI()
Pydantic helps validate and structure data, ensuring inputs are in the correct format and automatically converting types if needed. It is widely used for validating user input, API requests, and managing configurations effectively.
Step 2: Define the prompt template
Define a constant string that outlines the structure of the prompt for generating book summaries
PROMPT = """\
Given the book title, generate a brief summary of the book.
Book Title: {book_title}
Summary:
"""
The prompt includes a placeholder {book_title} that allows dynamic content insertion during execution.
Step 3: Create a custom prompt template class
In 4.2 - 6 Demo: Creating a Custom Prompt Template, you are building a custom LangChain class that dynamically structures a prompt by inspecting your own Python functions' source code and using it as context, which automates how you inject custom code logic directly into an LLM's prompt.
Create a Pydantic model to validate data and integrate dynamic content into templates
class StringPromptTemplate:
def __init__(self, template: str):
self.template = template
def format(self, **kwargs) -> str:
return self.template.format(**kwargs)
Here is a summary of what you are doing in this step:
StringPromptTemplate
__init__
format
In this step, you are creating a reusable text template that automatically plugs real data into specific placeholders so you don't have to rewrite your prompts by hand every time.
Defining a Custom Blueprint: You are building a custom class called StringPromptTemplate to act as a structured wrapper around a standard text prompt string.
Initializing the Template: The __init__ constructor takes a raw text string containing placeholders (like {code}) and permanently saves it inside the string as self.template.
Dynamic Variable Injection: The format method uses Python's keyword arguments (kwargs) to dynamically find and swap those placeholders with real data at runtime.
The Pydantic Connection: While this specific snippet shows standard Python, you are laying the groundwork to integrate LangChain's template logic with Pydantic to ensure that whatever variables get injected into format() are strictly validated before the prompt ever hits the LLM.
Define a BookSummarizerPrompt model to use the above template for structured prompt generation
class BookSummarizerPrompt(BaseModel):
book_title: str
template: StringPromptTemplate
model_config = ConfigDict(arbitrary_types_allowed=True)
def create_prompt(self) -> str:
return self.template.format(book_title=self.book_title)
One-Sentence Explanation
In this step, you are defining a Pydantic model called BookSummarizerPrompt that bundles a book title together with your prompt template and enforces strict validation to guarantee the data is formatted perfectly before being sent to the AI.
BookSummarizerPrompt
create_prompt
Key Takeaways
Here is the simple, step-by-step breakdown rewritten to show exactly how those variable lines work without a standard __init__ function:
Step 1: The BookSummarizerPrompt(BaseModel) Configuration
Inherits Pydantic Powers: Putting BaseModel in the parentheses imports Pydantic's data-checking engine into your class.
Declares Fields (No __init__ required): Instead of typing a manual constructor function, you just list book_title: str and template: StringPromptTemplate directly inside the class block. Pydantic reads these lines and automatically builds a hidden initialization function behind the scenes to accept and type-check your parameters.
Step 2: The model_config Safety Switch
Creates a Settings Instance: You assign an instance of a ConfigDict object directly to the model_config variable.
Bypasses Restrictions: Pydantic is highly strict by default and normally only allows basic, built-in Python types like strings, integers, or dictionaries.
Approves Your Custom Object: By passing arbitrary_types_allowed=True into this ConfigDict instance, you flip a safety switch that tells the system: "Allow my custom StringPromptTemplate class object to pass through validation without throwing a crash error."
Step 3: The create_prompt(self) Action Trigger
Creates an Internal Function: You define a function inside the class called create_prompt(self). The self keyword means: "Look inside this specific object to find the data."
Fetches the Stored Title: Instead of asking you to pass a new string into the function, it reaches into the class and pulls out the book_title string you already verified and saved.
Assembles and Returns: It takes that stored title, hands it down into your template object's .format() function to swap out the brackets, and returns the final completed text string.
Create an instance of the template and dynamically generate a formatted prompt
# Define the template - From past define
template = StringPromptTemplate(
"""
Given the book title, generate a brief summary of the book.
Book Title: {book_title}
Summary:
"""
)
# Create instance of BookSummarizerPrompt - From past define
book_summarizer_prompt = BookSummarizerPrompt(book_title="The Great Gatsby", template=template)
# Generate prompt
formatted_prompt = book_summarizer_prompt.create_prompt()
print("Generated Prompt:")
print(formatted_prompt)
Generated Prompt:
Given the book title, generate a brief summary of the book.
Book Title: The Great Gatsby
Summary:
One-Sentence Explanation
In this step, you are creating a real-world instance of your blueprint, passing in "The Great Gatsby" as your actual data, and running the code to verify that it successfully pieces together the final text prompt.
template
Instance of StringPromptTemplate: Initialized with a new, raw prompt string containing the {book_title} placeholder.
book_summarizer_prompt
Instance of BookSummarizerPrompt: Initialized with the actual book title string "The Great Gatsby" and the template object created above.
formatted_prompt
Executes .create_prompt(): Triggers the execution chain, causing the Pydantic model to feed the title into the template's .format() method to output the final text.
Key Takeaways
Setting Up the Shell: You define the actual text string with the {book_title} placeholder inside your StringPromptTemplate wrapper.
Instantiating the Pydantic Object: When you call BookSummarizerPrompt(book_title="The Great Gatsby", template=template), Pydantic instantly checks behind the scenes to verify that your title is a valid string and that your template object is present.
Executing the Swap: Calling .create_prompt() triggers the underlying .format() logic, pulling your input data out and locking it directly into the {book_title} slot.
Output Validation: The print statements prove your system is working flawlessly, outputting a fully compiled, human-readable prompt string ready to be handed off to an LLM.
Define a function to send the formatted prompt to the OpenAI API and get the AI-generated response
# Define OpenAI completion function
def get_completion(prompt, model="gpt-5-mini"):
response = client.chat.completions.create(
model=model,
messages=[
{"role": "user", "content": prompt}
],
max_completion_tokens=5000
)
return response.choices[0].message.content
Here is the simple, step-by-step breakdown of how this OpenAI function works:
The Function Setup (def get_completion(prompt, model="gpt-5-mini"):)
You create a function that takes in your final prompt text.
It has a default AI model already picked out (gpt-5-mini), but you can change it later if you want to.
The Order Form (client.chat.completions.create(...))
This is where your code reaches out over the internet to talk to OpenAI's servers. You are submitting a formal request for a response.
The Message Box (messages=[{"role": "user", "content": prompt}])
You package your prompt inside a list. By setting the role to "user", you are telling the AI: "Hey, act like a human just typed this specific message directly into the chatbox."
The Length Limit (max_completion_tokens=5000)
This sets a hard ceiling on how long the AI's answer can be, preventing the model from running on forever and wasting your API credits.
Digging Out the Answer (return response.choices.message.content)
OpenAI wraps its response inside a massive, nested data structure (like a Russian nesting doll).
This final line cuts through all the background data metadata, grabs the exact text reply written by the AI, and hands it back to you as a clean string.
Generate the summary by passing the custom prompt to the OpenAI model
# Get AI-generated summary
response = get_completion(formatted_prompt)
print("\nAI Response:")
print(response)
AI Response:
Narrated by Nick Carraway, The Great Gatsby follows the mysterious Jay Gatsby — a fabulously wealthy, self-made man — as he pursues his obsessive love for Daisy Buchanan during the Roaring Twenties. Set on Long Island in 1922, Gatsby throws extravagant parties to attract Daisy, who is married to the arrogant, old-money Tom Buchanan. As Nick becomes drawn into their circle, he witnesses the era’s glamour, moral carelessness, and social divisions. Gatsby’s idealized dream of rekindling the past collapses amid infidelity, deception, and violence, culminating in tragic consequences. Fitzgerald’s novel is a critique of the American Dream and a portrait of longing, illusion, and the hollow glitter of wealth.
And that is the grand finale! You just completed the entire, end-to-end pipeline.
Here is what you just accomplished in these final lines:
Passing the Baton (response = get_completion(formatted_prompt))
You took the perfectly formatted string you generated earlier (the one where "The Great Gatsby" was safely swapped into the {book_title} placeholder) and handed it directly into your OpenAI completion function.
The function sent it across the web, OpenAI processed it, and handed back the exact text block you see in your printout.
The Output (print(response))
Your screen printed out a beautifully written, accurate summary of the novel.
Look at what you built:
You created a reusable template structure (StringPromptTemplate).
You wrapped it in a Pydantic security guard to make sure data types are safe (BookSummarizerPrompt).
You passed it to a universal API gateway to talk to the AI (get_completion).
You now have a highly professional, robust blueprint for building AI applications! You can change "The Great Gatsby" to any book title in the world on line 1, and the entire system will automatically validate, format, and fetch the summary for you without changing a single other line of code.
Amazing job working through this step-by-step!
### Example: Recipe generator using custom template class
# Define a template class to manage dynamic text
class RecipePromptTemplate:
def __init__(self, template: str):
self.template = template
def format(self, **kwargs):
return self.template.format(**kwargs)
# Define a pydantic model for recipe inputs
class RecipeGenerator(BaseModel):
recipe_name: str
ingredients: List[str]
template: RecipePromptTemplate
def create_prompt(self):
# Join ingredients into a single string for the template
ingredients_text = ", ".join(self.ingredients)
return self.template.format(recipe_name=self.recipe_name, ingredients=ingredients_text)
model_config = ConfigDict(arbitrary_types_allowed=True)
# Define the prompt template
recipe_template = RecipePromptTemplate(
"""
Generate a detailed recipe for the following dish:
Recipe Name: {recipe_name}
Ingredients: {ingredients}
Instructions:
"""
)
# Create an instance of the recipe generator
recipe_data = {
"recipe_name": "Pizza",
"ingredients": ["Refined Flour", "eggs", "parmesan cheese", "Mushrooms", "black pepper"],
"template": recipe_template,
}
recipe_generator = RecipeGenerator(**recipe_data)
# Generate the prompt
prompt = recipe_generator.create_prompt()
print("Prompt:")
print(prompt)
response = get_completion(recipe_generator.create_prompt())
print("AI Response:")
print(response)
Prompt:
Generate a detailed recipe for the following dish:
Recipe Name: Pizza
Ingredients: Refined Flour, eggs, parmesan cheese, Mushrooms, black pepper
Instructions:
AI Response:
Pizza (Egg‑bound Parmesan & Mushroom Flatbread)
Makes 1 medium pizza (2–3 servings)
Total time: ~45 minutes (15 min active prep + 30 min cook/rest)
Ingredients
- 1 1/2 cups (about 190 g) refined/all‑purpose flour, plus extra for dusting
- 3 large eggs
- 3/4 cup (about 75 g) freshly grated Parmesan cheese, divided (½ cup for the dough, ¼ cup for topping)
- 1 cup (roughly 120–150 g) mushrooms, wiped clean and thinly sliced
- 1/2 teaspoon freshly ground black pepper, divided
- Optional pantry items if you have them: a pinch of salt, 1–2 teaspoons olive oil or butter (for cooking mushrooms and preventing sticking)
Note: This recipe is written to use only the ingredients you listed. If you have salt, oil, yeast, or tomato sauce available, see the Variations/Tips section for additions.
Instructions
1. Preheat and prep
- Preheat your oven to 230°C (450°F). If you have a pizza stone, place it in the oven. If not, use a baking sheet and line it with parchment paper.
- Set a large nonstick or well‑seasoned skillet on medium‑high to heat while you prepare the mushrooms.
2. Cook the mushrooms
- If you have oil or butter, add 1–2 teaspoons to the hot skillet. If not, a good nonstick pan will work dry but be patient—mushrooms will release then reabsorb moisture before browning.
- Add the sliced mushrooms in a single layer and let them sit 2–3 minutes to brown on one side, then stir occasionally until most moisture has evaporated and they’re nicely browned, about 6–8 minutes total.
- Season with about 1/4 teaspoon black pepper (and a pinch of salt if using). Remove from heat and set aside.
3. Make the egg‑bound dough
- In a mixing bowl, beat 2 of the eggs lightly.
- Add the 1 1/2 cups flour, the 1/2 cup grated Parmesan, and about 1/4 teaspoon black pepper. Mix until a shaggy dough forms.
- Knead briefly in the bowl or on a lightly floured surface until the dough comes together into a smooth, pliable ball, 1–2 minutes. If the dough is very crumbly, incorporate the remaining whole egg; if it’s too sticky, dust with a little more flour.
- You want a soft dough that can be rolled or pressed — it will be denser than a yeast crust because the eggs act as binder.
4. Shape the crust
- On a lightly floured surface, roll or press the dough into a roughly 10–12 inch (25–30 cm) round, about 1/4 inch (6 mm) thick. Transfer to the prepared parchment on a baking sheet or a floured pizza peel.
5. Par‑bake the crust
- Slide the crust into the preheated oven and bake 8–10 minutes, until the surface is set and the edges just begin to color. (This step ensures a cooked base once you add the toppings.)
6. Top and finish baking
- Remove the par‑baked crust from the oven. Evenly distribute the sautéed mushrooms over the crust.
- Sprinkle the remaining 1/4 cup grated Parmesan over the mushrooms.
- Beat the remaining egg lightly and brush the exposed crust edge (optional) for color and gloss.
- Return the pizza to the oven and bake another 6–10 minutes until the crust is golden and the Parmesan on top has melted/started to brown.
7. Rest and serve
- Remove pizza from the oven and let it rest 2–3 minutes (this helps any hot pockets of moisture settle).
- Cut into slices and serve warm, finishing with an extra crack of black pepper to taste.
Tips and variations
- If you have salt, add 1/2 teaspoon to the dough. Parmesan is salty on its own, so taste accordingly.
- For a crisper bottom, bake on a preheated pizza stone or a very hot baking sheet.
- If you prefer a skillet method (no oven): make a thinner batter using 1 cup flour + 2 eggs + 1/2 cup grated Parmesan + a little water as needed to make a pourable batter. Cook like a large pancake in a well‑oiled nonstick skillet, flip once, then top the cooked side with mushrooms and Parmesan, cover to melt the cheese.
- If you have additional pantry items: add a drizzle of olive oil, fresh herbs (oregano, thyme), garlic, tomato sauce, or mozzarella for a more traditional pizza profile.
Storage
- Leftovers keep refrigerated in an airtight container for up to 2 days. Reheat in a hot oven or skillet to revive crispness.
Enjoy your quick, egg‑bound Parmesan and mushroom pizza — a simple, savory pie made from the ingredients you specified.
Conclusion
By following these steps, you have learned how to create custom prompt templates using Pydantic models and custom classes. This approach helps validate inputs, dynamically format text, and build reusable, structured templates for various AI-driven workflows such as book summaries, recipes, and content generation.
No comments:
Post a Comment