-
Notifications
You must be signed in to change notification settings - Fork 31
/
llm-request-plz.el
171 lines (146 loc) · 6.65 KB
/
llm-request-plz.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
;;; llm-request-plz.el --- Curl request handling code -*- lexical-binding: t; package-lint-main-file: "llm.el"; -*-
;; Copyright (c) 2023, 2024 Free Software Foundation, Inc.
;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as
;; published by the Free Software Foundation; either version 3 of the
;; License, or (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;; General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; This file provides basic functions for providers who need to request data.
;; It assumes the server is using json.
;;; Code:
(require 'cl-lib)
(require 'json)
(require 'plz-event-source)
(require 'plz-media-type)
(require 'rx)
(require 'url-http)
(defcustom llm-request-plz-timeout nil
"The number of seconds to wait for a response from a HTTP server.
When set to nil, don't timeout while receiving a response.
Request timings are depending on the request. Requests that need
more output may take more time, and there is other processing
besides just token generation that can take a while. Sometimes
the LLM can get stuck, and you don't want it to take too long.
This should be balanced to be good enough for hard requests but
not very long so that we can end stuck requests."
:type 'integer
:group 'llm)
(defcustom llm-request-plz-connect-timeout 10
"The number of seconds to wait for a connection to a HTTP server."
:type 'integer
:group 'llm)
(defun llm-request-success (status)
"Return non-nil if STATUS is a successful HTTP status code."
(<= 200 status 299))
(cl-defun llm-request-plz-sync-raw-output (url &key headers data timeout)
"Make a request to URL. The raw text response will be returned.
HEADERS will be added in the Authorization header, in addition to
standard json header. This is optional.
DATA will be jsonified and sent as the request body.
This is required.
TIMEOUT is the number of seconds to wait for a response."
(condition-case error
(let ((resp (plz-media-type-request
'post url
:as `(media-types ,plz-media-types)
:body (when data
(encode-coding-string (json-encode data) 'utf-8))
:connect-timeout llm-request-plz-connect-timeout
:headers (append headers '(("Content-Type" . "application/json")))
:timeout (or timeout llm-request-plz-timeout))))
(if (llm-request-success (plz-response-status resp))
(plz-response-body resp)
(signal 'plz-http-error resp)))
(plz-error
(seq-let [error-sym message data] error
(cond
((eq 'plz-http-error error-sym)
(let ((response (plz-error-response data)))
(error "LLM request failed with code %d: %s (additional information: %s)"
(plz-response-status response)
(nth 2 (assq (plz-response-status response) url-http-codes))
(plz-response-body response))))
((and (eq 'plz-curl-error error-sym)
(eq 28 (car (plz-error-curl-error data))))
(error "LLM request timed out"))
(t (signal error-sym (list message data))))))))
(cl-defun llm-request-plz-sync (url &key headers data timeout)
"Make a request to URL. The parsed response will be returned.
HEADERS will be added in the Authorization header, in addition to
the standard json header. This is optional.
DATA will be jsonified and sent as the request body.
This is required.
TIMEOUT is the number of seconds to wait for a response."
(llm-request-plz-sync-raw-output url
:headers headers
:data data
:timeout timeout))
(defun llm-request-plz--handle-error (error on-error)
"Handle the ERROR with the ON-ERROR callback."
(cond ((plz-error-curl-error error)
(let ((curl-error (plz-error-curl-error error)))
(funcall on-error 'error
(format "curl error code %d: %s"
(car curl-error)
(cdr curl-error)))))
((plz-error-response error)
(when-let ((response (plz-error-response error))
(status (plz-response-status response))
(body (plz-response-body response)))
(funcall on-error 'error body)))
((plz-error-message error)
(funcall on-error 'error (plz-error-message error)))
(t (user-error "Unexpected error: %s" error))))
(cl-defun llm-request-plz-async (url &key headers data on-success media-type
on-error timeout)
"Make a request to URL.
Nothing will be returned.
HEADERS will be added in the Authorization header, in addition to
standard json header. This is optional.
DATA will be jsonified and sent as the request body.
This is required.
ON-SUCCESS will be called with the response body as a json
object. This is optional in the case that ON-SUCCESS-DATA is set,
and required otherwise.
ON-ERROR will be called with the error code and a response-body.
This is required.
MEDIA-TYPE is an optional argument that adds or overrides a media
type, useful for streaming formats. It is expected that this is
only used by other methods in this file."
(plz-media-type-request
'post url
:as `(media-types ,(if media-type
(cons media-type plz-media-types)
plz-media-types))
:body (when data
(encode-coding-string (json-encode data) 'utf-8))
:connect-timeout llm-request-plz-connect-timeout
:headers (append headers
'(("Content-Type" . "application/json")))
:then (lambda (response)
(when on-success
(funcall on-success (plz-response-body response))))
:else (lambda (error)
(when on-error
(llm-request-plz--handle-error error on-error)))
:timeout (or timeout llm-request-plz-timeout)))
;; This is a useful method for getting out of the request buffer when it's time
;; to make callbacks.
(defun llm-request-plz-callback-in-buffer (buf f &rest args)
"Run F with ARSG in the context of BUF.
But if BUF has been killed, use a temporary buffer instead.
If F is nil, nothing is done."
(when f
(if (buffer-live-p buf)
(with-current-buffer buf (apply f args))
(with-temp-buffer (apply f args)))))
(provide 'llm-request-plz)
;;; llm-request-plz.el ends here