Multi-turn Conversations

Build conversational AI that maintains context across multiple messages.

iOSAndroid

Overview

On-device models are stateless — they don't remember previous calls. You hold the conversation history in an array and pass the full history on every request. The model uses it to produce context-aware responses.

  • Messages — the running history you pass to the AI
  • System prompt — instructions that define behavior
  • Context — the model sees every message you send

How It Works

Maintain an array of messages, append each turn, and pass it to sendMessage() (or streamMessage()).

import { sendMessage, type LLMMessage } from 'expo-ai-kit';

const messages: LLMMessage[] = [];

// First turn
messages.push({ role: 'user', content: 'My name is Alex.' });
const r1 = await sendMessage(messages);
messages.push({ role: 'assistant', content: r1.text });
// AI: "Nice to meet you, Alex!"

// Second turn — the AI has context from the array
messages.push({ role: 'user', content: 'What is my name?' });
const r2 = await sendMessage(messages);
messages.push({ role: 'assistant', content: r2.text });
// AI: "Your name is Alex."

You own the history: append the user message before each call and the assistant's reply after it. The library adds no hidden state.

System Prompts

A system prompt defines the AI's behavior and persona. Provide it two ways:

Option 1 — the systemPrompt option

const response = await sendMessage(
  [{ role: 'user', content: 'Tell me a joke' }],
  { systemPrompt: 'You are a comedian who specializes in dad jokes.' }
);

Option 2 — a system message in the array

const response = await sendMessage([
  { role: 'system', content: 'You are a comedian who specializes in dad jokes.' },
  { role: 'user', content: 'Tell me a joke' },
]);

If a system message is present in the array, the systemPrompt option is ignored.

Conversation Hook

A reusable React hook for managing a multi-turn conversation:

hooks/useChat.tstypescript
import { useState, useCallback } from 'react';
import { sendMessage, type LLMMessage } from 'expo-ai-kit';

export function useChat(systemPrompt?: string) {
  const [messages, setMessages] = useState<LLMMessage[]>([]);
  const [isLoading, setIsLoading] = useState(false);

  const chat = useCallback(async (userMessage: string) => {
    setIsLoading(true);
    try {
      const next: LLMMessage[] = [
        ...messages,
        { role: 'user', content: userMessage },
      ];
      const response = await sendMessage(next, { systemPrompt });
      setMessages([...next, { role: 'assistant', content: response.text }]);
      return response.text;
    } finally {
      setIsLoading(false);
    }
  }, [messages, systemPrompt]);

  const clearChat = useCallback(() => setMessages([]), []);

  return { messages, isLoading, chat, clearChat };
}

Best Practices

1. Trim long conversations

As history grows, drop the oldest turns to stay responsive and within the model's context window.

const MAX_MESSAGES = 20;

function trim(messages: LLMMessage[]): LLMMessage[] {
  return messages.length <= MAX_MESSAGES ? messages : messages.slice(-MAX_MESSAGES);
}

const response = await sendMessage(trim(messages), { systemPrompt });

2. Send messages sequentially

Only one generation runs at a time — a concurrent call rejects with INFERENCE_BUSY. Wait for each response before sending the next.

// ❌ Bad — concurrent calls reject with INFERENCE_BUSY
await Promise.all([
  sendMessage([{ role: 'user', content: 'Question 1' }]),
  sendMessage([{ role: 'user', content: 'Question 2' }]),
]);

// ✅ Good — await each in turn
const r1 = await sendMessage([{ role: 'user', content: 'Question 1' }]);

3. Always include the full history

For context-aware replies, pass the complete conversation — not just the latest message.

// ❌ Loses context
await sendMessage([{ role: 'user', content: 'What did I just say?' }]);

// ✅ Includes history
await sendMessage([
  { role: 'user', content: 'My name is Alice.' },
  { role: 'assistant', content: 'Nice to meet you, Alice!' },
  { role: 'user', content: 'What is my name?' },
]);