Skip to content

Session 2: Building Conversational Applications

Synopsis

Covers how to manage multi-turn interactions, preserve dialogue context, and handle user intent in chat-style applications. Learners move from single calls to stateful LLM experiences.

Session Content

Session 2: Building Conversational Applications

Session Overview

In this session, learners will build a solid foundation for creating conversational applications with LLMs using Python and the OpenAI Responses API. The focus is on structuring chat interactions, maintaining conversation state, designing effective prompts, and implementing practical chatbot patterns.

By the end of this session, learners will be able to:

  • Understand the core building blocks of conversational AI applications
  • Use the OpenAI Python SDK with the Responses API for multi-turn conversations
  • Manage conversation history in a Python application
  • Design prompts for better conversational behavior
  • Build a simple command-line chatbot
  • Add basic safety and reliability improvements to a conversational app

Learning Objectives

After completing this session, learners should be able to:

  1. Explain how conversational applications differ from single-shot text generation
  2. Use the Responses API to generate assistant replies in Python
  3. Maintain and pass message history for multi-turn interactions
  4. Implement a reusable chatbot loop in Python
  5. Apply prompt design techniques for tone, behavior, and context control
  6. Add simple guardrails such as input validation and conversation limits

Agenda for a 45-Minute Session

  • 0–10 min: Theory — conversational AI fundamentals
  • 10–20 min: Theory + demo — the Responses API for chat-style apps
  • 20–30 min: Hands-on Exercise 1 — first multi-turn chatbot
  • 30–40 min: Hands-on Exercise 2 — chatbot with memory and behavior control
  • 40–45 min: Wrap-up, common pitfalls, and next steps

1. Theory: What Makes an Application Conversational?

A conversational application is different from a one-off text generation script because it must:

  • Handle multiple turns
  • Preserve context
  • Maintain a consistent assistant behavior
  • Respond appropriately to evolving user goals
  • Sometimes enforce rules, tone, or domain-specific scope

Key Concepts

1.1 Single-turn vs Multi-turn

  • Single-turn: The user sends one prompt and gets one answer
  • Multi-turn: The system tracks prior messages so responses remain coherent

1.2 Conversation State

A model does not automatically remember earlier turns unless you provide them in the request. Your application is responsible for storing and sending relevant context.

Typical state may include: - System/developer instructions - User messages - Assistant replies - Metadata such as timestamps or topic labels

1.3 Prompt Roles and Behavioral Control

Conversational quality often depends on how well you define: - The assistant’s role - The expected tone - Domain limits - Response format

Examples: - “You are a helpful Python tutor.” - “Answer in concise bullet points.” - “If the user asks outside the supported topic, politely say so.”

1.4 Context Window Awareness

As conversations get longer, passing the full history becomes expensive and less efficient. Applications often: - Keep only recent turns - Summarize older turns - Store structured facts separately


2. Using the OpenAI Responses API for Conversational Apps

In this session, we will use the OpenAI Python SDK and the Responses API.

Install the SDK

pip install openai

Set Your API Key

On macOS/Linux:

export OPENAI_API_KEY="your_api_key_here"

On Windows PowerShell:

setx OPENAI_API_KEY "your_api_key_here"

3. Minimal Conversational Request

The Responses API can be used to generate a reply from a user message.

Example: One User Message

from openai import OpenAI

# Create a client using the API key from the OPENAI_API_KEY environment variable.
client = OpenAI()

response = client.responses.create(
    model="gpt-5.4-mini",
    input="Explain what a conversational application is in one short paragraph."
)

# Print the model's text output.
print(response.output_text)

Example Output

A conversational application is a software system that interacts with users through back-and-forth dialogue, often using natural language. Unlike single-response systems, it keeps track of context across multiple turns so it can answer follow-up questions, clarify meaning, and provide a more natural user experience.

4. Structuring Chat Inputs

For conversational behavior, it is useful to send structured input representing prior turns and instructions.

Example: Instructions + User Message

from openai import OpenAI

client = OpenAI()

response = client.responses.create(
    model="gpt-5.4-mini",
    instructions=(
        "You are a helpful Python tutor. "
        "Answer clearly, briefly, and include one example when useful."
    ),
    input="What is the difference between a list and a tuple in Python?"
)

print(response.output_text)

Example Output

A list is mutable, which means you can change its contents after creation, while a tuple is immutable and cannot be modified. For example, you can append to a list like [1, 2], but you cannot append to a tuple like (1, 2).

5. Managing Conversation History in Python

A simple conversational app stores previous turns in a list and sends that list with each new user message.

Core Pattern

You maintain a conversation such as:

  • User: “Hi”
  • Assistant: “Hello!”
  • User: “Can you explain loops?”
  • Assistant: “Sure...”

Then you pass the relevant history on each turn.

Example: Manual Conversation History

from openai import OpenAI

client = OpenAI()

# Store conversation turns in a list.
conversation = [
    {
        "role": "user",
        "content": "I'm learning Python. What does a for loop do?"
    },
    {
        "role": "assistant",
        "content": "A for loop repeats a block of code for each item in a sequence."
    },
    {
        "role": "user",
        "content": "Can you show me a small example?"
    }
]

response = client.responses.create(
    model="gpt-5.4-mini",
    instructions=(
        "You are a beginner-friendly Python tutor. "
        "Keep answers short and include code examples when appropriate."
    ),
    input=conversation
)

print(response.output_text)

Example Output

Sure! Here is a simple example:

```python
for number in [1, 2, 3]:
    print(number)

This prints each number in the list one at a time.


> Note: Your application is responsible for updating the `conversation` list after each response.

---

## 6. Design Considerations for Better Conversational Apps

### 6.1 Keep Instructions Stable
Use the `instructions` field to define the assistant’s stable behavior:
- Persona
- Scope
- Tone
- Formatting expectations

### 6.2 Keep User Input Separate
Avoid mixing system-level rules with user content. This makes your app:
- Easier to maintain
- More secure
- More predictable

### 6.3 Be Careful with Long Histories
Long transcripts can:
- Increase cost
- Slow responses
- Reduce relevance

A common approach:
- Keep the last 5–10 turns
- Summarize older context
- Persist important user preferences separately

### 6.4 Add Simple App-Level Controls
Examples:
- Reject empty input
- Stop after a certain number of turns
- Limit response length with prompting
- Log requests for debugging during development

---

## 7. Hands-on Exercise 1: Build a Simple Multi-turn CLI Chatbot

### Goal
Create a command-line chatbot that:
- Accepts user input in a loop
- Maintains conversation history
- Sends the history to the Responses API
- Prints assistant replies
- Exits when the user types `quit`

### What You Will Learn
- Basic chatbot loop structure
- Persistent conversation state
- Using `instructions` for consistent assistant behavior

### Starter Code

```python
from openai import OpenAI

# Initialize the OpenAI client.
client = OpenAI()

# Store the full conversation history.
conversation = []

# Define stable assistant behavior.
INSTRUCTIONS = (
    "You are a helpful and friendly assistant for beginner Python developers. "
    "Answer clearly and briefly. If code is requested, provide simple examples."
)

print("Simple Chatbot")
print("Type 'quit' to exit.\n")

while True:
    # Read input from the user.
    user_input = input("You: ").strip()

    # Prevent empty messages from being sent.
    if not user_input:
        print("Assistant: Please enter a message.")
        continue

    # Exit condition.
    if user_input.lower() == "quit":
        print("Assistant: Goodbye!")
        break

    # Add the user's message to the conversation history.
    conversation.append({
        "role": "user",
        "content": user_input
    })

    # Send the full conversation to the model.
    response = client.responses.create(
        model="gpt-5.4-mini",
        instructions=INSTRUCTIONS,
        input=conversation
    )

    # Extract the assistant's text.
    assistant_reply = response.output_text

    # Show the assistant's reply.
    print(f"Assistant: {assistant_reply}\n")

    # Add the assistant response back into the conversation history.
    conversation.append({
        "role": "assistant",
        "content": assistant_reply
    })

Example Usage

Simple Chatbot
Type 'quit' to exit.

You: What is a function in Python?
Assistant: A function is a reusable block of code that performs a specific task. For example, you can define one with `def greet():` and call it whenever needed.

You: Show me a tiny example
Assistant: Sure:

```python
def greet():
    print("Hello!")

greet()

This defines a function and then runs it.

You: quit Assistant: Goodbye!


### Exercise Tasks

1. Run the chatbot and ask 3 related questions
2. Verify that the assistant can answer follow-up questions using prior context
3. Modify the instructions so the assistant always answers in bullet points
4. Test how behavior changes when you make the instructions more specific

### Reflection Questions

- What happens if you remove the conversation history?
- How does changing `instructions` affect tone and consistency?
- What are the pros and cons of storing every message?

---

## 8. Hands-on Exercise 2: Add Memory Limits and Behavior Control

### Goal
Improve the chatbot so it:
- Keeps only recent turns
- Uses stronger behavioral instructions
- Handles off-topic requests politely
- Prevents runaway conversation growth

### What You Will Learn
- Basic context management
- Lightweight guardrails
- More reusable chatbot structure

### Improved Chatbot Code

```python
from openai import OpenAI

# Initialize the OpenAI client.
client = OpenAI()

# Configuration constants.
MODEL_NAME = "gpt-5.4-mini"
MAX_TURNS_TO_KEEP = 6  # Number of most recent conversation messages to retain.

# Stronger behavioral instructions for a focused assistant.
INSTRUCTIONS = (
    "You are a teaching assistant for beginner Python developers. "
    "Be concise, accurate, and supportive. "
    "Stay focused on Python, programming basics, and learning advice. "
    "If the user asks about unrelated topics, politely explain that you focus on beginner Python learning. "
    "When appropriate, provide short code examples. "
    "Do not overwhelm the learner with too much detail."
)

def trim_conversation(history, max_messages):
    """
    Return only the most recent messages to keep context manageable.

    Args:
        history (list): Full conversation history.
        max_messages (int): Maximum number of messages to keep.

    Returns:
        list: Trimmed conversation history.
    """
    return history[-max_messages:]


def get_assistant_reply(conversation):
    """
    Send the conversation to the model and return the assistant's reply text.

    Args:
        conversation (list): Conversation history as a list of role/content dicts.

    Returns:
        str: Assistant response text.
    """
    response = client.responses.create(
        model=MODEL_NAME,
        instructions=INSTRUCTIONS,
        input=conversation
    )
    return response.output_text


def main():
    """
    Run the command-line chatbot.
    """
    conversation = []

    print("Python Learning Assistant")
    print("Type 'quit' to exit.\n")

    while True:
        user_input = input("You: ").strip()

        if not user_input:
            print("Assistant: Please type a message.\n")
            continue

        if user_input.lower() == "quit":
            print("Assistant: Goodbye and happy coding!")
            break

        # Add the user message.
        conversation.append({
            "role": "user",
            "content": user_input
        })

        # Trim older history to control context size.
        trimmed_conversation = trim_conversation(conversation, MAX_TURNS_TO_KEEP)

        # Generate assistant reply.
        assistant_reply = get_assistant_reply(trimmed_conversation)

        print(f"Assistant: {assistant_reply}\n")

        # Save the assistant reply in the conversation state.
        conversation.append({
            "role": "assistant",
            "content": assistant_reply
        })


if __name__ == "__main__":
    main()

Example Usage

Python Learning Assistant
Type 'quit' to exit.

You: What is a variable?
Assistant: A variable is a name that stores a value in your program. For example, `age = 25` stores the number 25 in the variable `age`.

You: Can you give me a string example too?
Assistant: Sure. A string example is `name = "Ava"`. Here, the variable `name` stores the text `"Ava"`.

You: Who will win the next football championship?
Assistant: I’m focused on helping with beginner Python and programming topics. If you want, I can help you build a Python program related to sports data instead.

You: quit
Assistant: Goodbye and happy coding!

Exercise Tasks

  1. Change MAX_TURNS_TO_KEEP from 6 to 2 and observe the effect on follow-up questions
  2. Ask an off-topic question and verify the assistant stays in scope
  3. Update the instructions to make the assistant always include:
  4. one short explanation
  5. one code example
  6. one practice question
  7. Refactor the code to store timestamps with each user message

Reflection Questions

  • What context was lost when fewer turns were retained?
  • How do scope-limiting instructions improve application reliability?
  • When should you summarize old context instead of dropping it?

9. Optional Extension: Add Simple Summarized Memory

When conversations get longer, instead of keeping every turn, you can store: - Recent messages - A short summary of earlier conversation

Example Idea

Track: - summary: “User is learning loops and functions, prefers short examples.” - recent_messages: last 4 exchanges

Then include both in the next request.

Example Pattern

from openai import OpenAI

client = OpenAI()

MODEL_NAME = "gpt-5.4-mini"

summary = (
    "The user is a beginner Python learner. "
    "They previously asked about variables, loops, and functions. "
    "They prefer short explanations and simple examples."
)

recent_messages = [
    {"role": "user", "content": "Can you remind me what a function returns?"},
    {"role": "assistant", "content": "A function can return a value using the `return` keyword."},
    {"role": "user", "content": "Please show a tiny example."}
]

response = client.responses.create(
    model=MODEL_NAME,
    instructions=(
        "You are a concise Python tutor for beginners. "
        "Use the provided context summary when helpful."
    ),
    input=[
        {
            "role": "user",
            "content": f"Conversation summary: {summary}"
        },
        *recent_messages
    ]
)

print(response.output_text)

Example Output

Here is a tiny example:

```python
def add(a, b):
    return a + b

result = add(2, 3)
print(result)

The function returns 5, which is stored in result. ```

In production, summaries are often generated automatically rather than written by hand.


10. Common Pitfalls

10.1 Not Saving Assistant Replies

If you store only user messages, follow-up context becomes incomplete.

10.2 Letting Context Grow Forever

Unlimited history can lead to: - higher cost - slower performance - reduced relevance

10.3 Weak or Ambiguous Instructions

If you do not clearly define the assistant’s role, answers may be inconsistent.

10.4 Mixing App Logic with Prompt Logic

Keep your Python control flow separate from model instructions: - Python handles loops, validation, storage - The model handles natural language generation

10.5 Ignoring Empty or Malformed Input

Always validate user input before sending it to the API.


11. Best Practices Checklist

  • Use a stable instructions string for assistant behavior
  • Keep conversation state in a structured Python list
  • Append both user and assistant messages
  • Limit conversation length
  • Validate user input
  • Keep app logic deterministic where possible
  • Test with follow-up questions and off-topic prompts
  • Refine instructions based on observed behavior

12. Mini Challenge

Build a chatbot for one of these use cases:

  1. Python Tutor Bot
  2. Explains beginner topics
  3. Gives one short example per answer

  4. Code Review Helper

  5. Reviews small snippets
  6. Gives 2–3 improvement suggestions

  7. Study Coach

  8. Helps a user plan a 7-day Python learning schedule
  9. Remembers the learner’s stated goals during the session

Requirements

  • Use gpt-5.4-mini
  • Use the OpenAI Python SDK
  • Use the Responses API
  • Maintain conversation state
  • Add at least one guardrail in code
  • Add at least one behavior rule in instructions

13. Useful Resources


14. Recap

In this session, you learned how to build basic conversational applications by:

  • Using the OpenAI Responses API in Python
  • Defining assistant behavior with instructions
  • Maintaining multi-turn conversation history
  • Building a CLI chatbot loop
  • Trimming history to manage context
  • Adding simple scope and behavior controls

These are the core building blocks for more advanced agentic systems, where applications not only converse, but also reason over tasks, use tools, and manage longer workflows.


15. Suggested Homework

  1. Build a chatbot that specializes in one topic:
  2. Python basics
  3. debugging help
  4. learning plans

  5. Add these features:

  6. conversation logging to a file
  7. a max turn limit
  8. a short memory summary string

  9. Test your bot with:

  10. related follow-up questions
  11. topic changes
  12. empty input
  13. very long questions

  14. Write down:

  15. what worked well
  16. when context was lost
  17. which instruction changes improved behavior most

End of Session

Next session: Prompt Design for Better Results


Back to Chapter | Back to Master Plan | Previous Session | Next Session