Skip to content

genkit-ai/genkit-dart

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Dart client library for Genkit

This library provides a Dart client for interacting with Genkit flows, enabling type-safe communication for both unary and streaming operations.

Getting Started

Import the library and define your remote actions.

import 'package:genkit/client.dart';

Defining remote actions

Remote actions represent a remote Genkit action (like flows, models and prompts) that can be invoked or streamed.

Creating a remote action

// Create a remote action for a flow that takes a String and returns a String
final stringAction = defineRemoteAction(
  url: 'http://localhost:3400/my-flow',
  fromResponse: (data) => data as String,
);

// Create a remote action for custom objects
final customAction = defineRemoteAction(
  url: 'http://localhost:3400/custom-flow',
  fromResponse: (data) => MyOutput.fromJson(data),
);

The code assumes that you have my-flow and custom-flow deployed at those URLs. See https://genkit.dev/docs/deploy-node/ or https://genkit.dev/go/docs/deploy/ for details.

Calling actions

Example: String to String

final action = defineRemoteAction(
  url: 'http://localhost:3400/echo-string',
  fromResponse: (data) => data as String,
);

try {
  final response = await action(input: 'Hello from Dart!');
  print('Flow Response: $response');
} catch (e) {
  print('Error calling flow: $e');
}

Example: Custom Object Input and Output

class MyInput {
  final String message;
  final int count;
  
  MyInput({required this.message, required this.count});
  
  Map<String, dynamic> toJson() => {
    'message': message,
    'count': count,
  };
}

class MyOutput {
  final String reply;
  final int newCount;
  
  MyOutput({required this.reply, required this.newCount});
  
  factory MyOutput.fromJson(Map<String, dynamic> json) => MyOutput(
    reply: json['reply'] as String,
    newCount: json['newCount'] as int,
  );
}

final action = defineRemoteAction(
  url: 'http://localhost:3400/process-object',
  fromResponse: (data) => MyOutput.fromJson(data),
);

final input = MyInput(message: 'Process this data', count: 10);

try {
  final output = await action(input: input);
  print('Flow Response: ${output.reply}, ${output.newCount}');
} catch (e) {
  print('Error calling flow: $e');
}

Calling Streaming Flows

Use the stream method for flows that stream multiple chunks of data and then return a final response. It returns an ActionStream, which is a Stream of chunks and also provides two ways to access the final result of the flow:

  • onResult: A Future that completes with the final result when the stream is fully consumed. This is the recommended way to get the final result, as it works naturally with async/await. If the stream is cancelled or encounters an error, this Future will complete with a GenkitException.
  • result: A synchronous getter that returns the final result. This should only be accessed after the stream has been fully consumed. It will throw an exception if the stream is not yet done or if it terminated with an error.

Example 1: Using onResult (Recommended)

This example shows how to asynchronously wait for the final result after consuming the stream.

final streamAction = defineRemoteAction(
  url: 'http://localhost:3400/stream-story',
  fromResponse: (data) => data as String,
  fromStreamChunk: (data) => data['chunk'] as String,
);

try {
  final stream = streamAction.stream(
    input: 'Tell me a short story about a Dart developer.',
  );

  print('Streaming chunks:');
  await for (final chunk in stream) {
    print('Chunk: $chunk');
  }

  // Use onResult to asynchronously get the final response.
  final finalResult = await stream.onResult;
  print('\nFinal Response: $finalResult');
} catch (e) {
  print('Error calling streamFlow: $e');
}

Example 2: Using result

If you have already consumed the stream (e.g., with await for), you can use the synchronous result getter.

  // ... (after awaiting the stream as in the example above)

  // Once the stream is done, you can access the result synchronously.
  final finalResult = stream.result;
  print('\nFinal Response: $finalResult');

Example: Custom Object Streaming

class StreamChunk {
  final String content;
  
  StreamChunk({required this.content});
  
  factory StreamChunk.fromJson(Map<String, dynamic> json) => StreamChunk(
    content: json['content'] as String,
  );
}

final streamAction = defineRemoteAction(
  url: 'http://localhost:3400/stream-process',
  fromResponse: (data) => MyOutput.fromJson(data),
  fromStreamChunk: (data) => StreamChunk.fromJson(data),
);

final input = MyInput(message: 'Stream this data', count: 5);

try {
  final stream = streamAction.stream(input: input);

  print('Streaming chunks:');
  await for (final chunk in stream) {
    print('Chunk: ${chunk.content}');
  }

  final finalResult = await stream.onResult;
  print('\nFinal Response: ${finalResult.reply}');
} catch (e) {
  print('Error calling streaming flow: $e');
}

Custom Headers

You can provide custom headers for individual requests:

final response = await action(
  input: 'test input',
  headers: {'Authorization': 'Bearer your-token'},
);

// For streaming
final streamResponse = action.stream(
  input: 'test input',
  headers: {'Authorization': 'Bearer your-token'},
);

You can also set default headers when creating the remote action:

final action = defineRemoteAction(
  url: 'http://localhost:3400/my-flow',
  fromResponse: (data) => data as String,
  defaultHeaders: {'Authorization': 'Bearer your-token'},
);

Error Handling

The library throws GenkitException for various error conditions:

try {
  final result = await action(input: 'test');
} on GenkitException catch (e) {
  print('Genkit error: ${e.message}');
  print('Status code: ${e.statusCode}');
  print('Details: ${e.details}');
} catch (e) {
  print('Other error: $e');
}

Type Parameters

  • O: The type of the output data from the flow's final response
  • S: The type of the data chunks streamed from the flow (use void for non-streaming flows)

Data Conversion Functions

  • fromResponse: Converts the JSON response data to your output type O
  • fromStreamChunk: (Optional) Converts JSON chunk data to your stream chunk type S

Make sure your custom classes have appropriate toJson() and fromJson() methods for serialization and deserialization.

Working with Genkit Data Objects

When interacting with Genkit models, you'll often work with a set of standardized data classes that represent the inputs and outputs of generative models. This library provides these classes to make it easy to construct requests and handle responses in a type-safe way.

Key data classes include:

  • GenerateResponse: The final response from a model generation call.
  • GenerateResponseChunk: A streaming chunk from a model generation call.
  • Message: Represents a message in a conversation, containing a role (e.g., user, model) and content.
  • Part: The content of a message is made up of one or more Part objects. Common parts include:
    • TextPart: For text content.
    • MediaPart: For media content like images.
    • ToolRequestPart: A request from the model to invoke a tool.
    • ToolResponsePart: The response from a tool invocation.
    • DataPart, CustomPart, ReasoningPart, ResourcePart: For other specialized data.

These classes include helpful getters like .text to easily extract string content and .media to get the first media object from responses and messages.

Example: Streaming with Genkit Data Objects

Here is an example of how to call a generative model and process the streaming response using the built-in data classes. See example/genkit_example.dart for a runnable version.

import 'package:genkit/client.dart';

// ...

final generateFlow = defineRemoteAction(
  url: 'http://localhost:3400/generate',
  fromResponse: (json) => GenerateResponse.fromJson(json),
  fromStreamChunk: (json) => GenerateResponseChunk.fromJson(json),
);

final stream = generateFlow.stream(
  input: Message(role: Role.user, content: [TextPart(text: "hello")]),
);

print('Streaming chunks:');
await for (final chunk in stream) {
  // Use the .text getter to easily access the text content of the chunk
  print('Chunk: ${chunk.text}');
}

final finalResult = await stream.onResult;
// The .text getter also works on the final response
print('Final Response: ${finalResult.text}');

About

Dart client for Genkit

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •