chat.scm 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. ;;; guile-openai --- An OpenAI API client for Guile
  2. ;;; Copyright © 2023 Andrew Whatson <whatson@tailcall.au>
  3. ;;;
  4. ;;; This file is part of guile-openai.
  5. ;;;
  6. ;;; guile-openai is free software: you can redistribute it and/or modify
  7. ;;; it under the terms of the GNU Affero General Public License as
  8. ;;; published by the Free Software Foundation, either version 3 of the
  9. ;;; License, or (at your option) any later version.
  10. ;;;
  11. ;;; guile-openai is distributed in the hope that it will be useful, but
  12. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. ;;; Affero General Public License for more details.
  15. ;;;
  16. ;;; You should have received a copy of the GNU Affero General Public
  17. ;;; License along with guile-openai. If not, see
  18. ;;; <https://www.gnu.org/licenses/>.
  19. (define-module (openai chat)
  20. #:use-module (openai api chat)
  21. #:use-module (openai client)
  22. #:use-module (openai utils colorized)
  23. #:use-module (openai utils stream)
  24. #:use-module (ice-9 match)
  25. #:use-module (json parser)
  26. #:use-module (srfi srfi-9)
  27. #:use-module (srfi srfi-9 gnu)
  28. #:use-module (srfi srfi-41)
  29. #:export (openai-default-chat-model
  30. openai-default-chat-temperature
  31. openai-default-chat-top-p
  32. chat?
  33. chat-content
  34. chat-stream
  35. call?
  36. call-function
  37. call-arguments
  38. call-stream
  39. openai-chat))
  40. (define-once openai-default-chat-model
  41. (make-parameter 'gpt-3.5-turbo))
  42. (define-once openai-default-chat-temperature
  43. (make-parameter *unspecified*))
  44. (define-once openai-default-chat-top-p
  45. (make-parameter *unspecified*))
  46. (define-once openai-default-chat-stream?
  47. (make-parameter #t))
  48. (define-record-type <Chat>
  49. (%make-chat content stream)
  50. chat?
  51. (content %chat-content)
  52. (stream chat-stream))
  53. (define (chat-content chat)
  54. (force (%chat-content chat)))
  55. (define-record-type <Call>
  56. (%make-call function arguments stream)
  57. call?
  58. (function call-function)
  59. (arguments %call-arguments)
  60. (stream call-stream))
  61. (define (call-arguments call)
  62. (force (%call-arguments call)))
  63. (define (make-chat-or-call result ix)
  64. (if (stream? result)
  65. ;; handle streamed responses
  66. (let* ((deltas (responses->delta-stream result ix))
  67. (message (stream-car deltas))
  68. (call (chat-message-function-call message)))
  69. (if (unspecified? call)
  70. ;; make a streamed chat response
  71. (let* ((strm (deltas->content-stream deltas))
  72. (content (delay (string-concatenate (stream->list strm)))))
  73. (%make-chat content strm))
  74. ;; make a streamed function call response
  75. (let* ((func (chat-function-call-name call))
  76. (strm (deltas->argument-stream deltas))
  77. (args (delay (json-string->scm
  78. (string-concatenate (stream->list strm))))))
  79. (%make-call func args strm))))
  80. ;; handle complete responses
  81. (let* ((message (response->message result ix))
  82. (call (chat-message-function-call message)))
  83. (if (unspecified? call)
  84. ;; make a complete chat response
  85. (let ((content (chat-message-content message)))
  86. (%make-chat (delay content) (stream content)))
  87. ;; make a complete function call response
  88. (let* ((func (chat-function-call-name call))
  89. (args (chat-function-call-arguments call)))
  90. (%make-call func (delay args) (stream args)))))))
  91. (define (make-chats-or-calls result n)
  92. (apply values (map (lambda (ix)
  93. (make-chat-or-call result ix))
  94. (iota n))))
  95. (define (response->message response n)
  96. (chat-choice-message
  97. (list-ref (chat-response-choices response) n)))
  98. (define (responses->delta-stream responses n)
  99. (stream-filter-map
  100. (lambda (response)
  101. (let ((choice (car (chat-response-choices response))))
  102. (and (eqv? n (chat-choice-index choice))
  103. (chat-choice-delta choice))))
  104. responses))
  105. (define (deltas->argument-stream deltas)
  106. (stream-filter-map
  107. (lambda (delta)
  108. (let ((call (chat-message-function-call delta)))
  109. (and (not (unspecified? call))
  110. (let ((args (chat-function-call-arguments call)))
  111. (and (string? args) args)))))
  112. deltas))
  113. (define (deltas->content-stream deltas)
  114. (stream-filter-map
  115. (lambda (delta)
  116. (let ((content (chat-message-content delta)))
  117. (and (string? content) content)))
  118. deltas))
  119. (define (print-chat chat port)
  120. (newline port)
  121. (stream-for-each (lambda (content)
  122. (display content port))
  123. (chat-stream chat)))
  124. (define (print-call call port)
  125. (newline port)
  126. (format port "function: ~a\n" (call-function call))
  127. (format port "arguments: ")
  128. (stream-for-each (lambda (arg-part)
  129. (display arg-part port))
  130. (call-stream call)))
  131. (set-record-type-printer! <Chat> print-chat)
  132. (set-record-type-printer! <Call> print-call)
  133. (add-color-scheme! `((,chat? CHAT ,color-stream (GREEN BOLD))))
  134. (add-color-scheme! `((,call? CHAT ,color-stream (GREEN BOLD))))
  135. (define parse-prompt
  136. (match-lambda
  137. ((? null?)
  138. '())
  139. ((? pair? msgs)
  140. (map parse-message msgs))
  141. (msg
  142. (list (parse-message msg)))))
  143. (define parse-message
  144. (match-lambda
  145. ((? string? msg)
  146. (make-chat-message "user" msg))
  147. (((and role (or 'system 'user 'assistant)) . (? string? msg))
  148. (make-chat-message (symbol->string role) msg))))
  149. (define* (openai-chat prompt #:key
  150. (model (openai-default-chat-model))
  151. (functions *unspecified*)
  152. (function-call *unspecified*)
  153. (temperature (openai-default-chat-temperature))
  154. (top-p (openai-default-chat-top-p))
  155. (n *unspecified*)
  156. (stream? (openai-default-chat-stream?))
  157. (stop *unspecified*)
  158. (max-tokens *unspecified*)
  159. (presence-penalty *unspecified*)
  160. (frequency-penalty *unspecified*)
  161. (logit-bias *unspecified*)
  162. (user (openai-default-user)))
  163. "Send a chat completion request. Returns a chat record.
  164. The PROMPT can be a string, which will be sent as a user message.
  165. Alternatively, prompt can be a list of `(role . content)' pairs, where
  166. content is a string and role is a symbol `system', `user', or
  167. `assistant'.
  168. The keyword arguments correspond to the request parameters described
  169. in the chat completion request documentation:
  170. #:n - The number of responses to generate, returned as multiple
  171. values.
  172. #:stream? - Whether to stream the response(s), defaults to `#t'.
  173. #:model - A symbol or string identifying the model to use. Defaults
  174. to `gpt-3.5-turbo', but if you're lucky you might be able to use
  175. `gpt-4' here.
  176. #:temperature - The sampling temperature to use, a number between 0
  177. and 2.
  178. #:top-p - An alternative sampling parameter, a number between 0 and 1.
  179. #:user - An optional username to associate with this request.
  180. The `#:stop', `#:max-tokens', `#:logit-bias', `#:presence-penalty',
  181. `#:frequency-penalty' parameters are implemented but untested."
  182. (let* ((model (if (symbol? model) (symbol->string model) model))
  183. (prompt (parse-prompt prompt))
  184. (stream? (or stream? *unspecified*))
  185. (request (make-chat-request model prompt functions function-call
  186. temperature top-p n stream? stop max-tokens
  187. presence-penalty frequency-penalty logit-bias user))
  188. (response (send-chat-request request)))
  189. (make-chats-or-calls response (if (unspecified? n) 1 n))))