Flutter 向量计算¶
将文本转换为高维向量(Embedding),实现语义搜索、相似度计算和聚类。
功能预览¶

左侧导航选择 🔗 向量计算 → 输入两段文本 → 计算相似度 → 柱状图可视化
💡 示例:输入 "Flutter 是什么" 和 "Dart 是什么",相似度约为 0.85; 输入 "Flutter 是什么" 和 "苹果是什么",相似度约为 0.15。数值越接近 1 表示语义越相近。
目录¶
基础概念¶
Embedding 将文本映射为高维向量空间中的一个点。语义相近的文本在向量空间中距离更近。
典型应用场景:
- 语义搜索:用查询向量与文档向量做相似度排序
- 聚类分析:对大量文本做主题聚类
- 推荐系统:基于用户兴趣向量推荐相似内容
调用向量 API¶
Future<List<double>> getEmbedding(String text) async {
final response = await http.post(
Uri.parse('${dotenv.env['APINEXUS_BASE_URL']!}/embeddings'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ${dotenv.env['APINEXUS_API_KEY']!}',
},
body: json.encode({
'model': 'text-embedding-3-small',
'input': text,
}),
);
final data = json.decode(response.body);
final embedding = data['data'][0]['embedding'] as List;
return embedding.cast<double>();
}
批量调用(多个文本一次请求):
Future<List<List<double>>> getBatchEmbeddings(
List<String> texts,
) async {
final response = await http.post(
Uri.parse('${dotenv.env['APINEXUS_BASE_URL']!}/embeddings'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ${dotenv.env['APINEXUS_API_KEY']!}',
},
body: json.encode({
'model': 'text-embedding-3-small',
'input': texts,
}),
);
final data = json.decode(response.body);
final results = data['data'] as List;
return [
for (final item in results)
(item['embedding'] as List).cast<double>()
];
}
余弦相似度¶
计算两个向量的余弦相似度,范围 [-1, 1],越接近 1 表示越相似:
double cosineSimilarity(List<double> a, List<double> b) {
assert(a.length == b.length);
double dotProduct = 0;
double normA = 0;
double normB = 0;
for (int i = 0; i < a.length; i++) {
dotProduct += a[i] * b[i];
normA += a[i] * a[i];
normB += b[i] * b[i];
}
return dotProduct / (sqrt(normA) * sqrt(normB));
}
示例:
final helloVec = await getEmbedding('你好');
final hiVec = await getEmbedding('嗨');
final goodbyeVec = await getEmbedding('再见');
cosineSimilarity(helloVec, hiVec); // 接近 1
cosineSimilarity(helloVec, goodbyeVec); // 较低
简单的语义搜索¶
class SemanticSearch {
final Map<String, List<double>> _index = {};
Future<void> addDocuments(List<String> docs) async {
final vectors = await getBatchEmbeddings(docs);
for (int i = 0; i < docs.length; i++) {
_index[docs[i]] = vectors[i];
}
}
List<MapEntry<String, double>> search(String query, {int topK = 5}) {
final queryVec = await getEmbedding(query);
final results = <MapEntry<String, double>>[];
_index.forEach((doc, vec) {
results.add(MapEntry(doc, cosineSimilarity(queryVec, vec)));
});
results.sort((a, b) => b.value.compareTo(a.value));
return results.take(topK).toList();
}
}
常用模型¶
| 模型 | 维度 | 说明 |
|---|---|---|
text-embedding-3-small | 1536 | 轻量快速,适合大多数场景 |
text-embedding-3-large | 3072 | 更精准,计算资源消耗更高 |
text-embedding-ada-002 | 1536 | 兼容老代码 |
dashscope-text-embedding-v3 | 1536 | 中文语义更强 |
bge-m3 | 1024 | 多语言,性价比高 |
完整示例¶
在 Flutter UI 中实现语义搜索:
class SearchPage extends StatefulWidget {
const SearchPage({super.key});
@override
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage> {
final _search = SemanticSearch();
final _controller = TextEditingController();
List<MapEntry<String, double>> _results = [];
bool _loaded = false;
bool _searching = false;
@override
void initState() {
super.initState();
_loadData();
}
Future<void> _loadData() async {
await _search.addDocuments([
'Flutter 是跨平台 UI 框架',
'Dart 支持异步编程 async/await',
'Provider 是轻量级状态管理方案',
'使用 http 包发起网络请求',
'ListView.builder 实现大列表懒加载',
'Navigator 管理页面路由栈',
]);
setState(() => _loaded = true);
}
Future<void> _doSearch() async {
if (!_loaded || _searching || _controller.text.isEmpty) return;
setState(() => _searching = true);
final results = await _search.search(_controller.text);
setState(() {
_results = results;
_searching = false;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('语义搜索')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Row(
children: [
Expanded(
child: TextField(
controller: _controller,
decoration: const InputDecoration(hintText: '搜索关键词'),
),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: _doSearch,
child: Text(_searching ? '搜索中...' : '搜索'),
),
],
),
const SizedBox(height: 16),
Expanded(
child: ListView.builder(
itemCount: _results.length,
itemBuilder: (_, i) => ListTile(
title: Text(_results[i].key),
subtitle: Text('相似度: ${_results[i].value.toStringAsFixed(3)}'),
),
),
),
],
),
),
);
}
}