headless_lms_models/
external_courses.rs1use crate::prelude::*;
2use pgvector::Vector;
3use serde::{Deserialize, Serialize};
4use utoipa::ToSchema;
5
6#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)]
7pub struct ExternalCourse {
8 pub id: Uuid,
9 pub created_at: DateTime<Utc>,
10 pub updated_at: DateTime<Utc>,
11 pub deleted_at: Option<DateTime<Utc>>,
12 pub name: String,
13 pub description: Option<String>,
14 pub url: String,
15 #[schema(value_type = Option<Vec<f32>>)]
16 pub name_embedding: Option<Vector>,
17 #[schema(value_type = Option<Vec<f32>>)]
18 pub description_embedding: Option<Vector>,
19}
20
21#[derive(Debug, Serialize)]
22pub struct ExternalCourseOutput {
23 id: Uuid,
24 name: String,
25 description: Option<String>,
26 url: String,
27}
28pub async fn get_external_courses_by_embeddings(
33 conn: &mut PgConnection,
34 keywords: Vec<String>,
35 embeddings: Vec<Vec<f32>>,
36) -> ModelResult<Vec<ExternalCourseOutput>> {
37 let embed_vecs: Vec<Vector> = embeddings.into_iter().map(Vector::from).collect();
38 let res = sqlx::query_as!(
39 ExternalCourseOutput,
40 r#"
41SELECT t.id AS "id!",
42 t.name AS "name!",
43 t.description,
44 t.url AS "url!"
45FROM (
46 SELECT
47 ec.*,
48 LEAST(MIN(name_embedding <#> v.embedding),
49 MIN(description_embedding <#> v.embedding)) AS distance
50 FROM external_courses ec
51 CROSS JOIN unnest($1::vector[]) AS v(embedding)
52 WHERE deleted_at IS NULL
53 GROUP BY id
54 ORDER BY distance ASC
55 LIMIT 5
56) t
57UNION
58SELECT ec.id,
59 ec.name,
60 ec.description,
61 ec.url
62FROM external_courses ec
63CROSS JOIN unnest($2::text[]) AS k(keyword)
64WHERE deleted_at IS NULL
65AND to_tsvector(
66 'english',
67 ec.name || ' ' || coalesce(ec.description, '')
68) @@ websearch_to_tsquery('english', k.keyword)
69
70 "#,
71 &embed_vecs as _,
72 &keywords,
73 )
74 .fetch_all(conn)
75 .await?;
76 Ok(res)
77}