Agentic Design Pattern: Multi Turn Dialogue

Written by

in


    """
Pattern 128: Multi-Turn Dialogue Management

Description:
    Manages coherent multi-turn conversations with context tracking, dialogue state,
    and appropriate responses across multiple exchanges.

Components:
    - Dialogue state tracking
    - Context management
    - Intent recognition
    - Response generation

Use Cases:
    - Conversational AI assistants
    - Customer service chatbots
    - Interactive tutoring systems

LangChain Implementation:
    Uses LangChain memory, state management, and conversation chains.
"""

import os
from typing import List, Dict, Any, Optional
from dotenv import load_dotenv
from datetime import datetime
from enum import Enum

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.output_parsers import StrOutputParser, JsonOutputParser
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
from langchain_core.runnables import RunnablePassthrough
from langchain_core.pydantic_v1 import BaseModel, Field

load_dotenv()


class DialogueState(str, Enum):
    """Dialogue states"""
    GREETING = "greeting"
    INFO_GATHERING = "info_gathering"
    CLARIFICATION = "clarification"
    PROCESSING = "processing"
    RESPONSE = "response"
    CLOSING = "closing"


class UserIntent(BaseModel):
    """Detected user intent"""
    intent: str = Field(description="Primary intent")
    confidence: float = Field(description="Confidence score")
    entities: Dict[str, Any] = Field(description="Extracted entities")


class DialogueContext(BaseModel):
    """Dialogue context"""
    state: str = Field(description="Current dialogue state")
    topic: Optional[str] = Field(description="Current topic")
    user_goal: Optional[str] = Field(description="User's goal")
    collected_info: Dict[str, Any] = Field(default_factory=dict)


class MultiTurnDialogueAgent:
    """Agent for managing multi-turn conversations"""
    
    def __init__(self, model: str = "gpt-4o-mini", temperature: float = 0.7):
        self.llm = ChatOpenAI(model=model, temperature=temperature)
        self.conversation_history: List[Dict[str, str]] = []
        self.context: DialogueContext = DialogueContext(
            state=DialogueState.GREETING.value,
            topic=None,
            user_goal=None,
            collected_info={}
        )
        
        # Intent detection prompt
        self.intent_prompt = ChatPromptTemplate.from_messages([
            ("system", """Analyze the user's message and detect their intent.
Common intents: greeting, question, request_info, provide_info, complaint, thanks, goodbye

Return JSON:
{{
  "intent": "intent_name",
  "confidence": 0.9,
  "entities": {{"key": "value"}}
}}"""),
            ("user", "{message}")
        ])
        
        # Response generation with context
        self.response_prompt = ChatPromptTemplate.from_messages([
            ("system", """You are a helpful conversational AI assistant.
Current conversation context:
- State: {state}
- Topic: {topic}
- User Goal: {user_goal}
- Collected Info: {collected_info}

Provide a natural, contextually appropriate response that:
1. Acknowledges the user's message
2. Maintains conversation flow
3. Helps achieve the user's goal
4. Asks follow-up questions if needed"""),
            MessagesPlaceholder(variable_name="history"),
            ("user", "{message}")
        ])
        
        self.intent_parser = JsonOutputParser()
        self.response_parser = StrOutputParser()
    
    def detect_intent(self, message: str) -> UserIntent:
        """Detect user intent from message"""
        try:
            chain = self.intent_prompt | self.llm | self.intent_parser
            result = chain.invoke({"message": message})
            
            return UserIntent(
                intent=result["intent"],
                confidence=result["confidence"],
                entities=result.get("entities", {})
            )
        except Exception as e:
            print(f"Intent detection error: {e}")
            return UserIntent(intent="unknown", confidence=0.0, entities={})
    
    def update_context(self, intent: UserIntent, message: str):
        """Update dialogue context based on intent"""
        # Update state based on intent
        if intent.intent == "greeting":
            self.context.state = DialogueState.GREETING.value
        elif intent.intent in ["question", "request_info"]:
            self.context.state = DialogueState.INFO_GATHERING.value
        elif intent.intent == "provide_info":
            self.context.state = DialogueState.PROCESSING.value
            # Store provided information
            self.context.collected_info.update(intent.entities)
        elif intent.intent == "goodbye":
            self.context.state = DialogueState.CLOSING.value
        
        # Extract topic if not set
        if not self.context.topic and len(self.conversation_history) > 0:
            # Simple topic extraction (could be enhanced)
            for entity_key in intent.entities:
                if entity_key not in ["time", "date", "number"]:
                    self.context.topic = intent.entities[entity_key]
                    break
    
    def generate_response(self, message: str) -> str:
        """Generate contextually appropriate response"""
        # Detect intent
        intent = self.detect_intent(message)
        print(f"\nšŸŽÆ Detected Intent: {intent.intent} (confidence: {intent.confidence:.2f})")
        
        # Update context
        self.update_context(intent, message)
        print(f"šŸ“Š Dialogue State: {self.context.state}")
        if self.context.topic:
            print(f"šŸ’” Topic: {self.context.topic}")
        
        # Prepare conversation history for prompt
        history = []
        for turn in self.conversation_history[-5:]:  # Last 5 turns
            if turn["role"] == "user":
                history.append(HumanMessage(content=turn["content"]))
            else:
                history.append(AIMessage(content=turn["content"]))
        
        # Generate response
        chain = self.response_prompt | self.llm | self.response_parser
        
        response = chain.invoke({
            "message": message,
            "state": self.context.state,
            "topic": self.context.topic or "not set",
            "user_goal": self.context.user_goal or "not determined",
            "collected_info": str(self.context.collected_info),
            "history": history
        })
        
        return response
    
    def chat(self, user_message: str) -> str:
        """Process user message and return response"""
        print(f"\n{'='*60}")
        print(f"šŸ‘¤ User: {user_message}")
        
        # Add to history
        self.conversation_history.append({
            "role": "user",
            "content": user_message,
            "timestamp": datetime.now().isoformat()
        })
        
        # Generate response
        response = self.generate_response(user_message)
        
        # Add response to history
        self.conversation_history.append({
            "role": "assistant",
            "content": response,
            "timestamp": datetime.now().isoformat()
        })
        
        print(f"šŸ¤– Assistant: {response}")
        
        return response
    
    def get_conversation_summary(self) -> str:
        """Generate summary of conversation"""
        if not self.conversation_history:
            return "No conversation yet."
        
        summary_prompt = ChatPromptTemplate.from_messages([
            ("system", "Summarize the following conversation in 2-3 sentences."),
            ("user", "{conversation}")
        ])
        
        conversation_text = "\n".join([
            f"{turn['role']}: {turn['content']}"
            for turn in self.conversation_history
        ])
        
        chain = summary_prompt | self.llm | StrOutputParser()
        summary = chain.invoke({"conversation": conversation_text})
        
        return summary
    
    def reset_conversation(self):
        """Reset conversation state"""
        self.conversation_history = []
        self.context = DialogueContext(
            state=DialogueState.GREETING.value,
            topic=None,
            user_goal=None,
            collected_info={}
        )
        print("\nšŸ”„ Conversation reset")


def demonstrate_multi_turn_dialogue():
    """Demonstrate multi-turn dialogue management"""
    print("=" * 80)
    print("Pattern 128: Multi-Turn Dialogue Management")
    print("=" * 80)
    
    agent = MultiTurnDialogueAgent()
    
    # Example 1: Customer service conversation
    print("\n" + "=" * 80)
    print("EXAMPLE 1: Customer Service Conversation")
    print("=" * 80)
    
    conversation_1 = [
        "Hi, I need help with my order",
        "My order number is 12345",
        "It was supposed to arrive yesterday but I haven't received it",
        "Yes, my address is 123 Main Street",
        "Thank you for your help!"
    ]
    
    for message in conversation_1:
        agent.chat(message)
    
    print("\n" + "-" * 60)
    print("šŸ“ Conversation Summary:")
    print(agent.get_conversation_summary())
    
    # Example 2: Technical support
    print("\n" + "=" * 80)
    print("EXAMPLE 2: Technical Support Conversation")
    print("=" * 80)
    
    agent.reset_conversation()
    
    conversation_2 = [
        "Hello, my computer won't start",
        "It's a laptop, about 2 years old",
        "When I press the power button, nothing happens at all",
        "No, there are no lights or sounds",
        "Okay, I'll try that. Thanks!"
    ]
    
    for message in conversation_2:
        agent.chat(message)
    
    print("\n" + "-" * 60)
    print("šŸ“ Conversation Summary:")
    print(agent.get_conversation_summary())
    
    # Example 3: Information gathering
    print("\n" + "=" * 80)
    print("EXAMPLE 3: Information Gathering Conversation")
    print("=" * 80)
    
    agent.reset_conversation()
    
    conversation_3 = [
        "I want to book a flight",
        "From New York to London",
        "Next week, preferably Tuesday",
        "Economy class is fine",
        "Great, please proceed with the booking"
    ]
    
    for message in conversation_3:
        agent.chat(message)
    
    print("\n" + "-" * 60)
    print("šŸ“ Conversation Summary:")
    print(agent.get_conversation_summary())
    
    print("\n" + "-" * 60)
    print("šŸ“Š Final Context State:")
    print(f"  State: {agent.context.state}")
    print(f"  Topic: {agent.context.topic}")
    print(f"  Collected Info: {agent.context.collected_info}")
    print(f"  Total Turns: {len(agent.conversation_history)}")
    
    # Summary
    print("\n" + "=" * 80)
    print("SUMMARY")
    print("=" * 80)
    print("""
Multi-Turn Dialogue Management Pattern:
- Maintains context across conversation turns
- Tracks dialogue state and user intent
- Generates contextually appropriate responses
- Manages information collection

Key Features:
āœ“ Intent detection
āœ“ State tracking
āœ“ Context maintenance
āœ“ Natural conversation flow
āœ“ Information extraction

Dialogue States:
• Greeting - Initial interaction
• Info Gathering - Collecting information
• Clarification - Resolving ambiguities
• Processing - Handling requests
• Response - Providing answers
• Closing - Ending conversation

Benefits:
āœ“ Coherent multi-turn conversations
āœ“ Context-aware responses
āœ“ Better user experience
āœ“ Goal-oriented dialogue
āœ“ Efficient information gathering
    """)


if __name__ == "__main__":
    demonstrate_multi_turn_dialogue()

    

Agentic Design Pattern: Multi Turn Dialogue

Pattern 128: Multi Turn Dialogue – Line-by-Line Explanation

Overview

This document provides a detailed line-by-line explanation of the Multi Turn Dialogue pattern implementation.


Pattern Description

Pattern 128: Multi-Turn Dialogue Management

Description: Manages coherent multi-turn conversations with context tracking, dialogue state, and appropriate responses across multiple exchanges.

Components: – Dialogue state tracking – Context management – Intent recognition – Response generation

Use Cases: – Conversational AI assistants – Customer service chatbots – Interactive tutoring systems

LangChain Implementation: Uses LangChain memory, state management, and conversation chains.


File Statistics

  • Total Lines: 357
  • Classes: 4
  • Functions: 1
  • Constants: 0

Module Documentation

"""
Pattern 128: Multi-Turn Dialogue Management

Description:
    Manages coherent multi-turn conversations with context tracking, dialogue state,
    and appropriate responses across multiple exchanges.

Components:
    - Dialogue state tracking
    - Context management
    - Intent recognition
    - Response generation

Use Cases:
    - Conversational AI assistants
    - Customer service chatbots
    - Interactive tutoring systems

LangChain Implementation:
    Uses LangChain memory, state management, and conversation chains.
"""

Purpose: This module implements the Multi Turn Dialogue pattern, providing a structured approach to agent design and implementation.


Import Statements

Standard Library Imports

import datetime.datetime
import enum.Enum
import langchain_core.messages.AIMessage
import langchain_core.messages.HumanMessage
import langchain_core.messages.SystemMessage
import langchain_core.output_parsers.JsonOutputParser
import langchain_core.output_parsers.StrOutputParser
import langchain_core.prompts.ChatPromptTemplate
import langchain_core.prompts.MessagesPlaceholder
import langchain_core.pydantic_v1.BaseModel
import langchain_core.pydantic_v1.Field
import langchain_core.runnables.RunnablePassthrough
import os
import typing.Any
import typing.Dict
import typing.List
import typing.Optional

Purpose: Core Python functionality: – datetime.datetime: Date and time handling – enum.Enum: Enumeration support – langchain_core.messages.AIMessage: Regular expression operations – langchain_core.messages.HumanMessage: Regular expression operations – langchain_core.messages.SystemMessage: Regular expression operations – langchain_core.output_parsers.JsonOutputParser: Regular expression operations – langchain_core.output_parsers.StrOutputParser: Regular expression operations – langchain_core.prompts.ChatPromptTemplate: Regular expression operations – langchain_core.prompts.MessagesPlaceholder: Regular expression operations – langchain_core.pydantic_v1.BaseModel: Regular expression operations – langchain_core.pydantic_v1.Field: Regular expression operations – langchain_core.runnables.RunnablePassthrough: Regular expression operations – os: Environment variable access and system operations – typing.Any: Type hints for better code documentation (List, Dict, Any, Optional) – typing.Dict: Type hints for better code documentation (List, Dict, Any, Optional) – typing.List: Type hints for better code documentation (List, Dict, Any, Optional) – typing.Optional: Type hints for better code documentation (List, Dict, Any, Optional)

LangChain Imports

from langchain_openai.ChatOpenAI

Purpose: LangChain framework components: – LLM Interface: Wrapper for OpenAI models – Chains: Composable building blocks for complex workflows

Other Imports

from dotenv.load_dotenv

Purpose: Additional dependencies for specialized functionality.


Classes

Class: DialogueState

Lines 39-46

Inherits from: str, Enum

Purpose:

Dialogue states

Class: UserIntent

Lines 49-53

Inherits from: BaseModel

Purpose:

Detected user intent

Class: DialogueContext

Lines 56-61

Inherits from: BaseModel

Purpose:

Dialogue context

Class: MultiTurnDialogueAgent

Lines 64-238

Purpose:

Agent for managing multi-turn conversations

Methods:

__init__(self, model, temperature)

Lines 67-110

Purpose: Initialize the class instance with required parameters.

Parameters: – model: Language model instance – temperature: Sampling temperature (0-1)

detect_intent(self, message)

Lines 112-125

Purpose:

Detect user intent from message

Parameters: – message: Message

Returns: UserIntent

update_context(self, intent, message)

Lines 127-147

Purpose:

Update dialogue context based on intent

Parameters: – intent: Intent – message: Message

generate_response(self, message)

Lines 149-181

Purpose:

Generate contextually appropriate response

Parameters: – message: Message

Returns: str

chat(self, user_message)

Lines 183-207

Purpose:

Process user message and return response

Parameters: – user_message: User message

Returns: str

get_conversation_summary(self)

Lines 209-227

Purpose:

Generate summary of conversation

Parameters:

Returns: str

reset_conversation(self)

Lines 229-238

Purpose:

Reset conversation state

Parameters:


Functions

demonstrate_multi_turn_dialogue()

Lines 241-352

Purpose:

Demonstrate multi-turn dialogue management

Key Concepts Summary

Pattern Overview

The Multi Turn Dialogue pattern provides a structured approach to building AI agents with specific capabilities and behaviors. This implementation leverages LangChain’s composable components to create a robust and maintainable solution.

Core Components

  1. Agent Class: Main implementation of the pattern logic
  2. Prompt Engineering: Carefully crafted prompts to guide LLM behavior
  3. Tool Integration: Optional tools for extended functionality
  4. Memory Management: State handling across interactions
  5. Output Parsing: Structured response processing

When to Use

āœ… Good for: – Scenarios requiring this specific pattern’s strengths – Applications needing structured agent behavior – Systems where explainability is important – Production environments requiring reliability

āŒ Not ideal for: – Simple single-prompt tasks – Scenarios requiring different pattern characteristics – Resource-constrained environments (if computationally intensive)


Best Practices Demonstrated

1. Environment Configuration

  • āœ… Use .env files for API keys
  • āœ… Never hardcode credentials
  • āœ… Validate environment variables on startup

2. Error Handling

  • āœ… Graceful degradation on failures
  • āœ… Informative error messages
  • āœ… Logging for debugging

3. Prompt Engineering

  • āœ… Clear, specific instructions
  • āœ… Structured output formats
  • āœ… Few-shot examples when beneficial

4. Code Organization

  • āœ… Separation of concerns (class-based design)
  • āœ… Type hints for clarity
  • āœ… Docstrings for documentation

5. Performance Optimization

  • āœ… Appropriate temperature settings
  • āœ… Token budget management
  • āœ… Caching where applicable

LangChain Components Used

  • ChatOpenAI: OpenAI model interface
  • Chains: Sequential component composition

Why LangChain? – Composable abstractions for LLM applications – Rich ecosystem of integrations – Production-ready patterns – Active community and development


Execution Flow

1. Initialize agent with configuration
   ↓
2. Load environment and dependencies
   ↓
3. Prepare prompt with input data
   ↓
4. Invoke LLM through LangChain
   ↓
5. Parse and validate response
   ↓
6. Return formatted result

Key Decision Points: – Model selection (temperature, model type) – Prompt structure (few-shot vs zero-shot) – Error handling strategy – Output format validation


Performance Considerations

Computational Cost

  • LLM Calls: Primary cost factor (based on tokens)
  • Latency: Depends on model and input size
  • Memory: Minimal for most patterns

Optimization Strategies

  1. Prompt Optimization: Minimize tokens while maintaining clarity
  2. Caching: Cache repeated queries/responses
  3. Model Selection: Use smaller models when appropriate
  4. Batch Processing: Group similar requests
  5. Streaming: For long responses, stream tokens

Cost Estimates (Approximate)

  • GPT-3.5-turbo: ~$0.002 per request
  • GPT-4: ~$0.03 per request
  • GPT-4-turbo: ~$0.01 per request

Actual costs vary based on input/output token counts


Common Pitfalls to Avoid

1. Prompt Design Issues

āŒ Vague or ambiguous instructions
āœ… Clear, specific, testable prompts

2. Error Handling

āŒ Ignoring API errors or rate limits
āœ… Implement retry logic and graceful degradation

3. Token Management

āŒ Exceeding model token limits
āœ… Truncate inputs, chunk processing

4. Security

āŒ Exposing API keys in code
āœ… Use environment variables

5. Testing

āŒ Only testing happy paths
āœ… Test edge cases, failures, malformed inputs

6. Cost Management

āŒ Unmonitored API usage
āœ… Implement budgets, alerts, and monitoring


Further Improvements

Production Enhancements

  1. Monitoring: Add logging, metrics, tracing
  2. Validation: Input/output schema validation
  3. Rate Limiting: Implement request throttling
  4. Caching: Redis or in-memory cache for responses
  5. Async Support: Non-blocking operations for scalability

Feature Extensions

  1. Multi-Model Support: Switch between providers
  2. Fallback Models: Graceful degradation strategy
  3. Custom Tools: Domain-specific function calling
  4. Advanced Memory: Vector stores for long-term memory
  5. Evaluation: Automated testing and benchmarking

Code Quality

  1. Testing: Unit tests, integration tests
  2. Documentation: API docs, usage examples
  3. Type Safety: Stricter type hints
  4. Configuration: Externalized config files

Related Patterns

  • Pattern 127
  • Pattern 129

Pattern Families: – Reasoning Patterns (001-011) – Multi-Agent Patterns (012-019) – Tool Use Patterns (020-025) – Memory Patterns (026-031) – And many more…


Summary

The Multi Turn Dialogue pattern provides a powerful approach to building AI agents with specific capabilities. This implementation demonstrates:

āœ… Key Strengths: – Clean, maintainable code structure – LangChain integration for composability – Type hints and documentation – Production-ready patterns

šŸŽÆ Best Use Cases: – Applications requiring this pattern’s characteristics – Systems needing structured agent behavior – Production environments requiring reliability

šŸ“š Learning Resources: – LangChain Documentation: https://python.langchain.com/ – Pattern Documentation: See related patterns – OpenAI API Reference: https://platform.openai.com/docs/


File: 128_multi_turn_dialogue.py
Total Lines: 357
Pattern Category: AI Agent Design Pattern #128


Generated from actual implementation code
For educational and reference purposes

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *