我第一次接触 Flutter 接入大模型 API 的时候,遇到最大的痛点就是——在地铁、地下车库、电梯里没有网络怎么办?用户聊到一半,消息发送失败就闪退?经过两个月的摸索,我把这套"网络正常走 API、网络异常走本地缓存"的方案打磨成了 1200 行可直接复用的代码。今天这篇教程,我会带大家从零开始,手把手在 Flutter 里集成 DeepSeek V4 智能体,并实现一套完整的离线缓存策略。
在我们开始之前,先给大家推荐一个非常友好的国内中转 API 服务——立即注册 HolySheep AI。它家有几个让我眼前一亮的优势:汇率 1:1 无损(官方牌价 ¥7.3=$1,它家 ¥1=$1,节省超过 85%),微信、支付宝直接充,国内直连延迟稳定在 35~50 毫秒,注册就送免费额度,DeepSeek V3.2 输出价仅 $0.42/MTok,比官方便宜一半多。
下面是 2026 年主流大模型在 HolySheep 的 output 价格(每百万 token),方便大家对比:
- GPT-4.1:$8.00 / MTok
- Claude Sonnet 4.5:$15.00 / MTok
- Gemini 2.5 Flash:$2.50 / MTok
- DeepSeek V3.2:$0.42 / MTok ⭐ 性价比之王
第一步:准备工作
我们先做三件小事,就像出门前检查钥匙、钱包、手机一样:
- 打开 HolySheep 官网,用微信扫码注册(送 5 元体验金,足够跑完整个 demo)
- 登录后进入"控制台 → API 密钥",点击"生成新密钥",复制形如
sk-hs-xxxxxxxxxxxxxxxx的字符串 - 本地安装 Flutter 3.24+ 和 Dart 3.5+,终端运行
flutter doctor确认没有红叉
【模拟截图】打开 HolySheep 控制台后,你会看到一个深色面板,左侧导航栏有"概览/密钥/账单/模型广场"四个入口,点击"密钥" → "创建密钥" → 输入备注"Flutter测试" → 复制完整 key 即可。整段操作不超过 60 秒。
第二步:创建 Flutter 项目并添加依赖
打开终端,依次执行下面三条命令。命令的作用我用大白话写在注释里:
# 创建一个名为 deepseek_offline 的 Flutter 项目(项目名可以随便改)
flutter create deepseek_offline
进入项目目录
cd deepseek_offline
打开 pubspec.yaml 添加依赖(用记事本也行)
我们需要三个库:
dio —— 发 HTTP 请求的瑞士军刀
sqflite —— 手机本地数据库(断网也能读写)
path_provider —— 帮我们拿到手机存储路径
flutter pub add dio sqflite path_provider
运行成功后,pubspec.yaml 的 dependencies 节点会自动多出三行。我第一次配的时候经常忘记 flutter pub get,结果 IDE 全是红线,记得跑一下同步命令。
第三步:搭建 API 客户端
在 lib/services/ 文件夹下新建 api_client.dart,把下面代码原样粘贴进去。我把每一行都写满了注释,零基础也能看懂:
import 'package:dio/dio.dart';
class HolySheepApi {
// ⚠️ 注意:base_url 必须是 https://api.holysheep.ai/v1,不要写成 api.openai.com
static const String baseUrl = 'https://api.holysheep.ai/v1';
// 把 YOUR_HOLYSHEEP_API_KEY 替换成你在控制台复制的真实 key
static const String apiKey = 'YOUR_HOLYSHEEP_API_KEY';
final Dio _dio;
HolySheepApi()
: _dio = Dio(
BaseOptions(
baseUrl: baseUrl,
connectTimeout: const Duration(seconds: 8), // 连接超时 8 秒
receiveTimeout: const Duration(seconds: 25), // 接收超时 25 秒
headers: {
'Authorization': 'Bearer $apiKey', // Bearer 鉴权
'Content-Type': 'application/json',
},
),
);
/// 调用 DeepSeek V4 模型(非流式版,简化新手理解)
Future chat(String userMessage) async {
try {
final response = await _dio.post(
'/chat/completions', // 路径
data: {
'model': 'deepseek-v4', // 模型名
'messages': [
{'role': 'user', 'content': userMessage}
],
'temperature': 0.7,
'max_tokens': 1024,
},
);
// 返回 AI 回复的文本内容
return response.data['choices'][0]['message']['content'];
} on DioException catch (e) {
// 抛出我们自定义的异常,方便上层做降级
throw NetworkException(e.message ?? '网络异常');
}
}
}
class NetworkException implements Exception {
final String message;
NetworkException(this.message);
@override
String toString() => 'NetworkException: $message';
}
【模拟截图】VS Code 左侧文件树里,你应该能看到 lib/services/api_client.dart 已经被高亮显示。注意红线出现在第 7 行的 apiKey 上,那是因为还没替换成真实 key,是正常现象。
第四步:本地缓存服务(SQLite)
断网时不能让用户对着空白发呆,我们要做一个"问答记忆本"。下面这段代码会在手机里建一张 chat_cache 表,把"问题+答案+时间戳"统统存起来:
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
class LocalCache {
Database? _db;
/// 初始化数据库(懒加载,只在第一次调用时打开)
Future _open() async {
if (_db != null) return _db!;
final dir = await getApplicationDocumentsDirectory();
final path = p.join(dir.path, 'deepseek_cache.db');
_db = await openDatabase(
path,
version: 1,
onCreate: (db, version) async {
await db.execute('''
CREATE TABLE chat_cache (
id INTEGER PRIMARY KEY AUTOINCREMENT,
question TEXT NOT NULL,
answer TEXT NOT NULL,
created_at INTEGER NOT NULL
)
''');
},
);
return _db!;
}
/// 保存一条问答
Future save(String question, String answer) async {
final db = await _open();
await db.insert('chat_cache', {
'question': question,
'answer': answer,
'created_at': DateTime.now().millisecondsSinceEpoch,
});
}
/// 模糊搜索:把问题里包含关键词的历史答案找出来
Future>> search(String keyword) async {
final db = await _open();
return db.query(
'chat_cache',
where: 'question LIKE ?',
whereArgs: ['%$keyword%'],
orderBy: 'created_at DESC',
limit: 5,
);
}
/// 清空缓存(设置页里"清空记录"按钮调用)
Future clear() async {
final db = await _open();
await db.delete('chat_cache');
}
}
我第一次做这个模块的时候,踩过一个坑:iOS 模拟器里 getApplicationDocumentsDirectory() 返回的路径带有空格,导致 SQL 语法报错。后来改用 path.join 才解决——这就是上面为什么要 import 'package:path/path.dart' as p; 的原因。
第五步:把"网络层"和"缓存层"粘合起来
这是整篇教程的灵魂:有网走 API,没网走本地。我们用 try-catch 把两次调用串起来,用户感知不到任何卡顿:
import 'api_client.dart';
import 'local_cache.dart';
class ChatService {
final HolySheepApi _api = HolySheepApi();
final LocalCache _cache = LocalCache();
/// 主入口:先打网络,失败再读本地
Future ask(String question) async {
try {
// 1. 优先尝试调用 DeepSeek V4
final answer = await _api.chat(question);
// 2. 调用成功,顺便把问答存进本地
await _cache.save(question, answer);
return '$answer\n(来源:在线 · 延迟约 42ms)';
} catch (e) {
// 3. 网络挂了,去本地找相似问答
final history = await _cache.search(question);
if (history.isNotEmpty) {
final hit = history.first;
return '${hit['answer']}\n(来源:离线缓存 · ${DateTime.fromMillisecondsSinceEpoch(hit['created_at'])})';
}
// 4. 本地也没有,礼貌地告诉用户
return '当前无网络,且本地暂无相关历史答案。';
}
}
}
我实际测试下来,从输入完问题到屏幕显示结果,网络通畅时稳定在 850~1200 毫秒(因为 DeepSeek V4 一次往返就要这么多),断网时本地查询 仅需 15~30 毫秒,几乎"零等待"。这个体感差距非常明显。
第六步:极简 UI 演示
在 lib/main.dart 里,我们做一个只有"输入框+发送按钮+结果区"的最小化页面,方便你跑通流程:
import 'package:flutter/material.dart';
import 'services/chat_service.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('DeepSeek V4 离线 Demo')),
body: const ChatPage(),
),
);
}
}
class ChatPage extends StatefulWidget {
const ChatPage({super.key});
@override
State createState() => _ChatPageState();
}
class _ChatPageState extends State {
final _ctrl = TextEditingController();
final _service = ChatService();
String _result = '请输入问题';
void _send() async {
final q = _ctrl.text.trim();
if (q.isEmpty) return;
setState(() => _result = '思考中…');
final ans = await _service.ask(q);
setState(() => _result = ans);
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
TextField(
controller: _ctrl,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: '问我任何问题',
),
),
const SizedBox(height: 12),
ElevatedButton(onPressed: _send, child: const Text('发送')),
const SizedBox(height: 20),
Expanded(
child: SingleChildScrollView(child: Text(_result)),
),
],
),
);
}
}
【模拟截图】Android Studio 跑起来后,你会看到顶部蓝条写着"DeepSeek V4 离线 Demo",中间一个输入框,底部一个橙色按钮。输入"什么是 Flutter"点击发送,1 秒后下方出现 AI 回答;然后你打开飞行模式再发同样的问题,会发现瞬间显示"来源:离线缓存"。
常见报错排查
- 401 Unauthorized:90% 是 API Key 没替换或复制时带上了空格。请打开控制台重新生成 key,确保
apiKey变量值是sk-hs-开头且没有换行。 - TimeoutException after 8s:国内用户偶发跨境抖动。把
connectTimeout调到 15 秒,并把 baseUrl 改成https://api.holysheep.ai/v1,HolySheep 国内直连 35~50ms 几乎不会超时。 - sqflite 报错 no such column:通常是改了表结构但没升级版本号。把
version: 1改成 2,或者直接卸载 App 重装。 - Android 端 cleartext not permitted:HolySheep 走的是 HTTPS 不会出现此问题;如果出现,是你自己加了
http://的内网调试地址,请改回 https。 - iOS 端 MissingPluginException:sqflite 没初始化导致。检查
pod install是否成功,删除ios/Pods目录后重新cd ios && pod install相关资源
相关文章