All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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")




© 2015 - 2024 Weber Informatics LLC | Privacy Policy