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:
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:
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:
Examples:
Aliases: hello, hi
conversations¶
List all your active conversations with NPCs.
Usage:
Aliases: convos
endconversation <npc>¶
End a conversation with an NPC. The NPC will say their farewell message.
Usage:
Examples:
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:
-
Global Rate Limit (
global_rate_limit_rpm): Maximum requests per minute across all players. Protects against server-wide overload. -
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. -
Per-NPC Cooldown (
cooldown_seconds): Minimum time between responses for each specific NPC. Set in the DialogueComponent. Adds realism and prevents rapid-fire conversations. -
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:
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:
-
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.
-
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¶
-
Be Specific: Instead of "friendly", say "warmly greets strangers but becomes guarded when asked personal questions"
-
Include Motivations: What does the NPC want? Fear? Value? This drives natural responses.
-
Add Quirks: Small details make characters memorable - a nervous laugh, a catchphrase, a superstition.
-
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¶
- Check that the NPC has both
DialogueComponentand eitherNPCComponentor is in the room - Verify
ai_enabled=Truein the DialogueComponent - Check if the global
MAID_AI_DIALOGUE_ENABLEDsetting istrue - Verify AI provider is configured with valid API key
- 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