Skip to content

AI-Powered NPC Dialogue Guide

Overview

MAID's AI dialogue system enables Non-Player Characters (NPCs) to have dynamic, context-aware conversations with players using Large Language Models (LLMs). The system supports multiple AI providers (Anthropic Claude, OpenAI GPT, and local Ollama models), maintains conversation history, and provides extensive customization options for NPC personalities and behaviors.

Key features: - Multiple AI Providers: Choose between Anthropic Claude (default), OpenAI GPT, or local Ollama models - Content-Filtered Responses: Responses are buffered and filtered for safety before delivery to ensure appropriate content (see Streaming vs Buffered Mode below) - Conversation Memory: NPCs remember the current conversation context - Personality System: Configure unique personalities, speaking styles, and knowledge domains - Behavioral Constraints: Control what topics NPCs will or will not discuss - Rate Limiting: Prevent abuse with configurable rate limits - Fallback Responses: Graceful degradation when AI is unavailable - Context Injection: NPCs are aware of world state, location, and player information

Quick Start

Here is a simple example of creating an AI-enabled NPC:

from maid_stdlib.components import DialogueComponent, DescriptionComponent, NPCComponent
from maid_engine.core.ecs import Entity

# Create an NPC entity
npc = Entity()

# Add required components
npc.add(DescriptionComponent(
    name="Grumbar",
    short_desc="A burly dwarf blacksmith",
    keywords=["blacksmith", "dwarf", "grumbar"],
))

npc.add(NPCComponent())

# Configure AI dialogue
npc.add(DialogueComponent(
    ai_enabled=True,
    personality="A gruff but kind-hearted dwarf blacksmith who takes pride in his craft.",
    speaking_style="Short sentences. Uses mining metaphors. Says 'aye' instead of yes.",
    npc_role="blacksmith",
    knowledge_domains=["weapons", "armor", "metalworking", "mining"],
    will_discuss=["smithing", "weapons", "local news"],
    wont_discuss=["his past", "his family"],
    greeting="*looks up from the forge* Aye? What can I craft for ye?",
    farewell="*nods and returns to work* Safe travels.",
    max_response_tokens=120,
    temperature=0.7,
))

# Add the NPC to a room in the world
world.add_entity(npc, room_id=forge_room.id)

Players can then interact with this NPC using commands like:

> talk blacksmith Hello! What weapons do you have?

Grumbar says, "Aye, got some fine blades fresh from the forge. Steel swords, iron daggers,
even got a war hammer that'd crack a troll's skull. *gestures at the weapon rack* Take
a look, but mind ye don't touch the hot metal."

DialogueComponent Fields

The DialogueComponent is the heart of NPC AI configuration. Here is a complete reference of all available fields:

AI Configuration

Field Type Default Description
ai_enabled bool True Enable/disable AI for this NPC. Set to False for NPCs that only use scripted responses.
provider_name str \| None None Override the default AI provider. Options: "anthropic", "openai", "ollama". None uses the server's default.
model_name str \| None None Override the default model. Examples: "claude-sonnet-4-20250514", "gpt-4o", "llama3.2". None uses the provider's default.

Personality Configuration

Field Type Default Description
personality str "" Description of the NPC's core personality traits. Guide the AI's response style and tone. Be specific about temperament, worldview, and distinctive characteristics.
speaking_style str "" How the NPC speaks - accent, vocabulary, mannerisms, speech patterns. Use *action* markers for physical actions during speech.
knowledge_domains list[str] [] Topics the NPC knows about and can discuss knowledgeably. The AI draws on these when crafting responses.
secret_knowledge list[str] [] Hidden information the NPC knows but should not readily reveal. The AI may hint at these secrets but won't directly disclose them without persuasion.

Role Configuration

Field Type Default Description
npc_role str "" The NPC's occupation or role in the world (e.g., "blacksmith", "tavern keeper", "town guard"). Helps the AI understand context.
faction str \| None None Faction affiliation for NPCs that belong to groups. Can influence how they respond to players of different factions.

Behavioral Constraints

Field Type Default Description
will_discuss list[str] [] Topics the NPC is comfortable discussing openly. Conversations flow naturally about these subjects.
wont_discuss list[str] [] Topics the NPC avoids or refuses to discuss. The AI will deflect, change subject, or give evasive answers.

Response Settings

Field Type Default Range Description
max_response_tokens int 150 50-500 Maximum length of AI responses in tokens. Lower values (80-100) create terse characters; higher values (150-200) allow more elaborate speech.
temperature float 0.7 0.0-1.0 AI creativity/variability. Lower values (0.3-0.5) create predictable, formal responses; higher values (0.7-0.9) create creative, varied responses.

Fallback Dialogue

Field Type Default Description
greeting str "Hello there." Default greeting when a player first speaks to the NPC. Sets the tone for conversation.
farewell str "Goodbye." Default farewell when ending conversation. Can include *action* markers.
fallback_response str "I have nothing to say about that." Response used when AI is disabled, fails, or the NPC doesn't want to discuss a topic.

Rate Limiting

Field Type Default Description
cooldown_seconds float 2.0 Minimum time between AI responses for this specific NPC. Prevents rapid-fire requests.
last_response_time float 0.0 Internal field tracking when the NPC last responded. Managed automatically.

Creating NPCs

Basic NPC Example: Bartender

A classic MUD archetype - the friendly tavern keeper who knows everyone's business:

from maid_stdlib.components import DialogueComponent

bartender_dialogue = DialogueComponent(
    ai_enabled=True,
    personality=(
        "A gruff but kind-hearted bartender who's seen it all. Wise about "
        "the ways of the world but never preachy. Has a soft spot for "
        "down-on-their-luck adventurers. Protective of his establishment "
        "and regular customers."
    ),
    speaking_style=(
        "Short sentences. Occasional grunts of acknowledgment. Uses 'aye' "
        "and 'nay' instead of yes and no. Wipes glasses while talking. "
        "Speaks in a low, measured voice. Sometimes pauses to serve other "
        "customers (indicated with *action* markers)."
    ),
    npc_role="bartender at The Rusty Tankard tavern",
    knowledge_domains=[
        "local gossip",
        "drinks and brewing",
        "travelers' tales",
        "tavern history",
        "regular customers",
        "local merchants",
        "inn prices",
    ],
    secret_knowledge=[
        "knows about the smuggler's tunnel beneath the tavern",
        "witnessed the murder last winter but fears retribution",
        "the mayor visits late at night in disguise",
    ],
    will_discuss=[
        "drinks",
        "gossip",
        "weather",
        "travelers",
        "local news",
        "room rates",
        "food menu",
        "directions",
    ],
    wont_discuss=[
        "his past",
        "the tunnel",
        "politics",
        "the murder",
        "why he limps",
    ],
    greeting="*looks up from polishing a glass* What'll it be?",
    farewell="*nods* Take care out there.",
    fallback_response="*grunts* I've got nothing to say about that.",
    max_response_tokens=100,
    temperature=0.7,
    cooldown_seconds=2.0,
)

Guard NPC (Formal, Low Temperature)

A by-the-book guard with formal speech patterns. The low temperature (0.5) produces more consistent, predictable responses:

guard_dialogue = DialogueComponent(
    ai_enabled=True,
    personality=(
        "A dutiful and formal town guard. Takes his job seriously and "
        "follows regulations to the letter. Slightly paranoid about "
        "outsiders and suspicious of anyone asking too many questions. "
        "Respects the chain of command but harbors private doubts about "
        "some of his superiors."
    ),
    speaking_style=(
        "Formal, clipped speech. Addresses civilians as 'citizen'. Uses "
        "military terminology and official-sounding phrases. Rarely uses "
        "contractions. Stands at attention while speaking. Sometimes "
        "glances around nervously when discussing sensitive topics."
    ),
    npc_role="town guard stationed at the main gate",
    knowledge_domains=[
        "town laws",
        "recent crimes",
        "patrol routes",
        "wanted criminals",
        "gate procedures",
        "local landmarks",
        "emergency protocols",
    ],
    secret_knowledge=[
        "the captain takes bribes from merchants",
        "something lurks in the old quarter at night",
        "the guards are spread too thin to properly patrol",
    ],
    will_discuss=[
        "laws",
        "directions",
        "safety warnings",
        "recent incidents",
        "curfew hours",
        "weapons policies",
        "checkpoint procedures",
    ],
    wont_discuss=[
        "bribes",
        "corruption",
        "personal life",
        "guard deployment details",
        "his captain's orders",
    ],
    greeting="Halt, citizen. State your business.",
    farewell="Move along. Stay out of trouble.",
    fallback_response="That's not something I'm at liberty to discuss.",
    max_response_tokens=120,
    temperature=0.5,  # Lower temperature for more formal/consistent responses
    cooldown_seconds=2.0,
)

Mysterious NPC (Creative, High Temperature)

An enigmatic merchant who speaks in riddles. The high temperature (0.9) produces more creative and varied responses:

merchant_dialogue = DialogueComponent(
    ai_enabled=True,
    personality=(
        "An enigmatic traveling merchant with a flair for the dramatic. "
        "Hints at secrets and treasures but never gives a straight answer. "
        "Seems to know more than they should about visitors. Has an unsettling "
        "way of appearing exactly when needed. Delights in verbal puzzles and "
        "wordplay."
    ),
    speaking_style=(
        "Flowery, elaborate speech with archaic vocabulary. Uses riddles "
        "and metaphors frequently. Dramatic pauses indicated with '...' in "
        "speech. Speaks with knowing smiles indicated by *smiles mysteriously* "
        "or *eyes glitter*. Uses third person occasionally. Questions often "
        "answered with questions."
    ),
    npc_role="mysterious traveling merchant specializing in rare artifacts",
    knowledge_domains=[
        "rare items",
        "distant lands",
        "ancient artifacts",
        "market prices",
        "magical properties of objects",
        "historical legends",
        "other planes of existence",
    ],
    secret_knowledge=[
        "sells cursed items knowingly but believes curses teach lessons",
        "is actually a disguised fey creature testing mortals",
        "can see the threads of fate connecting all things",
    ],
    will_discuss=[
        "wares",
        "prices",
        "exotic lands",
        "treasure rumors",
        "trade opportunities",
        "legends and myths",
        "the nature of desire",
    ],
    wont_discuss=[
        "true identity",
        "where items really come from",
        "other customers by name",
        "the source of their knowledge",
    ],
    greeting="Ah... a seeker of treasures, perhaps? Or... something more?",
    farewell="Until our paths cross again... and they will.",
    fallback_response="*smiles mysteriously* Some secrets are not for sale... yet.",
    max_response_tokens=150,
    temperature=0.9,  # Higher temperature for more creative/varied responses
    cooldown_seconds=3.0,
)

Non-AI NPC (Scripted Responses Only)

For background NPCs or when AI resources are limited, you can disable AI entirely:

simple_dialogue = DialogueComponent(
    ai_enabled=False,  # AI disabled - uses only fallback responses
    personality="",
    speaking_style="",
    npc_role="",
    knowledge_domains=[],
    secret_knowledge=[],
    will_discuss=[],
    wont_discuss=[],
    greeting="Hello there, traveler.",
    farewell="Safe journeys.",
    fallback_response="I'm just a simple villager. I don't know much about that.",
    max_response_tokens=150,
    temperature=0.7,
    cooldown_seconds=1.0,
)

When ai_enabled=False, the NPC will only use the greeting, farewell, and fallback_response fields. This is useful for: - Background NPCs that don't need dynamic dialogue - NPCs in areas where you want to conserve API usage - Testing without AI provider configuration

Using Factory Functions

The maid_classic_rpg package provides factory functions for common NPC archetypes:

from maid_classic_rpg.data.npcs.dialogue_configs import (
    create_bartender_dialogue,
    create_guard_dialogue,
    create_merchant_dialogue,
    create_quest_giver_dialogue,
    create_sage_dialogue,
    create_simple_dialogue,
)

# Create a bartender with custom tavern name
bartender = create_bartender_dialogue(tavern_name="The Golden Griffin")

# Create a guard with custom location
guard = create_guard_dialogue(location="the eastern watchtower")

# Create a merchant with custom specialty
merchant = create_merchant_dialogue(specialty="exotic spices and potions")

# Create a quest giver
quest_giver = create_quest_giver_dialogue(quest_type="goblin extermination")

# Create a sage NPC
sage = create_sage_dialogue(specialty="dragon lore")

# Override any field
custom_bartender = create_bartender_dialogue(
    tavern_name="The Drunken Dragon",
    temperature=0.8,  # More creative
    max_response_tokens=80,  # Shorter responses
)

Player Commands

Players interact with AI-enabled NPCs using these commands:

talk <npc> <message>

Start or continue a conversation with an NPC.

Usage:

talk <npc_keyword> <message>

Examples:

> talk bartender Hello! Do you have any news?
> talk guard What's the curfew around here?
> talk merchant I'm looking for a magical amulet.

Aliases: say to, tell

ask <npc> about <topic>

Ask an NPC about a specific topic. Reformats the question for clarity.

Usage:

ask <npc_keyword> about <topic>

Examples:

> ask bartender about trouble in town
> ask guard about the recent murders
> ask merchant about cursed items

greet <npc>

Send a generic greeting to an NPC to start a conversation.

Usage:

greet <npc_keyword>

Examples:

> greet blacksmith
> hello merchant
> hi guard

Aliases: hello, hi

conversations

List all your active conversations with NPCs.

Usage:

> conversations

Active Conversations:

  Bartender: 5 message(s)
  Guard: 2 message(s)

Aliases: convos

endconversation <npc>

End a conversation with an NPC. The NPC will say their farewell message.

Usage:

endconversation <npc_keyword>

Examples:

> endconversation bartender

Bartender says, "*nods* Take care out there."

Aliases: endconv, bye

Configuration

Environment Variables

Configure AI dialogue settings using environment variables with the MAID_AI_DIALOGUE_ prefix:

Core Settings

Variable Type Default Description
MAID_AI_DIALOGUE_ENABLED bool true Master switch for AI dialogue system
MAID_AI_DIALOGUE_DEFAULT_PROVIDER str "anthropic" Default AI provider (anthropic, openai, ollama)
MAID_AI_DIALOGUE_DEFAULT_MODEL str null Default model (uses provider default if not set)
MAID_AI_DIALOGUE_DEFAULT_MAX_TOKENS int 150 Default max response tokens (50-1000)
MAID_AI_DIALOGUE_DEFAULT_TEMPERATURE float 0.7 Default temperature (0.0-2.0)

Rate Limiting

Variable Type Default Description
MAID_AI_DIALOGUE_GLOBAL_RATE_LIMIT_RPM int 60 Global requests per minute across all players
MAID_AI_DIALOGUE_PER_PLAYER_RATE_LIMIT_RPM int 10 Requests per minute per player
MAID_AI_DIALOGUE_PER_NPC_COOLDOWN_SECONDS float 2.0 Default cooldown between NPC responses

Token Budgets

Variable Type Default Description
MAID_AI_DIALOGUE_DAILY_TOKEN_BUDGET int null Daily token limit for entire server (null = unlimited)
MAID_AI_DIALOGUE_PER_PLAYER_DAILY_BUDGET int 5000 Daily token limit per player

Context Settings

Variable Type Default Description
MAID_AI_DIALOGUE_INCLUDE_WORLD_CONTEXT bool true Include time/weather in prompts
MAID_AI_DIALOGUE_INCLUDE_LOCATION_CONTEXT bool true Include room info in prompts
MAID_AI_DIALOGUE_INCLUDE_PLAYER_CONTEXT bool true Include player info in prompts

Conversation Settings

Variable Type Default Description
MAID_AI_DIALOGUE_MAX_CONVERSATION_HISTORY int 10 Max messages to retain in conversation history
MAID_AI_DIALOGUE_CONVERSATION_TIMEOUT_MINUTES int 30 Minutes before inactive conversations are cleaned up

Safety Settings

Variable Type Default Description
MAID_AI_DIALOGUE_CONTENT_FILTERING bool true Enable content safety filtering
MAID_AI_DIALOGUE_LOG_CONVERSATIONS bool false Log conversation content (privacy consideration)

AI Provider Settings

Configure the underlying AI providers with the MAID_AI_ prefix:

Variable Type Description
MAID_AI_DEFAULT_PROVIDER str Default provider for all AI features
MAID_AI_ANTHROPIC_API_KEY str Anthropic API key
MAID_AI_ANTHROPIC_MODEL str Default Anthropic model
MAID_AI_OPENAI_API_KEY str OpenAI API key
MAID_AI_OPENAI_MODEL str Default OpenAI model
MAID_AI_OLLAMA_HOST str Ollama server URL
MAID_AI_OLLAMA_MODEL str Default Ollama model
MAID_AI_REQUEST_TIMEOUT float API request timeout in seconds

Example .env Configuration

# Enable AI dialogue
MAID_AI_DIALOGUE_ENABLED=true
MAID_AI_DIALOGUE_DEFAULT_PROVIDER=anthropic

# Rate limiting
MAID_AI_DIALOGUE_GLOBAL_RATE_LIMIT_RPM=120
MAID_AI_DIALOGUE_PER_PLAYER_RATE_LIMIT_RPM=15
MAID_AI_DIALOGUE_PER_NPC_COOLDOWN_SECONDS=1.5

# Token budgets (set reasonable limits for production)
MAID_AI_DIALOGUE_DAILY_TOKEN_BUDGET=100000
MAID_AI_DIALOGUE_PER_PLAYER_DAILY_BUDGET=3000

# Context (disable for faster responses or to save tokens)
MAID_AI_DIALOGUE_INCLUDE_WORLD_CONTEXT=true
MAID_AI_DIALOGUE_INCLUDE_LOCATION_CONTEXT=true
MAID_AI_DIALOGUE_INCLUDE_PLAYER_CONTEXT=true

# Conversation management
MAID_AI_DIALOGUE_MAX_CONVERSATION_HISTORY=8
MAID_AI_DIALOGUE_CONVERSATION_TIMEOUT_MINUTES=20

# Provider configuration
MAID_AI_ANTHROPIC_API_KEY=sk-ant-api03-...
MAID_AI_ANTHROPIC_MODEL=claude-sonnet-4-20250514

Rate Limiting

The AI dialogue system implements multiple layers of rate limiting to prevent abuse and control costs:

  1. Global Rate Limit (global_rate_limit_rpm): Maximum requests per minute across all players. Protects against server-wide overload.

  2. Per-Player Rate Limit (per_player_rate_limit_rpm): Maximum requests per minute for each individual player. Prevents any single player from monopolizing AI resources.

  3. Per-NPC Cooldown (cooldown_seconds): Minimum time between responses for each specific NPC. Set in the DialogueComponent. Adds realism and prevents rapid-fire conversations.

  4. Token Budgets: Daily token limits for the server and per player. Helps control API costs.

When a rate limit is hit, the player receives a polite message asking them to wait:

> talk bartender Tell me more!

Bartender says, "Just a moment, let me think..."

Content Filtering

When content_filtering is enabled, the system includes built-in restrictions in the AI prompt:

  • NPCs stay fully in character
  • NPCs never acknowledge being AI or break the fourth wall
  • NPCs refuse to provide harmful, illegal, or inappropriate content
  • NPCs do not suggest specific game commands unless explicitly configured

Streaming vs Buffered Mode

The NPC dialogue system supports two response delivery modes:

  1. Buffered Mode (default): The complete AI response is collected before being sent to the player. This is required for content filtering to work, as the filter needs to evaluate the entire response before any content reaches the player.

  2. Streaming Mode: Responses are sent incrementally to the player as they arrive from the AI provider. This provides lower perceived latency as players see text appearing in real-time.

Important: Content filtering is enabled by default (MAID_AI_DIALOGUE_CONTENT_FILTERING=true), which forces buffered mode. This means streaming is effectively disabled by default, even though MAID_AI_DIALOGUE_ENABLE_STREAMING defaults to true.

To enable true streaming:

# Disable content filtering (required for streaming)
MAID_AI_DIALOGUE_CONTENT_FILTERING=false

# Enable streaming (this is already the default)
MAID_AI_DIALOGUE_ENABLE_STREAMING=true

Trade-offs when disabling content filtering: - Responses appear faster (lower perceived latency) - No server-side safety evaluation before content reaches players - AI providers like Anthropic Claude and OpenAI GPT have their own built-in content moderation - Consider your game's audience and content policies before disabling

When to use each mode:

Mode Use When
Buffered (default) Production servers, public games, unknown audiences
Streaming Private servers, trusted environments, local development

Tips for NPC Design

Personality Tips

  1. Be Specific: Instead of "friendly", say "warmly greets strangers but becomes guarded when asked personal questions"

  2. Include Motivations: What does the NPC want? Fear? Value? This drives natural responses.

  3. Add Quirks: Small details make characters memorable - a nervous laugh, a catchphrase, a superstition.

  4. Consider Background: Where did they come from? How did they end up in their current role?

Speaking Style Examples

Character Type Speaking Style
Gruff Dwarf Short sentences. Uses "aye" and "nay". Mining metaphors. Rarely shows emotion.
Elegant Noble Flowery language. Never uses contractions. Formal address. Subtle condescension.
Street Urchin Slang and incomplete sentences. Suspicious of authority. Eager to bargain.
Wise Sage Measured, thoughtful speech. Speaks in questions. References ancient texts.
Nervous Scholar Stammers when excited. Constantly qualifies statements. Uses technical jargon.

Temperature Guidance

Temperature Use Case Example Characters
0.3-0.4 Highly predictable, formal Guards, officials, simple NPCs
0.5-0.6 Consistent but natural Merchants, craftsmen, quest givers
0.7-0.8 Balanced variety Bartenders, adventurers, most NPCs
0.9-1.0 Creative, unpredictable Mystics, tricksters, mad wizards

Knowledge Domain Suggestions

Occupational Knowledge: - Blacksmith: weapons, armor, metalworking, forge techniques, metal types - Innkeeper: local gossip, travelers, food/drink, room rates, entertainment - Guard: laws, criminals, patrol routes, safety, weapons regulations - Merchant: prices, trade routes, supply/demand, bargaining, rare goods - Healer: medicine, herbs, diseases, anatomy, healing magic

Local Knowledge: - Landmarks and directions - Local history and legends - Important people and factions - Recent events and gossip - Dangers and warnings

Secret Knowledge Ideas: - Hidden locations (tunnels, treasure caches) - Conspiracy information - Character secrets (who is having an affair, who committed a crime) - Prophecies or omens - True identities of disguised characters

Troubleshooting

NPC Not Responding

  1. Check that the NPC has both DialogueComponent and either NPCComponent or is in the room
  2. Verify ai_enabled=True in the DialogueComponent
  3. Check if the global MAID_AI_DIALOGUE_ENABLED setting is true
  4. Verify AI provider is configured with valid API key
  5. Check server logs for rate limiting messages

Responses Are Too Short/Long

Adjust max_response_tokens in the DialogueComponent: - 50-80 tokens: Very brief responses (yes/no, single sentences) - 80-120 tokens: Concise responses (1-2 sentences) - 120-180 tokens: Standard responses (2-3 sentences) - 180-300 tokens: Elaborate responses (3-5 sentences) - 300-500 tokens: Long-winded characters

Responses Are Too Repetitive

Increase the temperature value (try 0.8-0.9) for more variety. Also ensure: - The personality description is detailed enough - Knowledge domains cover relevant topics - Speaking style includes varied mannerisms

Responses Break Character

Lower the temperature value (try 0.4-0.6) for more consistency. Ensure: - Personality and speaking style are clearly defined - The wont_discuss list includes off-topic subjects - npc_role accurately describes the character

Rate Limit Errors

If players frequently hit rate limits: 1. Increase per_player_rate_limit_rpm in settings 2. Decrease cooldown_seconds for important NPCs 3. Consider using non-AI NPCs for background characters