把离线AI代理装进口袋里

3 阅读22分钟

本文译自「The Dawn of Offline AI Agents in Your Pocket」,原文链接medium.com/@denisov.sh…,由Sasha Denisov发布于2025年8月21日。

前言

在我之前的文章使用LoRA微调Gemma实现移动端推理中,我详细介绍了如何使用LoRA对Gemma模型进行微调,以实现设备端推理。我们涵盖了理论基础,并准备好了用于移动部署的模型。现在,激动人心的时刻到了——真正开始构建利用这些强大AI功能的移动或Web应用程序。

经过数月对flutter_gemma插件的开发和完善,我已经了解了在移动应用中实现设备端AI的哪些方法有效(哪些无效)。今天,我将分享我关于如何创建用户真正想要使用的、可用于生产环境的AI功能的全部心得。

我们正在进入移动开发的新时代。有了flutter_gemma,你不再只是构建连接到AI服务的应用程序——你创建的是完全自主、多模态的AI代理,它们完全驻留在用户的设备上。

想想这意味着什么。你的应用现在可以:

  • 通过摄像头观察和理解世界,分析图像和文档,无需向云端发送任何数据

  • 推理和思考复杂问题,并将思考过程透明地展示给用户

  • 执行操作通过调用函数和集成设备功能,成为真正的数字助理

  • 随时随地使用——无论是在飞机上、偏远地区,还是仅仅为了保护用户隐私

这不仅仅是为应用添加人工智能功能,而是从根本上重新定义移动应用。教育类应用可以成为理解手写作业的私人辅导老师;辅助功能应用可以成为与视障用户一同观察世界的AI伙伴;效率类应用可以成为不仅理解请求还能执行请求的智能代理。

你将学到什么

读完本文,你将掌握以下技能:

  • flutter_gemma 是什么,以及如何在现有的 Flutter 项目中进行设置

  • 为你的特定用例选择合适的模型

  • 构建能够理解文本和图像的多模态应用

  • 创建可以调用外部函数的 AI 助手

  • 实现“思考模式”来展示 AI 的推理过程

  • 应对移动 AI 特有的挑战(内存、性能、用户体验)

  • 将 AI 应用部署到生产环境

入门:你的第一个 AI 驱动的 Flutter 应用

我的旅程始于一个简单的目标:在 Flutter 应用中直接运行现代 AI 模型,完全离线。我探索了各种方案。有一些强大的工具,例如 llama.cpp,我的朋友 Georgios Soloupis 在他的文章 使用 llama.cpp 在移动设备上运行 Gemma 和 VLM 中出色地演示了它在 Android 上的应用。然而,就我使用 Flutter 的特定需求而言,我发现许多现有的解决方案,包括 llama.cpp 的封装,通常缺乏生产就绪的跨平台应用所需的稳定性和无缝集成。

突破性的进展来自于我发现了Google 的 MediaPipe。它功能强大,并针对设备端任务进行了优化,但有一个主要问题:它实际上并没有对 Flutter 提供官方支持。意识到这一缺陷后,我决定自己来弥补。这就是 flutter_gemma 的由来。

flutter_gemma 是一个 Flutter 插件,它通过 MediaPipe 将 Google 的 Gemma 系列和其他小型语言模型 (SLM) 的强大功能引入到你的应用中,使你能够在 iOS、Android 和 Web 上本地运行它们。自创建以来,它已被许多寻求稳定高效的方式来构建设备端 AI 功能的开发者所采用。

第一次运行它时,你会感觉像魔法一样,但这需要对细节的仔细关注。

完整的最新安装说明始终位于项目的 README.md 中。务必严格按照 iOS、Android 和 Web 平台的具体步骤进行操作。 iOS 的 Podfile 文件中哪怕出现一个小小的错误,或者缺少授权,都可能导致应用无法运行。

如果你遇到任何问题,仓库中包含一个完整的 示例应用,保证可以正常运行。你可以将其作为参考,与自己的配置进行比较。

flutter\_gemma 示例app

模型分发挑战

在深入代码之前,我们先来解决一个显而易见的问题:如何将 1~6GB 的 AI 模型部署到用户的设备上?

我最初接触 flutter_gemma 时,以为这很简单。但我错了。每个人在看完演示后都会问的第一个问题是:“这很酷,但我该如何将模型交付给生产环境的用户呢?”

手动部署方法

从技术上讲,你可以手动将模型部署到设备上进行测试:

  • Android:使用 ADB 推送模型文件:adb push model.task /sdcard/Android/data/your.app.id/files/

  • iOS:使用 Xcode 的设备窗口或 iTunes 文件共享将模型复制到应用的文档目录

  • Web:托管模型文件并提供直接下载链接

但是,让用户在使用你的应用之前手动下载并安装 AI 模型,这难道不是一件好事吗?这可不是我心目中可以投入生产使用的用户体验。想象一下,你跟别人说:“嘿,下载我们的聊天应用!哦,对了,请手动下载这个 3GB 的文件,然后把它放到手机上正确的文件夹里。” :)

嗯,这肯定不会发生。

引入 ModelManager:解决方案

幸运的是,flutter_gemma 插件包含一个强大的 ModelManager 类,它抽象化了模型分发的所有复杂性。它可以自动处理平台特定的文件操作、进度跟踪和错误恢复。

模型管理器提供了三种将模型部署到设备上的主要方法:

  1. 资源打包 — 用于小型模型和开发

  2. 网络下载 — 用于生产环境应用(推荐)

  3. 自定义路径 — 用于高级集成场景

下面我将逐一介绍每种方法及其适用场景:

资源打包方法(仅限有限使用)

Flutter 允许你将资源与应用打包在一起,最初,我认为这就是解决方案:

// Don't do this for large models
await modelManager.installModelFromAsset('assets/models/gemma_1b.task');

这种方法对于不太大的模型非常有效,但存在平台限制:iOS 和 Android 对资源包大小都有严格的限制。 iOS 应用如果大于 4GB,在 App Store 提交时将无法正常处理。Android 也有自己的限制:应用包总大小上限为 4GB,但任何单一设备配置的压缩下载大小限制为 200MB,而传统 APK 的大小上限仅为 100MB。

其实有“正确”的方法!

生产环境方案:智能模型加载

我经过数十个应用的实践,最终完善的解决方案就是我所说的“渐进式模型加载”。工作原理如下:

class ProductionModelManager {
  static const String MODEL_URL = 'https://your-cdn.com/models/qwen25_1_5b.task';
  static const String MODEL_FILENAME = 'qwen25_1_5b.task';
  Future<void> ensureModelReady() async {
    final modelManager = FlutterGemmaPlugin.instance.modelManager;        
    // Check if model is already downloaded
    if (await modelManager.isModelInstalled) {
      return; // We're good to go
    }        
    // Show download UI to user
    await _downloadModelWithProgress();
  }    
  
  Future<void> _downloadModelWithProgress() async {
    final modelManager = FlutterGemmaPlugin.instance.modelManager;        
    // This is where the magic happens - with real-time progress updates
    await modelManager.downloadModelFromNetwork(
      MODEL_URL,
      onProgress: (progress) {
        // progress is a double from 0.0 to 1.0
        final percentage = (progress * 100).toStringAsFixed(1);
        print('Download progress: $percentage%');                
        
        // Update UI with download progress
        setState(() {
          _downloadProgress = progress;
          _downloadStatus = 'Downloading AI model... $percentage%';
        });
      },
    );
  }    
  
  // UI method to show download progress
  void _updateDownloadProgress(double progress) {
    setState(() {
      _downloadProgress = progress;
      if (progress >= 1.0) {
        _downloadStatus = 'AI model ready!';
      } else {
        final percentage = (progress * 100).toStringAsFixed(1);
        _downloadStatus = 'Downloading AI model... $percentage%';
      }
    });
  }
}

为什么网络下载是生产标准

这种方法的主要优势如下:

  1. 应用体积小:你的应用只需几秒即可下载,无需几分钟

  2. 模型更新:无需在应用商店发布即可更新 AI 模型

  3. 基于 LoRA 的动态个性化:通过动态下载小型 LoRA 适配器文件(几兆字节),即可将不同的微调行为立即应用到基础模型

  4. 设备特定优化:根据设备功能提供不同的模型变体

  5. 成本控制:用户只需在需要时下载所需内容

  6. A/B 测试:针对不同的用户群体测试不同的模型

为确保下载快速可靠,该插件使用了 background_downloader 包以优化性能。

首次运行体验

以下是我发现效果最佳的用户流程:

  1. 用户下载你的应用(体积小,下载速度快)

  2. 应用打开后显示引导页面,介绍 AI 功能

  3. 用户点击“启用 AI 功能”

  4. 应用下载模型并显示进度指示器

  5. 用户下载完成后即可立即开始使用 AI 功能

我总结的关键用户体验洞察:始终显示进度 — 用户需要知道正在发生什么,显示文件大小信息 — “1.2GB 中的 3.2MB” 可以帮助用户理解等待时间,妥善处理错误 — 提供重试选项,而不仅仅是错误消息

应用下载模型示例页面

好了,我们已经找到了将模型交付到设备的方法。接下来我们来讨论推理。

理解推理类型:聊天模式 vs. 单次响应模式

flutter_gemma 插件提供了两种不同的 AI 交互模式,每种模式都针对不同的用途进行了优化。选择合适的模式是构建高效应用的关键:单次推理适用于一次性、无状态的请求,而聊天界面适用于有状态的、持续的对话。

单次推理:适用于无状态的单次任务

可以将单次推理理解为每次都向模型的一个全新实例提出问题。模型不会记住过去的交互。这种模式非常适合事务性任务,因为在事务性任务中,不需要从一次请求到下一次请求的上下文信息。

用途:

  • 文本摘要: 向模型提供一篇长文章,并请求其生成简洁的摘要。

  • 数据提取: 分析一段文本,提取特定信息,例如姓名、日期或情感倾向。

  • 简单问答: 回答一个独立的问题,例如“法国的首都是哪里?”

  • 图像分析: 使用具备视觉能力的模型描述单张图像的内容。

以下是如何使用它来概括一段文本:

// 1. Create a model instance
final model = await gemma.createModel(modelType: ModelType.general);

// 2. Create a stateless session
final session = await model.createSession();

// 3. Generate a single, streaming response
final article = "Your long article text goes here...";
final prompt = "Summarize the following article in three sentences: $article";
String summary = '';
await for (final token in session.generateResponseAsync(prompt)) {
  summary += token;
  // Update your UI with the streaming summary in real-time
}

print(summary);

// 4. Clean up the session
await session.close();

聊天界面:用于有状态的对话式 AI

聊天界面旨在构建对话体验。它会自动管理对话历史记录,因此每条新消息都能在之前所有对话的上下文中被理解。你可以使用此模式来构建聊天机器人、助手以及任何需要来回对话的功能。

用途:

  • 客户支持聊天机器人: 在多个回合中协助用户解决问题。

  • AI 导师: 通过持续的、不断发展的对话来帮助用户学习。

  • 多步骤任务执行: 引导用户完成一个流程,其中 AI 需要记住之前的回答。

以下是一个简单的多轮对话示例:

// 1. Create a model and a stateful chat instance
final model = await gemma.createModel(modelType: ModelType.general);
final chat = await model.createChat();

// 2. Send the first user message
await chat.addQuery(Message.text(text: "I'm planning a trip to Japan. What's a good city for a first-time visitor?", isUser: true));

// 3. Stream the AI's response
String aiResponse1 = '';
await for (final response in chat.generateChatResponseAsync()) {
  if (response is TextResponse) {
    aiResponse1 += response.token;
  }
}
print("AI: $aiResponse1"); // e.g., "Tokyo is a great choice..."

// 4. Ask a follow-up question. The AI remembers the context (Japan, Tokyo).
await chat.addQuery(Message.text(text: "What's the best way to get around there?", isUser: true));

// 5. Stream the second response
String aiResponse2 = '';
await for (final response in chat.generateChatResponseAsync()) {
  if (response is TextResponse) {
    aiResponse2 += response.token;
  }
}
print("AI: $aiResponse2"); // e.g., "The subway system in Tokyo is fantastic..."

关键区别很简单:单次推理用于无记忆的事务性请求,而聊天界面用于随着时间的推移构建上下文和关系。

你的第一个 AI 聊天

既然我们已经了解了核心概念,那么了解它们实际应用的最佳方法是查看一个完整的、可运行的示例。这里只提供一个简单的代码片段,我鼓励你探索代码库中包含的示例应用程序。

它包含一个完全实现的简单聊天界面,演示了如何管理模型状态、处理用户输入以及显示来自离线 AI 机器人的流式响应。它是完美的起点和可靠的参考资料。

Gemma 3 1B 聊天功能

你可以在示例应用程序文件夹中找到完整的实现。

我学到的关键经验:始终显示加载状态。模型初始化在移动设备上可能需要 10~30 秒,用户需要知道正在发生什么。

现在你已经了解如何实现聊天功能,接下来需要做出一个关键决定:为你的应用程序选择合适的 AI 模型。

了解模型功能:选择你的 AI 合作伙伴

并非所有模型都一样。经过数十种组合的测试,以下是我对不同应用场景下最佳模型的真实评估:

示例应用中可用的模型

实际模型选择示例

当我们构建 LiveCaptionsXR(一个用于实时字幕的 AI 辅助功能平台)时,我需要快速推理、具备处理屏幕内容的视觉能力以及合理的内存占用。我选择了 Gemma 3n 2B,因为它是满足所有标准(尤其是视觉支持)的最小模型。

关键不在于是否存在单一的“最佳”模型,而在于根据你的主要应用场景进行选择。以下是简要指南:

  • 纯函数调用: 如果你的应用的主要目标是将用户命令转换为操作(例如智能助手),Hammer 2.1 正是为此而设计的。它以极低的开销出色地使用各种工具。

  • 包含函数调用的通用聊天: 如果你需要一个优秀的对话者,并且能够可靠地使用各种工具,Qwen 2.5Gemma 2 都是绝佳的选择。

  • 简单轻量级聊天: 如果你只需要一个功能强大的对话式 AI,而不需要高级功能,Gemma 3 1B 是我的首选。它只有 500MB,下载和初始化速度都非常快。

  • 查看 AI 的推理过程: 如果你想了解模型的思考过程,DeepSeek R1 是唯一支持“思考模式”的选项。

  • 针对高度特定、精细化的任务: 当你的任务非常具体且需要最高效率时,超紧凑的 Gemma 3 270M 是使用 LoRA 进行精细化调整的理想选择。你可以利用其极小的资源占用,创建高度专业化的专家模型。

稍后我会详细讲解一些具体示例的实现。但在那之前,我们需要了解 flutter_gemma 的一些核心概念,这些概念将在每个实现中用到。

核心概念:消息、响应和流式传输

flutter_gemma API 构建于几个核心概念之上,在构建 UI 之前,理解这些概念至关重要。从简单的文本字符串过渡到结构化数据,是解锁插件最强大功能的关键。

消息类型:不仅仅是文本

首先,你发送给模型的每一条信息都会被封装在一个 Message 对象中。这是对话历史记录的基本构建模块。你不再直接发送原始字符串,而是创建一个 Message 对象来描述输入的性质。这种面向对象的方法支持多模态输入和函数调用等高级功能。

以下代码展示了你可以构建的不同类型的消息:

// Text-only message - the most common type
final textMsg = Message.text(text: "Hello AI!", isUser: true);

// Image + text (multimodal) - for vision-capable models
final imageMsg = Message.withImage(
  text: "What's in this image?",
  imageBytes: await getImageBytes(),
  isUser: true,
);

// Tool response - to feed the result of a function call back to the model
final toolMsg = Message.toolResponse(
  toolName: 'get_weather',
  response: {'temperature': 72, 'condition': 'sunny'},
);

// System information - for displaying info in the UI that isn't sent to the model
final systemMsg = Message.systemInfo(text: "Function completed");

// Thinking content - for displaying the model's reasoning process (DeepSeek only)
final thinkingMsg = Message.thinking(text: "Let me analyze this...");

响应类型:理解 AI 输出

正如你的输入是结构化的一样,模型的输出也是结构化的。模型不会仅仅返回一个最终的文本块。相反,它会返回一个 ModelResponse 对象流,其中每个对象代表一种不同的输出类型。模型可能生成纯文本,也可能决定调用你的某个函数。你的应用程序逻辑必须准备好处理来自模型的这些不同的“意图”。

这通常在响应流的 listen 代码块中处理,你可以在其中检查每个传入响应对象的类型并采取相应的操作:

chat.generateChatResponseAsync().listen((response) {
  if (response is TextResponse) {
    // This is a regular text token. Append it to your chat bubble.
    _updateChatBubble(response.token);      
  } else if (response is FunctionCallResponse) {
    // The AI wants to call a function. Execute it now.
    await _handleFunctionCall(response);      
  } else if (response is ThinkingResponse) {
    // The AI is "thinking" (DeepSeek only). Show this in the UI.
    _updateThinkingBubble(response.content);
  }
});

流式处理 vs. 批处理:用户体验差异

这是构建优秀 AI 应用最重要的概念之一。你应该始终流式处理模型的响应,而不是等待生成完整的文本(批处理)。

原因完全在于用户体验。等待完整的响应可能需要几秒钟,这会让应用感觉缓慢、卡顿或“冻结”。相比之下,逐个 token 地流式处理响应会让应用感觉非常快速且交互性强,因为用户可以实时看到 AI 的“输入”。即使总生成时间相同,流式版本的“感知性能”也明显更好。

以下是标准实现方式,你需要累积流式令牌,并在每个新事件发生时更新 UI:

String _accumulatedText = '';void _processStreamingResponse() async {
  await for (final response in chat!.generateChatResponseAsync()) {
    if (response is TextResponse) {
      setState(() {
        _accumulatedText += response.token;
        // Update the last message in your message list with the new accumulated text
        _messages.last = Message.text(text: _accumulatedText);
      });
    }
  }
}

现在我们已经了解了基本概念,接下来让我们通过详细示例来学习如何处理除文本之外的其他数据。

高级功能 #1:多模态 AI(让你的应用拥有视觉能力)

当我的 Flutter 应用实现图像理解功能时,我就知道移动 AI 已经跨越了一个门槛。以下是如何构建能够真正“看”和理解图像的应用。

设置支持视觉的模型

目前只有 Gemma 3 Nano 模型支持视觉功能。设置略有不同:

final model = await _gemma.createModel(
  modelType: ModelType.gemmaIt,
  supportImage: true,

  // Enable vision
  maxNumImages: 1,

  // How many images per message
  maxTokens: 4096,

  // Vision models need more tokens
);

final chat = await model.createChat(
  supportImage: true,
  tokenBuffer: 512,

  // Larger buffer for image processing
);

构建图像分析功能

以下是我用于分析图像的完整实现:​​

class ImageAnalyzer extends StatefulWidget {
  @override
  _ImageAnalyzerState createState() => _ImageAnalyzerState();
}

class _ImageAnalyzerState extends State<ImageAnalyzer> {
  Uint8List? _selectedImage;
  String _analysis = '';
  bool _analyzing = false;  
  Future<void> _pickAndAnalyzeImage() async {
    final picker = ImagePicker();
    final image = await picker.pickImage(source: ImageSource.gallery);        
    if (image != null) {
      final bytes = await image.readAsBytes();
      setState(() {
        _selectedImage = bytes;
        _analyzing = true;
      });            
      await _analyzeImage(bytes);
    }
  }  
  
  Future<void> _analyzeImage(Uint8List imageBytes) async {
    final message = Message.withImage(
      text: "Analyze this image in detail. What do you see?",
      imageBytes: imageBytes,
      isUser: true,
    );        
    await chat!.addQuery(message);        
    String analysis = '';
    await for (final response in chat!.generateChatResponseAsync()) {
      if (response is TextResponse) {
        setState(() {
          analysis += response.token;
          _analysis = analysis;
        });
      }
    }        
    setState(() => _analyzing = false);
  }    
  
  // ... UI implementation
}

Gemma 3n Multimodality

高级功能 #2:函数调用(当 AI 接触现实世界)

函数调用是设备端 AI 真正强大的地方。AI 不仅可以生成文本,还可以执行实际操作——调用 API、更新数据库、控制设备功能。

理解工具和函数

可以将工具视为你向 AI 公开的 API。 AI 会根据用户请求决定何时调用函数:

final List<Tool> appTools = [
  Tool(
    name: 'get_weather',
    description: 'Get current weather for a location',
    parameters: {
      'type': 'object',
      'properties': {
        'location': {
          'type': 'string',
          'description': 'City name or address'
        }
      },
      'required': ['location']
    }
  ),

  Tool(
    name: 'set_reminder',
    description: 'Create a reminder for the user',
    parameters: {
      'type': 'object', 
      'properties': {
        'title': {'type': 'string'},
        'datetime': {'type': 'string', 'format': 'datetime'},
        'priority': {'type': 'string', 'enum': ['low', 'medium', 'high']}
      },
      'required': ['title', 'datetime']
    }
  )
];

实现函数执行

当 AI 需要调用函数时,你需要执行该函数并返回结果:

Future<Map<String, dynamic>> _executeTool(FunctionCallResponse functionCall) async {
  switch (functionCall.name) {
    case 'get_weather':
      final location = functionCall.args['location'] as String;
      return await _getWeatherData(location);          
    case 'set_reminder':
      final title = functionCall.args['title'] as String;
      final datetime = DateTime.parse(functionCall.args['datetime']);
      await _createReminder(title, datetime);
      return {'status': 'success', 'reminder_id': '12345'};          
    default:
      return {'error': 'Unknown function: ${functionCall.name}'};
  }
}

Future<Map<String, dynamic>> _getWeatherData(String location) async {
  // Call actual weather API
  final response = await http.get(
    Uri.parse('https://api.weather.com/v1/current?location=$location')
  );    
  
  if (response.statusCode == 200) {
    final data = jsonDecode(response.body);
    return {
      'temperature': data['temperature'],
      'condition': data['condition'],
      'humidity': data['humidity']
    };
  } else {
    return {'error': 'Failed to get weather data'};
  }
}

Gemma 3n 和函数调用

高级功能 #3:思考模式(观察 AI 的推理过程)

某些模型型号提供的“思考模式”非常吸引人——你可以亲眼目睹 AI 一步步解决问题的过程。这就像拥有一个透明的 AI,可以清晰地展示它的工作原理。

理解思考模式

启用思考模式后,AI 的推理过程与其最终结果将分开记录:

final chat = await model.createChat(
  isThinking: true,
  modelType: ModelType.deepSeek,
  temperature: 0.7,
);

chat.generateChatResponseAsync().listen((response) {
  if (response is ThinkingResponse) {
    // AI is thinking - show reasoning process
    _showThinkingBubble(response.content);      
  } else if (response is TextResponse) {
    // Final answer - show normal response
    _showChatMessage(response.token);
  }
});

构建思考模式 UI 组件

我创建了可展开的“Thinking气泡”,让用户可以窥探 AI 的推理过程:

class ThinkingBubble extends StatefulWidget {
  final String thinkingContent;
  final bool isComplete;    
  
  @override
  _ThinkingBubbleState createState() => _ThinkingBubbleState();
}

class _ThinkingBubbleState extends State<ThinkingBubble> {
  bool _isExpanded = false;  
  
  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.symmetric(vertical: 4),
      decoration: BoxDecoration(
        color: Colors.blue.withOpacity(0.1),
        borderRadius: BorderRadius.circular(12),
        border: Border.all(color: Colors.blue.withOpacity(0.3)),
      ),
      child: Column(
        children: [
          ListTile(
            leading: Icon(Icons.psychology, color: Colors.blue),
            title: Text(
              'AI is thinking...',
              style: TextStyle(color: Colors.blue, fontWeight: FontWeight.w500),
            ),
            trailing: widget.isComplete 
              ? IconButton(
                  icon: Icon(_isExpanded ? Icons.expand_less : Icons.expand_more),
                  onPressed: () => setState(() => _isExpanded = !_isExpanded),
                )
              : SizedBox(
                  width: 16,
                  height: 16,
                  child: CircularProgressIndicator(strokeWidth: 2),
                ),
          ),                    
          
          if (_isExpanded && widget.thinkingContent.isNotEmpty)
            Container(
              padding: EdgeInsets.all(16),
              child: Text(
                widget.thinkingContent,
                style: TextStyle(
                  fontFamily: 'monospace',
                  fontSize: 12,
                  color: Colors.grey[700],
                ),
              ),
            ),
        ],
      ),
    );
  }
}

7.gif

何时使用思考模式

Thinking模式非常适合:

  • 教育类应用 — 学生可以了解 AI 如何解决问题

  • 调试工具 — 开发者可以了解 AI 的问题解决方法

  • 复杂推理任务 — 用户可以在信任结果之前验证 AI 的逻辑

但它也带来了一些问题延迟较高且会消耗更多Token,因此请谨慎使用。

实际应用案例

LiveCaptionsXR:AI 无障碍平台

LiveCaptionsXR 就是一个强大的实际应用案例,它是一个基于 AI 的无障碍平台。该项目源于加州聋人开发者 Craig Merry 的一个想法,我们合作开发了该项目,并参加了 Gemma 3n Challenge。我们的目标是解决听力障碍人士面临的一个关键问题:不仅转录语音,还能通过在 3D 空间渲染字幕来显示说话者的身份和位置。该项目目前正处于积极开发阶段,初始 MVP 版本现已发布。

该应用程序完全在设备端运行,以保护隐私并支持离线使用,它使用多模态 Gemma 模型来处理音频和视觉数据。它还利用 flutter_gemma 的函数调用功能,使用户能够通过自然语言语音命令控制辅助功能,例如字幕大小。你可以在 YouTube 上观看我们的宣传视频。

口袋里的离线增强现实

离线菜单翻译器

我们都遇到过这样的挑战:身处异国他乡,渴望品尝当地美食,却发现餐厅菜单上的语言你看不懂,而且没有 Wi-Fi。这正是设备端多模态人工智能的完美应用场景,正如 Csongor Vogel 在一篇精彩的文章 Using Gemma for Flutter apps 中展示的那样。

借助 flutter_gemma,你可以构建一个使用视觉模型作为个人翻译器的应用。用户只需将摄像头对准菜单即可。模型会处理图像,识别外语文本(例如日语),并将其翻译成用户的语言。关键在于,所有操作都在设备端即时完成,确保应用在任何地方都能正常运行,并且图像始终保存在用户的手机中。

其核心逻辑是将图像连同翻译请求一起发送给模型,如下方简化示例所示。

从左到右:应用加载 Gemma 3N 模型。然后,我们发送一张日文菜单图片进行翻译。最后,应用显示翻译后的菜单

MenuMind:你的智能营养指南

MenuMind 也诞生于 Gemma 3n 挑战赛,它是开发者 Mohamed Abdo 的项目。这款应用将菜单分析的概念更进一步。它不仅提供翻译功能,还能作为智能营养和过敏原指南。

用户可以拍摄菜单照片,多模态人工智能不仅会翻译菜品名称,还会识别潜在的过敏原或提供营养信息。这完美地展现了如何利用模型的推理能力,为有饮食限制的用户提供真正的价值。你可以在 YouTube 上观看该应用的演示视频。

紧急助手:离线急救助手

另一个令人振奋的项目是紧急助手,这是一款可靠的离线急救助手应用。这款应用由 Siddharth Joshi、Vera Austermann 和 Jakub Niemiec 共同开发,旨在为各种紧急情况提供分步指导,涵盖从割伤、烧伤到更严重的伤情。

正如他们在 YouTube 上的演示视频所示,该应用的关键特性在于其可在设备本地运行,即使没有网络连接也能可靠使用。为了确保指导的安全性和准确性,团队利用 LoRA 技术对 Gemma 模型进行了微调,使其能够更好地识别医学术语。

未来路线图和社区

flutter_gemma 插件正在积极发展。以下是即将推出的功能以及你如何参与其中:

即将推出的功能

接下来:

  • 完整的多模态 Web 支持: 在 Web 平台上实现与实体平台相同的图像输入功能。

  • 文本嵌入支持: 添加生成文本嵌入的功能,这是实现设备端搜索的关键第一步。

  • 设备端 RAG 管道: 实现辅助类和示例,用于构建完整的检索增强生成系统,该系统可以查询本地向量数据库。

更远的未来:

  • 桌面支持(macOS、Windows、Linux): 将设备端推理功能引入桌面平台。

  • 音频和视频输入: 扩展多模态功能,使其能够处理音频和视频流。

  • 音频输出(文本转语音): 集成设备端文本转语音功能,使 AI 能够以语音方式响应。

为项目做贡献

我创建 flutter_gemma 是因为我相信设备端 AI 是移动应用的未来。社区的反响非常热烈,我非常希望得到你的帮助,让它变得更好:

贡献方式:

  • 报告问题 — 发现 bug?请在 GitHub 上提交 issue。

  • 修复 bug — 发现你可以解决的未解决问题?欢迎提交 pull request。

  • 实现功能 — 受到路线图的启发?欢迎为新功能做出贡献。

  • 分享示例 — 开发了一些很棒的作品?与社区分享

  • 改进文档 — 帮助新手更轻松地上手

  • 测试新模型 — 试用新模型版本并分享性能数据

GitHub 代码库 github.com/DenisovAV/f…

特别感谢所有为改进本项目做出贡献的开发者,包括 Jhin LeeZemlianikin MaxAhmet TOKCsongor VogelAlex VegnerVinayak Amirtharajand

想了解更多人工智能驱动的 Flutter 内容?关注我,深入探讨移动人工智能、性能优化和实际应用策略。让我们携手共建智能移动应用的未来!

未来比你想象的更近,离线代理是其中的重要组成部分!

本文是我关于移动应用中人工智能实际应用系列文章的一部分。接下来是:“使用 FunctionGemma 进行设备端函数调用”——订阅即可获取最新资讯。

联系作者: GitHub, LinkedIn, Medium, X

欢迎搜索并关注 公众号「稀有猿诉」 获取更多的优质文章!

保护原创,请勿转载!