381 lines
9.8 KiB
Markdown
381 lines
9.8 KiB
Markdown
# Genkit Core Framework
|
|
|
|
Genkit Dart is an AI SDK for Dart that provides a unified interface for text generation, structured output, tool calling, and agentic workflows.
|
|
|
|
## Initialization
|
|
|
|
```dart
|
|
import 'package:genkit/genkit.dart';
|
|
import 'package:genkit_google_genai/genkit_google_genai.dart'; // Or any other plugin
|
|
|
|
void main() async {
|
|
// Pass plugins to use into the Genkit constructor
|
|
final ai = Genkit(plugins: [googleAI()]);
|
|
}
|
|
```
|
|
|
|
## Generate Text
|
|
|
|
```dart
|
|
final response = await ai.generate(
|
|
model: googleAI.gemini('gemini-2.5-flash'), // Needs a model reference from a plugin
|
|
prompt: 'Explain quantum computing in simple terms.',
|
|
);
|
|
|
|
print(response.text);
|
|
```
|
|
|
|
## Stream Responses
|
|
```dart
|
|
final stream = ai.generateStream(
|
|
model: googleAI.gemini('gemini-2.5-flash'),
|
|
prompt: 'Write a short story about a robot learning to paint.',
|
|
);
|
|
|
|
await for (final chunk in stream) {
|
|
print(chunk.text);
|
|
}
|
|
```
|
|
|
|
## Embed Text
|
|
```dart
|
|
final embeddings = await ai.embedMany(
|
|
documents: [
|
|
DocumentData(content: [TextPart(text: 'Hello world')]),
|
|
],
|
|
embedder: googleAI.textEmbedding('text-embedding-004'),
|
|
);
|
|
|
|
print(embeddings.first.embedding);
|
|
```
|
|
|
|
## Define Tools
|
|
Models can use define actions and access external data via custom defined tools.
|
|
Requires the `schemantic` library for schema definitions.
|
|
|
|
```dart
|
|
import 'package:schemantic/schemantic.dart';
|
|
|
|
@Schema()
|
|
abstract class $WeatherInput {
|
|
String get location;
|
|
}
|
|
|
|
final weatherTool = ai.defineTool(
|
|
name: 'getWeather',
|
|
description: 'Gets the current weather for a location',
|
|
inputSchema: WeatherInput.$schema,
|
|
fn: (input, _) async {
|
|
// Call your weather API here
|
|
return 'Weather in ${input.location}: 72°F and sunny';
|
|
},
|
|
);
|
|
|
|
final response = await ai.generate(
|
|
model: googleAI.gemini('gemini-2.5-flash'),
|
|
prompt: 'What\'s the weather like in San Francisco?',
|
|
toolNames: ['getWeather'], // Use the tools
|
|
);
|
|
```
|
|
|
|
## Structured Output
|
|
|
|
You can ensure the generative model returns a typed JSON object by providing an `outputSchema`.
|
|
|
|
```dart
|
|
@Schema()
|
|
abstract class $Person {
|
|
String get name;
|
|
int get age;
|
|
}
|
|
|
|
// ... inside main ...
|
|
|
|
final response = await ai.generate(
|
|
model: googleAI.gemini('gemini-2.5-flash'),
|
|
prompt: 'Generate a person named John Doe, age 30',
|
|
outputSchema: Person.$schema, // Force the model to return this schema
|
|
);
|
|
|
|
final person = response.output; // Typed Person object
|
|
print('Name: ${person.name}, Age: ${person.age}');
|
|
```
|
|
|
|
## Define Flows
|
|
Wrap your AI logic in flows for better observability, testing, and deployment:
|
|
|
|
```dart
|
|
final jokeFlow = ai.defineFlow(
|
|
name: 'tellJoke',
|
|
inputSchema: .string(),
|
|
outputSchema: .string(),
|
|
fn: (topic, _) async {
|
|
final response = await ai.generate(
|
|
model: googleAI.gemini('gemini-2.5-flash'),
|
|
prompt: 'Tell me a joke about $topic',
|
|
);
|
|
return response.text; // Value return
|
|
},
|
|
);
|
|
|
|
final joke = await jokeFlow('programming');
|
|
print(joke);
|
|
```
|
|
|
|
### Streaming Flows
|
|
Stream data from your flows using `context.sendChunk(...)` and returning the final value:
|
|
|
|
```dart
|
|
final streamStory = ai.defineFlow(
|
|
name: 'streamStory',
|
|
inputSchema: .string(),
|
|
outputSchema: .string(),
|
|
streamSchema: .string(),
|
|
fn: (topic, context) async {
|
|
final stream = ai.generateStream(
|
|
model: googleAI.gemini('gemini-2.5-flash'),
|
|
prompt: 'Write a story about $topic',
|
|
);
|
|
|
|
await for (final chunk in stream) {
|
|
context.sendChunk(chunk.text); // Stream the chunks
|
|
}
|
|
return 'Story complete'; // Value return
|
|
},
|
|
);
|
|
```
|
|
|
|
## Calling remote Flows from a dart client
|
|
The `genkit` package provides `package:genkit/client.dart` representing remote Genkit actions that can be invoked or streamed using type-safe definitions.
|
|
|
|
1. Defines a remote action
|
|
```dart
|
|
import 'package:genkit/client.dart';
|
|
|
|
final stringAction = defineRemoteAction(
|
|
url: 'http://localhost:3400/my-flow',
|
|
inputSchema: .string(),
|
|
outputSchema: .string(),
|
|
);
|
|
```
|
|
|
|
2. Call the Remote Action (Non-streaming)
|
|
```dart
|
|
final response = await stringAction(input: 'Hello from Dart!');
|
|
print('Flow Response: $response');
|
|
```
|
|
|
|
3. Call the Remote Action (Streaming)
|
|
Use the `.stream()` method on the action flow, and access `stream.onResult` to wait on the async return value.
|
|
```dart
|
|
final streamAction = defineRemoteAction(
|
|
url: 'http://localhost:3400/stream-story',
|
|
inputSchema: .string(),
|
|
outputSchema: .string(),
|
|
streamSchema: .string(),
|
|
);
|
|
|
|
final stream = streamAction.stream(
|
|
input: 'Tell me a short story about a Dart developer.',
|
|
);
|
|
|
|
await for (final chunk in stream) {
|
|
print('Chunk: $chunk');
|
|
}
|
|
|
|
final finalResult = await stream.onResult;
|
|
print('\nFinal Response: $finalResult');
|
|
```
|
|
|
|
## Calling remote Flows from a Javascript client
|
|
|
|
Install `genkit` npm package:
|
|
|
|
```bash
|
|
npm install genkit
|
|
```
|
|
|
|
1. Call a remote flow (non-streaming)
|
|
|
|
```ts
|
|
import { runFlow } from 'genkit/beta/client';
|
|
|
|
async function callHelloFlow() {
|
|
try {
|
|
const result = await runFlow({
|
|
url: 'http://127.0.0.1:3400/helloFlow', // Replace with your deployed flow's URL
|
|
input: { name: 'Genkit User' },
|
|
});
|
|
console.log('Non-streaming result:', result.greeting);
|
|
} catch (error) {
|
|
console.error('Error calling helloFlow:', error);
|
|
}
|
|
}
|
|
|
|
callHelloFlow();
|
|
```
|
|
|
|
2. Call a remote flow (streaming)
|
|
|
|
```ts
|
|
import { streamFlow } from 'genkit/beta/client';
|
|
|
|
async function streamHelloFlow() {
|
|
try {
|
|
const result = streamFlow({
|
|
url: 'http://127.0.0.1:3400/helloFlow', // Replace with your deployed flow's URL
|
|
input: { name: 'Streaming User' },
|
|
});
|
|
|
|
// Process the stream chunks as they arrive
|
|
for await (const chunk of result.stream) {
|
|
console.log('Stream chunk:', chunk);
|
|
}
|
|
|
|
// Get the final complete response
|
|
const finalOutput = await result.output;
|
|
console.log('Final streaming output:', finalOutput.greeting);
|
|
} catch (error) {
|
|
console.error('Error streaming helloFlow:', error);
|
|
}
|
|
}
|
|
|
|
streamHelloFlow();
|
|
```
|
|
|
|
## Data Models
|
|
|
|
Genkit uses standard data models for representing prompts (messages & parts) and responses. These classes are implemented using schemantic library.
|
|
|
|
```dart
|
|
import 'package:genkit/genkit.dart';
|
|
import 'package:schemantic/schemantic.dart';
|
|
|
|
@Schema()
|
|
abstract class $MyDataModel {
|
|
// uses Genkit's Message schema (not schemantic's Message)
|
|
List<$Message> get messages;
|
|
List<$Part> get parts;
|
|
}
|
|
|
|
void example() {
|
|
// --- Parts ---
|
|
// A Text part
|
|
final textPart = TextPart(text: 'some text', metadata: {'foo': 'bar'});
|
|
|
|
// A Media/Image part
|
|
final mediaPart = MediaPart(
|
|
media: Media(url: 'https://...', contentType: 'image/png'),
|
|
metadata: {'foo': 'bar'},
|
|
);
|
|
|
|
// A Tool Request initiated by the model
|
|
final toolRequestPart = ToolRequestPart(
|
|
toolRequest: ToolRequest(
|
|
name: 'get_weather',
|
|
ref: 'abc',
|
|
input: {'location': 'Paris, France'},
|
|
),
|
|
metadata: {'foo': 'bar'},
|
|
);
|
|
|
|
// The resulting data from a Tool execution
|
|
final toolResponsePart = ToolResponsePart(
|
|
toolResponse: ToolResponse(
|
|
name: 'get_weather',
|
|
ref: 'abc',
|
|
output: {'temperature': '20C'},
|
|
),
|
|
metadata: {'foo': 'bar'},
|
|
);
|
|
|
|
// Model reasoning (e.g. for Claude's "thinking" models)
|
|
final reasoningPart = ReasoningPart(
|
|
reasoning: 'thinking...',
|
|
metadata: {'foo': 'bar'},
|
|
);
|
|
|
|
// A custom fallback part
|
|
final customPart = CustomPart(
|
|
custom: {'provider': {'specific': 'data'}},
|
|
metadata: {'foo': 'bar'},
|
|
);
|
|
|
|
// --- Messages ---
|
|
final systemMessage = Message(
|
|
role: Role.system,
|
|
content: [textPart, mediaPart],
|
|
metadata: {'foo': 'bar'},
|
|
);
|
|
|
|
final userMessage = Message(
|
|
role: Role.user,
|
|
content: [textPart, mediaPart], // Can contain media (multimodal)
|
|
);
|
|
|
|
final modelMessage = Message(
|
|
role: Role.model,
|
|
// Models can emit text, tool requests, reasoning, or custom parts
|
|
content: [textPart, toolRequestPart, reasoningPart, customPart],
|
|
);
|
|
|
|
// --- Ergonomic Data Access (schema_extensions.dart) ---
|
|
// The Genkit SDK provides extensions on `Message` and `Part` to easily access fields
|
|
// without needing to cast them manually.
|
|
|
|
// Get concatenated text from all TextParts in a Message
|
|
print(modelMessage.text);
|
|
|
|
// Get the first Media object from a Message
|
|
print(modelMessage.media?.url);
|
|
|
|
// Iterate over tool requests in a Message
|
|
for (final toolReq in modelMessage.toolRequests) {
|
|
print(toolReq.name);
|
|
}
|
|
|
|
// Inspect individual parts
|
|
for (final part in modelMessage.content) {
|
|
if (part.isText) print(part.text);
|
|
if (part.isMedia) print(part.media?.url);
|
|
if (part.isToolRequest) print(part.toolRequest?.name);
|
|
if (part.isToolResponse) print(part.toolResponse?.name);
|
|
if (part.isReasoning) print(part.reasoning);
|
|
if (part.isCustom) print(part.custom);
|
|
}
|
|
|
|
// --- Streaming Chunks ---
|
|
// Data emitted by ai.generateStream() calls
|
|
final generateResponseChunk = ModelResponseChunk(
|
|
content: [textPart],
|
|
index: 0, // Index of the message this chunk belongs to
|
|
aggregated: false,
|
|
);
|
|
|
|
// Chunks also have text and media accessors
|
|
print(generateResponseChunk.text);
|
|
|
|
// --- Advanced: Schemas ---
|
|
// Use Genkit type schemas directly in Schemantic validations
|
|
final messageSchema = Message.$schema;
|
|
final partSchema = Part.$schema;
|
|
|
|
final mySchema = SchemanticType.map(
|
|
.string(),
|
|
.list(Message.$schema), // Requires a list of Messages
|
|
);
|
|
|
|
// --- Generate Response ---
|
|
// ai.generate() returns a GenerateResponseHelper which provides ergonomic getters
|
|
// over the underlying ModelResponse:
|
|
final response = await ai.generate(...);
|
|
|
|
print(response.text); // Concatenated text
|
|
print(response.media?.url); // First media part
|
|
print(response.toolRequests); // All tool requests
|
|
print(response.interrupts); // Tool requests that triggered an interrupt
|
|
print(response.messages); // Full history of the conversation, including the request and response
|
|
print(response.output); // Structured typed output (if outputSchema was used)
|
|
}
|
|
```
|