Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
4ad0ecd
initial port
csells Dec 18, 2025
86b9f84
port to json_schema_builder
csells Dec 18, 2025
2033059
updated tests
csells Dec 18, 2025
9a41d31
changelog, pubspec
csells Dec 18, 2025
dbeae4a
pubspec
csells Dec 18, 2025
f301f5d
pana feedback
csells Dec 18, 2025
48b1401
pana feedback
csells Dec 18, 2025
ae9bf1b
copyrights
csells Dec 18, 2025
c7cdf9a
Update packages/genai_primitives/lib/src/chat_message.dart
csells Dec 18, 2025
4c6c8e2
Update packages/genai_primitives/lib/src/message_parts.dart
csells Dec 18, 2025
a95f613
formatting
csells Dec 18, 2025
d2007d5
Update packages/genai_primitives/lib/src/message_parts.dart
csells Dec 18, 2025
4f3b62f
Update packages/genai_primitives/lib/src/chat_message.dart
csells Dec 18, 2025
3f1169d
double quotes
csells Dec 18, 2025
457a3f5
PR feedback: rename ToolPart `id` to `interactionId` and `name` to `t…
csells Dec 20, 2025
3c7057e
chore: Update SDK dependencies and enhance fake A2A client with nulla…
csells Dec 21, 2025
9947103
Merge branch 'main' into initial_primitives
polina-c Dec 22, 2025
7303be9
revert changes in packages/genui_a2ui/pubspec.yaml
polina-c Dec 22, 2025
41ec2e8
Update README.md (#620)
polina-c Dec 18, 2025
a597c29
Update diagram and table. (#621)
polina-c Dec 18, 2025
76ad5d3
fix(genui): fix `DateTimeInput` core catalog item (#622)
andrewkolos Dec 18, 2025
25c6fb3
Return dartantic to monorepo, update changelogs. (#624)
gspencergoog Dec 19, 2025
8707e5f
Switch to local A2A Dart client library (#627)
gspencergoog Dec 20, 2025
e6a840a
revert changes in packages/genui_a2ui/pubspec.yaml
polina-c Dec 22, 2025
6a1f186
Merge branch 'initial_primitives' of https://github.com/csells/genui …
csells Dec 22, 2025
35d2f87
Merge branch 'main' into initial_primitives
csells Dec 22, 2025
bc16a51
initial port
csells Dec 18, 2025
6836134
port to json_schema_builder
csells Dec 18, 2025
73ae75d
updated tests
csells Dec 18, 2025
f9b9a9e
changelog, pubspec
csells Dec 18, 2025
165b7f7
pubspec
csells Dec 18, 2025
106f6dd
pana feedback
csells Dec 18, 2025
3e88ed2
pana feedback
csells Dec 18, 2025
179a79b
copyrights
csells Dec 18, 2025
86b2c26
Update packages/genai_primitives/lib/src/chat_message.dart
csells Dec 18, 2025
bd045bc
Update packages/genai_primitives/lib/src/message_parts.dart
csells Dec 18, 2025
3b40f10
formatting
csells Dec 18, 2025
1f2b758
Update packages/genai_primitives/lib/src/message_parts.dart
csells Dec 18, 2025
eeddae9
Update packages/genai_primitives/lib/src/chat_message.dart
csells Dec 18, 2025
70cb971
double quotes
csells Dec 18, 2025
313a827
PR feedback: rename ToolPart `id` to `interactionId` and `name` to `t…
csells Dec 20, 2025
891c3be
chore: Update SDK dependencies and enhance fake A2A client with nulla…
csells Dec 21, 2025
ea96b0e
revert changes in packages/genui_a2ui/pubspec.yaml
polina-c Dec 22, 2025
e23a58f
Switch to local A2A Dart client library (#627)
gspencergoog Dec 20, 2025
68ce736
revert changes in packages/genui_a2ui/pubspec.yaml
polina-c Dec 22, 2025
0b6a929
Merge branch 'initial_primitives' of https://github.com/csells/genui …
csells Dec 22, 2025
8597a0a
chore: Update package dependencies and adjust `genai_primitives` mess…
csells Dec 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions packages/genai_primitives/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# `genai_primitives` Changelog

## 0.0.1

- Initial version.
## 0.1.0

- Initial release
10 changes: 0 additions & 10 deletions packages/genai_primitives/example/example.dart

This file was deleted.

120 changes: 120 additions & 0 deletions packages/genai_primitives/example/main.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Copyright 2025 The Flutter Authors.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:convert';
import 'dart:typed_data';

import 'package:genai_primitives/genai_primitives.dart';
import 'package:json_schema_builder/json_schema_builder.dart';

void main() {
print('--- GenAI Primitives Example ---');

// 1. Define a Tool
final ToolDefinition<Object> getWeatherTool = ToolDefinition(
name: 'get_weather',
description: 'Get the current weather for a location',
inputSchema: Schema.object(
properties: {
'location': Schema.string(
description: 'The city and state, e.g. San Francisco, CA',
),
'unit': Schema.string(
enumValues: ['celsius', 'fahrenheit'],
description: 'The unit of temperature',
),
},
required: ['location'],
),
);

print('\n[Tool Definition]');
print(const JsonEncoder.withIndent(' ').convert(getWeatherTool.toJson()));

// 2. Create a conversation history
final history = <ChatMessage>[
// System message
ChatMessage.system(
'You are a helpful weather assistant. '
'Use the get_weather tool when needed.',
),

// User message asking for weather
ChatMessage.user('What is the weather in London?'),
];

print('\n[Initial Conversation]');
for (final msg in history) {
print('${msg.role.name}: ${msg.text}');
}

// 3. Simulate Model Response with Tool Call
final modelResponse = ChatMessage.model(
'', // Empty text for tool call
parts: [
const TextPart('Thinking: User wants weather for London...'),
const ToolPart.call(
callId: 'call_123',
toolName: 'get_weather',
arguments: {'location': 'London', 'unit': 'celsius'},
),
],
);
history.add(modelResponse);

print('\n[Model Response with Tool Call]');
if (modelResponse.hasToolCalls) {
for (final ToolPart call in modelResponse.toolCalls) {
print('Tool Call: ${call.toolName}(${call.arguments})');
}
}

// 4. Simulate Tool Execution & Result
final toolResult = ChatMessage.user(
'', // User role is typically used for tool results in many APIs
parts: [
const ToolPart.result(
callId: 'call_123',
toolName: 'get_weather',
result: {'temperature': 15, 'condition': 'Cloudy'},
),
],
);
history.add(toolResult);

print('\n[Tool Result]');
print('Result: ${toolResult.toolResults.first.result}');

// 5. Simulate Final Model Response with Data (e.g. an image generated or
// returned)
final finalResponse = ChatMessage.model(
'Here is a chart of the weather trend:',
parts: [
DataPart(
Uint8List.fromList([0x89, 0x50, 0x4E, 0x47]), // Fake PNG header
mimeType: 'image/png',
name: 'weather_chart.png',
),
],
);
history.add(finalResponse);

print('\n[Final Model Response with Data]');
print('Text: ${finalResponse.text}');
if (finalResponse.parts.any((p) => p is DataPart)) {
final DataPart dataPart = finalResponse.parts.whereType<DataPart>().first;
print(
'Attachment: ${dataPart.name} '
'(${dataPart.mimeType}, ${dataPart.bytes.length} bytes)',
);
}

// 6. Demonstrate JSON serialization of the whole history
print('\n[Full History JSON]');
print(
const JsonEncoder.withIndent(
' ',
).convert(history.map((m) => m.toJson()).toList()),
);
}
10 changes: 4 additions & 6 deletions packages/genai_primitives/lib/genai_primitives.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/// Support for doing something awesome.
///
/// More dartdocs go here.
/// A set of primitives for working with generative AI.
library;

export 'src/genai_primitives_base.dart';

// TODO: Export any libraries intended for clients of this package.
export 'src/chat_message.dart';
export 'src/message_parts.dart';
export 'src/tool_definition.dart';
141 changes: 141 additions & 0 deletions packages/genai_primitives/lib/src/chat_message.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// Copyright 2025 The Flutter Authors.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:meta/meta.dart';

import 'message_parts.dart';
import 'utils.dart';

/// A message in a conversation between a user and a model.
@immutable
class ChatMessage {
/// Creates a new message.
const ChatMessage({
required this.role,
required this.parts,
this.metadata = const {},
});

/// Creates a message from a JSON-compatible map.
factory ChatMessage.fromJson(Map<String, dynamic> json) => ChatMessage(
role: ChatMessageRole.values.byName(json['role'] as String),
parts: (json['parts'] as List<dynamic>)
.map((p) => Part.fromJson(p as Map<String, dynamic>))
.toList(),
metadata: (json['metadata'] as Map<String, dynamic>?) ?? const {},
);

/// Creates a system message.
factory ChatMessage.system(
String text, {
List<Part> parts = const [],
Map<String, dynamic>? metadata,
}) => ChatMessage(
role: ChatMessageRole.system,
parts: [TextPart(text), ...parts],
metadata: metadata ?? const {},
);

/// Creates a user message with text.
factory ChatMessage.user(
String text, {
List<Part> parts = const [],
Map<String, dynamic>? metadata,
}) => ChatMessage(
role: ChatMessageRole.user,
parts: [TextPart(text), ...parts],
metadata: metadata ?? const {},
);

/// Creates a model message with text.
factory ChatMessage.model(
String text, {
List<Part> parts = const [],
Map<String, dynamic>? metadata,
}) => ChatMessage(
role: ChatMessageRole.model,
parts: [TextPart(text), ...parts],
metadata: metadata ?? const {},
);

/// The role of the message author.
final ChatMessageRole role;

/// The content parts of the message.
final List<Part> parts;

/// Optional metadata associated with this message.
/// Can include information like suppressed content, warnings, etc.
final Map<String, dynamic> metadata;

/// Gets the text content of the message by concatenating all text parts.
String get text => parts.whereType<TextPart>().map((p) => p.text).join();

/// Checks if this message contains any tool calls.
bool get hasToolCalls =>
parts.whereType<ToolPart>().any((p) => p.kind == ToolPartKind.call);

/// Gets all tool calls in this message.
List<ToolPart> get toolCalls => parts
.whereType<ToolPart>()
.where((p) => p.kind == ToolPartKind.call)
.toList();

/// Checks if this message contains any tool results.
bool get hasToolResults =>
parts.whereType<ToolPart>().any((p) => p.kind == ToolPartKind.result);

/// Gets all tool results in this message.
List<ToolPart> get toolResults => parts
.whereType<ToolPart>()
.where((p) => p.kind == ToolPartKind.result)
.toList();

/// Converts the message to a JSON-compatible map.
Map<String, dynamic> toJson() => {
'role': role.name,
'parts': parts.map((p) => p.toJson()).toList(),
'metadata': metadata,
};

@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is ChatMessage &&
other.role == role &&
listEquals(other.parts, parts) &&
mapEquals(other.metadata, metadata);
}

@override
int get hashCode => Object.hash(
role,
Object.hashAll(parts),
Object.hashAll(metadata.entries),
);

@override
String toString() =>
'Message(role: $role, parts: $parts, metadata: $metadata)';
}

/// The role of a message author.
///
/// The role indicates the source of the message or the intended perspective.
/// For example, a system message is sent to the model to set context,
/// a user message is sent to the model, and a model message is a response
/// to the user.
enum ChatMessageRole {
Copy link
Collaborator

@polina-c polina-c Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add more details to doc strings for enum and its items?

I can represent a confused user here:

  • doc for enum is saying that each item is a role of a message author, while doc for each item is describing type of message, not author

  • can you elaborate what is 'system'?

  • documentation is saying 'from', but not 'to'. Is it correct that system always send messages to model? If yes, should we specify it here? And the same about model: is it correct that model's messages are intended to be handled by system and then system will show to the user whatever is intended for user?

  • or my assumptions above are completely wrong and the enum relates to difference between 'user prompt' and 'system prompt' somehow?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe in this library instead of ChatMessage having field ChatMessageRole role, it make sense for it to have the field 'String type', so that every dependent can define their set of message types.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option:

class ChatMessage<T>{
  ...
  final T type;
  ...
}

Then users will be able to pass their enums for the message type. Maybe some of them will have more than one model and more than one system.

Copy link
Collaborator

@polina-c polina-c Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or, we can remove type completely, so that users just derive from ChatMessage and add whatever fields they want to add. Or users can compose ChatMessage into another object, that contains envelope information.

This seems to be cleanest option for me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll elaborate on what the enums mean, but across all of the LLM APIs (and I've built against most of them by now, certainly all of the major ones), it always boils down to role (user or system or model|assistant) and parts. Anything more than that is over engineering, imo.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think having roles itemized makes this library too opinionated.

I would remove role from the message completely, allowing clients to define the roles in their systems.

If you disagree let's merge this as is and have this discussion outside of this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's talk after the merge.

/// A message from the system that sets context or instructions for the model.
///
/// System messages are typically sent to the model to define its behavior
/// or persona ("system prompt"). They are not usually shown to the end user.
system,

/// A message from the end user to the model ("user prompt").
user,

/// A message from the model to the user ("model response").
model,
}
10 changes: 0 additions & 10 deletions packages/genai_primitives/lib/src/genai_primitives_base.dart

This file was deleted.

Loading
Loading