add similar search to api

does a cool thing",
This commit is contained in:
Travis Shears 2026-03-12 09:29:09 +01:00
parent c7bca62df4
commit 2393faf9d0
Signed by: travisshears
GPG key ID: CB9BF1910F3F7469
7 changed files with 74 additions and 2 deletions

View file

@ -1,5 +1,5 @@
meta { meta {
name: get_snippet name: get
type: http type: http
seq: 5 seq: 5
} }

View file

@ -1,5 +1,5 @@
meta { meta {
name: get_snippets name: list
type: http type: http
seq: 4 seq: 4
} }

View file

@ -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"]
}
}

View file

@ -6,6 +6,7 @@
[snippets.use-cases.view] [snippets.use-cases.view]
[snippets.use-cases.delete] [snippets.use-cases.delete]
[snippets.use-cases.create] [snippets.use-cases.create]
[snippets.use-cases.similar-search]
[snippets.use-cases.edit] [snippets.use-cases.edit]
[snippets.infra.config :as config] [snippets.infra.config :as config]
[muuntaja.middleware :as mm] [muuntaja.middleware :as mm]
@ -60,6 +61,14 @@
{:status 200 {:status 200
:body (snippets.use-cases.view/view-snippets-by-tag tag)})) :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}] (defn handle-view-snippet-by-slug [{params :query-params}]
(let [slug (get params "slug") (let [slug (get params "slug")
snippet (snippets.use-cases.view/view-snippet-by-slug slug)] snippet (snippets.use-cases.view/view-snippet-by-slug slug)]
@ -83,6 +92,7 @@
["/tags" {:get handle-view-tags}] ["/tags" {:get handle-view-tags}]
["/tag" {:get handle-view-snippets-by-tag}] ["/tag" {:get handle-view-snippets-by-tag}]
["/snippet-by-slug" {:get handle-view-snippet-by-slug}] ["/snippet-by-slug" {:get handle-view-snippet-by-slug}]
["/similar" {:get handle-view-similar-snippets}]
["/snippets" {:get handle-view-snippets}] ["/snippets" {:get handle-view-snippets}]
["/snippet" {:post handle-create-snippet ["/snippet" {:post handle-create-snippet
:patch handle-edit-snippet :patch handle-edit-snippet

View file

@ -122,6 +122,17 @@
:where [?e :snippet/slug ?slug]]] :where [?e :snippet/slug ?slug]]]
(ffirst (d/q query db 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 (defn- get-snippet-by-slug-from-db
"Get a single snippet by its slug." "Get a single snippet by its slug."
[slug] [slug]

View file

@ -69,3 +69,21 @@
[snippet] [snippet]
(let [embed (get-embed snippet)] (let [embed (get-embed snippet)]
(save-embed snippet embed))) (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] '[]))))

View file

@ -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 %)))))