From 2393faf9d00b4ad56a0aa7d568170d39a4cd4054 Mon Sep 17 00:00:00 2001 From: Travis Shears Date: Thu, 12 Mar 2026 09:29:09 +0100 Subject: [PATCH] add similar search to api does a cool thing", --- .../CodeSnippets/{get_snippet.bru => get.bru} | 2 +- .../{get_snippets.bru => list.bru} | 2 +- bruno/CodeSnippets/similar.bru | 23 +++++++++++++++++++ src/snippets/infra/api.clj | 10 ++++++++ src/snippets/infra/db.clj | 11 +++++++++ src/snippets/infra/text_embed.clj | 18 +++++++++++++++ src/snippets/use_cases/similar_search.clj | 10 ++++++++ 7 files changed, 74 insertions(+), 2 deletions(-) rename bruno/CodeSnippets/{get_snippet.bru => get.bru} (93%) rename bruno/CodeSnippets/{get_snippets.bru => list.bru} (93%) create mode 100644 bruno/CodeSnippets/similar.bru create mode 100644 src/snippets/use_cases/similar_search.clj diff --git a/bruno/CodeSnippets/get_snippet.bru b/bruno/CodeSnippets/get.bru similarity index 93% rename from bruno/CodeSnippets/get_snippet.bru rename to bruno/CodeSnippets/get.bru index 6115b49..fb9ed8b 100644 --- a/bruno/CodeSnippets/get_snippet.bru +++ b/bruno/CodeSnippets/get.bru @@ -1,5 +1,5 @@ meta { - name: get_snippet + name: get type: http seq: 5 } diff --git a/bruno/CodeSnippets/get_snippets.bru b/bruno/CodeSnippets/list.bru similarity index 93% rename from bruno/CodeSnippets/get_snippets.bru rename to bruno/CodeSnippets/list.bru index a04a48a..9728add 100644 --- a/bruno/CodeSnippets/get_snippets.bru +++ b/bruno/CodeSnippets/list.bru @@ -1,5 +1,5 @@ meta { - name: get_snippets + name: list type: http seq: 4 } diff --git a/bruno/CodeSnippets/similar.bru b/bruno/CodeSnippets/similar.bru new file mode 100644 index 0000000..0e11126 --- /dev/null +++ b/bruno/CodeSnippets/similar.bru @@ -0,0 +1,23 @@ +meta { + name: similar + type: http + seq: 10 +} + +get { + url: {{host}}/api/similar?slug=rg-output + body: none + auth: none +} + +params:query { + slug: rg-output +} + +body:json { + { + "title": "Test Snippet", + "markdown": "## Cool Snippet\ndoes a cool thing", + "tags": ["git", "jj"] + } +} diff --git a/src/snippets/infra/api.clj b/src/snippets/infra/api.clj index 1e2622a..9da5f33 100644 --- a/src/snippets/infra/api.clj +++ b/src/snippets/infra/api.clj @@ -6,6 +6,7 @@ [snippets.use-cases.view] [snippets.use-cases.delete] [snippets.use-cases.create] + [snippets.use-cases.similar-search] [snippets.use-cases.edit] [snippets.infra.config :as config] [muuntaja.middleware :as mm] @@ -60,6 +61,14 @@ {:status 200 :body (snippets.use-cases.view/view-snippets-by-tag tag)})) +(defn handle-view-similar-snippets [{params :query-params}] + (let [slug (get params "slug")] + (cond + (nil? slug) {:status 400 + :body "Slug parameter is required"} + :else {:status 200 + :body (snippets.use-cases.similar-search/search-similar slug)}))) + (defn handle-view-snippet-by-slug [{params :query-params}] (let [slug (get params "slug") snippet (snippets.use-cases.view/view-snippet-by-slug slug)] @@ -83,6 +92,7 @@ ["/tags" {:get handle-view-tags}] ["/tag" {:get handle-view-snippets-by-tag}] ["/snippet-by-slug" {:get handle-view-snippet-by-slug}] + ["/similar" {:get handle-view-similar-snippets}] ["/snippets" {:get handle-view-snippets}] ["/snippet" {:post handle-create-snippet :patch handle-edit-snippet diff --git a/src/snippets/infra/db.clj b/src/snippets/infra/db.clj index a0dde8e..9d3b45d 100644 --- a/src/snippets/infra/db.clj +++ b/src/snippets/infra/db.clj @@ -122,6 +122,17 @@ :where [?e :snippet/slug ?slug]]] (ffirst (d/q query db slug)))) +(defn- get-by-db-id-from-db + "Get a snippet by db id" + [id] + (let [conn (get-conn) + db (d/db conn) + entity (d/pull db '[* :snippet] id)] + (if (= (keys entity) [:db/id]) nil entity))) + +(def get-by-db-id + (wrap-snippet-return get-by-db-id-from-db)) + (defn- get-snippet-by-slug-from-db "Get a single snippet by its slug." [slug] diff --git a/src/snippets/infra/text_embed.clj b/src/snippets/infra/text_embed.clj index 17f8e40..9024019 100644 --- a/src/snippets/infra/text_embed.clj +++ b/src/snippets/infra/text_embed.clj @@ -69,3 +69,21 @@ [snippet] (let [embed (get-embed snippet)] (save-embed snippet embed))) + +(defn search + "Search for similar snippet in Qdrant + returns example: [{:id 101155069755600, :version 204, :score 0.85372585}] or []" + [slug] + (let [db-id (db/slug-to-db-id slug) + api-key (:qdrant-api-key (config)) + host (str (:qdrant-host (config)) "/collections/snippets-dev/points/query")] + (t/log! {:level :info :data {:slug slug :db-id db-id}} "Searching Qdrant for similar snippets") + (when (nil? db-id) + (throw (ex-info "Invalid slug" {:slug slug}))) + (-> + (http/post host + {:headers {"api-key" api-key} + :content-type :json + :form-params {:query db-id :limit 3 :score_threshold 0.7} + :as :json}) + (get-in [:body :result :points] '[])))) diff --git a/src/snippets/use_cases/similar_search.clj b/src/snippets/use_cases/similar_search.clj new file mode 100644 index 0000000..2fd4f6d --- /dev/null +++ b/src/snippets/use_cases/similar_search.clj @@ -0,0 +1,10 @@ +(ns snippets.use-cases.similar-search + (:require [snippets.infra.text-embed :as embed] + [taoensso.telemere :as t] + [snippets.infra.db :as db])) + +(defn search-similar + [slug] + (t/log! {:level :info :data {:slug slug}} "Making a similar search by slug") + (->> (embed/search slug) + (map #(hash-map :snippet (db/get-by-db-id (:id %)), :score (:score %)))))