com.github.jlangch.venice.cargo-qdrant.venice Maven / Gradle / Ivy
;;;; __ __ _
;;;; \ \ / /__ _ __ (_) ___ ___
;;;; \ \/ / _ \ '_ \| |/ __/ _ \
;;;; \ / __/ | | | | (_| __/
;;;; \/ \___|_| |_|_|\___\___|
;;;;
;;;;
;;;; Copyright 2017-2024 Venice
;;;;
;;;; Licensed under the Apache License, Version 2.0 (the "License");
;;;; you may not use this file except in compliance with the License.
;;;; You may obtain a copy of the License at
;;;;
;;;; http://www.apache.org/licenses/LICENSE-2.0
;;;;
;;;; Unless required by applicable law or agreed to in writing, software
;;;; distributed under the License is distributed on an "AS IS" BASIS,
;;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;;;; See the License for the specific language governing permissions and
;;;; limitations under the License.
;;;; Docker Qdrant cargo testcontainer
;;;; This is just a configuration wrapper on top of the :cargo module to
;;;; simplify using Qdrant testcontainers
;;;; docker run -p 6333:6333 -p 6334:6334 \
;;;; -v /data/qdrant-storage:/qdrant/storage:z \
;;;; qdrant/qdrant
(ns cargo-qdrant)
(load-module :docker)
(load-module :cargo)
(import :com.github.jlangch.venice.ShellException)
;; Qdrant is now accessible through these APIs
;; REST API: localhost:6333
;; Web UI: localhost:6333/dashboard
;; GRPC API: localhost:6334
(defonce internal-rest-port 6333)
(defonce internal-grpc-port 6334)
;; ---------------------------------------------------------------------------
;; Qdrant start & stop container
;; ---------------------------------------------------------------------------
(defn
^{ :arglists '(
"(cargo-qdrant/start cname version storage-dir)"
"(cargo-qdrant/start cname version storage-dir config-file log)"
"(cargo-qdrant/start cname version mapped-rest-port mapped-grpc-port storage-dir config-file log)")
:doc """
Starts a Qdrant container.
Qdrant is vector database often used for LLM embeddings.
Telemetry reporting is disabled by setting the env variable
QDRANT__TELEMETRY_DISABLED to `true`.
Start rules:
* If a container with another version exists for the container
name remove the container and the image
* Pull the image if not yet locally available
* If the container already runs - use it
* If the container is available but does not run - start it `(docker/start ...)`
* If the container is not available - run it `(docker/run ...)`
* Finally check for a successful startup. The container log must
contain the string ".*Qdrant HTTP listening on.*" on the
last line.
Args:
| [![width: 20%]] | [![width: 80%]] |
| cname | A unique container name |
| version | The Qdrant version to use. E.g.: "1.8.3" |
| mapped-rest-port | The published (mapped) Qdrant REST port on the \
host. Defaults to 6333 |
| mapped-grpc-port | The published (mapped) Qdrant GRPC port on the \
host. Defaults to 6334 |
| storage-dir | Directory where Qdrant persists all the data. |
| config-file | An optional custom configuration yaml file |
| log | A log function, may be *nil*. \
E.g: `(fn [s] (println "Qdrant:" s))`|
"""
:examples '(
"""
(do
(load-module :cargo-qdrant ['cargo-qdrant :as 'cq])
;; Run a Qdrant container labeled as "qdrant"
(cq/start "qdrant" "1.8.3" "./qdrant-storage"))
""")
:see-also '(
"cargo-qdrant/stop"
"cargo-qdrant/running?"
"cargo-qdrant/logs") }
start
([cname version storage-dir]
(start cname version storage-dir nil nil))
([cname version storage-dir config-file log]
(start cname
version
internal-rest-port ;; default REST port
internal-grpc-port ;; default GRPC port
storage-dir
config-file
log))
([cname version mapped-rest-port mapped-grpc-port storage-dir config-file log]
{ :pre [(string? cname)
(string? version)
(or (int? mapped-rest-port) (long? mapped-rest-port))
(< 0 mapped-rest-port 65536)
(or (int? mapped-grpc-port) (long? mapped-grpc-port))
(< 0 mapped-grpc-port 65536)
(or (string? storage-dir)
(io/file? storage-dir))
(or (nil? config-file)
(string? config-file)
(io/file? config-file))] }
(when-not (io/exists-dir? storage-dir)
(throw (ex :VncException
"The Qdrant storage directory \"~(io/file-canonical storage-dir)\" does not exist!")))
(when (and (some? config-file) (not (io/exists-file? config-file)))
(throw (ex :VncException
"The Qdrant config file \"~(io/file-canonical config-file)\" does not exist!")))
(let [publish ["~{mapped-rest-port}:~{internal-rest-port}"
"~{mapped-grpc-port}:~{internal-grpc-port}"]
;; QDRANT__LOG_LEVEL=INFO
;; QDRANT__SERVICE__HTTP_PORT=6333
;; QDRANT__SERVICE__ENABLE_TLS=1
;; QDRANT__TLS__CERT=./tls/cert.pem
;; QDRANT__TLS__CERT_TTL=3600
envs ["QDRANT__TELEMETRY_DISABLED=true"]
volumes (if (nil? config-file)
["~{storage-dir}:/qdrant/storage:z"]
["~{storage-dir}:/qdrant/storage:z"
"~{config-file}:/qdrant/config/production.yaml"])
args []
log (or log default-log)
wait-after-start-secs 3
ready-check-max-secs 30]
(log "starting ~{version} as \"~{cname}\" @ port ~{publish} ...")
(cargo/start cname "qdrant/qdrant" version
publish
envs
volumes
args
qdrant-ready?
log
wait-after-start-secs
ready-check-max-secs))))
(defn
^{ :arglists '(
"(cargo-qdrant/stop cname)"
"(cargo-qdrant/stop cname log)")
:doc """
Stops a Qdrant container
Args:
| cname | A unique container name |
"""
:examples '(
"""
(do
(load-module :cargo-qdrant ['cargo-qdrant :as 'cq])
;; Stop the Qdrant container labeled as "qdrant"
(cq/stop "qdrant"))
""")
:see-also '(
"cargo-qdrant/start"
"cargo-qdrant/running?") }
stop
([cname]
(stop cname nil))
([cname log]
{ :pre [(string? cname)] }
(let [log (or log default-log)]
(cargo/stop cname log))))
(defn
^{ :arglists '("(cargo-qdrant/running? cname)")
:doc """
Returns true if a container with the specified name is running.
Args:
| cname | A unique container name |
"""
:examples '(
"""
;; Test if Qdrant container is running
(do
(load-module :cargo-qdrant ['cargo-qdrant :as 'cq])
(cq/running? "qdrant"))
""")
:see-also '(
"cargo-qdrant/logs"
"cargo-qdrant/start",
"cargo-qdrant/stop") }
running? [cname]
{ :pre [(string? cname)] }
(docker/container-running-with-name? cname))
(defn
^{ :arglists '(
"(cargo-qdrant/logs cname)"
"(cargo-qdrant/logs cname lines)")
:doc """
Prints the Qdrant docker container logs
Args:
| cname | A unique container name |
| lines | The number of tail lines |
"""
:examples '(
"""
(do
(load-module :cargo-qdrant ['cargo-qdrant :as 'cq])
(cq/logs "qdrant"))
""",
"""
(do
(load-module :cargo-qdrant ['cargo-qdrant :as 'cq])
(cq/logs "qdrant" 100))
""")
:see-also '(
"cargo-qdrant/start"
"cargo-qdrant/running?") }
logs
([cname]
(logs cname nil))
([cname lines]
{ :pre [(string? cname) (or (nil? lines) (long? lines))] }
(if (nil? lines)
(println (docker/container-logs cname))
(println (docker/container-logs cname :tail lines)))))
;; ---------------------------------------------------------------------------
;; utils
;; ---------------------------------------------------------------------------
(defn default-log [s]
(println "Qdrant:" s))
(defn qdrant-ready? [cname]
(->> (docker/container-logs cname :tail 10)
(str/split-lines)
(map str/trim)
(filter #(match? % #".*Qdrant HTTP listening on [0-9]{4}.*"))
(count)
(pos?)))
(defn dir-empty? [dir]
(zero? (count (io/list-files dir))))
(defn dir-not-empty? [dir]
(pos? (count (io/list-files dir))))
(defn admin-url [port]
"http://localhost:6333/dashboard")