Skip to content

Setting Up the AI Chat SDK

Pro

What you'll learn

  • Generating the AI Chat SDK with your chosen provider (OpenAI, Anthropic, or both)
  • Understanding the multi-package structure: api, impl, and provider-specific packages
  • Configuring API keys securely using String.fromEnvironment
  • Consuming the streaming chat API in a Flutter widget

Prerequisites

Step 1: Generate the AI Chat SDK

bash
archipelago generate ai_chat_sdk

You will be prompted for:

  • aiChatProvideropenai, anthropic, or both (default: openai)
  • defaultModel — e.g. gpt-4o-mini or claude-3-haiku-20240307 (default: gpt-4o-mini)

Or use a config file:

json
{
  "aiChatProvider": "openai",
  "defaultModel": "gpt-4o-mini"
}
bash
archipelago generate ai_chat_sdk --config ai_chat_config.json

Step 2: Understand the Generated Structure

The AI Chat SDK uses the API/Impl split because the chat contract is consumed by multiple feature packages, and the provider implementation can be swapped or combined without touching feature code:

features/ai_chat/
├── ai_chat_api/
│   └── lib/src/
│       ├── ai_chat_sdk.dart           # AIChatSDK abstract interface
│       ├── chat_message.dart          # ChatMessage model
│       ├── chat_chunk.dart            # Streaming chunk model
│       └── message_role.dart          # MessageRole enum (user/assistant/system)
├── ai_chat_impl/
│   └── lib/src/
│       ├── ai_chat_sdk_impl.dart      # Orchestrates provider(s), history persistence
│       └── di/
│           └── ai_chat_module.dart    # DI registration
├── ai_chat_openai_impl/               # Generated if aiChatProvider is openai or both
│   └── lib/src/
│       ├── openai_chat_provider.dart  # OpenAI streaming adapter
│       └── di/
│           └── openai_module.dart
└── ai_chat_anthropic_impl/            # Generated if aiChatProvider is anthropic or both
    └── lib/src/
        ├── anthropic_chat_provider.dart
        └── di/
            └── anthropic_module.dart

Note: If you selected both, both provider packages are generated, but only the first registered handler is active at runtime. Wire only the provider you need, or implement a routing strategy in ai_chat_sdk_impl.dart.

Step 3: Configure Your API Key

Never hardcode API keys. Use String.fromEnvironment and pass values via --dart-define in your CI configuration.

OpenAI:

dart
// ai_chat_openai_impl/lib/src/di/openai_module.dart
@module
abstract class AppAIModule {
  @lazySingleton
  AIChatConfig get aiChatConfig => const AIChatConfig(
    apiKey: String.fromEnvironment('OPENAI_API_KEY'),
    defaultModel: 'gpt-4o-mini',
  );
}

Anthropic:

dart
@module
abstract class AppAIModule {
  @lazySingleton
  AIChatConfig get aiChatConfig => const AIChatConfig(
    apiKey: String.fromEnvironment('ANTHROPIC_API_KEY'),
    defaultModel: 'claude-3-haiku-20240307',
  );
}

Pass the key at build time:

bash
flutter run --dart-define=OPENAI_API_KEY=sk-...

In CI (GitHub Actions):

yaml
- name: Build
  run: flutter build apk --dart-define=OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY }}

Step 4: Register the Feature

In your shell app's bootstrap.dart:

dart
import 'package:ai_chat_impl/ai_chat_impl.dart';

FeatureRegistry.register(AIChatSdkImpl());

Step 5: Use the Streaming API in a Widget

The SDK returns a Stream<ChatChunk> so you can render tokens as they arrive:

dart
class ChatPage extends StatefulWidget {
  const ChatPage({super.key});

  @override
  State<ChatPage> createState() => _ChatPageState();
}

class _ChatPageState extends State<ChatPage> {
  final _chatSDK = getIt<AIChatSDK>();
  final _messages = <ChatMessage>[];
  String _streamingResponse = '';

  Future<void> _sendMessage(String text) async {
    final userMessage = ChatMessage(role: MessageRole.user, content: text);
    setState(() {
      _messages.add(userMessage);
      _streamingResponse = '';
    });

    final stream = _chatSDK.sendMessage(userMessage);
    await for (final chunk in stream) {
      setState(() => _streamingResponse += chunk.delta);
    }

    setState(() {
      _messages.add(
        ChatMessage(role: MessageRole.assistant, content: _streamingResponse),
      );
      _streamingResponse = '';
    });
  }
}

Step 6: Load and Clear History

Chat history is persisted per session using SharedPreferences:

dart
// Load history on page init
final history = await _chatSDK.loadHistory();

// Clear history (e.g. on "New chat" button tap)
await _chatSDK.clearHistory();

Step 7: Swap Providers

Features import only ai_chat_api. To switch from OpenAI to Anthropic, change the registered DI module in bootstrap.dart — no feature code changes needed:

dart
// Before (OpenAI)
FeatureRegistry.register(AIChatSdkImpl(provider: OpenAIChatProvider()));

// After (Anthropic)
FeatureRegistry.register(AIChatSdkImpl(provider: AnthropicChatProvider()));

Key Configuration Points

CustomizationWhere to Change
Change default modelAIChatConfig.defaultModel in the provider DI module
Add system promptChatMessage(role: MessageRole.system, content: '...') prepended to history
Persist across sessionsOverride loadHistory — swap SharedPreferences for a local database
Route between providersSubclass AIChatSDK in ai_chat_impl, dispatch by message content or user tier

Next Steps

Built by Banua Coder