PlayKit.ai

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.

When to Use NPCClient

FeatureChatClientNPCClient
Automatic history managementNoYes
Character personalityManualBuilt-in
Memory systemNoYes
Reply predictionsNoYes
Action/tool callingYesYes (simplified)
Save/Load conversationsManualBuilt-in

Use NPCClient when:

  • Building game NPCs with persistent personalities
  • Need automatic conversation history tracking
  • Want dynamic memory that affects NPC behavior
  • Need suggested player responses

Use ChatClient when:

  • Single Q&A without history
  • Full control over message management
  • Non-character AI interactions

Create NPCClient

import { PlayKitSDK } from 'playkit-sdk';

const sdk = new PlayKitSDK({
  gameId: 'your-game-id',
  developerToken: 'your-token'
});

await sdk.initialize();

// Create with default settings
const npc = sdk.createNPCClient();

// Create with configuration
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);
// NPC remembers the previous exchange

Streaming Responses

Display responses in real-time as they generate:

await npc.talkStream(
  'Tell me about the ancient kingdom',
  // Called for each text chunk
  (chunk) => {
    process.stdout.write(chunk);
  },
  // Called when complete (optional)
  (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 before

Update 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);
  // Display as clickable options in UI
  displayReplyOptions(predictions);
});

await npc.talk('Welcome, adventurer! I have a dangerous mission.');
// Predictions generated automatically after response

Manual Prediction

// Generate predictions on demand
const predictions = await npc.generateReplyPredictions(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

OptionTypeDefaultDescription
characterDesignstring-NPC personality and behavior prompt
modelstring-AI model to use
temperaturenumber0.7Response randomness (0.0-2.0)
maxHistoryLengthnumber50Maximum messages to keep
generateReplyPredictionbooleanfalseAuto-generate reply suggestions
predictionCountnumber4Number of predictions (2-6)
fastModelstring-Faster model for predictions

Action Parameter Types

TypeDescription
stringFree text input
numberNumeric value
booleanTrue/false
stringEnumOne 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 design

3. 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',
  developerToken: 'your-token'
});

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