Skip to main content

Core concepts

ai_abstracted rests on four ideas. Once you know them, every provider and every capability reads the same way. This page covers all four, then points you at progress reporting and error handling.

1. Capability contracts

Each capability is one interface with one async method. TextGenerator turns a TextRequest into a result. ImageGenerator turns an ImageRequest into a result. The other four (VideoGenerator, SpeechGenerator, SoundEffectGenerator, MusicGenerator) follow the same shape.

The type exists so you can inject it. Your code depends on the contract, not on a specific provider:

import 'package:ai_abstracted/ai_abstracted.dart';

Future<String> summarize(TextGenerator generator, String article) async {
final result = await generator.generateText(
TextRequest(prompt: 'Summarize this article:\n$article'),
);
return result.text;
}

summarize does not know which provider it talks to. In production you pass a real client. In a test you pass a fake:

import 'package:ai_abstracted/ai_abstracted.dart';

void main() async {
final fake = FakeTextGenerator(text: 'A short summary.');
final summary = await summarize(fake, 'Long article text...');
print(summary); // A short summary.
}

Every capability ships an in-memory fake (FakeTextGenerator, FakeImageGenerator, and so on), so you never hit a paid API in a unit test.

2. Typed requests

Every request extends GenerationRequest. The base carries three shared fields: prompt (required), seed, and model. Each subclass adds the fields that make sense for its medium and reports its kind.

import 'package:ai_abstracted/ai_abstracted.dart';

const text = TextRequest(
prompt: 'Write a haiku about rain.',
maxTokens: 256,
);

const image = ImageRequest(
prompt: 'a red bicycle leaning on a white wall',
width: 1024,
height: 1024,
seed: '42',
);

Requests are const constructible and immutable, so you can define them once and reuse them. seed is a String? and only takes effect when the provider supports reproducible output. model overrides the client default when set, and stays null to use the default.

3. GenerationResult

Every capability returns the same type: GenerationResult. It holds bytes, the mimeType of those bytes, the kind of medium, and a GenerationMetadata object. For text results, result.text decodes the bytes as UTF-8 so you do not handle bytes by hand.

import 'dart:io';

import 'package:ai_abstracted/ai_abstracted.dart';

Future<void> saveImage(ImageGenerator generator) async {
final result = await generator.generateImage(
const ImageRequest(prompt: 'a red bicycle'),
);

// The package never writes files. You decide where the bytes go.
await File('bicycle.png').writeAsBytes(result.bytes);

print(result.mimeType); // for example image/png
print('${result.metadata.width}x${result.metadata.height}');
}

result.bytes is a Uint8List. Writing it to disk uses dart:io, which lives in your code, not in the package. ai_abstracted itself stays free of dart:io so it runs anywhere Dart runs.

The metadata field describes what came back: the model that produced it, optional width and height, hasAudio for video, durationMs (also exposed as metadata.duration), and any provider-specific extra fields.

4. Credentials

ProviderCredentials carries the API key and any connection settings a provider needs. You can build one directly:

import 'package:ai_abstracted/ai_abstracted.dart';

const credentials = ProviderCredentials(apiKey: 'your-api-key');

More often you read it from the environment. credentialsFromEnv takes a ProviderId and an environment map, and returns the credentials for that provider, or null when the key is absent:

import 'dart:io';

import 'package:ai_abstracted/ai_abstracted.dart';

void main() {
final credentials = credentialsFromEnv(
ProviderId.gemini,
Platform.environment,
);
if (credentials == null) {
stderr.writeln('Set GEMINI_API_KEY in your environment.');
return;
}
// Hand credentials to a provider client or the registry.
}

Each provider reads a known variable (Gemini reads GEMINI_API_KEY, OpenAI reads OPENAI_API_KEY, and so on). Ollama is keyless, so it returns an empty key rather than null. To load every provider you have keys for at once, use allCredentialsFromEnv.

Progress

Every capability method takes an optional onProgress callback. It receives a GenerationProgress with a stage from GenerationStage. Immediate providers emit running then done. Providers that poll a long job (Flux, Veo, Suno) also emit queued and downloading. Use it to drive a status line. See progress reporting.

Errors

When a call fails, it throws an AiException subclass that tells you what went wrong: AiAuthException for bad keys, AiRateLimitException for 429s, AiInvalidRequestException for a malformed request, and AiTransientException for retryable network and server errors. Catch AiException to handle any of them. See error handling.

Next