diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a06419..d6c55fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how > Released N/A * Add support for `Audio` requests (#9) +* Implemented `auth-source` and add arguments for `content-type` and `org-id` (#13) ## 0.1.0 > Released N/A diff --git a/README.md b/README.md index 17d8c55..b9b6fce 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,14 @@ You will need to set up your API key before you can use this library. (setq openai-key "[YOUR API KEY]") ``` +Alternatively you can configure a function to retrieve the key from some +external source. A function, `openai-key-auth-source` is provided to retrieve +the key from an auth-source entry under the `:host` key `api.openai.com` + +```elisp +(setq openai-key #'openai-key-auth-source) +``` + For requests that need your user identifier, ```elisp diff --git a/openai-audio.el b/openai-audio.el index 16b0371..f9ed72f 100644 --- a/openai-audio.el +++ b/openai-audio.el @@ -34,7 +34,9 @@ ;;;###autoload (cl-defun openai-audio-create-transcription ( file callback &key + (content-type "application/json") (key openai-key) + org-id (model "whisper-1") prompt response-format @@ -46,16 +48,15 @@ Argument FILE is audio file to transcribe, in one of these formats: mp3, mp4, mpeg, mpga, m4a, wav, or webm. CALLBACK is the execuation after request is made. -Arguments KEY is global options; however, you can overwrite the value by passing -it in. +Arguments CONTENT-TYPE, KEY, and ORG-ID are global options; however, you +can overwrite the value by passing it in. The rest of the arugments are optional, please see OpenAI API reference page for more information. Arguments here refer to MODEL PROMPT, RESPONSE-FORMAT, TEMPERATURE, and LANGUAGE." (openai-request "https://api.openai.com/v1/audio/transcriptions" :type "POST" - :headers `(("Content-Type" . "application/json") - ("Authorization" . ,(concat "Bearer " key))) + :headers (openai--headers content-type key org-id) :data (openai--json-encode `(("model" . ,model) ("file" . ,file) @@ -71,7 +72,9 @@ TEMPERATURE, and LANGUAGE." ;;;###autoload (cl-defun openai-audio-create-translation ( file callback &key + (content-type "application/json") (key openai-key) + org-id (model "whisper-1") prompt response-format @@ -79,18 +82,18 @@ TEMPERATURE, and LANGUAGE." "Send translate audio request. Argument FILE is the audio file to translate, in one of these formats: mp3, mp4, -mpeg, mpga, m4a, wav, or webm. CALLBACK is the execuation after request is made. +mpeg, mpga, m4a, wav, or webm. CALLBACK is the execuation after request is +made. -Arguments KEY is global options; however, you can overwrite the value by passing -it in. +Arguments CONTENT-TYPE, KEY and ORG-ID are global options; however, you +can overwrite the value by passing it in. The rest of the arugments are optional, please see OpenAI API reference page for more information. Arguments here refer to MODEL PROMPT, RESPONSE-FORMAT, and TEMPERATURE." (openai-request "https://api.openai.com/v1/audio/transcriptions" :type "POST" - :headers `(("Content-Type" . "application/json") - ("Authorization" . ,(concat "Bearer " key))) + :headers (openai--headers content-type key org-id) :data (openai--json-encode `(("model" . ,model) ("file" . ,file) diff --git a/openai-chat.el b/openai-chat.el index c2c84e5..b4d586b 100644 --- a/openai-chat.el +++ b/openai-chat.el @@ -34,7 +34,9 @@ ;;;###autoload (cl-defun openai-chat ( messages callback &key + (content-type "application/json") (key openai-key) + org-id (model "gpt-3.5-turbo") temperature top-p @@ -51,16 +53,15 @@ Arguments MESSAGES and CALLBACK are required for this type of request. MESSAGES is the conversation data. CALLBACK is the execuation after request is made. -Arguments KEY and USER are global options; however, you can overwrite the value -by passing it in. +Arguments CONTENT-TYPE, KEY, ORG-ID and USER are global options; however, you +can overwrite the value by passing it in. The rest of the arugments are optional, please see OpenAI API reference page for more information. Arguments here refer to MODEL, TEMPERATURE, TOP-P, N, STREAM, STOP, MAX-TOKENS, PRESENCE-PENALTY, FREQUENCY-PENALTY, and LOGIT-BIAS." (openai-request "https://api.openai.com/v1/chat/completions" :type "POST" - :headers `(("Content-Type" . "application/json") - ("Authorization" . ,(concat "Bearer " key))) + :headers (openai--headers content-type key org-id) :data (openai--json-encode `(("model" . ,model) ("messages" . ,messages) diff --git a/openai-completion.el b/openai-completion.el index 80db56e..58c5bd9 100644 --- a/openai-completion.el +++ b/openai-completion.el @@ -34,7 +34,9 @@ ;;;###autoload (cl-defun openai-completion ( prompt callback &key + (content-type "application/json") (key openai-key) + org-id (model "text-davinci-003") suffix max-tokens @@ -56,8 +58,8 @@ Arguments PROMPT and CALLBACK are required for this type of request. PROMPT is either the question or instruction to OpenAI. CALLBACK is the execuation after request is made. -Arguments KEY and USER are global options; however, you can overwrite the value -by passing it in. +Arguments CONTENT-TYPE, KEY, ORG-ID and USER are global options; however, you +can overwrite the value by passing it in. The rest of the arugments are optional, please see OpenAI API reference page for more information. Arguments here refer to MODEL, SUFFIX, MAX-TOKENS, @@ -65,8 +67,7 @@ TEMPERATURE, TOP-P, N, STREAM, LOGPROBS, ECHO, STOP, PRESENCE-PENALTY, FREQUENCY-PENALTY, BEST-OF, and LOGIT-BIAS." (openai-request "https://api.openai.com/v1/completions" :type "POST" - :headers `(("Content-Type" . "application/json") - ("Authorization" . ,(concat "Bearer " key))) + :headers (openai--headers content-type key org-id) :data (openai--json-encode `(("model" . ,model) ("prompt" . ,prompt) diff --git a/openai-edit.el b/openai-edit.el index ce7b220..a4fa403 100644 --- a/openai-edit.el +++ b/openai-edit.el @@ -34,27 +34,29 @@ (cl-defun openai-edit-create ( input instruction callback &key + (content-type "application/json") (key openai-key) + org-id (model "text-davinci-edit-001") temperature top-p n) - "Creates a new edit for the provided input, instruction, and parameters. + "Create a new edit for the provided input, instruction, and parameters. The INPUT is text to use as a starting point for the edit. The INSTRUCTION that tells the model how to edit the prompt. The argument CALLBACK is execuated after request is made. -Arguments KEY is global options; however, you can overwrite the value by passing -it in. +Arguments CONTENT-TYPE, KEY, and ORG-ID are global options; however, you +can overwrite the value by passing it in. The rest of the arugments are optional, please see OpenAI API reference page -for more information. Arguments here refer to TEMPERATURE, TOP-P, and N." +for more information. Arguments here refer to MODEL, TEMPERATURE, TOP-P, and +N." (openai-request "https://api.openai.com/v1/edits" :type "POST" - :headers `(("Content-Type" . "application/json") - ("Authorization" . ,(concat "Bearer " key))) + :headers (openai--headers content-type key org-id) :data (openai--json-encode `(("model" . ,model) ("input" . ,input) diff --git a/openai-embedding.el b/openai-embedding.el index 1ebd828..87abf9d 100644 --- a/openai-embedding.el +++ b/openai-embedding.el @@ -34,27 +34,28 @@ (cl-defun openai-embedding-create ( input callback &key + (content-type "application/json") (key openai-key) + org-id (model "text-embedding-ada-002") (user openai-user)) - "Creates an embedding vector representing the input text. + "Create an embedding vector representing the input text. INPUT text to get embeddings for, encoded as a string or array of tokens. To get embeddings for multiple inputs in a single request, pass an array of -strings or array of token arrays. Each input must not exceed 8192 tokens in +strings or array of token arrays. Each input must not exceed 8192 tokens in length. The argument CALLBACK is execuated after request is made. -Arguments KEY and USER are global options; however, you can overwrite the value -by passing it in. +Arguments CONTENT-TYPE, KEY, ORG-ID and USER are global options; however, you +can overwrite the value by passing it in. The rest of the arugments are optional, please see OpenAI API reference page for more information. Arguments here refer to MODEL." (openai-request "https://api.openai.com/v1/embeddings" :type "POST" - :headers `(("Content-Type" . "application/json") - ("Authorization" . ,(concat "Bearer " key))) + :headers (openai--headers content-type key org-id) :data (openai--json-encode `(("model" . ,model) ("input" . ,input) diff --git a/openai-engine.el b/openai-engine.el index 759d6bc..ca9c412 100644 --- a/openai-engine.el +++ b/openai-engine.el @@ -36,18 +36,19 @@ (cl-defun openai-engine-list ( callback &key - (key openai-key)) + (content-type "application/json") + (key openai-key) + org-id) "Lists the currently available (non-finetuned) models, and provides basic information about each one such as the owner and availability. The argument CALLBACK is execuated after request is made. -Arguments KEY is global option; however, you can overwrite the value by passing -it in." +Arguments CONTENT-TYPE, KEY, ORG-ID and USER are global options; however, you +can overwrite the value by passing it in." (openai-request "https://api.openai.com/v1/engines" :type "GET" - :headers `(("Content-Type" . "application/json") - ("Authorization" . ,(concat "Bearer " key))) + :headers (openai--headers content-type key org-id) :parser 'json-read :complete (cl-function (lambda (&key data &allow-other-keys) @@ -55,7 +56,9 @@ it in." (cl-defun openai-engine-retrieve ( engine-id callback &key - (key openai-key)) + (content-type "application/json") + (key openai-key) + org-id) "Retrieves a model instance, providing basic information about it such as the owner and availability. @@ -63,12 +66,11 @@ The argument ENGINE-ID is the engine to use for this request. The argument CALLBACK is execuated after request is made. -Arguments KEY is global option; however, you can overwrite the value by passing -it in." +Arguments CONTENT-TYPE, KEY, ORG-ID and USER are global options; however, you +can overwrite the value by passing it in." (openai-request (format "https://api.openai.com/v1/engines/%s" engine-id) :type "GET" - :headers `(("Content-Type" . "application/json") - ("Authorization" . ,(concat "Bearer " key))) + :headers (openai--headers content-type key org-id) :parser 'json-read :complete (cl-function (lambda (&key data &allow-other-keys) diff --git a/openai-file.el b/openai-file.el index 9844460..a7f6f0e 100644 --- a/openai-file.el +++ b/openai-file.el @@ -34,17 +34,18 @@ (cl-defun openai-file-list ( callback &key - (key openai-key)) + (content-type "application/json") + (key openai-key) + org-id) "Return a list of files that belong to the user's organization. The argument CALLBACK is execuated after request is made. -Arguments KEY is global option; however, you can overwrite the value by passing -it in." +Arguments CONTENT-TYPE, KEY, and ORG-ID are global options; however, you +can overwrite the value by passing it in." (openai-request "https://api.openai.com/v1/files" :type "GET" - :headers `(("Content-Type" . "application/json") - ("Authorization" . ,(concat "Bearer " key))) + :headers (openai--headers content-type key org-id) :parser 'json-read :complete (cl-function (lambda (&key data &allow-other-keys) @@ -52,7 +53,9 @@ it in." (cl-defun openai-file-upload ( file purpose callback &key - (key openai-key)) + (content-type "application/json") + (key openai-key) + org-id) "Upload a file that contain document(s) to be used across various endpoints/features. @@ -68,12 +71,11 @@ uploaded file. Argument CALLBACK is function with data pass in. -Arguments KEY is global option; however, you can overwrite the value by passing -it in." +Arguments CONTENT-TYPE, KEY, and ORG-ID are global options; however, you +can overwrite the value by passing it in." (openai-request "https://api.openai.com/v1/files" :type "POST" - :headers `(("Content-Type" . "application/json") - ("Authorization" . ,(concat "Bearer " key))) + :headers (openai--headers content-type key org-id) :data (openai--json-encode `(("file" . ,file) ("purpose" . ,purpose))) @@ -84,19 +86,20 @@ it in." (cl-defun openai-file-delete ( file-id callback &key - (key openai-key)) + (content-type "application/json") + (key openai-key) + org-id) "Delete a file. The arument FILE-ID is id of the file to use for this request. Argument CALLBACK is function with data pass in. -Arguments KEY is global option; however, you can overwrite the value by passing -it in." +Arguments CONTENT-TYPE, KEY, and ORG-ID are global options; however, you +can overwrite the value by passing it in." (openai-request "https://api.openai.com/v1/files" :type "DELETE" - :headers `(("Content-Type" . "application/json") - ("Authorization" . ,(concat "Bearer " key))) + :headers (openai--headers content-type key org-id) :data (openai--json-encode `(("file_id" . ,file-id))) :parser 'json-read @@ -106,19 +109,20 @@ it in." (cl-defun openai-file-retrieve ( file-id callback &key - (key openai-key)) + (content-type "application/json") + (key openai-key) + org-id) "Return information about a specific file. The arument FILE-ID is id of the file to use for this request. The argument CALLBACK is execuated after request is made. -Arguments KEY is global option; however, you can overwrite the value by passing -it in." +Arguments CONTENT-TYPE, KEY, and ORG-ID are global options; however, you +can overwrite the value by passing it in." (openai-request (format "https://api.openai.com/v1/files/%s" file-id) :type "GET" - :headers `(("Content-Type" . "application/json") - ("Authorization" . ,(concat "Bearer " key))) + :headers (openai--headers content-type key org-id) :data (openai--json-encode `(("file_id" . ,file-id))) :parser 'json-read @@ -128,19 +132,21 @@ it in." (cl-defun openai-file-retrieve-content ( file-id callback &key - (key openai-key)) + (content-type "application/json") + (key openai-key) + org-id) "Return the contents of the specified file The arument FILE-ID is id of the file to use for this request. The argument CALLBACK is execuated after request is made. -Arguments KEY is global option; however, you can overwrite the value by passing -it in." + +Arguments CONTENT-TYPE, KEY, and ORG-ID are global options; however, you +can overwrite the value by passing it in." (openai-request (format "https://api.openai.com/v1/files/%s/content" file-id) :type "GET" - :headers `(("Content-Type" . "application/json") - ("Authorization" . ,(concat "Bearer " key))) + :headers (openai--headers content-type key org-id) :data (openai--json-encode `(("file_id" . ,file-id))) :parser 'json-read diff --git a/openai-fine-tune.el b/openai-fine-tune.el index a426a41..42b704b 100644 --- a/openai-fine-tune.el +++ b/openai-fine-tune.el @@ -33,7 +33,9 @@ (cl-defun openai-fine-tune-create ( training-file callback &key + (content-type "application/json") (key openai-key) + org-id (model "curie") validation-file n-epochs @@ -45,7 +47,7 @@ classification-positive-class classification-betas suffix) - "Creates a job that fine-tunes a specified model from a given dataset. + "Create a job that fine-tunes a specified model from a given dataset. Response includes details of the enqueued job including job status and the name of the fine-tuned models once complete. @@ -55,8 +57,8 @@ data. The argument CALLBACK is execuated after request is made. -Arguments KEY is global option; however, you can overwrite the value by passing -it in. +Arguments CONTENT-TYPE, KEY, and ORG-ID are global options; however, you +can overwrite the value by passing it in. The rest of the arugments are optional, please see OpenAI API reference page for more information. Arguments here refer to MODEL, VALIDATION-FILE, N-EPOCHS, @@ -65,8 +67,7 @@ COMPUTE-CLASSIFICATION-METRICS, CLASSIFICATION-N-CLASSES, CLASSIFICATION-POSITIVE-CLASS, CLASSIFICATION-BETAS, and SUFFIX" (openai-request "https://api.openai.com/v1/fine-tunes" :type "POST" - :headers `(("Content-Type" . "application/json") - ("Authorization" . ,(concat "Bearer " key))) + :headers (openai--headers content-type key org-id) :data (openai--json-encode `(("model" . ,model) ("training_file" . ,training-file) @@ -87,14 +88,18 @@ CLASSIFICATION-POSITIVE-CLASS, CLASSIFICATION-BETAS, and SUFFIX" (cl-defun openai-fine-tune-list ( callback &key - (key openai-key)) + (content-type "application/json") + (key openai-key) + org-id) "List your organization's fine-tuning jobs. -The argument CALLBACK is execuated after request is made." +The argument CALLBACK is execuated after request is made. + +Arguments CONTENT-TYPE, KEY, and ORG-ID are global options; however, you +can overwrite the value by passing it in." (openai-request "https://api.openai.com/v1/fine-tunes" :type "GET" - :headers `(("Content-Type" . "application/json") - ("Authorization" . ,(concat "Bearer " key))) + :headers (openai--headers content-type key org-id) :parser 'json-read :complete (cl-function (lambda (&key data &allow-other-keys) @@ -102,16 +107,20 @@ The argument CALLBACK is execuated after request is made." (cl-defun openai-fine-tune-retrieve ( fine-tune-id callback &key - (key openai-key)) - "Gets info about the fine-tune job. + (content-type "application/json") + (key openai-key) + org-id) + "Get info about the fine-tune job. The FINE-TUNE-ID of the fine-tune job. -The argument CALLBACK is execuated after request is made." +The argument CALLBACK is execuated after request is made. + +Arguments CONTENT-TYPE, KEY, and ORG-ID are global options; however, you +can overwrite the value by passing it in." (openai-request (format "https://api.openai.com/v1/fine-tunes/%s" fine-tune-id) :type "GET" - :headers `(("Content-Type" . "application/json") - ("Authorization" . ,(concat "Bearer " key))) + :headers (openai--headers content-type key org-id) :parser 'json-read :complete (cl-function (lambda (&key data &allow-other-keys) @@ -119,16 +128,20 @@ The argument CALLBACK is execuated after request is made." (cl-defun openai-fine-tune-cancel ( fine-tune-id callback &key - (key openai-key)) + (content-type "application/json") + (key openai-key) + org-id) "Immediately cancel a fine-tune job. The FINE-TUNE-ID of the fine-tune job to cancel. -The argument CALLBACK is execuated after request is made." +The argument CALLBACK is execuated after request is made. + +Arguments CONTENT-TYPE, KEY, and ORG-ID are global options; however, you +can overwrite the value by passing it in." (openai-request (format "https://api.openai.com/v1/fine-tunes/%s/cancel" fine-tune-id) :type "POST" - :headers `(("Content-Type" . "application/json") - ("Authorization" . ,(concat "Bearer " key))) + :headers (openai--headers content-type key org-id) :parser 'json-read :complete (cl-function (lambda (&key data &allow-other-keys) @@ -136,16 +149,20 @@ The argument CALLBACK is execuated after request is made." (cl-defun openai-fine-tune-list-events ( fine-tune-id callback &key - (key openai-key)) - "Get fine-grained status updates for a fine-tune job. + (content-type "application/json") + (key openai-key) + org-id) + "Get fine-grained status update for a fine-tune job. The FINE-TUNE-ID of the fine-tune job to get events for. -The argument CALLBACK is execuated after request is made." +The argument CALLBACK is execuated after request is made. + +Arguments CONTENT-TYPE, KEY, and ORG-ID are global options; however, you +can overwrite the value by passing it in." (openai-request (format "https://api.openai.com/v1/fine-tunes/%s/events" fine-tune-id) :type "GET" - :headers `(("Content-Type" . "application/json") - ("Authorization" . ,(concat "Bearer " key))) + :headers (openai--headers content-type key org-id) :parser 'json-read :complete (cl-function (lambda (&key data &allow-other-keys) @@ -153,16 +170,20 @@ The argument CALLBACK is execuated after request is made." (cl-defun openai-fine-tune-delete ( model callback &key - (key openai-key)) + (content-type "application/json") + (key openai-key) + org-id) "Delete a fine-tuned model. You must have the Owner role in your organization. The MODEL to delete. -The argument CALLBACK is execuated after request is made." +The argument CALLBACK is execuated after request is made. + +Arguments CONTENT-TYPE, KEY, and ORG-ID are global options; however, you +can overwrite the value by passing it in." (openai-request (format "https://api.openai.com/v1/models/%s" model) :type "DELETE" - :headers `(("Content-Type" . "application/json") - ("Authorization" . ,(concat "Bearer " key))) + :headers (openai--headers content-type key org-id) :parser 'json-read :complete (cl-function (lambda (&key data &allow-other-keys) diff --git a/openai-image.el b/openai-image.el index 2f7a95d..ae17ac0 100644 --- a/openai-image.el +++ b/openai-image.el @@ -33,26 +33,27 @@ (cl-defun openai-image ( prompt callback &key + (content-type "application/json") (key openai-key) + org-id n size response-format (user openai-user)) - "Creates an image given a PROMPT. + "Create an image given a PROMPT. Arguments PROMPT and CALLBACK are required for this type of request. PROMPT is either the question or instruction to OpenAI. CALLBACK is the execuation after request is made. -Arguments KEY and USER are global options; however, you can overwrite the value -by passing it in. +Arguments CONTENT-TYPE, KEY, ORG-ID and USER are global options; however, you +can overwrite the value by passing it in. The rest of the arugments are optional, please see OpenAI API reference page for more information. Arguments here refer to N, SIZE, and RESPONSE-FORMAT." (openai-request "https://api.openai.com/v1/images/generations" :type "POST" - :headers `(("Content-Type" . "application/json") - ("Authorization" . ,(concat "Bearer " key))) + :headers (openai--headers content-type key org-id) :data (openai--json-encode `(("prompt" . ,prompt) ("n" . ,n) @@ -66,27 +67,29 @@ for more information. Arguments here refer to N, SIZE, and RESPONSE-FORMAT." (cl-defun openai-image-edit ( image prompt callback &key + content-type (key openai-key) + org-id mask n size response-format (user openai-user)) - "Creates an edited or extended image given an original IMAGE and a PROMPT. + "Create an edited or extended image given an original IMAGE and a PROMPT. Arguments IMAGE, PROMPT and CALLBACK are required for this type of request. PROMPT is a text description of the desired image(s). IMAGE is the image file to edit. CALLBACK is the execuation after request is made. -Arguments KEY and USER are global options; however, you can overwrite the value -by passing it in. +Arguments CONTENT-TYPE, KEY, ORG-ID and USER are global options; however, you +can overwrite the value by passing it in. The rest of the arugments are optional, please see OpenAI API reference page for more information. Arguments here refer to MASK, N, SIZE, and RESPONSE-FORMAT." (openai-request "https://api.openai.com/v1/images/edits" :type "POST" - :headers `(("Authorization" . ,(concat "Bearer " key))) + :headers (openai--headers content-type key org-id) :data (openai--json-encode `(("image" . ,image) ("prompt" . ,prompt) @@ -102,26 +105,28 @@ RESPONSE-FORMAT." (cl-defun openai-image-variation ( image callback &key + content-type (key openai-key) + org-id mask n size response-format (user openai-user)) - "Creates a variation of a given IMAGE. + "Create a variation of a given IMAGE. Argument CALLBACK is function with data pass in, and the argument IMAGE must be a valid PNG file, less than 4MB, and square. -Arguments KEY and USER are global options; however, you can overwrite the value -by passing it in. +Arguments CONTENT-TYPE, KEY, ORG-ID and USER are global options; however, you +can overwrite the value by passing it in. The rest of the arugments are optional, please see OpenAI API reference page for more information. Arguments here refer to MASK, N, SIZE, and RESPONSE-FORMAT." (openai-request "https://api.openai.com/v1/images/variations" :type "POST" - :headers `(("Authorization" . ,(concat "Bearer " key))) + :headers (openai--headers content-type key org-id) :data (openai--json-encode `(("image" . ,image) ("mask" . ,mask) diff --git a/openai-model.el b/openai-model.el index 14533f0..866be95 100644 --- a/openai-model.el +++ b/openai-model.el @@ -31,15 +31,16 @@ (cl-defun openai-models ( callback &key - (key openai-key)) + (content-type "application/json") + (key openai-key) + org-id) "Return models data and execute the CALLBACK. -Arguments KEY is global options; however, you can overwrite the value by passing -it in." +Arguments CONTENT-TYPE, KEY, and ORG-ID are global options; however, you +can overwrite the value by passing it in." (openai-request "https://api.openai.com/v1/models" :type "GET" - :headers `(("Content-Type" . "application/json") - ("Authorization" . ,(concat "Bearer " key))) + :headers (openai--headers content-type key org-id) :parser 'json-read :complete (cl-function (lambda (&key data &allow-other-keys) @@ -47,15 +48,17 @@ it in." (cl-defun openai-model ( model callback &key - (key openai-key)) + (content-type "application/json") + (key openai-key) + org-id) "Return MODEL data and execute the CALLBACK. -Arguments KEY is global options; however, you can overwrite the value by passing -it in." + +Arguments CONTENT-TYPE, KEY, and ORG-ID are global options; however, you +can overwrite the value by passing it in." (openai-request (format "https://api.openai.com/v1/models/%s" model) :type "GET" - :headers `(("Content-Type" . "application/json") - ("Authorization" . ,(concat "Bearer " key))) + :headers (openai--headers content-type key org-id) :parser 'json-read :complete (cl-function (lambda (&key data &allow-other-keys) diff --git a/openai-moderation.el b/openai-moderation.el index ce5b6d1..88c651a 100644 --- a/openai-moderation.el +++ b/openai-moderation.el @@ -34,7 +34,9 @@ (cl-defun openai-moderation-create ( input callback &key + (content-type "application/json") (key openai-key) + org-id (model "text-moderation-latest")) "Classifies if text violates OpenAI's Content Policy. @@ -42,12 +44,14 @@ Argument INPUT is the text to classify. The argument CALLBACK is execuated after request is made. -Arguments KEY is global options; however, you can overwrite the value by passing -it in." +Arguments CONTENT-TYPE, KEY, and ORG-ID are global options; however, you +can overwrite the value by passing it in. + +The rest of the arugments are optional, please see OpenAI API reference page +for more information. Arguments here refer to MODEL." (openai-request "https://api.openai.com/v1/embeddings" :type "POST" - :headers `(("Content-Type" . "application/json") - ("Authorization" . ,(concat "Bearer " key))) + :headers (openai--headers content-type key org-id) :data (openai--json-encode `(("model" . ,model) ("input" . ,input))) diff --git a/openai.el b/openai.el index b50cd95..f418a1e 100644 --- a/openai.el +++ b/openai.el @@ -31,6 +31,7 @@ ;;; Code: +(require 'auth-source) (require 'cl-lib) (require 'let-alist) (require 'pcase) @@ -60,25 +61,62 @@ ;; ;;; Request +;;;###autoload +(defun openai-key-auth-source () + "Retrieve the OpenAI API key from auth-source." + (if-let ((auth-info (auth-source-search :max 1 + :host "api.openai.com" + :require '(:user :secret)))) + (funcall (plist-get (car auth-info) :secret)) + (error "OpenAI API key not found in auth-source"))) + (defvar openai-key "" - "Generated API key.") + "Variable storing the openai key or a function name to retrieve it. + +The function should take no arguments and return a string containing the key. + +A function, `openai-key-auth-source', that retrieves the key from +auth-source is provided for convenience.") (defvar openai-user "" "A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse.") +(defun openai--resolve-key (key) + "If the given KEY is a function call it and return the result, otherwise +return KEY." + (cond ((functionp key) (funcall key)) + ((and (stringp key) (not (string-empty-p key))) key) + (t (user-error "[INFO] Invalid API key, please set it to the correct value: %s" key)))) + +(defun open--alist-omit-null (alist) + "Omit null value or empty string in ALIST." + (cl-remove-if (lambda (pair) + (let ((value (cdr pair))) + (or (null value) ; ignore null + (and (stringp value) ; ignore empty string + (string-empty-p value))))) + alist)) + +(defun openai--headers (content-type key org-id) + "Construct request headers. + +Arguments CONTENT-TYPE, KEY, and ORG-ID are common request headers." + (setq key (openai--resolve-key key)) + (open--alist-omit-null `(("Content-Type" . ,content-type) + ("Authorization" . ,(if (or (null key) + (string-empty-p key)) + "" + (concat "Bearer " key))) + ("OpenAI-Organization" . ,org-id)))) + (defun openai--json-encode (object) - "Wrapper for function `json-encode' but it removes `nil' value before + "Wrapper for function `json-encode' but it remove nil value before constructing JSON data. The argument OBJECT is an alist that can be construct to JSON data; see function `json-encode' for the detials." - (let* ((object (cl-remove-if (lambda (pair) - (let ((value (cdr pair))) - (or (null value) ; ignore null - (and (stringp value) ; ignore empty string - (string-empty-p value))))) - object)) + (let* ((object (open--alist-omit-null object)) (encoded (json-encode object))) (openai--log "[ENCODED]: %s" encoded) encoded)) @@ -104,8 +142,7 @@ See https://beta.openai.com/docs/guides/error-codes/api-errors." The URL is the url for `request' function; then BODY is the arguments for rest." (declare (indent 1)) - `(if (string-empty-p openai-key) - (user-error "[INFO] Invalid API key, please set it to the correct value: %s" openai-key) + `(progn (setq openai-error nil) (request ,url :error (cl-function