-
Notifications
You must be signed in to change notification settings - Fork 105
Updates to make the library applicable for wider range of use cases, and more stable. #644
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
/gemini review |
|
@csells , how does it look? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request introduces several significant and beneficial changes to make the library more flexible and robust. Key changes include removing the enforced ChatMessageRole, renaming ChatMessage to Message, and using constants for JSON keys to reduce message size. The test coverage has been significantly improved, including a new test for the example, which is excellent. My review focuses on some opportunities to improve code clarity and leverage more modern Dart features, such as using const constructors for immutable classes and refactoring switch statements/expressions for better readability. Overall, this is a solid pull request that improves the library's design and usability.
|
Options for name for the class ChatMessage:
|
|
I think you left one option:
Can I ask what problem is solved by renaming it? |
Sure! This library is intended to serve very wide range of client applications, interacting with AI. 'Chat' is implying type of interaction, where history is preserved, and UI looks as timeline. But, there can be many other types. And, I am sure we do not know these types as they do not exist yet. Does it help? |
|
That sounds abstract. My understanding of the goal of this package was to concretely provide base types for chat history, which would eliminate the need to provide mapping layers between GenUI, dartantic and other packages like the Flutter AI Toolkit. What concrete scenarios do you have in mind? |
For example, interaction with some history-less agent without chat-like UI. |
|
To which agent are you referring that provides such a history-based API without it being conversational? I'm unfamiliar with any that work like that. |
|
|
||
| /// A message in a conversation between a user and a model. | ||
| @immutable | ||
| class Message { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this class intended to be implemented? If not, let's mark it using the base modifier.
| static const metadata = 'metadata'; | ||
| } | ||
|
|
||
| /// A message in a conversation between a user and a model. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not super-clear what "between" means. Does it mean that a Message can be sent by the user to the model, and by the model to the user? If so, consider spelling it out, if not in the intro paragraph, then in a second paragraph.
| /// A message in a conversation between a user and a model. | ||
| @immutable | ||
| class Message { | ||
| /// Creates a new message. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Document the arguments. Special cases, such as when parts and metadata are both empty, could be particularly important.
|
|
||
| /// Creates a new message, taking a text string as separate parameter. | ||
| Message( | ||
| String text, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this is a convenience constructor. Typically, the default constructor is the main constructor, and named constructors are conveniences. That is, I'd make fromParts the default, and this one named Message.fromText.
| /// Creates a new message, taking a text string as separate parameter. | ||
| Message( | ||
| String text, { | ||
| List<Part> parts = const [], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if one of the parts is a TextPart? Is it OK to have two text parts?
| } | ||
|
|
||
| int mapHashCode(Map<dynamic, dynamic>? map) { | ||
| if (map == null) return 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's avoid redefining the hash code for null. null already has hashCode, and it is equal to 2011 on the Dart VM, and 0 on the web. Let's just return null.hashCode and let the language/compiler do its thing.
| @immutable | ||
| abstract class Part { | ||
| /// Creates a new part. | ||
| const Part(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need a constructor for creating an empty part?
| 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(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I have parts "happy", "new", "year", "world", will the concatenated text be "happynewyearworld"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, but luckily the text parts include whitespace, so we're good.
| /// 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Checks" is an unusual way to describe a getter. It's as if it fails if there are no tool calls. Typically we say, "Returns whether this message contains...", or "Returns true if this message contains...".
This comment applies to other getters in the library.
| .where((p) => p.kind == ToolPartKind.result) | ||
| .toList(); | ||
|
|
||
| /// Converts the message to a JSON-compatible map. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: consider "Serializes" instead of "Converts". The word "convert" implies that this same object transforms/mutates to something else, but what this method does is create a new object that represents this object in JSON format.
Contributes to #607