> ## Documentation Index
> Fetch the complete documentation index at: https://genai.codewithsiva.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Prompt Engineering

> Learn the art and science of communicating effectively with Large Language Models

<a id="table-of-contents" />

## 📋 Table of Contents

* [Chapter 1: Introduction to LangChain](#chapter-1-introduction-to-langchain)
* [Chapter 2: Prompt Templates & Message Structures](#chapter-2-prompt-templates--message-structures)
* [Chapter 3: LCEL & Runnables](#chapter-3-lcel--runnables)
* [Chapter 4: Output Parsers](#chapter-4-output-parsers)
* [Chapter 5: Model Hyperparameters](#chapter-5-model-hyperparameters)
* [Chapter 6: Few-Shot & Sequential Prompting](#chapter-6-few-shot--sequential-prompting)

## 💻 Workshop Practice Notebook

Master all the concepts from this guide with hands-on practice:

* **Practice in VS Code**: Open the notebook in your local editor. Requires a local `.env` file containing your API keys.
* **Practice in Google Colab**: Open the notebook directly in Colab. Setup cells are included to install packages and request API keys.

[💻 VS Code](vscode://file/Users/sivaprasad/Downloads/GenAI%20With%20Python/public/notebooks/prompt-engg/prompt-engineering-practice-vscode.ipynb) | [🚀 Colab](https://colab.research.google.com/github/prasad230776/genai-course/blob/master/public/notebooks/prompt-engg/prompt-engineering-practice-colab.ipynb) | <a href="/public/notebooks/prompt-engg/prompt-engineering-practice-vscode.ipynb" download>📥 Download Notebook</a>

## <a id="chapter-1-introduction-to-langchain" />Chapter 1: Introduction to LangChain

Large Language Models (LLMs) have transformed how we build software. However, building production-grade GenAI applications requires orchestration. This module introduces the fundamentals of LangChain, explains the problems it solves, and walks you through setting up a modern GenAI project.

### 1. Traditional vs. GenAI Applications

Building applications with Generative AI requires a paradigm shift from traditional software development:

| Aspect           | Traditional Software                                                    | GenAI Applications                                                                                  |
| ---------------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| **Logic**        | Deterministic and rule-based (defined by code loops, `if-else` blocks). | Probabilistic (guided by LLM semantic reasoning and prompts).                                       |
| **Input/Output** | Structured data (JSON, databases, arguments).                           | Unstructured natural language (text, speech, images).                                               |
| **Execution**    | Consistent and predictable; same inputs yield exact same outputs.       | Dynamic; outputs can vary (non-deterministic) depending on context, temperature, and model updates. |

### 2. Two Kinds of GenAI Applications

LLM-powered systems are generally categorized into two workflow architectures:

1. **Sequential Workflows (Deterministic)**:
   The execution path is hardcoded and predefined by the developer. The inputs and outputs flow sequentially from one step to another (e.g., Prompt -> LLM -> Parser -> Database).
2. **Agentic Workflows (Autonomous)**:
   The LLM operates as an autonomous agent inside a loop. Given a task, the model evaluates the current state and dynamically decides which actions to take or tools (such as web search, calculator, or DB query) to invoke at runtime.

### 3. The Challenges of Raw API Integrations

Directly writing code against raw LLM provider APIs (like OpenAI, Google, or Anthropic) introduces several challenges in real-world software engineering:

* **API Fragmentation**: Every model provider has its own proprietary SDK, request payload structure, and response format. Switching providers means rewriting your entire code integration.
* **Complex Pipeline Orchestration**: Real-world GenAI applications rarely rely on a single API call. They require linking prompts, vector search retrievers, output parsers, and custom tools in sequence.
* **State & Memory Management**: LLMs are stateless by design. Developers must manually manage conversation history and context window limits.

#### How LangChain Solves This

LangChain acts as a **unified abstraction layer** over LLMs:

1. **Standardized Interfaces**: Write code against generic classes (`ChatModel`, `PromptTemplate`, `BaseOutputParser`) and easily swap underlying models/providers with a single line of code.
2. **LangChain Expression Language (LCEL)**: A declarative composition system utilizing the pipe operator (`|`) to build and stream multi-step GenAI pipelines.
3. **Ecosystem Modularity**: It splits components into light, specialized libraries (`langchain-core`, provider packages like `langchain-groq`, and `langchain-community`).

### 4. Direct APIs vs. LangChain

To understand why LangChain is needed, let's compare direct API integrations for three popular providers (OpenAI, Gemini, Hugging Face) against LangChain's unified syntax.

#### 3.1 Direct Provider APIs (Fragmentation)

Every provider requires a unique SDK, setup protocol, and response extraction syntax:

##### OpenAI Direct API

```python theme={null}
from openai import OpenAI
client = OpenAI(api_key="your_openai_key")

res = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "What is Python?"}]
)
## Accessing content requires deep nesting:
print(res.choices[0].message.content)
```

##### Google Gemini Direct API

```python theme={null}
import google.generativeai as genai
genai.configure(api_key="your_gemini_key")
model = genai.GenerativeModel("gemini-2.5-flash")

res = model.generate_content("What is Python?")
## Accessing content uses .text:
print(res.text)
```

##### Hugging Face Inference API

```python theme={null}
import requests
API_URL = "https://api-inference.huggingface.co/models/gpt2"
headers = {"Authorization": "Bearer your_hf_token"}

res = requests.post(API_URL, headers=headers, json={"inputs": "What is Python?"})
## Accessing content requires list/dictionary parsing:
print(res.json()[0]['generated_text'])
```

#### 3.2 LangChain's Simplified & Unified Syntax

LangChain unifies all these disparate APIs behind a single interface. Switching between providers only requires changing model configuration variables:

```python theme={null}
from langchain.chat_models import init_chat_model

## Standardized Initialization:
llm = init_chat_model("gemini-2.5-flash", model_provider="google_genai")
## To switch to OpenAI: llm = init_chat_model("gpt-4o", model_provider="openai")
## To switch to Groq: llm = init_chat_model("llama-3.3-70b-versatile", model_provider="groq")

## Standardized Invocation & Response Extraction (.content)
response = llm.invoke("What is Python?")
print(response.content)
```

### 5. Main LangChain Modules

LangChain divides its components into specialized modules for clean dependency management:

* **`langchain-core`**: The foundational package defining interfaces for models (`BaseChatModel`), templates (`BasePromptTemplate`), and the LCEL chaining logic.
* **Provider Integration Packages**: Specific packages (e.g. `langchain-google-genai`, `langchain-groq`) containing lightweight wrapper logic for provider-specific APIs.
* **`langchain-community`**: Integrations maintained by the community for third-party vector databases, document loaders, and tools.

### 6. Setting Up a GenAI Project (Step-by-Step)

We will use **`uv`**, a fast, modern package and project manager for Python, to set up our application.

#### Step 6.1: Initialize the Project & Virtual Environment

Open your terminal and run the following commands:

```bash theme={null}
## Initialize a new project directory
uv init genai-app
cd genai-app

## Create and activate a virtual environment
uv venv
source .venv/bin/activate
```

#### Step 6.2: Add Dependencies

Add the core LangChain package, provider integration packages, and a library to read environment variables:

```bash theme={null}
## Add LangChain core and provider-specific integrations
uv add langchain-core langchain-groq langchain-google-genai python-dotenv
```

#### Step 6.3: Set Up Your Keys (`.env`)

Create a file named `.env` in the root of your project directory and add your API keys:

```ini theme={null}
## Groq API Key (Fast inference for open models)
GROQ_API_KEY=gsk_your_groq_api_key_here

## Google Gemini API Key
GOOGLE_API_KEY=AIzaSyYourGeminiApiKeyHere
```

#### Step 6.4: Load Environment Variables in Python

To read the keys from your `.env` file and make them available to your application:

1. Import `load_dotenv` from the `dotenv` library.
2. Call `load_dotenv()` at the very start of your python script.

```python theme={null}
from dotenv import load_dotenv

# Search and load keys from the local .env file
load_dotenv()
```

This loads your secret API keys into Python's `os.environ` system dictionary. LangChain automatically looks for variables named `GROQ_API_KEY` and `GOOGLE_API_KEY` in `os.environ`, allowing you to initialize models without hardcoding credentials in your source code.

### 7. Initializing and Calling Models

Here is how to write python scripts to call either Groq or Google Gemini using LangChain.

#### 7.1 Initializing with Groq

```python theme={null}
import os
from dotenv import load_dotenv
from langchain.chat_models import init_chat_model

## Load variables from .env
load_dotenv()

## Initialize the Groq model
llm = init_chat_model(
    "llama-3.3-70b-versatile",
    model_provider="groq"
)

## Invoke the model
response = llm.invoke("Explain why developers use virtual environments in Python.")
print(response.content)
```

#### 7.2 Initializing with Google Gemini

```python theme={null}
import os
from dotenv import load_dotenv
from langchain.chat_models import init_chat_model

## Load variables from .env
load_dotenv()

## Initialize the Gemini model
llm = init_chat_model(
    "gemini-2.5-flash",
    model_provider="google_genai"
)

## Invoke the model
response = llm.invoke("What is the difference between concurrency and parallelism?")
print(response.content)
```

> \[!NOTE]
> When using `init_chat_model`, LangChain automatically detects the `GROQ_API_KEY` or `GOOGLE_API_KEY` from your environment variables.

### 8. Practice Exercises

#### Practice 1: Dual-Provider Setup & Comparison

Write a script that loads environment variables, prompts **both** Groq (`llama-3.3-70b-versatile`) and Google (`gemini-2.5-flash`) with the question `"State the main goal of prompt engineering in 5 words."`, and prints the response from each model.

<Accordion title="Solution">
  ```python theme={null}
  import os
  from dotenv import load_dotenv
  from langchain.chat_models import init_chat_model

  ## Load environment variables
  load_dotenv()

  ## Initialize Groq
  groq_llm = init_chat_model(
      "llama-3.3-70b-versatile",
      model_provider="groq"
  )

  ## Initialize Gemini
  gemini_llm = init_chat_model(
      "gemini-2.5-flash",
      model_provider="google_genai"
  )

  prompt = "State the main goal of prompt engineering in 5 words."

  print("--- Groq Response ---")
  print(groq_llm.invoke(prompt).content.strip())

  print("\n--- Gemini Response ---")
  print(gemini_llm.invoke(prompt).content.strip())
  ```
</Accordion>

## 💻 Practice Notebooks

Master all the concepts from this module with hands-on practice:

* **Practice in VS Code**: Open the notebook in your local editor. Requires a local `.env` file containing your API keys.
* **Practice in Google Colab**: Open the notebook directly in Colab. Setup cells are included to install packages and request API keys.

[💻 VS Code](vscode://file/Users/sivaprasad/Downloads/GenAI%20With%20Python/public/notebooks/prompt-engg/prompt-engg-practice-vscode.ipynb) | [🚀 Colab](https://colab.research.google.com/github/prasad230776/genai-course/blob/master/public/notebooks/prompt-engg/prompt-engg-practice-colab.ipynb) | <a href="/public/notebooks/prompt-engg/prompt-engg-practice-vscode.ipynb" download>📥 Download Notebook</a>

[Back to Top](#table-of-contents)

## <a id="chapter-2-prompt-templates--message-structures" />Chapter 2: Prompt Templates & Message Structures

When building LLM applications, managing prompts dynamically is essential. LangChain provides powerful abstractions like `PromptTemplate` and `ChatPromptTemplate` to build reusable prompts, manage conversation messages, and parse variables.

## 1. PromptTemplate (String-Based Prompts)

`PromptTemplate` is used to create simple, string-based prompts. It is ideal for non-conversational LLMs or basic text generation pipelines.

### 1.1 Code Examples

#### Example 1: Concept Explanation

```python theme={null}
from langchain_core.prompts import PromptTemplate
from langchain.chat_models import init_chat_model

# Define a template with a variable {concept}
template_str = "Explain the concept of {concept} in simple terms."
prompt_template = PromptTemplate.from_template(template_str)

# Fill in the variable
prompt = prompt_template.invoke({"concept": "machine learning"})

# 1. Print the formatted template string
print("--- Formatted Prompt ---")
print(prompt.to_string())

# 2. Initialize the chat model and invoke
llm = init_chat_model("llama-3.3-70b-versatile", model_provider="groq")
response = llm.invoke(prompt)

print("\n--- LLM Response ---")
print(response.content)
```

**Output:**

```text theme={null}
Explain the concept of machine learning in simple terms.
```

#### Example 2: Automated Code Reviewer

Create a prompt template that takes `language` and `code` variables and instructs the model to review the code.

```python theme={null}
from langchain_core.prompts import PromptTemplate
from langchain.chat_models import init_chat_model

reviewer_template = PromptTemplate.from_template(
    "Review the following {language} code for security vulnerabilities and performance bottlenecks:\n\n\\`\\`\\`{language}\n{code}\n\\`\\`\\`"
)

# Paste the code manually
code_to_review = """
def read_file(filename):
    import os
    os.system('cat ' + filename)
"""

formatted_prompt = reviewer_template.format(
    language="Python", 
    code=code_to_review
)

# 1. Print the formatted template string
print("--- Formatted Prompt ---")
print(formatted_prompt)

# 2. Initialize the chat model and invoke
llm = init_chat_model("llama-3.3-70b-versatile", model_provider="groq")
response = llm.invoke(formatted_prompt)

print("\n--- LLM Response ---")
print(response.content)
```

**Output:**

````text theme={null}
Review the following Python code for security vulnerabilities and performance bottlenecks:

```Python
def read_file(filename):
    import os
    os.system('cat ' + filename)
```
````

### 1.2 Exercises for PromptTemplate

#### Exercise 1: Recipe Generator

Define a `PromptTemplate` that takes an `ingredients` list (e.g., "tomato, cheese, basil") and a `cuisine` type (e.g., "Italian"), and prompts the model to generate a recipe.

<Accordion title="Solution">
  ```python theme={null}
  from langchain_core.prompts import PromptTemplate

  recipe_template = PromptTemplate.from_template(
      "Create a traditional {cuisine} recipe using these ingredients: {ingredients}."
  )
  prompt = recipe_template.invoke({"cuisine": "Italian", "ingredients": "tomato, cheese, basil"})
  print(prompt.to_string())
  ```
</Accordion>

#### Exercise 2: Technical Definition Writer

Define a `PromptTemplate` that takes a `term` and an `audience_level` (e.g., "5-year-old" or "PhD student") and generates a customized definition.

<Accordion title="Solution">
  ```python theme={null}
  from langchain_core.prompts import PromptTemplate

  definition_template = PromptTemplate.from_template(
      "Define the term '{term}' in a way that a {audience_level} can easily understand."
  )
  prompt = definition_template.invoke({"term": "Quantum Computing", "audience_level": "5-year-old"})
  print(prompt.to_string())
  ```
</Accordion>

## 2. Message Types & Chat Structures

Chat models communicate using lists of structured messages rather than a single block of text. This helps maintain role-based boundaries and conversational context.

LangChain provides three main message classes in `langchain_core.messages`:

* **`SystemMessage`**: Sets the behavior, persona, rules, or constraints for the assistant. This message is usually sent first.
* **`HumanMessage`**: Represents input sent by the user.
* **`AIMessage`**: Represents responses generated by the model.

### 2.1 Why Message Objects are Important

Message objects allow API providers (like Google Gemini, OpenAI, or Anthropic) to handle conversations structure-selectively. They let the backend know exactly who said what, which prevents the LLM from confusing system guardrails with user input.

### 2.2 Invoking ChatModels with Message Objects

You can pass a list of message objects directly to a Chat Model to initiate or continue a multi-turn conversation.

```python theme={null}
from langchain_core.messages import SystemMessage, HumanMessage
from langchain.chat_models import init_chat_model

# Initialize the chat model
llm = init_chat_model("llama-3.3-70b-versatile", model_provider="groq")

# Construct the dialogue using message objects
messages = [
    SystemMessage(content="You are a strict Python security auditor. Review the user's code for safety issues."),
    HumanMessage(content="Here is my code:\n\nx = input('Enter command: ')\neval(x)")
]

# Invoke the model directly with the list of message objects
response = llm.invoke(messages)

print("--- Auditor Response ---")
print(response.content)
```

## 3. ChatPromptTemplate (Message-Based Prompts)

`ChatPromptTemplate` structures conversation flows for Chat Models using lists of system, human, and AI instructions.

### 3.1 Code Examples

#### Example 1: Customer Service Ticket Auto-Classifier

Categorize customer support tickets into Hardware, Software, or Billing issues.

```python theme={null}
from langchain_core.prompts import ChatPromptTemplate
from langchain.chat_models import init_chat_model

chat_template = ChatPromptTemplate.from_messages([
    ("system", "You are an automated support classifier. Categorize the ticket into one of: Hardware, Software, Billing."),
    ("human", "Ticket: {ticket_description}")
])

prompt = chat_template.format_messages(ticket_description="My screen keeps flickering.")

# 1. Print the formatted template messages
print("--- Formatted Prompt ---")
for msg in prompt:
    print(f"{msg.type.upper()}: {msg.content}")

# 2. Initialize model and invoke
llm = init_chat_model("llama-3.3-70b-versatile", model_provider="groq")
response = llm.invoke(prompt)

print("\n--- LLM Response ---")
print(response.content)
```

#### Example 2: Geography Expert (Few-Shot Chat)

Simulate flag color retrieval with few-shot examples embedded inside a chat dialogue.

```python theme={null}
from langchain_core.prompts import ChatPromptTemplate
from langchain.chat_models import init_chat_model

chat_template = ChatPromptTemplate.from_messages([
    ("system", "You are a geography expert that returns the colors present in a country's flag."),
    ("human", "France"),
    ("ai", "blue, white, red"),
    ("human", "{country}")
])

prompt = chat_template.invoke({"country": "Japan"})

# 1. Print the formatted template messages
print("--- Formatted Prompt ---")
for msg in prompt.to_messages():
    print(f"{msg.type.upper()}: {msg.content}")

# 2. Initialize model and invoke
llm = init_chat_model("llama-3.3-70b-versatile", model_provider="groq")
response = llm.invoke(prompt)

print("\n--- LLM Response ---")
print(response.content)
```

### 3.2 Exercises for ChatPromptTemplate

#### Exercise 1: History Guide Roleplay

Create a `ChatPromptTemplate` simulating a historical dialogue.

* System message: `"You are \{historical_figure\}, a historical figure. Answer in their character."`
* Human: `"What was your greatest achievement?"`
* AI: `"My greatest achievement was \{achievement\}."`
* Human: `"Why was \{achievement\} important?"`

Invoke this template with `historical_figure="Julius Caesar"` and `achievement="crossing the Rubicon"`. Print the generated list of messages.

<Accordion title="Solution">
  ```python theme={null}
  from langchain_core.prompts import ChatPromptTemplate

  chat_template = ChatPromptTemplate.from_messages([
      ("system", "You are {historical_figure}, a historical figure. Answer in their character."),
      ("human", "What was your greatest achievement?"),
      ("ai", "My greatest achievement was {achievement}."),
      ("human", "Why was {achievement} important?")
  ])

  prompt = chat_template.invoke({
      "historical_figure": "Julius Caesar",
      "achievement": "crossing the Rubicon"
  })

  for msg in prompt.to_messages():
      print(f"{msg.type.upper()}: {msg.content}")
  ```
</Accordion>

#### Exercise 2: Code Translator

Create a `ChatPromptTemplate` representing a code translation engine.

* System message: `"You are an expert software engineer that translates source code from \{source_lang\} to \{target_lang\}."`
* Human: `"Translate this code:\n\n\{code\}"`

Invoke this template with `source_lang="Python"`, `target_lang="JavaScript"`, and `code="print('Hello World')"` and print the messages.

<Accordion title="Solution">
  ```python theme={null}
  from langchain_core.prompts import ChatPromptTemplate

  chat_template = ChatPromptTemplate.from_messages([
      ("system", "You are an expert software engineer that translates source code from {source_lang} to {target_lang}."),
      ("human", "Translate this code:\n\n{code}")
  ])

  prompt = chat_template.invoke({
      "source_lang": "Python",
      "target_lang": "JavaScript",
      "code": "print('Hello World')"
  })

  for msg in prompt.to_messages():
      print(f"{msg.type.upper()}: {msg.content}")
  ```
</Accordion>

## 4. Variable Passing Mechanisms

When invoking templates or chains, you pass variables depending on the count of placeholders:

* **Single-Variable Shortcut**: If the template has exactly one placeholder (e.g., `\{variable\}`), you can pass a raw string. LangChain maps it automatically.
  ```python theme={null}
  prompt = ChatPromptTemplate.from_template("Explain {topic}")
  prompt.invoke("Python") # Shortcut
  ```
* **Multi-Variable Dictionary**: If the template has multiple placeholders, you must pass a dictionary of key-value pairs.
  ```python theme={null}
  prompt = ChatPromptTemplate.from_template("Translate {text} to {lang}")
  prompt.invoke({"text": "Hello", "lang": "Spanish"})
  ```

## 5. Extracting Responses: `.content` vs `.text` vs Direct Output

Depending on the component you invoke, the returned value has different structures. It is crucial to know how to extract the raw text response:

### 5.1 Use `.content` (For ChatModels)

When you invoke a **Chat Model** (e.g., initialized using `init_chat_model` for Groq or Gemini), the return value is an `AIMessage` object. To access the generated text, you **must use `.content`**.

```python theme={null}
response = llm.invoke("Hi")
print(type(response))   # <class 'langchain_core.messages.ai.AIMessage'>
print(response.content) # Extracts the raw text string
```

### 5.2 Use `.text` (For Few-Shot / Legacy formatting and outputs)

When formatting older or specific templates (like `FewShotPromptTemplate`), the formatted result is a `PromptValue` object. In these cases, you access the raw string representation using `.text`.

Additionally, some legacy LLM completion model classes (as opposed to modern `ChatModel` classes) or generation results return response structures where the generated text output itself is accessed via `.text`.

```python theme={null}
# 1. Formatting templates via format_prompt() returns a PromptValue
prompt_val = few_shot_prompt.format_prompt(input="hello")
print(prompt_val.text) # Returns raw string representation

# 2. Legacy model outputs or raw generation lists sometimes expose .text
```

### 5.3 Direct Output

If you are invoking a local pipeline (e.g., `HuggingFacePipeline`) or a chain containing a **StrOutputParser**, the return value is already a plain Python string (`str`), so you can print or use it directly.

```python theme={null}
# Using a parser extracts the content automatically
chain = prompt | llm | StrOutputParser()
response = chain.invoke({"topic": "AI"})

print(type(response)) # <class 'str'>
print(response)       # Prints directly
```

## 💻 Practice Notebooks

Master all the concepts from this page with hands-on practice:

* **Practice in VS Code**: Open the notebook in your local editor. Requires a local `.env` file containing your API keys.
* **Practice in Google Colab**: Open the notebook directly in Colab. Setup cells are included to install packages and request API keys.

[💻 VS Code](vscode://file/Users/sivaprasad/Downloads/GenAI%20With%20Python/public/notebooks/prompt-engg/prompt-engg-templates-vscode.ipynb) | [🚀 Colab](https://colab.research.google.com/github/prasad230776/genai-course/blob/master/public/notebooks/prompt-engg/prompt-engg-templates-colab.ipynb) | <a href="/public/notebooks/prompt-engg/prompt-engg-templates-vscode.ipynb" download>📥 Download Notebook</a>

[Back to Top](#table-of-contents)

## <a id="chapter-3-lcel--runnables" />Chapter 3: LCEL & Runnables

LangChain Expression Language (LCEL) is a declarative way to build LLM applications, allowing you to compose components using the pipe operator (`|`).

## 1. What is LCEL & Why Use It?

Instead of writing imperative code to link prompts, models, and parsers, you connect them together like a Unix pipeline:

```python theme={null}
# A basic LCEL chain structure
chain = prompt | llm
```

### Why Use LCEL?

* **Simple**: Chain complex components in just a few lines of code.
* **Readable**: Easy to inspect the flow of inputs and outputs.
* **Composable**: Swap prompts, LLMs, retrievers, or parsers effortlessly.
* **Built-in Support**: Handles streaming and parallel operations out of the box.

## 2. LCEL Code Examples

### Example 1: Basic QA Chain

A simple chain that takes a topic, formats a prompt, invokes the model, and extracts the response content.

```python theme={null}
from langchain_core.prompts import PromptTemplate
from langchain.chat_models import init_chat_model

# Initialize components
llm = init_chat_model("llama-3.3-70b-versatile", model_provider="groq")
prompt = PromptTemplate.from_template("Tell me a short joke about \{topic\}.")

# Compose LCEL Chain: prompt | llm
chain = prompt | llm

# Invoke the chain (returns an AIMessage)
response = chain.invoke({"topic": "programming"})
print(response.content)
```

### Example 2: Subject Line Generator

Generate a professional email subject line using dynamic topic and tone variables.

```python theme={null}
from langchain_core.prompts import PromptTemplate
from langchain.chat_models import init_chat_model

# Initialize components
llm = init_chat_model("llama-3.3-70b-versatile", model_provider="groq")
prompt = PromptTemplate.from_template("Write a single email subject line for a request about \{request_topic\} in a \{tone\} tone.")

# Compose LCEL Chain
chain = prompt | llm

# Invoke the chain
response = chain.invoke({"request_topic": "server maintenance delay", "tone": "apologetic"})
print(response.content)
```

## 3. Exercises for LCEL

### Exercise 1: Marketing Pitch Generator

Create a chain that takes a product name and a target audience and generates a catchy marketing slogan.

**Instructions:**

1. Import `PromptTemplate` and `init_chat_model`.
2. Define a string prompt template containing two variables: `{product_name}` and `{target_audience}`.
3. Initialize the Groq model `llama-3.3-70b-versatile`.
4. Compose an LCEL chain linking the prompt template and the chat model.
5. Invoke the chain passing a dictionary with values for `"product_name"` (e.g., `"EcoWater Bottle"`) and `"target_audience"` (e.g., `"fitness enthusiasts"`).
6. Print the model's text response using `.content`.

<Accordion title="Solution">
  ```python theme={null}
  from langchain_core.prompts import PromptTemplate
  from langchain.chat_models import init_chat_model

  llm = init_chat_model("llama-3.3-70b-versatile", model_provider="groq")

  prompt = PromptTemplate.from_template(
      "Create a catchy one-sentence marketing slogan for {product_name} targeting {target_audience}."
  )

  # Compose the chain
  pitch_chain = prompt | llm

  # Run
  result = pitch_chain.invoke({
      "product_name": "EcoWater Bottle",
      "target_audience": "fitness enthusiasts"
  })
  print(result.content)
  ```
</Accordion>

### Exercise 2: Tech Tag Extractor

Create a chain that takes an article excerpt and lists the top 3 technology keywords mentioned.

**Instructions:**

1. Import `PromptTemplate` and `init_chat_model`.
2. Define a string prompt template containing a variable `{text}` that asks the model to list the top 3 technology keywords mentioned in the text.
3. Initialize the Groq model `llama-3.3-70b-versatile`.
4. Compose an LCEL chain linking the prompt template and the chat model.
5. Invoke the chain passing a dictionary containing a sample text paragraph.
6. Print the model's text response using `.content`.

<Accordion title="Solution">
  ```python theme={null}
  from langchain_core.prompts import PromptTemplate
  from langchain.chat_models import init_chat_model

  llm = init_chat_model("llama-3.3-70b-versatile", model_provider="groq")

  prompt = PromptTemplate.from_template(
      "List the top 3 technology keywords mentioned in this text: '{text}'."
  )

  # Compose the chain
  tag_chain = prompt | llm

  # Run
  result = tag_chain.invoke({
      "text": "Kubernetes is an open-source container orchestration system for automating software deployment and scaling. It was originally designed by Google."
  })
  print(result.content)
  ```
</Accordion>

## 4. Invoking vs. Streaming

### 4.1 `invoke()`

Waits for the entire model execution to complete and returns the full response at once.

```python theme={null}
response = chain.invoke({"topic": "Python"})
print(response.content)
```

### 4.2 `stream()`

Yields the response progressively, token-by-token. This is crucial for interactive chat interfaces to improve perceived user latency.

```python theme={null}
for chunk in chain.stream({"topic": "Python generators"}):
    # ChatModel stream yields BaseMessageChunks; print chunk.content
    print(chunk.content, end="", flush=True)
```

### Invocation Input Cheat Sheet

| What are you invoking?            | Input type           | Example                             |
| --------------------------------- | -------------------- | ----------------------------------- |
| `llm.invoke()`                    | String               | `"What is Python?"`                 |
| Prompt with one variable          | Dictionary or String | `{"topic": "Python"}` or `"Python"` |
| Prompt with multiple variables    | Dictionary           | `{"name": "Siva", "age": 25}`       |
| LCEL chain starting with a prompt | Dictionary           | `{"topic": "Generators"}`           |

## 5. LangChain Runnables

A **Runnable** is the fundamental building block in LangChain. Any component that implements `invoke()`, `batch()`, or `stream()` is a Runnable.

### 5.1 RunnableSequence

Chains multiple runnables sequentially so the output of one component becomes the input of the next. The pipe operator (`|`) automatically creates a `RunnableSequence`.

```python theme={null}
# The pipe creates a RunnableSequence under the hood
chain = prompt | llm
```

### 5.2 RunnablePassthrough

Forwards the input value as-is. This is useful for passing unchanged variables down a chain or creating multi-keyed inputs.

```python theme={null}
from langchain_core.runnables import RunnablePassthrough, RunnableLambda

# Combine Passthrough with Lambda mapping
chain = {
    "original": RunnablePassthrough(),
    "upper": RunnableLambda(lambda x: x.upper())
}

result = chain.invoke("hello")
print(result)
```

**Output:**

```text theme={null}
{'original': 'hello', 'upper': 'HELLO'}
```

### 5.3 RunnableParallel

Executes multiple runnables concurrently on the same input, returning their outputs as a unified dictionary.

```python theme={null}
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnableParallel

prompt1 = PromptTemplate.from_template("Summarize \{topic\}")
prompt2 = PromptTemplate.from_template("List advantages of \{topic\}")

parallel_chain = RunnableParallel(
    summary=prompt1 | llm,
    advantages=prompt2 | llm
)

result = parallel_chain.invoke({"topic": "Artificial Intelligence"})
print(result)
```

**Output:**

```text theme={null}
{
  "summary": AIMessage(content="AI is the simulation..."),
  "advantages": AIMessage(content="1. Automation...")
}
```

## 6. Practice Exercises

### Practice 1: Basic LCEL Translation Pipeline

Create a simple LCEL chain combining a prompt template (`"Translate the word '{word}' into German."`) and a chat model. Invoke it with the word `"apple"` and print the response content.

<Accordion title="Solution">
  ```python theme={null}
  from langchain_core.prompts import PromptTemplate
  from langchain.chat_models import init_chat_model

  llm = init_chat_model("groq:llama-3.3-70b-versatile", model_provider="groq")
  prompt = PromptTemplate.from_template("Translate the word '{word}' into German.")

  # Create the LCEL chain
  chain = prompt | llm

  # Invoke the chain
  result = chain.invoke({"word": "apple"})
  print(result.content)
  ```
</Accordion>

## 💻 Practice Notebooks

Master all the concepts from this page with hands-on practice:

* **Practice in VS Code**: Open the notebook in your local editor. Requires a local `.env` file containing your API keys.
* **Practice in Google Colab**: Open the notebook directly in Colab. Setup cells are included to install packages and request API keys.

[💻 VS Code](vscode://file/Users/sivaprasad/Downloads/GenAI%20With%20Python/public/notebooks/prompt-engg/prompt-engg-lcel-vscode.ipynb) | [🚀 Colab](https://colab.research.google.com/github/prasad230776/genai-course/blob/master/public/notebooks/prompt-engg/prompt-engg-lcel-colab.ipynb) | <a href="/public/notebooks/prompt-engg/prompt-engg-lcel-vscode.ipynb" download>📥 Download Notebook</a>

[Back to Top](#table-of-contents)

## <a id="chapter-4-output-parsers" />Chapter 4: Output Parsers

Large Language Models output plain text. However, applications often require structured data to feed into APIs, databases, or frontend components. Output Parsers bridge this gap.

## 1. Introduction to Output Parsers

LangChain provides several output parsers to structure model outputs:

| Parser                           | Output Type                 | Example Use Case                                                 |
| -------------------------------- | --------------------------- | ---------------------------------------------------------------- |
| `StrOutputParser`                | `str` (String)              | Extracting clean text response (bypassing `AIMessage` wrappers). |
| `JsonOutputParser`               | `dict` (Dictionary)         | Extracting structured JSON keys and values.                      |
| `PydanticOutputParser`           | `BaseModel` (Python Object) | Parsing and validating outputs against a strict data schema.     |
| `CommaSeparatedListOutputParser` | `list` (List of strings)    | Splitting comma-separated words into a Python list.              |

## 2. Using Output Parsers

### 2.1 StrOutputParser

Converts the output of a chat model (`AIMessage`) into a clean, raw string.

```python theme={null}
from langchain_core.output_parsers import StrOutputParser

parser = StrOutputParser()
response = parser.invoke("Hello World")

print(response)       # "Hello World"
print(type(response))  # <class 'str'>
```

### 2.2 JsonOutputParser

Parses JSON-formatted strings generated by LLMs into a native Python dictionary.

```python theme={null}
from langchain_core.output_parsers import JsonOutputParser

parser = JsonOutputParser()
response = parser.invoke("""
{
   "name": "John Doe",
   "age": 30,
   "occupation": "Software Engineer"
}
""")

print(response)       # {'name': 'John Doe', 'age': 30, 'occupation': 'Software Engineer'}
print(type(response))  # <class 'dict'>
```

### 2.3 PydanticOutputParser

Validates the output against a Pydantic model definition. This ensures type safety and field presence.

```python theme={null}
from pydantic import BaseModel
from langchain_core.output_parsers import PydanticOutputParser

# Define the schema
class Person(BaseModel):
    name: str
    age: int
    occupation: str

parser = PydanticOutputParser(pydantic_object=Person)
response = parser.invoke("""
{
   "name": "John Doe",
   "age": 30,
   "occupation": "Software Engineer"
}
""")

print(response)            # Person(name='John Doe', age=30, occupation='Software Engineer')
print(type(response))      # <class 'Person'>
print(response.name)       # "John Doe"
```

### 2.4 CommaSeparatedListOutputParser

Splits comma-separated lists generated by the model into a Python list of strings.

```python theme={null}
from langchain_core.output_parsers import CommaSeparatedListOutputParser

parser = CommaSeparatedListOutputParser()
response = parser.invoke("Python, Java, JavaScript, Go, Rust")

print(response)       # ['Python', 'Java', 'JavaScript', 'Go', 'Rust']
print(type(response))  # <class 'list'>
```

## 3. Practice Exercises

### Practice 1: Comma Separated List Parsing

Create a prompt template that requests the model to list the top 3 programming languages for web development, and chain it with the `CommaSeparatedListOutputParser` to obtain a Python list.

<Accordion title="Solution">
  ```python theme={null}
  from langchain_core.prompts import PromptTemplate
  from langchain_core.output_parsers import CommaSeparatedListOutputParser
  from langchain.chat_models import init_chat_model

  llm = init_chat_model("groq:llama-3.3-70b-versatile")
  parser = CommaSeparatedListOutputParser()

  prompt = PromptTemplate(
      template="List 3 top programming languages for {use_case} as a comma-separated list.",
      input_variables=["use_case"]
  )

  chain = prompt | llm | parser
  result = chain.invoke({"use_case": "web development"})

  print(result)
  print(type(result)) # <class 'list'>
  ```
</Accordion>

## 💻 Practice Notebooks

Master all the concepts from this page with hands-on practice:

* **Practice in VS Code**: Open the notebook in your local editor. Requires a local `.env` file containing your API keys.
* **Practice in Google Colab**: Open the notebook directly in Colab. Setup cells are included to install packages and request API keys.

[💻 VS Code](vscode://file/Users/sivaprasad/Downloads/GenAI%20With%20Python/public/notebooks/prompt-engg/prompt-engg-parsers-vscode.ipynb) | [🚀 Colab](https://colab.research.google.com/github/prasad230776/genai-course/blob/master/public/notebooks/prompt-engg/prompt-engg-parsers-colab.ipynb) | <a href="/public/notebooks/prompt-engg/prompt-engg-parsers-vscode.ipynb" download>📥 Download Notebook</a>

[Back to Top](#table-of-contents)

## <a id="chapter-5-model-hyperparameters" />Chapter 5: Model Hyperparameters

When invoking Large Language Models, various hyperparameters control how the model selects the next token. Tuning these parameters is vital for tailoring responses to fit specific use cases (e.g., deterministic code generation vs. creative brainstorming).

## 1. Key Hyperparameters

### 1.1 Temperature

Controls the **randomness** of predictions.

* **Low Temperature (closer to 0)**: The model behaves deterministically, favoring the highest-probability tokens.
* **High Temperature (closer to 1 or higher)**: The model flattens token probability distributions, allowing less common words to be chosen, creating creative or diverse responses.

#### Example Scenario:

Given the following vocabulary probabilities:

* `cat`: `0.70`

* `dog`: `0.20`

* `tiger`: `0.08`

* `elephant`: `0.02`

* **Temperature = 0**: Always outputs `cat` (deterministic).

* **Temperature = 0.2**: Mostly outputs `cat`, occasionally `dog`.

* **Temperature = 1.0**: Uses original probabilities as-is.

* **Temperature = 2.0**: The probabilities flatten out, making even `elephant` highly possible.

#### Typical Values

| Use Case         | Suggested Temperature |
| ---------------- | --------------------- |
| Factual QA       | `0.0` - `0.3`         |
| Coding / Math    | `0.0` - `0.2`         |
| Summarization    | `0.2` - `0.5`         |
| Creative Writing | `0.8` - `1.2`         |

### 1.2 Max Tokens

Sets the **maximum limit** on the number of tokens the model is allowed to generate in a single request. This prevents excessive cost and runtime.

```python theme={null}
# Terminate generation after roughly 50 tokens
llm.invoke("Explain Artificial Intelligence", max_tokens=50)
```

### 1.3 Top-K Sampling

Limits token selection to the **K most likely** tokens. Unlikely tokens outside the top K are discarded entirely, preventing the model from generating random gibberish.

* **Top-K = 2**: If the top tokens are `cat` (0.40), `dog` (0.30), and `tiger` (0.15), only `cat` and `dog` are kept. The rest are ignored.

### 1.4 Top-P (Nucleus Sampling)

Instead of keeping a static count like Top-K, Top-P selects enough tokens to reach a **cumulative probability threshold P**.

* **Top-P = 0.8**: If `cat` (0.40), `dog` (0.30), and `tiger` (0.15) sum to `0.85`, the model stops adding tokens and samples only from these three.
* **Top-P = 0.95**: Includes a wider pool of less-likely tokens.

## 2. Summary Table

| Parameter       | Purpose                                       | Typical Production Default                |
| --------------- | --------------------------------------------- | ----------------------------------------- |
| **Temperature** | Controls token randomness/creativity          | `0.2` (Factual) or `0.7` (Conversational) |
| **Max Tokens**  | Restricts generation length                   | `1000`                                    |
| **Top-K**       | Truncates choices to K tokens                 | Disabled or `40` - `50`                   |
| **Top-P**       | Truncates choices to cumulative probability P | `0.9`                                     |

## 3. Practice Exercises

### Practice 1: Configuring Parameters in LangChain

Configure a chat model using `init_chat_model` with a temperature of `0.0` and a max token limit of `100` to answer the question: `"State the value of Pi to 10 decimal places."`

<Accordion title="Solution">
  ```python theme={null}
  from langchain.chat_models import init_chat_model

  # Initialize with deterministic settings
  llm = init_chat_model(
      "llama-3.3-70b-versatile",
      model_provider="groq",
      temperature=0.0,
      max_tokens=100
  )

  response = llm.invoke("State the value of Pi to 10 decimal places.")
  print(response.content)
  ```
</Accordion>

## 💻 Practice Notebooks

Master all the concepts from this page with hands-on practice:

* **Practice in VS Code**: Open the notebook in your local editor. Requires a local `.env` file containing your API keys.
* **Practice in Google Colab**: Open the notebook directly in Colab. Setup cells are included to install packages and request API keys.

[💻 VS Code](vscode://file/Users/sivaprasad/Downloads/GenAI%20With%20Python/public/notebooks/prompt-engg/prompt-engg-params-vscode.ipynb) | [🚀 Colab](https://colab.research.google.com/github/prasad230776/genai-course/blob/master/public/notebooks/prompt-engg/prompt-engg-params-colab.ipynb) | <a href="/public/notebooks/prompt-engg/prompt-engg-params-vscode.ipynb" download>📥 Download Notebook</a>

[Back to Top](#table-of-contents)

## <a id="chapter-6-few-shot--sequential-prompting" />Chapter 6: Few-Shot & Sequential Prompting

This section covers advanced prompting patterns: guiding model output format using examples (Few-Shot Prompting) and linking prompts in sequence where the output of one step informs the next (Sequential Prompting).

## 1. Few-Shot Prompting

"Shots" refer to the examples provided to the model inside the prompt to show it how to perform a task.

* **Zero-Shot Prompting**: No examples are provided. The model relies entirely on pre-trained instructions.
* **One-Shot Prompting**: One example is provided to illustrate the target structure.
* **Few-Shot Prompting**: Multiple examples are provided. This is highly recommended for complex logic, custom styles, or structural outputs.

### 1.1 Why Few-Shot Prompting is Required & Its Advantages

While modern LLMs are capable of zero-shot completions, they often struggle when:

* **Complex Formatting**: You need the model to return data in a highly specific structure or syntax (e.g., custom JSON format, exact punctuation, or nested schemas) that is hard to explain in instructions alone.
* **Domain Specificity**: The task requires adhering to a specific company tone, shorthand notation, or industry-specific classification schemas.
* **Edge-Case Safety**: You want to train the model's behavior on complex logic boundaries (e.g., math problems or entity relationships) by showing correct resolutions.

#### Key Advantages:

1. **Structural Consistency**: Forces the model to align with the visual and structural formatting of your examples.
2. **Improved Accuracy**: Demonstrating tasks reduces reasoning errors and context hallucination.
3. **No Fine-Tuning Required**: Achieve custom model behaviors inside the context window at runtime, avoiding the cost of fine-tuning the model weights.

### 1.2 Few-Shot Code Examples

#### Example 1: Math Assistant

Create a few-shot prompt to demonstrate basic math calculations and then execute the prompt using a chat model.

```python theme={null}
from langchain_core.prompts import PromptTemplate, FewShotPromptTemplate
from langchain.chat_models import init_chat_model

# 1. Define list of examples
examples = [
    {"question": "2 + 2", "answer": "4"},
    {"question": "3 + 3", "answer": "6"}
]

# 2. Define formatting prompt template for each example
example_prompt = PromptTemplate(
    input_variables=["question", "answer"],
    template="Question: {question}\nAnswer: {answer}"
)

# 3. Create FewShotPromptTemplate
few_shot_prompt = FewShotPromptTemplate(
    examples=examples,
    example_prompt=example_prompt,
    suffix="Question: {input}\nAnswer:",
    input_variables=["input"]
)

# 4. Format the final prompt
formatted_prompt = few_shot_prompt.format(input="10 + 10")
print("--- Formatted Prompt ---")
print(formatted_prompt)

# 5. Initialize model and invoke
llm = init_chat_model("llama-3.3-70b-versatile", model_provider="groq")
response = llm.invoke(formatted_prompt)

print("\n--- LLM Response ---")
print(response.content)
```

**Output:**

```text theme={null}
--- Formatted Prompt ---
Question: 2 + 2
Answer: 4

Question: 3 + 3
Answer: 6

Question: 10 + 10
Answer:

--- LLM Response ---
20
```

#### Example 2: Sentiment Classifier

Demonstrate sentiment analysis classification (Positive/Negative) using few-shot templates.

```python theme={null}
from langchain_core.prompts import PromptTemplate, FewShotPromptTemplate
from langchain.chat_models import init_chat_model

# 1. Define few-shot examples
examples = [
    {"text": "I love this product.", "sentiment": "Positive"},
    {"text": "This is terrible.", "sentiment": "Negative"},
    {"text": "This movie is amazing.", "sentiment": "Positive"}
]

# 2. Define example prompt template
example_prompt = PromptTemplate(
    input_variables=["text", "sentiment"],
    template="Text: {text}\nSentiment: {sentiment}"
)

# 3. Create FewShotPromptTemplate
few_shot_prompt = FewShotPromptTemplate(
    examples=examples,
    example_prompt=example_prompt,
    suffix="Text: {input}\nSentiment:",
    input_variables=["input"]
)

formatted_prompt = few_shot_prompt.format(input="The customer support was extremely unhelpful and rude.")
print("--- Formatted Prompt ---")
print(formatted_prompt)

# 4. Initialize model and invoke
llm = init_chat_model("llama-3.3-70b-versatile", model_provider="groq")
response = llm.invoke(formatted_prompt)

print("\n--- LLM Response ---")
print(response.content.strip())
```

**Output:**

```text theme={null}
--- Formatted Prompt ---
Text: I love this product.
Sentiment: Positive

Text: This is terrible.
Sentiment: Negative

Text: This movie is amazing.
Sentiment: Positive

Text: The customer support was extremely unhelpful and rude.
Sentiment:

--- LLM Response ---
Negative
```

### 1.3 Few-Shot Practice Exercise

#### Exercise: Few-Shot Entity Extraction

Create a few-shot prompt using `FewShotPromptTemplate` that formats examples for extracting a person and their company from text.

**Instructions:**

1. Import `PromptTemplate` and `FewShotPromptTemplate`.
2. Define a list containing two example dictionaries matching variables `text` and `output`.
   * Example 1: `"John works at Google."` -> `{"person": "John", "company": "Google"}`
   * Example 2: `"Alice joined Microsoft."` -> `{"person": "Alice", "company": "Microsoft"}`
3. Configure the `example_prompt` template formatting.
4. Assemble the `FewShotPromptTemplate` specifying a suffix to query for `"Bob works at Amazon."`.
5. Invoke a chat model using this formatted template and print the result.

<Accordion title="Solution">
  ```python theme={null}
  from langchain_core.prompts import PromptTemplate, FewShotPromptTemplate
  from langchain.chat_models import init_chat_model

  # 1. Define few-shot examples
  examples = [
      {"text": "John works at Google.", "output": '{"person": "John", "company": "Google"}'},
      {"text": "Alice joined Microsoft.", "output": '{"person": "Alice", "company": "Microsoft"}'}
  ]

  # 2. Define example prompt template
  example_prompt = PromptTemplate(
      input_variables=["text", "output"],
      template="Text: {text}\nOutput: {output}"
  )

  # 3. Define FewShotPromptTemplate
  few_shot_template = FewShotPromptTemplate(
      examples=examples,
      example_prompt=example_prompt,
      suffix="Text: {input}\nOutput:",
      input_variables=["input"]
  )

  # 4. Format prompt
  formatted_prompt = few_shot_template.format(input="Bob works at Amazon.")

  # 5. Initialize model and invoke
  llm = init_chat_model("llama-3.3-70b-versatile", model_provider="groq")
  response = llm.invoke(formatted_prompt)

  print(response.content)
  ```
</Accordion>

## 2. Sequential Prompting

Sequential prompting chains multiple prompts together so the output of one LLM call is automatically passed as an input variable into the next.

```text theme={null}
User Input -> [Prompt 1] -> LLM -> [Response 1] -> [Prompt 2 (utilizes Response 1)] -> LLM -> Final Response
```

### 2.1 Sequential Chains using LCEL

You can construct sequential chains cleanly using LangChain Expression Language:

```python theme={null}
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain.chat_models import init_chat_model

llm = init_chat_model("groq:llama-3.3-70b-versatile")

# First Prompt: Generates detailed information
info_prompt = PromptTemplate(
    input_variables=["topic"],
    template="Provide a detailed overview of {topic}."
)

# Second Prompt: Summarizes information generated by Prompt 1
summary_prompt = PromptTemplate(
    input_variables=["details"],
    template="Summarize the following details in 2 bullet points:\n{details}"
)

# Compose the sequential chain
# We map the output of the first chain to the input variable 'details' for the second prompt
sequential_chain = (
    {"details": info_prompt | llm | StrOutputParser()}
    | summary_prompt
    | llm
    | StrOutputParser()
)

response = sequential_chain.invoke({"topic": "Docker"})
print(response)
```

### 2.2 Sequential Practice Exercise

#### Exercise: Sequential Learning Planner

Write a sequential chain that takes a goal activity (e.g., `"learn to swim"`), asks the LLM to write a comprehensive learning guide, and then passes that guide to a second prompt that formats it as a 1-week crash course schedule.

**Instructions:**

1. Import `PromptTemplate`, `StrOutputParser`, and `init_chat_model`.
2. Define `learning_prompt` using `PromptTemplate` to suggest a step-by-step plan for learning `{activity}`.
3. Define `time_prompt` using `PromptTemplate` to create a concise 1-week schedule for a `{learning_plan}`.
4. Compose the sequential chain using LCEL, mapping the first sub-chain output to the variable `"learning_plan"`.
5. Call `.invoke()` passing `"learn to swim"` and print the response.

<Accordion title="Solution">
  ```python theme={null}
  from langchain_core.prompts import PromptTemplate
  from langchain_core.output_parsers import StrOutputParser
  from langchain.chat_models import init_chat_model

  llm = init_chat_model("groq:llama-3.3-70b-versatile")

  learning_prompt = PromptTemplate(
      input_variables=["activity"],
      template="I want to learn how to {activity}. Can you suggest how I can learn this step-by-step?"
  )

  time_prompt = PromptTemplate(
      input_variables=["learning_plan"],
      template="I only have one week. Can you create a concise plan to help me hit this goal: {learning_plan}."
  )

  # Complete the sequential chain with LCEL
  seq_chain = (
      {"learning_plan": learning_prompt | llm | StrOutputParser()}
      | time_prompt
      | llm
      | StrOutputParser()
  )

  # Call the chain
  result = seq_chain.invoke({"activity": "learn to swim"})
  print(result)
  ```
</Accordion>

## 💻 Practice Notebooks

Master all the concepts from this page with hands-on practice:

* **Practice in VS Code**: Open the notebook in your local editor. Requires a local `.env` file containing your API keys.
* **Practice in Google Colab**: Open the notebook directly in Colab. Setup cells are included to install packages and request API keys.

[💻 VS Code](vscode://file/Users/sivaprasad/Downloads/GenAI%20With%20Python/public/notebooks/prompt-engg/prompt-engg-fewshot-vscode.ipynb) | [🚀 Colab](https://colab.research.google.com/github/prasad230776/genai-course/blob/master/public/notebooks/prompt-engg/prompt-engg-fewshot-colab.ipynb) | <a href="/public/notebooks/prompt-engg/prompt-engg-fewshot-vscode.ipynb" download>📥 Download Notebook</a>
