Flutter Firebase集成插件langchain_firebase的使用

Flutter Firebase集成插件langchain_firebase的使用

langchain_firebase 是一个用于在 Flutter 应用中集成 Firebase 的库。本文将详细介绍如何使用 langchain_firebase 插件来实现与 Firebase 的集成。

功能

  • Chat模型
    • ChatFirebaseVertexAI:这是 Vertex AI for Firebase API 的封装,支持 Gemini 模型。

许可证

langchain_firebase 项目受 MIT 许可证保护。

完整示例代码

// 忽略以下规则:公共成员应公开文档,避免打印
// 版权所有 2024 Google LLC
//
// 根据Apache许可证2.0版本("许可证")授权;
// 你不能在遵守许可证的情况下使用此文件。
// 你可以从http://www.apache.org/licenses/LICENSE-2.0获得许可证副本。
//
// 除非适用法律要求或书面同意,否则根据许可证分发的软件
// 是按“原样”分发的,没有任何形式的保证或条件。
// 有关许可证下具体语言的许可和限制,请参阅许可证。

import 'dart:convert';

import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:langchain/langchain.dart';
import 'package:langchain_firebase/langchain_firebase.dart';

void main() async {
  await initFirebase();
  runApp(const GenerativeAISample());
}

Future<void> initFirebase() async {
  await Firebase.initializeApp(
    // 替换为你的Firebase项目配置值
    options: const FirebaseOptions(
      apiKey: 'apiKey',
      appId: 'appId',
      projectId: 'projectId',
      storageBucket: 'storageBucket',
      messagingSenderId: 'messagingSenderId',
    ),
  );
}

class GenerativeAISample extends StatelessWidget {
  const GenerativeAISample({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter + Firebase Vertex AI + LangChain.dart',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          brightness: Brightness.dark,
          seedColor: const Color.fromARGB(255, 171, 222, 244),
        ),
        useMaterial3: true,
      ),
      home: const ChatScreen(
        title: 'Flutter + Firebase Vertex AI + LangChain.dart',
      ),
    );
  }
}

class ChatScreen extends StatefulWidget {
  const ChatScreen({super.key, required this.title});

  final String title;

  [@override](/user/override)
  State<ChatScreen> createState() => _ChatScreenState();
}

class _ChatScreenState extends State<ChatScreen> {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: const ChatWidget(),
    );
  }
}

class ChatWidget extends StatefulWidget {
  const ChatWidget({
    super.key,
  });

  [@override](/user/override)
  State<ChatWidget> createState() => _ChatWidgetState();
}

class _ChatWidgetState extends State<ChatWidget> {
  late final ChatFirebaseVertexAI _model;
  late final RunnableSequence<ChatMessage, ChatResult> _chain;
  late final ConversationBufferMemory _memory;
  late final Tool exchangeRateTool;

  final ScrollController _scrollController = ScrollController();
  final TextEditingController _textController = TextEditingController();
  final FocusNode _textFieldFocus = FocusNode();
  final List<({Image? image, String? text, bool fromUser})> _generatedContent =
      <({Image? image, String? text, bool fromUser})>[];
  bool _loading = false;

  [@override](/user/override)
  void initState() {
    super.initState();
    _memory = ConversationBufferMemory(returnMessages: true);
    exchangeRateTool = Tool.fromFunction(
      name: 'findExchangeRate',
      description: '返回给定日期的货币汇率。',
      inputJsonSchema: {
        'type': 'object',
        'properties': {
          'currencyDate': {
            'type': 'string',
            'description': 'YYYY-MM-DD格式的日期或未指定时间段时的"latest"。',
          },
          'currencyFrom': {
            'type': 'string',
            'description': '要转换的货币代码,如"USD"。',
          },
          'currencyTo': {
            'type': 'string',
            'description': '要转换到的货币代码,如"USD"。',
          },
        },
        'required': ['currencyDate', 'currencyFrom', 'currencyTo'],
      },
      func: (Map<String, Object?> input) async => {
        // 这个假设的API返回JSON数据,例如:
        // {"base":"USD","date":"2024-04-17","rates":{"SEK": 0.091}}
        'date': input['currencyDate'],
        'base': input['currencyFrom'],
        'rates': {input['currencyTo']! as String: 0.091},
      },
    );
    final promptTemplate = ChatPromptTemplate.fromTemplates(const [
      (ChatMessageType.system, '你是一个有用的助手。'),
      (ChatMessageType.messagesPlaceholder, 'history'),
      (ChatMessageType.messagePlaceholder, 'input'),
    ]);
    final baseChain = Runnable.mapInput(
      (ChatMessage input) async => {
        'input': input,
        ...await _memory.loadMemoryVariables(),
      },
    ).pipe(promptTemplate);

    _model = ChatFirebaseVertexAI(
      defaultOptions: ChatFirebaseVertexAIOptions(
        model: 'gemini-1.5-pro',
        tools: [exchangeRateTool],
      ),
      // location: 'us-central1',
    );
    _chain = baseChain.pipe(_model);
  }

  void _scrollDown() {
    WidgetsBinding.instance.addPostFrameCallback(
      (_) async => _scrollController.animateTo(
        _scrollController.position.maxScrollExtent,
        duration: const Duration(milliseconds: 750),
        curve: Curves.easeOutCirc,
      ),
    );
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    final textFieldDecoration = InputDecoration(
      contentPadding: const EdgeInsets.all(15),
      hintText: '输入提示...',
      border: OutlineInputBorder(
        borderRadius: const BorderRadius.all(Radius.circular(14)),
        borderSide: BorderSide(color: Theme.of(context).colorScheme.secondary),
      ),
      focusedBorder: OutlineInputBorder(
        borderRadius: const BorderRadius.all(Radius.circular(14)),
        borderSide: BorderSide(color: Theme.of(context).colorScheme.secondary),
      ),
    );

    return Padding(
      padding: const EdgeInsets.all(8),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Expanded(
            child: ListView.builder(
              controller: _scrollController,
              itemBuilder: (context, idx) {
                final content = _generatedContent[idx];
                return MessageWidget(
                  text: content.text,
                  image: content.image,
                  isFromUser: content.fromUser,
                );
              },
              itemCount: _generatedContent.length,
            ),
          ),
          Padding(
            padding: const EdgeInsets.symmetric(vertical: 25, horizontal: 15),
            child: Row(
              children: [
                Expanded(
                  child: TextField(
                    autofocus: true,
                    focusNode: _textFieldFocus,
                    decoration: textFieldDecoration,
                    controller: _textController,
                    onSubmitted: _sendChatMessage,
                  ),
                ),
                const SizedBox.square(dimension: 15),
                IconButton(
                  tooltip: 'tokenCount 测试',
                  onPressed: !_loading
                      ? () async {
                          await _testCountToken();
                        }
                      : null,
                  icon: Icon(
                    Icons.numbers,
                    color: _loading
                        ? Theme.of(context).colorScheme.secondary
                        : Theme.of(context).colorScheme.primary,
                  ),
                ),
                IconButton(
                  tooltip: '函数调用测试',
                  onPressed: !_loading
                      ? () async {
                          await _testFunctionCalling();
                        }
                      : null,
                  icon: Icon(
                    Icons.functions,
                    color: _loading
                        ? Theme.of(context).colorScheme.secondary
                        : Theme.of(context).colorScheme.primary,
                  ),
                ),
                IconButton(
                  tooltip: '图像提示',
                  onPressed: !_loading
                      ? () async {
                          await _sendImagePrompt(_textController.text);
                        }
                      : null,
                  icon: Icon(
                    Icons.image,
                    color: _loading
                        ? Theme.of(context).colorScheme.secondary
                        : Theme.of(context).colorScheme.primary,
                  ),
                ),
                IconButton(
                  tooltip: '存储提示',
                  onPressed: !_loading
                      ? () async {
                          await _sendStorageUriPrompt(_textController.text);
                        }
                      : null,
                  icon: Icon(
                    Icons.folder,
                    color: _loading
                        ? Theme.of(context).colorScheme.secondary
                        : Theme.of(context).colorScheme.primary,
                  ),
                ),
                if (!_loading)
                  IconButton(
                    onPressed: () async {
                      await _sendChatMessage(_textController.text);
                    },
                    icon: Icon(
                      Icons.send,
                      color: Theme.of(context).colorScheme.primary,
                    ),
                  )
                else
                  const CircularProgressIndicator(),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Future<void> _sendStorageUriPrompt(String message) async {
    setState(() {
      _loading = true;
    });
    try {
      final chatMessage = ChatMessage.human(
        ChatMessageContent.multiModal([
          ChatMessageContent.text(message),
          ChatMessageContent.image(
            mimeType: 'image/jpeg',
            data: 'gs://vertex-ai-example-ef5a2.appspot.com/foodpic.jpg',
          ),
        ]),
      );

      _generatedContent.add((image: null, text: message, fromUser: true));

      final response = await _chain.invoke(chatMessage);
      final text = response.output.content;
      _generatedContent.add((image: null, text: text, fromUser: false));

      if (text.isEmpty) {
        await _showError('API未响应。');
        return;
      } else {
        setState(() {
          _loading = false;
          _scrollDown();
        });
      }
    } catch (e) {
      await _showError(e.toString());
      setState(() {
        _loading = false;
      });
    } finally {
      _textController.clear();
      setState(() {
        _loading = false;
      });
      _textFieldFocus.requestFocus();
    }
  }

  Future<void> _sendImagePrompt(String message) async {
    setState(() {
      _loading = true;
    });
    try {
      final ByteData catBytes = await rootBundle.load('assets/images/cat.jpg');
      final ByteData sconeBytes = await rootBundle.load('assets/images/scones.jpg');
      final chatMessage = ChatMessage.human(
        ChatMessageContent.multiModal([
          ChatMessageContent.text(message),
          ChatMessageContent.image(
            mimeType: 'image/jpeg',
            data: base64Encode(catBytes.buffer.asUint8List()),
          ),
          ChatMessageContent.image(
            mimeType: 'image/jpeg',
            data: base64Encode(sconeBytes.buffer.asUint8List()),
          ),
        ]),
      );

      _generatedContent
        ..add(
          (
            image: Image.asset('assets/images/cat.jpg'),
            text: message,
            fromUser: true,
          ),
        )
        ..add(
          (
            image: Image.asset('assets/images/scones.jpg'),
            text: null,
            fromUser: true,
          ),
        );

      final response = await _chain.invoke(chatMessage);

      final text = response.output.content;
      _generatedContent.add((image: null, text: text, fromUser: false));

      if (text.isEmpty) {
        await _showError('API未响应。');
        return;
      } else {
        await _memory.saveContext(
          inputValues: {'input': chatMessage},
          outputValues: {'output': response.output},
        );
        setState(() {
          _loading = false;
          _scrollDown();
        });
      }
    } catch (e) {
      await _showError(e.toString());
      setState(() {
        _loading = false;
      });
    } finally {
      _textController.clear();
      setState(() {
        _loading = false;
      });
      _textFieldFocus.requestFocus();
    }
  }

  Future<void> _sendChatMessage(String message) async {
    setState(() {
      _textController.clear();
      _loading = true;
    });

    try {
      final chatMessage = ChatMessage.humanText(message);

      _generatedContent.add((image: null, text: message, fromUser: true));
      final response = await _chain.invoke(chatMessage);
      final text = response.output.content;
      _generatedContent.add((image: null, text: text, fromUser: false));

      if (text.isEmpty) {
        await _showError('API未响应。');
        return;
      } else {
        await _memory.saveContext(
          inputValues: {'input': chatMessage},
          outputValues: {'output': response.output},
        );
        setState(() {
          _loading = false;
          _scrollDown();
        });
      }
    } catch (e) {
      await _showError(e.toString());
      setState(() {
        _loading = false;
      });
    } finally {
      _textController.clear();
      setState(() {
        _loading = false;
      });
      _textFieldFocus.requestFocus();
    }
  }

  Future<void> _testFunctionCalling() async {
    setState(() {
      _loading = true;
    });
    final chatMessage = ChatMessage.humanText(
      '50美元等于多少瑞典克朗?',
    );

    // 发送消息到生成模型。
    var response = await _chain.invoke(chatMessage);
    await _memory.saveContext(
      inputValues: {'input': chatMessage},
      outputValues: {'output': response.output},
    );

    final toolCalls = response.output.toolCalls;
    // 当模型响应函数调用时,调用该函数。
    if (toolCalls.isNotEmpty) {
      final toolCall = toolCalls.first;
      final result = switch (toolCall.name) {
        // 将参数转发到假设的API。
        'findExchangeRate' => await exchangeRateTool.invoke(toolCall.arguments),
        // 如果模型尝试调用未声明的函数,则抛出异常。
        _ => throw UnimplementedError(
            '未实现的函数: ${toolCall.name}',
          ),
      };
      // 将响应发送回模型,以便它可以使用结果为用户生成文本。
      final toolMessage = ChatMessage.tool(
        toolCallId: toolCall.id,
        content: jsonEncode(result),
      );

      response = await _chain.invoke(toolMessage);
      await _memory.saveContext(
        inputValues: {'input': chatMessage},
        outputValues: {'output': response.output},
      );
    }
    // 当模型响应非空文本内容时,打印它。
    if (response.output.content.isNotEmpty) {
      _generatedContent
          .add((image: null, text: response.output.content, fromUser: false));
      setState(() {
        _loading = false;
      });
    }
  }

  Future<void> _testCountToken() async {
    setState(() {
      _loading = true;
    });

    const prompt = '讲述一个短故事';
    final response = await _model.countTokens(PromptValue.string(prompt));
    print('token: $response');

    setState(() {
      _loading = false;
    });
  }

  Future<void> _showError(String message) async {
    await showDialog<void>(
      context: context,
      builder: (context) {
        return AlertDialog(
          title: const Text('出错了'),
          content: SingleChildScrollView(
            child: SelectableText(message),
          ),
          actions: [
            TextButton(
              onPressed: () {
                Navigator.of(context).pop();
              },
              child: const Text('确定'),
            ),
          ],
        );
      },
    );
  }
}

class MessageWidget extends StatelessWidget {
  final Image? image;
  final String? text;
  final bool isFromUser;

  const MessageWidget({
    super.key,
    this.image,
    this.text,
    required this.isFromUser,
  });

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment:
          isFromUser ? MainAxisAlignment.end : MainAxisAlignment.start,
      children: [
        Flexible(
          child: Container(
            constraints: const BoxConstraints(maxWidth: 600),
            decoration: BoxDecoration(
              color: isFromUser
                  ? Theme.of(context).colorScheme.primaryContainer
                  : Theme.of(context).colorScheme.surfaceContainerHighest,
              borderRadius: BorderRadius.circular(18),
            ),
            padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 20),
            margin: const EdgeInsets.only(bottom: 8),
            child: Column(
              children: [
                if (text case final text?) MarkdownBody(data: text),
                if (image case final image?) image,
              ],
            ),
          ),
        ),
      ],
    );
  }
}

更多关于Flutter Firebase集成插件langchain_firebase的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter Firebase集成插件langchain_firebase的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


langchain_firebase 是一个用于在 Flutter 应用中集成 Firebase 和 LangChain 的插件。LangChain 是一个用于构建基于语言模型的应用的框架,而 Firebase 提供了后端服务,如身份验证、数据库和存储等。通过 langchain_firebase,你可以在 Flutter 应用中轻松地将这两者结合起来。

集成步骤

  1. 创建 Firebase 项目:

    • 访问 Firebase 控制台 并创建一个新项目。
    • 在 Firebase 控制台中,添加一个 Android 或 iOS 应用,并按照指示下载 google-services.json(Android)或 GoogleService-Info.plist(iOS)文件。
  2. 配置 Flutter 项目:

    • 将下载的 google-services.json 文件放入 android/app 目录。
    • GoogleService-Info.plist 文件放入 ios/Runner 目录。
  3. 添加依赖: 在 pubspec.yaml 文件中添加 langchain_firebase 和其他必要的依赖:

    dependencies:
      flutter:
        sdk: flutter
      firebase_core: latest_version
      firebase_auth: latest_version
      cloud_firestore: latest_version
      langchain_firebase: latest_version
    

    然后运行 flutter pub get 安装依赖。

  4. 初始化 Firebase: 在 main.dart 文件中初始化 Firebase:

    import 'package:firebase_core/firebase_core.dart';
    import 'package:flutter/material.dart';
    
    void main() async {
      WidgetsFlutterBinding.ensureInitialized();
      await Firebase.initializeApp();
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Firebase LangChain',
          home: HomeScreen(),
        );
      }
    }
    
  5. 使用 langchain_firebase: 你可以在应用中使用 langchain_firebase 提供的功能。例如,使用 Firebase 身份验证和 LangChain 进行语言模型交互:

    import 'package:firebase_auth/firebase_auth.dart';
    import 'package:langchain_firebase/langchain_firebase.dart';
    import 'package:flutter/material.dart';
    
    class HomeScreen extends StatefulWidget {
      @override
      _HomeScreenState createState() => _HomeScreenState();
    }
    
    class _HomeScreenState extends State<HomeScreen> {
      final FirebaseAuth _auth = FirebaseAuth.instance;
      final LangChainFirebase _langChain = LangChainFirebase();
    
      String _response = '';
    
      Future<void> _signIn() async {
        try {
          UserCredential userCredential = await _auth.signInAnonymously();
          print('Signed in: ${userCredential.user!.uid}');
        } catch (e) {
          print('Error signing in: $e');
        }
      }
    
      Future<void> _askQuestion() async {
        try {
          String response = await _langChain.askQuestion("What is Flutter?");
          setState(() {
            _response = response;
          });
        } catch (e) {
          print('Error asking question: $e');
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Flutter Firebase LangChain'),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                ElevatedButton(
                  onPressed: _signIn,
                  child: Text('Sign In Anonymously'),
                ),
                ElevatedButton(
                  onPressed: _askQuestion,
                  child: Text('Ask a Question'),
                ),
                Text('Response: $_response'),
              ],
            ),
          ),
        );
      }
    }
回到顶部