Skip to content

Commit

Permalink
Switch back to types as symbols, support properties correctly
Browse files Browse the repository at this point in the history
  • Loading branch information
ahyatt committed Jan 19, 2025
1 parent 3bcd2b7 commit 7b2d8b7
Show file tree
Hide file tree
Showing 8 changed files with 58 additions and 39 deletions.
6 changes: 3 additions & 3 deletions README.org
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ Some examples:
(llm-chat my-provider (llm-make-chat-prompt
"How many countries are there? Return the result as JSON."
:response-format
'(:type "object" :properties (:num (:type "integer")) :required ["num"])))
'(:type object :properties (:num (:type "integer")) :required ["num"])))
#+end_src

#+RESULTS:
Expand All @@ -171,7 +171,7 @@ Some examples:
(llm-chat my-provider (llm-make-chat-prompt
"Which editor is hard to quit? Return the result as JSON."
:response-format
'(:type "object" :properties (:editor (:enum ["emacs" "vi" "vscode"])
'(:type object :properties (:editor (:enum ["emacs" "vi" "vscode"])
:authors (:type "array" :items (:type "string")))
:required ["editor" "authors"])))
#+end_src
Expand Down Expand Up @@ -223,7 +223,7 @@ The way to call functions is to attach a list of functions to the =tools= slot i
:description "Get the capital of a country."
:args '((:name "country"
:description "The country whose capital to look up."
:type "string"))
:type string))
:async t))))
#+end_src

Expand Down
4 changes: 2 additions & 2 deletions llm-integration-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
:description "Get the capital of a country."
:args '((:name "country"
:description "The country whose capital to look up."
:type "string"))
:type string))
:async t))))

(defconst llm-integration-test-fc-answer
Expand Down Expand Up @@ -299,7 +299,7 @@ else. We really just want to see if it's in the right ballpark."
:response-format
'(:type "object"
:properties
(:cities (:type "array" :items (:type "string")))
(:cities (:type array :items (:type string)))
:required ["cities"])))))
(should (equal
(llm-test-normalize '(:cities ["Lyon" "Marseille" "Paris"]))
Expand Down
2 changes: 1 addition & 1 deletion llm-ollama.el
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ PROVIDER is the llm-ollama provider."
"Return the response format for FORMAT."
(if (eq format 'json)
:json
format))
(llm-provider-utils-convert-to-serializable format)))

(cl-defmethod llm-provider-chat-request ((provider llm-ollama) prompt streaming)
(llm-provider-utils-combine-to-system-prompt prompt llm-ollama-example-prelude)
Expand Down
3 changes: 2 additions & 1 deletion llm-openai.el
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@ PROVIDER is the Open AI provider struct."
:json_schema (:name "response"
:strict t
:schema ,(append
format
(llm-provider-utils-convert-to-serializable
format)
'(:additionalProperties :false))))))

(defun llm-openai--build-model (provider)
Expand Down
18 changes: 13 additions & 5 deletions llm-provider-utils-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,18 @@
(list
;; A required string arg
'(:name "location"
:type "string"
:type string
:description "The city and state, e.g. San Francisco, CA")
;; A string arg with an enum
;; A string arg with an name
'(:name "unit"
:type "string"
:type string
:description "The unit of temperature, either 'celsius' or 'fahrenheit'"
:enum ["celsius" "fahrenheit"]
:optional t)
'(:name "postal_codes"
:type "array"
:type array
:description "Specific postal codes"
:items (:type "string")
:items (:type string)
:optional t)))
(result (llm-provider-utils-openai-arguments args))
(expected
Expand All @@ -59,6 +59,14 @@
:required ["location"])))
(should (equal result expected))))

(ert-deftest llm-provider-utils-convert-to-serializable ()
(should (equal (llm-provider-utils-convert-to-serializable '(:a 1 :b 2))
'(:a 1 :b 2)))
(should (equal (llm-provider-utils-convert-to-serializable '(:a "1" :b foo))
'(:a "1" :b "foo")))
(should (equal (llm-provider-utils-convert-to-serializable '(:inner '(:a foo :b bar)))
'(:inner '(:a "foo" :b "bar")))))

(ert-deftest llm-provider-utils-combine-to-system-prompt ()
(let* ((interaction1 (make-llm-chat-prompt-interaction :role 'user :content "Hello"))
(example1 (cons "Request 1" "Response 1"))
Expand Down
57 changes: 33 additions & 24 deletions llm-provider-utils.el
Original file line number Diff line number Diff line change
Expand Up @@ -505,44 +505,47 @@ If MODEL cannot be found, warn and return DEFAULT, which by default is 4096."
(intern (substring s 1))
sym)))

(defun llm-provider-utils-convert-to-serializable (plist)
"Convert PLIST to a serializable form.
The expectation is that any symbol values will be converted to strings
for plist and any nested plists."
(mapcan (lambda (elem-pair)
(cond ((symbolp (nth 1 elem-pair))
(list (car elem-pair)
(symbol-name (nth 1 elem-pair))))
((consp (nth 1 elem-pair))
(list (car elem-pair)
(llm-provider-utils-convert-to-serializable (nth 1 elem-pair))))
(t elem-pair)))
(seq-partition plist 2)))

(defun llm-provider-utils-openai-arguments (args)
"Convert ARGS to the OpenAI function calling spec.
ARGS is a list of llm argument plists.
Each plist has the structure:
(:name STRING
:type STRING-OR-PLIST
:type SYMBOL
:description STRING
:optional BOOLEAN
:enum LIST-OF-STRINGS
:items (PLIST :type STRING-OR-PLIST))
:properties PLIST
:enum VECTOR
:items (PLIST :type SYMBOL :enum VECTOR :properties PLIST))
:type can be either a simple string (e.g. \"string\") or a JSON Schema
object plist, a direct representation of JSON schema as a plist."
:type is a symbol, one of `string', `number', `boolean', `object', or
`array'."
(let ((properties '())
(required-names '()))
(dolist (arg args)
(let* ((arg-name (plist-get arg :name))
(type-or-schema (let ((raw-type (plist-get arg :type)))
(if (symbolp raw-type)
(symbol-name raw-type)
raw-type)))
(type (symbol-name (plist-get arg :type)))
(description (plist-get arg :description))
(required (not (plist-get arg :optional)))
(enum (plist-get arg :enum))
(items (plist-get arg :items))

;; Build the schema for this argument. If :type is itself a plist
;; with :type inside it, we treat it as a JSON Schema object.
;; Otherwise we just create a simple schema with :type "string"
;; etc.
(schema
(cond
((and (consp type-or-schema)
(plist-get type-or-schema :type))
;; It's a JSON Schema plist, use it as-is
type-or-schema)
(t ;; It's just a string type, or something simple
(list :type type-or-schema)))))
(obj-properties (llm-provider-utils-convert-to-serializable
(plist-get arg :properties)))
(schema (list :type type)))

;; Add :description if present
(when description
Expand All @@ -554,7 +557,12 @@ object plist, a direct representation of JSON schema as a plist."
(setq schema (plist-put schema :enum enum)))

(when items
(setq schema (plist-put schema :items items)))
(setq schema (plist-put schema
:items
(llm-provider-utils-convert-to-serializable items))))

(when obj-properties
(setq schema (plist-put schema :properties obj-properties)))

;; Track required argument names if :required is t
(when required
Expand All @@ -564,7 +572,8 @@ object plist, a direct representation of JSON schema as a plist."

;; Finally, put this schema into the :properties
(setq properties
(plist-put properties (intern (concat ":" arg-name)) schema))))
(plist-put properties (llm-provider-utils--encolon arg-name)
schema))))
;; Build the final spec
(let ((spec `(:type "object" :properties ,properties)))
(when required-names
Expand Down
4 changes: 2 additions & 2 deletions llm-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,8 @@
:tools (list (llm-make-tool-function
:name "func"
:description "desc"
:args '((:name "arg1" :description "desc1" :type "string")
(:name "arg2" :description "desc2" :type "integer" :optional t))))))
:args '((:name "arg1" :description "desc1" :type string)
(:name "arg2" :description "desc2" :type integer :optional t))))))
:openai
(:model "model"
:messages [(:role "user" :content "Hello world")]
Expand Down
3 changes: 2 additions & 1 deletion llm-vertex.el
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,8 @@ nothing to add, in which case it is nil."
"application/json"))
(unless (eq 'json format)
(setq params-plist (plist-put params-plist :response_schema
(llm-chat-prompt-response-format prompt)))))
(llm-provider-utils-convert-to-serializable
(llm-chat-prompt-response-format prompt))))))
(when params-plist
`(:generationConfig ,params-plist))))

Expand Down

0 comments on commit 7b2d8b7

Please sign in to comment.