provider: thread-isolated LLM requests + in-package fix
Some checks failed
Deploy (Gitea) / deploy (push) Failing after 2s
Some checks failed
Deploy (Gitea) / deploy (push) Failing after 2s
- Replace bt:with-timeout with thread-per-request + polling loop (bt:with-timeout cannot interrupt blocking SSL reads) - Worker thread makes the HTTP call; main thread polls for result with configurable LLM_REQUEST_TIMEOUT (default 30s) - Returns timeout error after deadline; worker thread finishes naturally - Added (in-package :passepartout) for standalone compilation
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
(in-package :passepartout)
|
||||||
|
|
||||||
(defparameter *provider-configs*
|
(defparameter *provider-configs*
|
||||||
'((:local . (:base-url nil :key-env nil :url-env "LOCAL_BASE_URL" :default-model "llama3"))
|
'((:local . (:base-url nil :key-env nil :url-env "LOCAL_BASE_URL" :default-model "llama3"))
|
||||||
(:openrouter . (:base-url "https://openrouter.ai/api/v1" :key-env "OPENROUTER_API_KEY" :default-model "openrouter/auto"))
|
(:openrouter . (:base-url "https://openrouter.ai/api/v1" :key-env "OPENROUTER_API_KEY" :default-model "openrouter/auto"))
|
||||||
@@ -50,20 +52,33 @@
|
|||||||
(messages . (( (role . "system") (content . ,system-prompt) )
|
(messages . (( (role . "system") (content . ,system-prompt) )
|
||||||
( (role . "user") (content . ,prompt) )))))))
|
( (role . "user") (content . ,prompt) )))))))
|
||||||
(handler-case
|
(handler-case
|
||||||
(bt:with-timeout (timeout)
|
(let* ((result nil)
|
||||||
(let* ((response (dex:post url :headers headers :content body
|
(lock (bt:make-lock "llm-lock"))
|
||||||
:connect-timeout (min 10 timeout)
|
(done nil)
|
||||||
:read-timeout (max 10 (- timeout 5))))
|
(thread (bt:make-thread
|
||||||
(json (cl-json:decode-json-from-string response))
|
(lambda ()
|
||||||
(choices (cdr (assoc :choices json)))
|
(handler-case
|
||||||
(first-choice (car choices))
|
(let* ((response (dex:post url :headers headers :content body
|
||||||
(message (cdr (assoc :message first-choice)))
|
:connect-timeout (min 10 timeout)
|
||||||
(content (cdr (assoc :content message))))
|
:read-timeout (max 10 (- timeout 5))))
|
||||||
(if content
|
(json (cl-json:decode-json-from-string response))
|
||||||
(list :status :success :content content)
|
(choices (cdr (assoc :choices json)))
|
||||||
(list :status :error :message (format nil "~a: No content in response (~s)" provider json)))))
|
(first-choice (car choices))
|
||||||
(bt:timeout ()
|
(message (cdr (assoc :message first-choice)))
|
||||||
(list :status :error :message (format nil "~a: Request timed out after ~d seconds" provider timeout)))
|
(content (cdr (assoc :content message))))
|
||||||
|
(setf result (if content
|
||||||
|
(list :status :success :content content)
|
||||||
|
(list :status :error :message (format nil "~a: No content" provider)))))
|
||||||
|
(error (c)
|
||||||
|
(setf result (list :status :error :message (format nil "~a Error: ~a" provider c)))))
|
||||||
|
(bt:with-lock-held (lock) (setf done t)))
|
||||||
|
:name (format nil "llm-~a" provider))))
|
||||||
|
(loop for waited from 0 below timeout
|
||||||
|
when (bt:with-lock-held (lock) done)
|
||||||
|
do (return result)
|
||||||
|
do (sleep 0.5))
|
||||||
|
(or result
|
||||||
|
(list :status :error :message (format nil "~a: Request timed out after ~d seconds" provider timeout))))
|
||||||
(error (c)
|
(error (c)
|
||||||
(list :status :error :message (format nil "~a Failure: ~a" provider c))))))
|
(list :status :error :message (format nil "~a Failure: ~a" provider c))))))
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ Providers register themselves at boot. No API key? That provider doesn't registe
|
|||||||
|
|
||||||
** Provider registry
|
** Provider registry
|
||||||
#+begin_src lisp
|
#+begin_src lisp
|
||||||
|
(in-package :passepartout)
|
||||||
|
|
||||||
(defparameter *provider-configs*
|
(defparameter *provider-configs*
|
||||||
'((:local . (:base-url nil :key-env nil :url-env "LOCAL_BASE_URL" :default-model "llama3"))
|
'((:local . (:base-url nil :key-env nil :url-env "LOCAL_BASE_URL" :default-model "llama3"))
|
||||||
(:openrouter . (:base-url "https://openrouter.ai/api/v1" :key-env "OPENROUTER_API_KEY" :default-model "openrouter/auto"))
|
(:openrouter . (:base-url "https://openrouter.ai/api/v1" :key-env "OPENROUTER_API_KEY" :default-model "openrouter/auto"))
|
||||||
@@ -78,20 +80,33 @@ Providers register themselves at boot. No API key? That provider doesn't registe
|
|||||||
(messages . (( (role . "system") (content . ,system-prompt) )
|
(messages . (( (role . "system") (content . ,system-prompt) )
|
||||||
( (role . "user") (content . ,prompt) )))))))
|
( (role . "user") (content . ,prompt) )))))))
|
||||||
(handler-case
|
(handler-case
|
||||||
(bt:with-timeout (timeout)
|
(let* ((result nil)
|
||||||
(let* ((response (dex:post url :headers headers :content body
|
(lock (bt:make-lock "llm-lock"))
|
||||||
:connect-timeout (min 10 timeout)
|
(done nil)
|
||||||
:read-timeout (max 10 (- timeout 5))))
|
(thread (bt:make-thread
|
||||||
(json (cl-json:decode-json-from-string response))
|
(lambda ()
|
||||||
(choices (cdr (assoc :choices json)))
|
(handler-case
|
||||||
(first-choice (car choices))
|
(let* ((response (dex:post url :headers headers :content body
|
||||||
(message (cdr (assoc :message first-choice)))
|
:connect-timeout (min 10 timeout)
|
||||||
(content (cdr (assoc :content message))))
|
:read-timeout (max 10 (- timeout 5))))
|
||||||
(if content
|
(json (cl-json:decode-json-from-string response))
|
||||||
(list :status :success :content content)
|
(choices (cdr (assoc :choices json)))
|
||||||
(list :status :error :message (format nil "~a: No content in response (~s)" provider json)))))
|
(first-choice (car choices))
|
||||||
(bt:timeout ()
|
(message (cdr (assoc :message first-choice)))
|
||||||
(list :status :error :message (format nil "~a: Request timed out after ~d seconds" provider timeout)))
|
(content (cdr (assoc :content message))))
|
||||||
|
(setf result (if content
|
||||||
|
(list :status :success :content content)
|
||||||
|
(list :status :error :message (format nil "~a: No content" provider)))))
|
||||||
|
(error (c)
|
||||||
|
(setf result (list :status :error :message (format nil "~a Error: ~a" provider c)))))
|
||||||
|
(bt:with-lock-held (lock) (setf done t)))
|
||||||
|
:name (format nil "llm-~a" provider))))
|
||||||
|
(loop for waited from 0 below timeout
|
||||||
|
when (bt:with-lock-held (lock) done)
|
||||||
|
do (return result)
|
||||||
|
do (sleep 0.5))
|
||||||
|
(or result
|
||||||
|
(list :status :error :message (format nil "~a: Request timed out after ~d seconds" provider timeout))))
|
||||||
(error (c)
|
(error (c)
|
||||||
(list :status :error :message (format nil "~a Failure: ~a" provider c))))))
|
(list :status :error :message (format nil "~a Failure: ~a" provider c))))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|||||||
Reference in New Issue
Block a user