Proveedores de LLM personalizados
Cómo integrarse con otras características de Flutter.
El protocolo que conecta un LLM y el LlmChatView se expresa en la
interfaz 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);
}
El LLM podría estar en la nube o ser local,
podría estar alojado en Google Cloud Platform
o en algún otro proveedor de nube,
podría ser un LLM propietario o de código abierto.
Cualquier LLM o endpoint similar a un LLM que pueda usarse
para implementar esta interfaz se puede conectar en
la vista de chat como un proveedor de LLM. El AI Toolkit
viene con dos proveedores listos para usar,
los cuales implementan la interfaz LlmProvider
que se requiere para conectar el proveedor en lo siguiente:
- El proveedor de Firebase AI Logic,
que envuelve el paquete
firebase_ai - El proveedor Echo, que es útil como un ejemplo de proveedor mínimo
Implementación
#Para crear tu propio proveedor, necesitas implementar la interfaz LlmProvider
con estas cosas en mente:
Proporcionando soporte de configuración completo
Manejo del historial
Traduciendo mensajes y archivos adjuntos al LLM subyacente
Llamando al LLM subyacente
-
Configuración Para soportar la configurabilidad completa en tu proveedor personalizado, deberías permitir al usuario crear el modelo subyacente y pasarlo como un parámetro, como lo hace MyLlmProvider:
class MyLlmProvider extends LlmProvider ... {
@immutable
MyLlmProvider({
required GenerativeModel model,
...
}) : _model = model,
...
final GenerativeModel _model;
...
}
De esta manera, sin importar qué cambios ocurran en el modelo subyacente en el futuro, todos los controles de configuración estarán disponibles para el usuario de tu proveedor personalizado.
- Historial El historial es una parte importante de cualquier proveedor; no solo el proveedor debe permitir que el historial se manipule directamente, sino que también tiene que notificar a los oyentes cuando cambie. Además, para soportar la serialización y el cambio de parámetros del proveedor, también debe admitir guardar el historial como parte del proceso de construcción.
El proveedor de Firebase maneja esto como se muestra:
class MyLlmProvider extends LlmProvider with ChangeNotifier {
@immutable
MyLlmProvider({
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();
}
...
}
Notarás varias cosas en este código:
- El uso de
ChangeNotifierpara implementar los requisitos del métodoListenablede la interfazLlmProvider - La capacidad de pasar el historial inicial como parámetro del constructor
- Notificar a los oyentes cuando hay un nuevo par de prompt de usuario/respuesta de LLM
- Notificar a los oyentes cuando el historial se cambia manualmente
- Crear un nuevo chat cuando el historial cambia, usando el nuevo historial
Esencialmente, un proveedor personalizado gestiona el historial para una sesión de chat única con el LLM subyacente. A medida que cambia el historial, el chat subyacente debe mantenerse actualizado automáticamente (como lo hace el proveedor de Firebase cuando llamas a los métodos específicos de chat subyacentes) o recrearse manualmente (como lo hace el proveedor de Firebase cada vez que el historial se establece manualmente).
- Mensajes y archivos adjuntos
Los archivos adjuntos deben mapearse desde la clase estándar
ChatMessage expuesta por el tipo LlmProvider
a lo que sea manejado por el LLM subyacente.
Por ejemplo, el proveedor de Firebase mapea desde la
clase ChatMessage del AI Toolkit al
tipo Content proporcionado por el Firebase Logic AI SDK,
como se muestra en el siguiente ejemplo:
import 'package:firebase_ai/firebase_ai.dart';
...
class MyLlmProvider 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),
],
);
}
El método _contentFrom se llama cada vez que se necesita enviar un prompt del usuario al
LLM subyacente. Cada proveedor debe encargarse de su propio mapeo.
- Llamando al LLM
Cómo llamas al LLM subyacente para implementar los
métodos generateStream y sendMessageStream
depende del protocolo que este exponga.
El proveedor de Firebase en el AI Toolkit
maneja la configuración y el historial, pero las llamadas a
generateStream y sendMessageStream terminan
cada una en una llamada a una API de Firebase Logic AI SDK:
class MyLlmProvider 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();
}
}
Ejemplos
#La implementación del proveedor de Firebase proporciona un buen punto de partida para tu propio proveedor personalizado. Si quieres ver un ejemplo de implementación de proveedor con todas las llamadas al LLM subyacente eliminadas, echa un vistazo a la aplicación de ejemplo Echo, que simplemente formatea el prompt y los archivos adjuntos del usuario como Markdown para enviárselos de vuelta al usuario como respuesta.
A menos que se indique lo contrario, la documentación de este sitio refleja Flutter 3.44.0. Página actualizada por última vez el 2026-05-05. Ver código fuente oreportar un problema.