自定义 LLM 提供程序
连接大型语言模型 (LLM) 和 LlmChatView
的协议在 LlmProvider
接口 中表达:
abstract class LlmProvider implements Listenable {
Stream<String> generateStream(String prompt, {Iterable<Attachment> attachments});
Stream<String> sendMessageStream(String prompt, {Iterable<Attachment> attachments});
Iterable<ChatMessage> get history;
set history(Iterable<ChatMessage> history);
}
LLM 可以位于云端或本地,它可以托管在 Google Cloud Platform 或其他云提供商上,它可以是专有 LLM 或开源 LLM。任何可用于实现此接口的 LLM 或类似 LLM 的端点都可以作为 LLM 提供程序插入聊天视图。AI 工具包自带三个提供程序,所有这些都实现了将提供程序插入以下内容所需的 LlmProvider
接口:
- Gemini 提供程序,它包装了
google_generative_ai
包 - Vertex 提供程序,它包装了
firebase_vertexai
包 - Echo 提供程序,它可以用作最小的提供程序示例
实现
#要构建你自己的提供程序,你需要记住以下几点来实现 LlmProvider
接口:
提供完整的配置支持
处理历史记录
将消息和附件转换为底层 LLM
调用底层 LLM
配置 为了支持自定义提供程序中的完整可配置性,你应该允许用户创建底层模型并将其作为参数传入,就像 Gemini 提供程序那样:
class GeminiProvider extends LlmProvider ... {
@immutable
GeminiProvider({
required GenerativeModel model,
...
}) : _model = model,
...
final GenerativeModel _model;
...
}
这样,无论未来底层模型发生什么变化,自定义提供程序的用户都可以使用所有配置旋钮。
- 历史记录 历史记录是任何提供程序的重要组成部分——提供程序不仅需要允许直接操作历史记录,而且还必须在历史记录更改时通知侦听器。此外,为了支持序列化和更改提供程序参数,它还必须支持将历史记录保存为构建过程的一部分。
Gemini 提供程序的处理方式如下所示:
class GeminiProvider extends LlmProvider with ChangeNotifier {
@immutable
GeminiProvider({
required GenerativeModel model,
Iterable<ChatMessage>? history,
...
}) : _model = model,
_history = history?.toList() ?? [],
... { ... }
final GenerativeModel _model;
final List<ChatMessage> _history;
...
@override
Stream<String> sendMessageStream(
String prompt, {
Iterable<Attachment> attachments = const [],
}) async* {
final userMessage = ChatMessage.user(prompt, attachments);
final llmMessage = ChatMessage.llm();
_history.addAll([userMessage, llmMessage]);
final response = _generateStream(
prompt: prompt,
attachments: attachments,
contentStreamGenerator: _chat!.sendMessageStream,
);
yield* response.map((chunk) {
llmMessage.append(chunk);
return chunk;
});
notifyListeners();
}
@override
Iterable<ChatMessage> get history => _history;
@override
set history(Iterable<ChatMessage> history) {
_history.clear();
_history.addAll(history);
_chat = _startChat(history);
notifyListeners();
}
...
}
你会在这段代码中注意到一些事情:
- 使用
ChangeNotifier
来实现LlmProvider
接口的Listenable
方法要求 - 能够将初始历史记录作为构造函数参数传入
- 在有新的用户提示/LLM 响应对时通知侦听器
- 在手动更改历史记录时通知侦听器
- 使用新的历史记录在历史记录更改时创建一个新的聊天
本质上,自定义提供程序管理与底层 LLM 进行的单个聊天会话的历史记录。随着历史记录的变化,底层聊天要么需要自动保持最新(就像 Dart 的 Gemini AI SDK 在你调用底层特定于聊天的方法时那样),要么需要手动重新创建(就像 Gemini 提供程序在每次手动设置历史记录时那样)。
- 消息和附件
必须将附件从 LlmProvider
类型公开的标准 ChatMessage
类映射到底层 LLM 处理的任何内容。例如,Gemini 提供程序将 AI 工具包的 ChatMessage
类映射到 Dart 的 Gemini AI SDK 提供的 Content
类型,如下例所示:
import 'package:google_generative_ai/google_generative_ai.dart';
...
class GeminiProvider extends LlmProvider with ChangeNotifier {
...
static Part _partFrom(Attachment attachment) => switch (attachment) {
(final FileAttachment a) => DataPart(a.mimeType, a.bytes),
(final LinkAttachment a) => FilePart(a.url),
};
static Content _contentFrom(ChatMessage message) => Content(
message.origin.isUser ? 'user' : 'model',
[
TextPart(message.text ?? ''),
...message.attachments.map(_partFrom),
],
);
}
每当需要将用户提示发送到底层 LLM 时,都会调用 _contentFrom
方法。每个提供程序都需要提供自己的映射。
- 调用 LLM
如何调用底层 LLM 来实现 generateStream
和 sendMessageStream
方法取决于它公开的协议。AI 工具包中的 Gemini 提供程序处理配置和历史记录,但对 generateStream
和 sendMessageStream
的调用最终都会调用 Dart 的 Gemini AI SDK 的 API:
class GeminiProvider extends LlmProvider with ChangeNotifier {
...
@override
Stream<String> generateStream(
String prompt, {
Iterable<Attachment> attachments = const [],
}) =>
_generateStream(
prompt: prompt,
attachments: attachments,
contentStreamGenerator: (c) => _model.generateContentStream([c]),
);
@override
Stream<String> sendMessageStream(
String prompt, {
Iterable<Attachment> attachments = const [],
}) async* {
final userMessage = ChatMessage.user(prompt, attachments);
final llmMessage = ChatMessage.llm();
_history.addAll([userMessage, llmMessage]);
final response = _generateStream(
prompt: prompt,
attachments: attachments,
contentStreamGenerator: _chat!.sendMessageStream,
);
yield* response.map((chunk) {
llmMessage.append(chunk);
return chunk;
});
notifyListeners();
}
Stream<String> _generateStream({
required String prompt,
required Iterable<Attachment> attachments,
required Stream<GenerateContentResponse> Function(Content)
contentStreamGenerator,
}) async* {
final content = Content('user', [
TextPart(prompt),
...attachments.map(_partFrom),
]);
final response = contentStreamGenerator(content);
yield* response
.map((chunk) => chunk.text)
.where((text) => text != null)
.cast<String>();
}
@override
Iterable<ChatMessage> get history => _history;
@override
set history(Iterable<ChatMessage> history) {
_history.clear();
_history.addAll(history);
_chat = _startChat(history);
notifyListeners();
}
}
示例
#Gemini 提供程序 和 Vertex 提供程序 的实现几乎相同,并为自定义提供程序提供了一个良好的起点。如果你想查看一个示例提供程序实现,其中所有对底层 LLM 的调用都被剥离,请查看Echo 示例应用程序,它只是将用户的提示和附件格式化为 Markdown 以作为其响应发送回用户。
除非另有说明,否则本网站上的文档反映的是 Flutter 的最新稳定版本。页面最后更新于 2025-01-30。 查看源代码 或 报告问题。