NPC Conversations
Create intelligent game characters with NPCClient
NPC Conversations
NPCClient is a client class designed specifically for game NPC (Non-Player Character) conversations. It features automatic history management, character design, memory systems, action triggering, and reply predictions - letting you focus on character design and dialogue logic.
ChatClient vs NPCClient
The SDK provides two clients for AI conversation, each with a different design goal.
ChatClient — developer-facing
ChatClient exposes the minimum necessary API: you supply the messages, it returns a response. History, context, and output format are entirely yours to manage.
const chat = sdk.createChatClient();
const response = await chat.chat('What items do you sell?', 'You are a merchant.');
// You own the message list, output format, and any parsingUse it for: custom chat UIs, structured data generation (generateStructuredByName), tool/function calling pipelines, multi-step AI workflows.
NPCClient — game-designer-facing
NPCClient is a character system. You define the personality; the SDK handles history, memory, reply predictions, and action dispatch. The API surface is deliberately small.
const npc = sdk.createNPCClient({
characterDesign: 'You are a gruff blacksmith named Arn...'
});
const reply = await npc.talk('I need a sword.');Use it for: interactive NPCs, companion characters, dialogue scenes.
Why NPC uses Actions instead of structured output
NPCClient has no structured output method by design. The alternative is the Actions system:
Speed. Structured output adds tokens and a parsing step — the model must produce valid JSON before your game can react. Actions are embedded inline in the dialogue and execute the moment the model signals them, with no parsing overhead.
Character integrity. A structured-output call produces a JSON object, not a sentence — the character voice breaks. Actions let the NPC speak naturally and trigger game logic in the same response, without exposing a data format to the player.
const response = await npc.talkWithActions('I want to buy a sword.', [
{ actionName: 'OpenShop',
description: 'Open the shop inventory' },
{ actionName: 'GiveItem',
description: 'Give an item to the player',
parameters: [{ name: 'item_name', type: 'string', description: 'Item name', required: true }] }
]);
console.log(response.text); // NPC's natural dialogue
for (const action of response.actionCalls) {
console.log(action.actionName, action.arguments); // inline triggers
}For structured data that lives outside the character (item stats, quest definitions, world state), use chat.generateStructuredByName() — not the NPC.
At a glance
ChatClient | NPCClient | |
|---|---|---|
| Audience | Developers | Game designers |
| History | Manual | Automatic |
| Output | Raw string / typed object | Natural dialogue + action calls |
| Structured data | generateStructuredByName | Use Actions instead |
| Game logic integration | Parse response manually | Action system |
| Memory | — | setMemory / getMemory |
| Save / restore | — | saveHistory / loadHistory |
Create NPCClient
import { PlayKitSDK } from 'playkit-sdk';
const sdk = new PlayKitSDK({
gameId: 'your-game-id',
apiKey: 'your-api-key'
});
await sdk.initialize();
const npc = sdk.createNPCClient();
const wizard = sdk.createNPCClient({
characterDesign: 'You are an ancient wizard who speaks in riddles.',
temperature: 0.8,
maxHistoryLength: 30
});Basic Conversation
Simple Talk
const response = await npc.talk('Hello, who are you?');
console.log('NPC:', response);
const response2 = await npc.talk('What quest do you have for me?');
console.log('NPC:', response2);Streaming Responses
Display responses in real-time as they generate:
await npc.talkStream(
'Tell me about the ancient kingdom',
(chunk) => {
process.stdout.write(chunk);
},
(fullText) => {
console.log('\n--- Response complete ---');
}
);Check Speaking Status
if (npc.isTalking) {
console.log('NPC is still responding...');
}Character Design
Character design defines the NPC's personality, background, and behavior.
Set Character Design
const npc = sdk.createNPCClient({
characterDesign: `You are Eldric, a mysterious wizard.
Background: You have lived for 300 years in the Enchanted Forest.
Personality: Wise but cryptic, often speaking in riddles.
Goal: Guide adventurers while testing their wisdom.
Speech style: Formal, uses archaic words like "thee" and "hark".`
});
// Or set after creation
npc.setCharacterDesign(`You are a cheerful tavern keeper named Rose.
You love gossip and know everything happening in town.`);Get Current Design
const design = npc.getCharacterDesign();
console.log('Current character:', design);Memory System
Memories let you dynamically add context that affects NPC responses without changing the base character design.
Set Memories
// Add player information
npc.setMemory('playerName', 'Aragorn');
npc.setMemory('playerClass', 'Ranger');
npc.setMemory('relationship', 'Friendly - helped the NPC before');
// Add world state
npc.setMemory('currentQuest', 'Defeat the Dragon of Mount Doom');
npc.setMemory('worldState', 'The kingdom is at war with the northern tribes');
// NPC responses now incorporate these memories
const response = await npc.talk('Do you remember me?');
// NPC will reference knowing Aragorn, the ranger who helped beforeUpdate Memories
// Memories can be updated as the game progresses
npc.setMemory('relationship', 'Very friendly - saved the NPC\'s life');
npc.setMemory('playerLevel', '15');Remove Memories
// Remove specific memory
npc.setMemory('temporaryBuff', null); // or empty string
// Clear all memories (keeps character design)
npc.clearMemories();Get Memories
// Get specific memory
const playerName = npc.getMemory('playerName');
// Get all memory names
const memoryNames = npc.getMemoryNames();
console.log('Stored memories:', memoryNames);
// ['playerName', 'playerClass', 'relationship', ...]Actions (Tool Calling)
NPCs can trigger game actions based on conversation context.
Define Actions
const actions = [
{
actionName: 'give_item',
description: 'Give an item to the player as a reward or gift',
parameters: [
{
name: 'itemName',
type: 'string',
description: 'Name of the item to give',
required: true
},
{
name: 'quantity',
type: 'number',
description: 'How many to give',
required: false
}
]
},
{
actionName: 'start_quest',
description: 'Offer a new quest to the player',
parameters: [
{
name: 'questId',
type: 'string',
description: 'The quest identifier',
required: true
}
]
},
{
actionName: 'change_attitude',
description: 'Change NPC attitude toward player',
parameters: [
{
name: 'attitude',
type: 'stringEnum',
description: 'New attitude level',
enumOptions: ['hostile', 'neutral', 'friendly', 'ally']
}
]
}
];Talk with Actions
const response = await npc.talkWithActions(
'I completed your quest! The dragon is defeated.',
actions
);
console.log('NPC says:', response.text);
if (response.hasActions) {
for (const action of response.actionCalls) {
console.log('Action:', action.actionName);
console.log('Arguments:', action.arguments);
// Execute in your game
switch (action.actionName) {
case 'give_item':
givePlayerItem(action.arguments.itemName, action.arguments.quantity);
break;
case 'start_quest':
startQuest(action.arguments.questId);
break;
case 'change_attitude':
updateNpcAttitude(action.arguments.attitude);
break;
}
}
}Streaming with Actions
await npc.talkWithActionsStream(
'Can you help me?',
actions,
(chunk) => {
displayText(chunk);
},
(response) => {
if (response.hasActions) {
handleActions(response.actionCalls);
}
}
);Report Action Results
For multi-turn interactions where the NPC needs to know action outcomes:
const response = await npc.talkWithActions('Open the chest', actions);
// Execute actions and report results back
for (const action of response.actionCalls) {
const result = executeAction(action);
npc.reportActionResult(action.id, result);
}
// Or report multiple at once
npc.reportActionResults({
'call_abc123': 'Chest opened successfully. Found 50 gold.',
'call_def456': 'Player received the golden key.'
});Reply Predictions
Automatically generate suggested responses the player might say next.
Enable Auto-Prediction
const npc = sdk.createNPCClient({
characterDesign: 'You are a quest giver.',
generateReplyPrediction: true,
predictionCount: 4 // Generate 4 suggestions
});
// Listen for predictions
npc.on('replyPredictions', (predictions) => {
console.log('Suggested replies:', predictions);
displayReplyOptions(predictions);
});
await npc.talk('Welcome, adventurer! I have a dangerous mission.');Manual Prediction
// Generate predictions on demand (first arg is optional tempPrompt, second is count)
const predictions = await npc.generateReplyPredictions(undefined, 4);
console.log(predictions);
// [
// "What kind of mission?",
// "I'm ready for anything!",
// "What's the reward?",
// "Sounds too dangerous for me."
// ]Configure Predictions
// Enable/disable
npc.setGenerateReplyPrediction(true);
// Change count (2-6)
npc.setPredictionCount(3);History Management
View History
// Get full history
const history = npc.getHistory();
console.log(`${history.length} messages in conversation`);
// Get history length
const length = npc.getHistoryLength();Clear History
// Clear conversation (keeps character design and memories)
npc.clearHistory();Revert Messages
// Undo last user/assistant exchange
const reverted = npc.revertHistory();
if (reverted) {
console.log('Last exchange removed');
}
// Remove specific number of messages
const removed = npc.revertChatMessages(4);
console.log(`Removed ${removed} messages`);Append Messages
// Manually add to history
npc.appendMessage({
role: 'user',
content: 'This is injected context'
});
// Shorthand
npc.appendChatMessage('assistant', 'Previous response to remember');Save and Load
Persist conversations across game sessions.
Save Conversation
// Serialize to JSON string
const saveData = npc.saveHistory();
// Store in your game's save system
localStorage.setItem('npc_wizard_conversation', saveData);
// Or: saveToDatabase(playerId, 'wizard_npc', saveData);Load Conversation
// Retrieve saved data
const saveData = localStorage.getItem('npc_wizard_conversation');
if (saveData) {
const success = npc.loadHistory(saveData);
if (success) {
console.log('Conversation restored!');
// NPC remembers previous conversation
}
}Save Data Structure
The saved data includes:
- Character design
- All memories
- Conversation history
interface ConversationSaveData {
characterDesign: string;
memories: Array<{ name: string; content: string }>;
history: Message[];
}Events
NPCClient extends EventEmitter for reactive programming.
// Response events
npc.on('response', (text) => {
console.log('NPC responded:', text);
});
npc.on('actions', (actionCalls) => {
console.log('NPC triggered actions:', actionCalls);
});
npc.on('replyPredictions', (predictions) => {
updateUI(predictions);
});
// Memory events
npc.on('memory_set', (name, content) => {
console.log(`Memory "${name}" set to:`, content);
});
npc.on('memory_removed', (name) => {
console.log(`Memory "${name}" removed`);
});
npc.on('memories_cleared', () => {
console.log('All memories cleared');
});
// History events
npc.on('history_cleared', () => {
console.log('Conversation reset');
});
npc.on('history_reverted', () => {
console.log('History reverted');
});
npc.on('history_loaded', () => {
console.log('Previous conversation loaded');
});Configuration Reference
NPCConfig Options
| Option | Type | Default | Description |
|---|---|---|---|
characterDesign | string | - | NPC personality and behavior prompt |
model | string | - | AI model to use |
temperature | number | 0.7 | Response randomness (0.0-2.0) |
maxHistoryLength | number | 50 | Maximum messages to keep |
generateReplyPrediction | boolean | false | Auto-generate reply suggestions |
predictionCount | number | 4 | Number of predictions (2-6) |
fastModel | string | - | Faster model for predictions |
Action Parameter Types
| Type | Description |
|---|---|
string | Free text input |
number | Numeric value |
boolean | True/false |
stringEnum | One of specified options |
Best Practices
1. Design Rich Characters
// Good: detailed character with clear personality
const npc = sdk.createNPCClient({
characterDesign: `You are Greta, the blacksmith.
Background: Former adventurer, retired after losing her arm to a dragon.
Personality: Gruff but kind-hearted. Respects strength and courage.
Speech: Direct, uses forge metaphors. Calls everyone "spark".
Secret: She knows the location of the legendary Dragonbane sword.`
});
// Bad: vague character
const npc = sdk.createNPCClient({
characterDesign: 'You are a blacksmith.'
});2. Use Memories for Dynamic Context
// Good: update memories as game state changes
npc.setMemory('playerReputation', 'Hero of the village');
npc.setMemory('lastInteraction', 'Player bought a sword yesterday');
npc.setMemory('shopInventory', 'Low on iron, has rare mithril');
// Bad: putting everything in character design3. Limit History for Performance
// Good: reasonable history limit
const npc = sdk.createNPCClient({
characterDesign: '...',
maxHistoryLength: 20 // Enough context, good performance
});
// Bad: unlimited history
const npc = sdk.createNPCClient({
characterDesign: '...',
maxHistoryLength: 1000 // Too much, slow and expensive
});4. Handle Actions Properly
// Good: validate and execute actions safely
if (response.hasActions) {
for (const action of response.actionCalls) {
if (canExecuteAction(action.actionName)) {
const result = executeAction(action);
npc.reportActionResult(action.id, result);
} else {
npc.reportActionResult(action.id, 'Action not available');
}
}
}5. Save Conversations at Key Points
// Good: save at natural break points
async function endConversation() {
const saveData = npc.saveHistory();
await saveToCloud(playerId, npcId, saveData);
}
// Save when player leaves area, game saves, etc.Complete Example
import { PlayKitSDK } from 'playkit-sdk';
const sdk = new PlayKitSDK({
gameId: 'your-game-id',
apiKey: 'your-api-key'
});
await sdk.initialize();
// Create NPC
const merchant = sdk.createNPCClient({
characterDesign: `You are Marcus, a traveling merchant.
You sell potions, scrolls, and curiosities from distant lands.
You're friendly but always looking for a good deal.
You have rumors about nearby dungeons to share with good customers.`,
temperature: 0.8,
generateReplyPrediction: true,
predictionCount: 3
});
// Set up memories
merchant.setMemory('playerName', 'Hero');
merchant.setMemory('playerGold', '500');
merchant.setMemory('previousPurchases', 'Health potion x2');
// Define shop actions
const shopActions = [
{
actionName: 'sell_item',
description: 'Sell an item to the player',
parameters: [
{ name: 'item', type: 'string', required: true },
{ name: 'price', type: 'number', required: true }
]
},
{
actionName: 'share_rumor',
description: 'Share information about nearby locations',
parameters: [
{ name: 'location', type: 'string', required: true },
{ name: 'details', type: 'string', required: true }
]
}
];
// Listen for events
merchant.on('replyPredictions', (predictions) => {
displayDialogueOptions(predictions);
});
// Have conversation
const greeting = await merchant.talk('Hello there!');
displayNpcText(greeting);
// Player wants to buy something
const response = await merchant.talkWithActions(
'Do you have any healing potions?',
shopActions
);
displayNpcText(response.text);
if (response.hasActions) {
for (const action of response.actionCalls) {
if (action.actionName === 'sell_item') {
showPurchaseDialog(action.arguments.item, action.arguments.price);
}
}
}
// Save for later
const saveData = merchant.saveHistory();
localStorage.setItem('merchant_conversation', saveData);Next Steps
- Learn about Text Generation for lower-level AI control
- Explore Structured Output for generating game data
- Check API Reference for complete method details