flutter_ai_sdk 1.4.0 copy "flutter_ai_sdk: ^1.4.0" to clipboard
flutter_ai_sdk: ^1.4.0 copied to clipboard

A unified wrapper for AI APIs (OpenAI, Anthropic, Google AI, Ollama) with streaming, tool calling, context management, and multimodal support.

Flutter AI SDK #

A unified Flutter/Dart wrapper for integrating various AI APIs (OpenAI, Anthropic, Google AI) with streaming, context management, and multimodal support.

CI Version License: MIT

Features #

  • 🔄 Unified API - Single interface for multiple AI providers
  • 🏠 Local Models - Run Llama, Qwen, Gemma... locally via Ollama
  • 🌊 Streaming Support - Real-time response streaming
  • 💬 Context Management - Automatic conversation history and memory
  • 🖼️ Multimodal Support - Text, images, audio, and documents
  • 🛠️ Function Calling - Tool/function support for all providers
  • 🤖 Tool Runner - Automatic agentic tool-calling loop
  • 🔒 Type Safety - Full Dart type safety with null safety
  • Error Handling - Comprehensive error types and retry logic
  • 📊 Token Counting - Exact counts via provider endpoints (Anthropic, Google AI) or local estimation
  • 💾 Prompt Caching - Up to 90% cheaper repeated contexts (PromptCaching)

Supported Providers #

Provider Text Vision Audio Tools Streaming
OpenAI
Anthropic
Google AI
Ollama (local)

Installation #

Add to your pubspec.yaml:

dependencies:
  flutter_ai_sdk: ^1.0.0

Or run:

flutter pub add flutter_ai_sdk

Quick Start #

Basic Chat #

import 'package:flutter_ai_sdk/flutter_ai_sdk.dart';

// Initialize the SDK
final ai = FlutterAI(
  provider: AIProvider.openai,
  config: AIConfig(
    apiKey: 'your-api-key',
    model: 'gpt-5.5',
  ),
);

// Simple chat
final response = await ai.chat('Hello, how are you?');
print(response.text);

// Don't forget to dispose when done
ai.dispose();

Streaming Responses #

final ai = FlutterAI(
  provider: AIProvider.anthropic,
  config: AIConfig(
    apiKey: 'your-api-key',
    model: 'claude-opus-4-8',
  ),
);

// Stream responses
await for (final chunk in ai.streamChat('Tell me a story')) {
  if (chunk.isDelta) {
    print(chunk.delta); // Print each chunk as it arrives
  }
}

Multi-turn Conversations #

final ai = FlutterAI(
  provider: AIProvider.openai,
  config: AIConfig(
    apiKey: 'your-api-key',
    systemPrompt: 'You are a helpful coding assistant.',
  ),
);

// Context is automatically maintained
await ai.chat('What is Dart?');
await ai.chat('Can you show me an example?');
await ai.chat('How does it compare to JavaScript?');

// Access conversation history
print(ai.history.length);

Vision / Image Analysis #

final ai = FlutterAI(
  provider: AIProvider.openai,
  config: AIConfig(
    apiKey: 'your-api-key',
    model: 'gpt-5.5',
  ),
);

// Analyze an image from URL
final response = await ai.chatWithContent([
  TextContent('What is in this image?'),
  ImageContent.fromUrl('https://example.com/image.png'),
]);

// Or from local bytes
final imageBytes = await File('image.png').readAsBytes();
final response = await ai.chatWithContent([
  TextContent('Describe this image'),
  ImageContent.fromBytes(imageBytes, mimeType: 'image/png'),
]);

Document input support #

Provider Base64 (PDF...) URL
Anthropic
Google AI
OpenAI ⚠️ passed as a text reference
Ollama
final response = await ai.chatWithContent([
  TextContent('Summarize this report'),
  DocumentContent.fromBase64(base64Pdf, mimeType: 'application/pdf', name: 'report.pdf'),
]);

Function Calling / Tools #

// Define a tool
final weatherTool = Tool(
  name: 'get_weather',
  description: 'Get the current weather for a location',
  parameters: ToolParameters(
    properties: {
      'location': ToolProperty.string(
        description: 'The city and country, e.g., "Paris, France"',
      ),
      'unit': ToolProperty.enumeration(
        description: 'Temperature unit',
        values: ['celsius', 'fahrenheit'],
      ),
    },
    required: ['location'],
  ),
);

// Use the tool
final response = await ai.chatWithTools(
  'What is the weather in Paris?',
  tools: [weatherTool],
);

// Handle tool calls
if (response.hasToolCalls) {
  for (final call in response.toolCalls!) {
    // Execute the tool
    final result = await executeWeatherCall(call.arguments);
    
    // Submit result back to the AI
    final finalResponse = await ai.submitToolResult(
      toolCallId: call.id,
      name: call.name,
      result: result,
    );
    print(finalResponse.text);
  }
}

Tool Runner (automatic execution) #

Let the SDK run the full agentic loop for you: it executes every tool call the model requests and feeds the results back until the model produces a final answer.

final runner = ToolRunner.create(
  provider: AIProvider.anthropic,
  config: AIConfig(apiKey: 'sk-ant-...'),
  tools: [
    ExecutableTool(
      definition: weatherTool,
      executor: (args) async => fetchWeather(args['location'] as String),
    ),
  ],
);

final result = await runner.run('What is the weather in Paris?');
print(result.text);       // Final answer
print(result.iterations); // Number of tool rounds

Error Handling #

try {
  final response = await ai.chat('Hello');
} on AIAuthenticationError catch (e) {
  print('Invalid API key: ${e.message}');
} on AIRateLimitError catch (e) {
  print('Rate limited. Retry after: ${e.retryAfter}');
  await Future.delayed(e.retryAfter ?? Duration(seconds: 60));
  // Retry the request
} on AIContextLengthError catch (e) {
  print('Context too long: ${e.message}');
  ai.clearContext(); // Clear and retry
} on AIError catch (e) {
  print('AI error: ${e.message}');
}

Configuration Options #

final config = AIConfig(
  // Required
  apiKey: 'your-api-key',
  
  // Model selection
  model: 'gpt-5.5', // Provider-specific model name
  
  // Generation parameters
  maxTokens: 4096,
  temperature: 0.7,      // 0.0 - 2.0, higher = more random
  topP: 0.9,             // Alternative to temperature
  frequencyPenalty: 0.0, // -2.0 to 2.0
  presencePenalty: 0.0,  // -2.0 to 2.0
  stopSequences: ['END'], // Stop generation at these sequences
  
  // System behavior
  systemPrompt: 'You are a helpful assistant.',
  
  // Response format
  responseFormat: ResponseFormat.json(), // JSON mode
  // Or guaranteed structured output (OpenAI, Anthropic, Google AI, Ollama):
  // responseFormat: ResponseFormat.json(schema: {
  //   'type': 'object',
  //   'properties': {'name': {'type': 'string'}},
  //   'required': ['name'],
  // }),
  
  // Tools/Functions
  tools: [myTool],
  toolChoice: ToolChoice.auto(),
  
  // Network settings
  baseUrl: 'https://custom-endpoint.com', // Custom API endpoint
  timeout: Duration(seconds: 30),
  headers: {'X-Custom-Header': 'value'},
);

Structured Outputs #

Pass a JSON schema to get responses that are guaranteed to match it — each provider uses its native structured output mechanism (OpenAI json_schema, Anthropic output_config, Gemini responseJsonSchema, Ollama schema format):

final ai = FlutterAI(
  provider: AIProvider.openai,
  config: AIConfig(
    apiKey: 'sk-...',
    responseFormat: ResponseFormat.json(
      schema: {
        'type': 'object',
        'properties': {
          'name': {'type': 'string'},
          'age': {'type': 'integer'},
        },
        'required': ['name', 'age'],
      },
      strict: true, // strict validation (OpenAI)
    ),
  ),
);

final response = await ai.chat('Extract: John Smith, 42 years old');
final data = jsonDecode(response.text); // guaranteed valid

Prompt Caching #

Cache the repeated prefix of your prompts (long system prompt, documents, conversation history) to cut input costs by up to ~90%:

final ai = FlutterAI(
  provider: AIProvider.anthropic,
  config: AIConfig(
    apiKey: 'sk-ant-...',
    systemPrompt: veryLongSystemPrompt,
    promptCaching: PromptCaching(), // or PromptCaching(ttl: PromptCacheTtl.oneHour)
  ),
);

final response = await ai.chat('First question');
print(response.usage?.cacheWriteTokens); // prefix written to the cache
final followUp = await ai.chat('Second question');
print(followUp.usage?.cachedTokens);     // prefix read from the cache (~10% of the price)
Provider Behavior
Anthropic Explicit — enabled by promptCaching (5 min or 1 h TTL)
OpenAI Automatic for prompts ≥ ~1024 tokens; hits reported in usage.cachedTokens
Google AI Implicit caching automatic; hits reported in usage.cachedTokens
Ollama Local KV-cache, always on

Token Counting #

Count tokens before sending a request — Anthropic and Google AI use their exact server-side counting endpoints; other providers fall back to a local estimation:

final tokens = await ai.countTokens(message: 'My long prompt...');
if (tokens > 100000) {
  // Trim the context before sending
}

Context Management #

The SDK includes built-in context management to handle conversation history:

final ai = FlutterAI(
  provider: AIProvider.openai,
  config: AIConfig(apiKey: 'your-key'),
);

// Messages are automatically tracked
await ai.chat('Hello');
await ai.chat('Tell me more');

// Access the context manager
print(ai.context.estimatedTokens);
print(ai.context.availableTokens);

// Clear context
ai.clearContext();

// Reset with new system prompt
ai.reset(systemPrompt: 'New personality');

// Get conversation for serialization
final json = ai.conversation.toJson();

Custom Context Manager #

final contextManager = ContextManager(
  maxTokens: 8000,
  reservedTokens: 1000, // Reserve for response
  systemPrompt: 'You are helpful.',
  windowStrategy: WindowStrategy.slidingWindow,
);

final ai = FlutterAI(
  provider: AIProvider.openai,
  config: AIConfig(apiKey: 'your-key'),
  contextManager: contextManager,
);

// Listen to context updates
contextManager.updates.listen((update) {
  print('Context updated: ${update.type}');
  print('Messages: ${update.messageCount}');
  print('Tokens: ${update.estimatedTokens}');
});

Provider-Specific Features #

OpenAI #

final ai = FlutterAI(
  provider: AIProvider.openai,
  config: AIConfig(
    apiKey: 'sk-...',
    model: 'gpt-5.5', // or gpt-5.4, gpt-5.4-mini, etc.
  ),
);

Supported models: gpt-5.5, gpt-5.4, gpt-5.4-mini, gpt-5.4-nano, gpt-5.1

Anthropic (Claude) #

final ai = FlutterAI(
  provider: AIProvider.anthropic,
  config: AIConfig(
    apiKey: 'sk-ant-...',
    model: 'claude-opus-4-8',
  ),
);

Supported models: claude-opus-4-8, claude-sonnet-5, claude-sonnet-4-6, claude-haiku-4-5

Google AI (Gemini) #

final ai = FlutterAI(
  provider: AIProvider.googleAI,
  config: AIConfig(
    apiKey: 'your-google-ai-key',
    model: 'gemini-3.5-flash',
  ),
);

Supported models: gemini-3.5-flash, gemini-3.1-pro-preview, gemini-3.1-flash-lite

Ollama (local models) #

Run open models locally — no API key, no cloud.

final ai = FlutterAI(
  provider: AIProvider.ollama,
  config: AIConfig(
    apiKey: '', // not required
    model: 'llama3.1',
    // baseUrl: 'http://192.168.1.10:11434/api', // remote Ollama server
  ),
);

Popular models: llama3.1, deepseek-r1, qwen3, gemma3, qwen3-coder

Architecture #

The SDK is organized in small, focused modules:

lib/src/
├── config/       # AIConfig, response formats, per-provider defaults
├── models/       # Messages, content types (sealed), tools, responses
├── providers/    # One folder per provider: thin provider + wire mapper
│   ├── anthropic/  openai/  google_ai/  ollama/
│   └── provider_registry.dart   # factory: AIProvider -> BaseProvider
├── runner/       # ToolRunner: automatic tool-calling loop
├── context/      # Conversation history and memory
├── errors/       # Typed error hierarchy
└── utils/        # HTTP client (retry, SSE/NDJSON), token counting

Key design points:

  • Strategy + template method - BaseProvider owns the streaming loop; each provider only implements its transport and wire format
  • Mappers - request building / response parsing are stateless classes, isolated from HTTP concerns and independently testable
  • Factory registry - ProviderRegistry.register lets you plug custom provider implementations without forking the SDK

Flutter Widget Integration #

class ChatWidget extends StatefulWidget {
  @override
  _ChatWidgetState createState() => _ChatWidgetState();
}

class _ChatWidgetState extends State<ChatWidget> {
  late FlutterAI _ai;
  final _messages = <Message>[];
  String _streamingContent = '';
  bool _isLoading = false;

  @override
  void initState() {
    super.initState();
    _ai = FlutterAI(
      provider: AIProvider.openai,
      config: AIConfig(apiKey: 'your-key'),
    );
  }

  Future<void> _sendMessage(String text) async {
    setState(() {
      _messages.add(Message.user(text));
      _isLoading = true;
      _streamingContent = '';
    });

    await for (final chunk in _ai.streamChat(text)) {
      if (chunk.isDelta) {
        setState(() {
          _streamingContent += chunk.delta ?? '';
        });
      }
      if (chunk.isDone) {
        setState(() {
          _messages.add(Message.assistant(_streamingContent));
          _streamingContent = '';
          _isLoading = false;
        });
      }
    }
  }

  @override
  void dispose() {
    _ai.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Expanded(
          child: ListView.builder(
            itemCount: _messages.length,
            itemBuilder: (context, index) {
              final message = _messages[index];
              return ListTile(
                title: Text(message.text),
                subtitle: Text(message.role.name),
              );
            },
          ),
        ),
        if (_streamingContent.isNotEmpty)
          Padding(
            padding: EdgeInsets.all(8),
            child: Text(_streamingContent),
          ),
        // Add your message input UI here
      ],
    );
  }
}

API Reference #

FlutterAI #

Method Description
chat(String message) Send a text message
chatWithContent(List<Content> content) Send multimodal content
chatWithTools(String message, {required List<Tool> tools}) Chat with tools
submitToolResult({...}) Submit tool result
streamChat(String message) Stream a response
streamChatWithContent(List<Content> content) Stream multimodal
clearContext() Clear conversation
reset({String? systemPrompt}) Reset with new prompt

Content Types #

Type Description
TextContent(String text) Plain text
ImageContent.fromUrl(String url) Image from URL
ImageContent.fromBytes(Uint8List bytes) Image from bytes
AudioContent.fromUrl(String url) Audio from URL
DocumentContent.fromUrl(String url) Document from URL

Error Types #

Error Description
AIAuthenticationError Invalid API key
AIRateLimitError Rate limit exceeded
AIInvalidRequestError Bad request parameters
AIContextLengthError Context too long
AIContentFilterError Content blocked
AINetworkError Network issues
AIServerError Server errors

Best Practices #

  1. Always dispose - Call ai.dispose() when done to release resources
  2. Handle errors - Use try-catch for all API calls
  3. Monitor tokens - Check context.estimatedTokens before sending large requests
  4. Stream for long responses - Use streamChat for better UX
  5. Secure API keys - Never hardcode keys, use environment variables or secure storage

Support #

License #

This project is licensed under the MIT License.

0
likes
130
points
153
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A unified wrapper for AI APIs (OpenAI, Anthropic, Google AI, Ollama) with streaming, tool calling, context management, and multimodal support.

Repository (GitHub)
View/report issues
Contributing

Topics

#ai #llm #openai #anthropic #ollama

License

MIT (license)

Dependencies

collection, dio, equatable, flutter, http, json_annotation, logger, meta, mime, path, retry, rxdart, uuid

More

Packages that depend on flutter_ai_sdk