使用 Flutter 和 OpenAI 构建内容推荐应用程序

向用户推荐相关内容对于保持用户对应用程序的兴趣至关重要。尽管这是我们希望在应用程序中拥有的常见功能,但构建它并不简单。随着矢量数据库和开放人工智能的出现,这种情况发生了变化。今天,我们只需对向量数据库
  • ** 🤟** 一站式轻松构建小程序、Web网站、移动应用  :👉****在线地址********
  • 💅小程序共同学习群,有两百多套小程序源码,可免费培训,一起畅谈摸鱼

使用 Flutter 和 OpenAI 构建内容推荐应用程序

使用 Flutter 和 OpenAI 构建内容推荐应用程序

向用户推荐相关内容对于保持用户对应用程序的兴趣至关重要。尽管这是我们希望在应用程序中拥有的常见功能,但构建它并不简单。随着矢量数据库和开放人工智能的出现,这种情况发生了变化。今天,我们只需对向量数据库进行一次查询,就可以执行高度了解内容上下文的语义搜索。在本文中,我们将介绍如何创建一个 Flutter 观影应用,该应用根据用户正在观看的内容推荐另一部电影。

作为快速免责声明,本文概述了您可以使用向量数据库构建的内容,因此不会深入探讨实现的每个细节。您可以在本文中找到该应用程序的完整代码库,以查找更多详细信息。

为什么要使用矢量数据库来推荐内容

在机器学习中,经常使用将一段内容转换为向量表示的过程,称为嵌入,因为它允许我们从数学上分析语义内容。假设我们有一个引擎,可以创建非常了解数据上下文的嵌入,我们可以查看每个嵌入之间的距离,看看这两个内容是否相似。Open AI 提供了一个训练有素的模型,用于将文本内容转换为嵌入,因此使用它可以让我们创建一个高质量的推荐引擎。

向量数据库有很多选择,但在本文中,我们将使用 Supabase 作为我们的向量数据库,因为我们也想存储非嵌入数据,并且我们希望能够从我们的 Flutter 应用程序中轻松查询它们。

我们将构建什么

我们将构建一个电影列表应用程序。 想想Netflix,除非用户无法实际观看电影。此应用程序的目的是演示如何显示相关内容以保持用户的参与度。

使用 Flutter 和 OpenAI 构建内容推荐应用程序

使用的工具/技术

  • Flutter – 用于创建应用的界面
  • Supabase – 用于在数据库中存储嵌入和其他电影数据
  • Open AI API – 用于将电影数据转换为嵌入
  • TMDB API – 获取电影数据的免费API

创建应用程序

我们首先需要用一些关于电影及其嵌入的数据来填充数据库。为此,我们将使用 Supabase 边缘函数调用 TMDB API 和 Open AI API 来获取电影数据并生成嵌入。一旦我们有了数据,我们就会把它们存储在 Supabase 数据库中,然后从我们的 Flutter 应用程序中查询它们。

使用 Flutter 和 OpenAI 构建内容推荐应用程序

步骤 1:创建表

我们将为这个项目准备一张桌子,那就是电影桌子。Films 表将存储有关每部电影的一些基本信息,例如标题或发行数据,以及嵌入每部电影的概述,以便我们可以对彼此进行向量相似性搜索。

sql
复制代码
-- Enable pgvector extension create extension vector with schema extensions; -- Create table create table public.films ( id integer primary key, title text, overview text, release_date date, backdrop_path text, embedding vector(1536) ); -- Enable row level security alter table public.films enable row level security; -- Create policy to allow anyone to read the films table create policy "Fils are public." on public.films for select using (true);

第 2 步:获取动画数据

获取电影数据相对简单。TMDB API提供了一个易于使用的电影端点,用于查询有关电影的信息,同时提供了广泛的过滤器来缩小查询结果的范围。

我们需要一个后端来安全地调用 API,为此,我们将使用 Supabase Edge Functions。第 2 步到第 4 步将构造此边缘函数代码,完整的代码示例可在此处找到。

以下代码将为我们提供给定年份最受欢迎的 20 部电影。

csharp
复制代码
const searchParams = new URLSearchParams() searchParams.set('sort_by', 'popularity.desc') searchParams.set('page', '1') searchParams.set('language', 'en-US') searchParams.set('primary_release_year', `${year}`) searchParams.set('include_adult', 'false') searchParams.set('include_video', 'false') searchParams.set('region', 'US') searchParams.set('watch_region', 'US') searchParams.set('with_original_language', 'en') const tmdbResponse = await fetch( `https://api.themoviedb.org/3/discover/movie?${searchParams.toString()}`, { method: 'GET', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${tmdbApiKey}`, }, } ) const tmdbJson = await tmdbResponse.json() const tmdbStatus = tmdbResponse.status if (!(200 <= tmdbStatus && tmdbStatus <= 299)) { return returnError({ message: 'Error retrieving data from tmdb API', }) } const films = tmdbJson.results

第 3 步:生成嵌入

我们可以从上一步中获取电影数据,并为每个数据生成嵌入。在这里,我们调用 Open AI Embeddings API 将每部电影的概述转换为嵌入。“概述”包含每个电影的摘要,是创建表示每个电影的嵌入的良好来源。

php
复制代码
const response = await fetch('https://api.openai.com/v1/embeddings', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${openAiApiKey}`, }, body: JSON.stringify({ input: film.overview, model: 'text-embedding-3-small', }), }) const responseData = await response.json() if (responseData.error) { return returnError({ message: `Error obtaining Open API embedding: ${responseData.error.message}`, }) } const embedding = responseData.data[0].embedding

第 4 步:将数据存储在 Supabase 数据库中

一旦我们有了电影数据以及嵌入数据,我们就剩下存储它们的任务了。我们可以在 Supabase 客户端上调用 upsert() 函数来轻松存储数据。

同样,为了简单起见,我在这里省略了很多代码,但您可以在此处找到步骤 2 到步骤 4 的完整边缘函数代码。

php
复制代码
// Code from Step 2 // Get movie data and store them in `films` variable ... for(const film of films) { // Code from Step 3 // Get the embedding and store it in `embeddings` variable filmsWithEmbeddings.push({ id: film.id, title: film.title, overview: film.overview, release_date: film.release_date, backdrop_path: film.backdrop_path, embedding, }) } // Store each movies as well as their embeddings into Supabase database const { error } = await supabase.from('films').upsert(filmsWithEmbeddings)

步骤五:创建数据库函数查询相似影片

为了使用 Supabase 执行向量相似性搜索,我们需要创建一个数据库函数。此数据库函数将采用嵌入和film_id作为其参数。embedding 参数将是用于在数据库中搜索类似电影的嵌入,film_id将用于筛选出正在查询的同一部电影。

此外,我们将在嵌入列上设置 HSNW 索引,以便即使使用大型数据集也能有效地运行查询。

sql
复制代码
-- Set index on embedding column create index on films using hnsw (embedding vector_cosine_ops); -- Create function to find related films create or replace function get_related_film(embedding vector(1536), film_id integer) returns setof films language sql as $$ select * from films where id != film_id order by films.embedding <=> get_related_film.embedding limit 6; $$ security invoker;

第 6 步:创建 Flutter 接口

现在我们已经准备好了后端,我们需要做的就是创建一个界面来显示和查询数据。由于本文的主要重点是演示使用向量进行相似性搜索,因此我不会详细介绍 Flutter 实现的所有细节,但你可以在这里找到完整的代码库。

我们的应用程序将包含以下页面:

  • 主页:应用程序的入口点,并显示电影列表
  • DetailsPage:显示电影及其相关电影的详细信息
css
复制代码
lib/ ├── components/ │ └── film_cell.dart # Component displaying a single movie. ├── models/ │ └── film.dart # A data model representing a single movie. ├── pages/ │ ├── details_page.dart # A page to display the details of a movie and other recommended movies. │ └── home_page.dart # A page to display a list of movies. └── main.dart

components/film_cell.dart 是一个共享组件,用于显示主页和详细信息页面的可点击单元格。models/film.dart 包含表示单个电影的数据模型。

这两个页面如下所示。神奇的事情发生在详细信息页面底部标有“您可能还喜欢:”的部分。我们正在执行矢量相似性搜索,以使用我们之前实现的数据库函数获取与所选电影相似的电影列表。

使用 Flutter 和 OpenAI 构建内容推荐应用程序

以下是主页的代码。这是一个简单的 ListView,带有来自我们 films 表的标准选择查询。这里没什么特别的。

scala
复制代码
import 'package:filmsearch/components/film_cell.dart'; import 'package:filmsearch/main.dart'; import 'package:filmsearch/models/film.dart'; import 'package:flutter/material.dart'; class HomePage extends StatefulWidget { const HomePage({super.key}); @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { final filmsFuture = supabase .from('films') .select<List<Map<String, dynamic>>>() .withConverter<List<Film>>((data) => data.map(Film.fromJson).toList()); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Films'), ), body: FutureBuilder( future: filmsFuture, builder: (context, snapshot) { if (snapshot.hasError) { return Center( child: Text(snapshot.error.toString()), ); } if (!snapshot.hasData) { return const Center(child: CircularProgressIndicator()); } final films = snapshot.data!; return ListView.builder( itemBuilder: (context, index) { final film = films[index]; return FilmCell(film: film); }, itemCount: films.length, ); }), ); } }

在详情页中,我们调用步骤5中创建的get_related_film数据库函数,获取最相关的6部电影并展示出来。

less
复制代码
import 'package:filmsearch/components/film_cell.dart'; import 'package:filmsearch/main.dart'; import 'package:filmsearch/models/film.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; class DetailsPage extends StatefulWidget { const DetailsPage({super.key, required this.film}); final Film film; @override State<DetailsPage> createState() => _DetailsPageState(); } class _DetailsPageState extends State<DetailsPage> { late final Future<List<Film>> relatedFilmsFuture; @override void initState() { super.initState(); // Create a future that calls the get_related_film function to query // related movies. relatedFilmsFuture = supabase.rpc('get_related_film', params: { 'embedding': widget.film.embedding, 'film_id': widget.film.id, }).withConverter<List<Film>>((data) => List<Map<String, dynamic>>.from(data).map(Film.fromJson).toList()); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.film.title), ), body: ListView( children: [ Hero( tag: widget.film.imageUrl, child: Image.network(widget.film.imageUrl), ), Padding( padding: const EdgeInsets.all(8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( DateFormat.yMMMd().format(widget.film.releaseDate), style: const TextStyle(color: Colors.grey), ), const SizedBox(height: 8), Text( widget.film.overview, style: const TextStyle(fontSize: 16), ), const SizedBox(height: 24), const Text( 'You might also like:', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), ], ), ), // Display the list of related movies FutureBuilder<List<Film>>( future: relatedFilmsFuture, builder: (context, snapshot) { if (snapshot.hasError) { return Center( child: Text(snapshot.error.toString()), ); } if (!snapshot.hasData) { return const Center(child: CircularProgressIndicator()); } final films = snapshot.data!; return Wrap( children: films .map((film) => InkWell( onTap: () { Navigator.of(context).push(MaterialPageRoute( builder: (context) => DetailsPage(film: film))); }, child: FractionallySizedBox( widthFactor: 0.5, child: FilmCell( film: film, isHeroEnabled: false, fontSize: 16, ), ), )) .toList(), ); }), ], ), ); } }

我们现在有一个功能正常的相似性推荐系统,该系统由 Open AI 提供支持,内置在我们的 Flutter 应用程序中。今天使用的上下文是电影,但你可以很容易地想象相同的概念也可以应用于其他类型的内容。

总结

在本文中,我们研究了如何拍摄一部电影,并推荐了与所选电影相似的电影列表。这很有效,但我们只有一个样本可以从中获得相似性。如果我们想根据用户过去观看的 10 部电影推荐要观看的电影列表,该怎么办?有多种方法可以解决这样的问题,我希望通读这篇文章能激发你的求知欲来解决这样的问题。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

给TA打赏
共{{data.count}}人
人已打赏
人工智能

Sora 模型:为测试行业带来的智能化变革

2024-5-30 5:39:14

人工智能

世界排名第二的大语言模型,你听说过吗?

2024-5-30 9:39:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索