Skip to content

AWS Bedrock Support #670

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open

Conversation

felipeochoa
Copy link

Closes #379

Use it with this:

(gptel-make-bedrock "bedrock" :region "us-east-1")

I've manually tested with streaming on and off. Tool use, media, and context all work

gptel-bedrock.el Outdated
@@ -0,0 +1,586 @@
;;; gptel-bedrock.el --- AWS Bedrock support for gptel -*- lexical-binding: t; -*-

;; Copyright (C) 2024 [Your Name]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By 2025, all files in the project appear to have copyright attributed to the main author, @karthink. This structure could facilitate any future changes in licensing or transfers of copyright to an organization like the FSF, if he so decides.

Additionally, ensure that the Author field is accurate or omitted if anonymity is preferred?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

"Parse TOOLS and return a list of ToolSpecification objects.

TOOLS is a list of `gptel-tool' structs, which see."
;; https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_ToolSpecification.html
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about having these URLs in the docstring instead? (of course longer than default byte-compile-docstring-max-column which might need adjusting in file local variables)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The URLs I think are more developer docs no?

gptel-bedrock.el Outdated
(require 'gptel-anthropic)
(require 'mail-parse)

(defvar json-object-type)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, why exactly is defvar this needed? Is json.el used at all?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's used when native JSON parsing isn't compiled in. But the gptel--json* macros all defvar whatever they need, so I don't think this is necessary after all. I've removed

:protocol protocol
:endpoint "" ; Url is dynamically constructed based on other args
:stream stream
:coding-system (and stream 'binary)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compiler-macro error for gptel--make-bedrock: (error "Keyword argument :coding-system not one of (:name :host :header :protocol :stream :endpoint :key :models :url :request-params :curl-args)")

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How are you triggering this error? I'm not able to reproduce this on an emacs -Q, but I see something similar when I've loaded the previous gptel-openai.el without the new field

@marcbowes
Copy link

marcbowes commented Mar 4, 2025

Thank you for doing this! Excited to try it out.

Unfortunately, currently getting this error.. (error is still here for others..)

Debugger entered--Lisp error: (error "Keyword argument :coding-system not one of (:name :host :header :protocol :stream :endpoint :key :models :url :request-params :curl-args)")
  error("Keyword argument %s not one of (:name :host :header :protocol :stream :endpoint :key :models :url :request-params :curl-args)" :coding-system)
  (cond ((memq (car --cl-keys--) '(:name :host :header :protocol :stream :endpoint :key :models :url :request-params :curl-args :allow-other-keys)) (if (cdr --cl-keys--) nil (error "Missing argument for %s" (car --cl-keys--))) (setq --cl-keys-- (cdr (cdr --cl-keys--)))) ((car (cdr (memq ':allow-other-keys --cl-rest--))) (setq --cl-keys-- nil)) (t (error "Keyword argument %s not one of (:name :host :header :protocol :stream :endpoint :key :models :url :request-params :curl-args)" (car --cl-keys--))))
  (while --cl-keys-- (cond ((memq (car --cl-keys--) '(:name :host :header :protocol :stream :endpoint :key :models :url :request-params :curl-args :allow-other-keys)) (if (cdr --cl-keys--) nil (error "Missing argument for %s" (car --cl-keys--))) (setq --cl-keys-- (cdr (cdr --cl-keys--)))) ((car (cdr (memq ':allow-other-keys --cl-rest--))) (setq --cl-keys-- nil)) (t (error "Keyword argument %s not one of (:name :host :header :protocol :stream :endpoint :key :models :url :request-params :curl-args)" (car --cl-keys--)))))
  (let ((--cl-keys-- --cl-rest--)) (while --cl-keys-- (cond ((memq (car --cl-keys--) '(:name :host :header :protocol :stream :endpoint :key :models :url :request-params :curl-args :allow-other-keys)) (if (cdr --cl-keys--) nil (error "Missing argument for %s" (car --cl-keys--))) (setq --cl-keys-- (cdr (cdr --cl-keys--)))) ((car (cdr (memq ':allow-other-keys --cl-rest--))) (setq --cl-keys-- nil)) (t (error "Keyword argument %s not one of (:name :host :header :protocol :stream :endpoint :key :models :url :request-params :curl-args)" (car --cl-keys--))))))
  (progn (let ((--cl-keys-- --cl-rest--)) (while --cl-keys-- (cond ((memq (car --cl-keys--) '(:name :host :header :protocol :stream :endpoint :key :models :url :request-params :curl-args :allow-other-keys)) (if (cdr --cl-keys--) nil (error "Missing argument for %s" (car --cl-keys--))) (setq --cl-keys-- (cdr (cdr --cl-keys--)))) ((car (cdr (memq ... --cl-rest--))) (setq --cl-keys-- nil)) (t (error "Keyword argument %s not one of (:name :host :header :protocol :stream :endpoint :key :models :url :request-params :curl-args)" (car --cl-keys--)))))) (progn (record 'gptel-bedrock name host header protocol stream endpoint key models url request-params curl-args)))
  (let* ((name (car (cdr (plist-member --cl-rest-- ':name)))) (host (car (cdr (plist-member --cl-rest-- ':host)))) (header (car (cdr (plist-member --cl-rest-- ':header)))) (protocol (car (cdr (plist-member --cl-rest-- ':protocol)))) (stream (car (cdr (plist-member --cl-rest-- ':stream)))) (endpoint (car (cdr (plist-member --cl-rest-- ':endpoint)))) (key (car (cdr (plist-member --cl-rest-- ':key)))) (models (car (cdr (plist-member --cl-rest-- ':models)))) (url (car (cdr (plist-member --cl-rest-- ':url)))) (request-params (car (cdr (plist-member --cl-rest-- ':request-params)))) (curl-args (car (cdr (plist-member --cl-rest-- ':curl-args))))) (progn (let ((--cl-keys-- --cl-rest--)) (while --cl-keys-- (cond ((memq (car --cl-keys--) '...) (if (cdr --cl-keys--) nil (error "Missing argument for %s" ...)) (setq --cl-keys-- (cdr ...))) ((car (cdr ...)) (setq --cl-keys-- nil)) (t (error "Keyword argument %s not one of (:name :host :header :protocol :stream :endpoint :key :models :url :request-params :curl-args)" (car --cl-keys--)))))) (progn (record 'gptel-bedrock name host header protocol stream endpoint key models url request-params curl-args))))
  gptel--make-bedrock(:name "bedrock" :host "bedrock-runtime.us-west-1.amazonaws.com" :header nil :models (claude-3-5-sonnet-20241022 claude-3-5-sonnet-20240620 claude-3-5-haiku-20241022 claude-3-opus-20240229 claude-3-sonnet-20240229 claude-3-haiku-20240307) :protocol "https" :endpoint "" :stream nil :coding-system nil :curl-args #f(lambda () [(region "us-west-1")] (gptel-bedrock--curl-args region)) :url #f(lambda () [(host "bedrock-runtime.us-west-1.amazonaws.com") (protocol "https") (stream nil)] (concat protocol "://" host "/model/" (gptel-bedrock--get-model-id gptel-model) "/" (if stream "converse-stream" "converse"))))
  (let* ((p (assoc name gptel--known-backends #'equal)) (v (gptel--make-bedrock :name name :host host :header nil :models (gptel--process-models models) :protocol protocol :endpoint "" :stream stream :coding-system (and stream 'binary) :curl-args #'(lambda nil (gptel-bedrock--curl-args region)) :url #'(lambda nil (concat protocol "://" host "/model/" (gptel-bedrock--get-model-id gptel-model) "/" (if stream "converse-stream" "converse")))))) (progn (if p (setcdr p v) (setq gptel--known-backends (cons (setq p (cons name v)) gptel--known-backends))) v))
  (let ((host (format "bedrock-runtime.%s.amazonaws.com" region))) (let* ((p (assoc name gptel--known-backends #'equal)) (v (gptel--make-bedrock :name name :host host :header nil :models (gptel--process-models models) :protocol protocol :endpoint "" :stream stream :coding-system (and stream 'binary) :curl-args #'(lambda nil (gptel-bedrock--curl-args region)) :url #'(lambda nil (concat protocol "://" host "/model/" ... "/" ...))))) (progn (if p (setcdr p v) (setq gptel--known-backends (cons (setq p (cons name v)) gptel--known-backends))) v)))
  (progn (let ((--cl-keys-- --cl-rest--)) (while --cl-keys-- (cond ((memq (car --cl-keys--) '(:region :models :stream :protocol :allow-other-keys)) (if (cdr --cl-keys--) nil (error "Missing argument for %s" (car --cl-keys--))) (setq --cl-keys-- (cdr (cdr --cl-keys--)))) ((car (cdr (memq ... --cl-rest--))) (setq --cl-keys-- nil)) (t (error "Keyword argument %s not one of (:region :models :stream :protocol)" (car --cl-keys--)))))) (let ((host (format "bedrock-runtime.%s.amazonaws.com" region))) (let* ((p (assoc name gptel--known-backends #'equal)) (v (gptel--make-bedrock :name name :host host :header nil :models (gptel--process-models models) :protocol protocol :endpoint "" :stream stream :coding-system (and stream 'binary) :curl-args #'(lambda nil ...) :url #'(lambda nil ...)))) (progn (if p (setcdr p v) (setq gptel--known-backends (cons (setq p ...) gptel--known-backends))) v))))
  (let* ((region (car (cdr (plist-member --cl-rest-- ':region)))) (models (car (cdr (or (plist-member --cl-rest-- ':models) (list nil gptel--bedrock-models))))) (stream (car (cdr (plist-member --cl-rest-- ':stream)))) (protocol (car (cdr (or (plist-member --cl-rest-- ':protocol) '(nil "https")))))) (progn (let ((--cl-keys-- --cl-rest--)) (while --cl-keys-- (cond ((memq (car --cl-keys--) '...) (if (cdr --cl-keys--) nil (error "Missing argument for %s" ...)) (setq --cl-keys-- (cdr ...))) ((car (cdr ...)) (setq --cl-keys-- nil)) (t (error "Keyword argument %s not one of (:region :models :stream :protocol)" (car --cl-keys--)))))) (let ((host (format "bedrock-runtime.%s.amazonaws.com" region))) (let* ((p (assoc name gptel--known-backends #'equal)) (v (gptel--make-bedrock :name name :host host :header nil :models (gptel--process-models models) :protocol protocol :endpoint "" :stream stream :coding-system (and stream ...) :curl-args #'... :url #'...))) (progn (if p (setcdr p v) (setq gptel--known-backends (cons ... gptel--known-backends))) v)))))
  gptel-make-bedrock("bedrock" :region "us-west-1")
  eval((gptel-make-bedrock "bedrock" :region "us-west-1") t)

UPDATE: I think I had something hanging around my environment. After nuking everything, restarting, then manually eval'ing each file things seem to work.

I'm now getting HTTP 403s saying the request signature is invalid. Turning logging on, I see:

{"message":"The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details."}

I'm using

curl --version
curl 8.7.1 (x86_64-apple-darwin24.0) libcurl/8.7.1 (SecureTransport) LibreSSL/3.3.6 zlib/1.2.12 nghttp2/1.63.0
Release-Date: 2024-03-27

I was able to fix that by switching to homebrew's version

curl 8.12.1 (aarch64-apple-darwin24.2.0) libcurl/8.12.1 OpenSSL/3.4.1 (SecureTransport) zlib/1.2.12 brotli/1.1.0 zstd/1.5.7 AppleIDN libssh2/1.11.1 nghttp2/1.64.0 librtmp/2.3
Release-Date: 2025-02-13

@felipeochoa
Copy link
Author

An easy way to test if your curl supports the AWS signature is with this command:

curl -XPOST -d'{"messages":[{"role":"user","content":[{"text":"Who are you?"}]}]}' \
    --user "$AWS_ACCESS_KEY_ID:${AWS_SECRET_ACCESS_KEY}"  "-Hx-amz-security-token: ${AWS_SECURITY_TOKEN}" \
    --aws-sigv4 "aws:amz:us-east-1:bedrock" --output "/dev/stdout" \
    'https://bedrock-runtime.us-east-1.amazonaws.com/model/meta.llama3-8b-instruct-v1:0/converse' \
| jq -r '.output.message.content[0].text'

You'll need to ensure your account has access to the model first

@marcbowes
Copy link

Just to be clear - my curl had support for AWS, and I had access. The issue was with the macOS version, as updating to the homebrew version fixed the problem. I'll leave my error message in this thread so others can find it with a search.

Things appear to be working for me now. Thanks again.


(defvar gptel--bedrock-models
(let ((known-ids (mapcar #'car gptel-bedrock-model-ids)))
(cl-remove-if-not (lambda (model) (memq (car model) known-ids)) gptel--anthropic-models))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, why did you opt to do this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean why re-use the anthropic model descriptions? Just because it felt silly to duplicate those here

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this do an intersection?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes exactly.

My rationale was as follows:

Bedrock has lots of model families (Claude, Jamba, Titan, Llama, Mistral, etc.), and their offering is constantly evolving. In fact, users can create custom models (through fine-tuning, distillation, or more pre-training), so the list of models is not really finite. So the situation for this backend is somewhere between Ollama (where gptel doesn't provide a default model list) and Anthropic (where gptel provides a comprehensive model list).

I therefore decided to provide a default model list which was useful (the Anthropic subset that's served by AWS) but not exhaustive. I could be convinced to add more model families to the default list; perhaps Llama and Mistral?

@marcbowes
Copy link

Would you be willing to add something like this to the PR?

(defun gptel-bedrock--get-credentials-from-env ()
  "Return AWS credentials from environment variables.

Returns a list of 2-3 elements, depending on whether a session
token is needed, with this form: (AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY
AWS_SESSION_TOKEN)."
  (let ((key-id (getenv "AWS_ACCESS_KEY_ID"))
        (secret-key (getenv "AWS_SECRET_ACCESS_KEY"))
        (token (getenv "AWS_SESSION_TOKEN")))
    (cond
     ((and key-id secret-key token) (cl-values key-id secret-key token))
     ((and key-id secret-key) (cl-values key-id secret-key))
     (t (user-error "Missing AWS credentials; currently only environment variables are supported")))))

(defcustom gptel-bedrock-credentials-function #'gptel-bedrock--get-credentials-from-env
  "Function to get AWS credentials for Bedrock API requests.
This function should return a list of 2-3 elements:
(AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY [AWS_SESSION_TOKEN])
where the session token is optional.

By default, credentials are read from environment variables."
  :group 'gptel
  :type '(choice (function :tag "Credential function")
                 (const :tag "No credentials" nil)))

(defun gptel-bedrock--get-credentials ()
  "Return the AWS credentials to use for the request.

Calls `gptel-bedrock-credentials-function' to retrieve credentials.
Convenient to use with `cl-multiple-value-bind'."
  (if gptel-bedrock-credentials-function
      (funcall gptel-bedrock-credentials-function)
    (user-error "No credential function configured for AWS Bedrock")))

@felipeochoa
Copy link
Author

Would you be willing to add something like this to the PR?

I was envisioning adding support for more credential sources in a follow up PR. I'd like to either use the AWS CLI or at least replicate the logic for where it looks for credential sources.

With the current code, it doesn't seem like the defcustom buys us anything yet? If users have to write their own elisp to fetch credentials, they might as well use advice to use an alternate gptel-bedrock--get-credentials

@marcbowes
Copy link

I don't think the AWS CLI lets you print credentials - probably for security reasons. So I think we're either forced to write/reuse some elisp to replicate the functionality or have the impl use some thing other than curl that does it for us.

With the current code, it doesn't seem like the defcustom buys us anything yet? If users have to write their own elisp to fetch credentials, they might as well use advice to use an alternate gptel-bedrock--get-credentials

Fair point, provided the fn name is stable.

@felipeochoa
Copy link
Author

Fair point, provided the fn name is stable.

We could rename to gptel-bedrock-get-credentials?

forced to write/reuse some elisp to replicate the functionality

That's my assumption. Selfishly I'd like to make it easy to use aws-vault, which seems easier to do if the credential system is written in elisp 😄

@karthink
Copy link
Owner

I haven't looked at gptel-bedrock.el yet, added a couple of remarks about the changes to the rest of gptel in my comments.

It looks like you're only modifying Curl processes. gptel requires parity between (non-streaming) Curl requests and url-retrieve, so the requisite changes need to be made in gptel--url-get-response as well.

@@ -240,9 +240,14 @@ all at once. This wait is asynchronous.
:type 'boolean)
(make-obsolete-variable 'gptel-playback 'gptel-stream "0.3.0")

(defcustom gptel-use-curl (and (executable-find "curl") t)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why rename this defcustom? If you have multiple versions of curl on your path, you can set Emacs' process environment appropriately to pick the right one.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went off the discussion at #145

My gut reaction as someone whose platform curl doesn't support AWS signatures is that messing with process path is kind of painful to do. I would probably just put an updated curl in ~/.local/bin and accept that curl is de facto no longer managed by my distro, but that seems kind of icky

@@ -1556,41 +1561,43 @@ implementation, used by OpenAI-compatible APIs and Ollama."
:name (gptel-tool-name tool)
:description (gptel-tool-description tool))
(and (gptel-tool-args tool) ;no parameters if args is nil
(list
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain why you split this out into a separate function? Since this is a cl-defmethod, it's useful to keep it self-contained. gptel--tool-args-to-json-schema does not indicate that it only applies to OpenAI and Ollama, for instance.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a fairly generic piece of functionality that would be useful across many backends:

  • The specification for the function "convert args from the gptel format to JSON schema" makes no reference to any backend
  • OpenAI, Ollama, Bedrock, and even Anthropic could use this helper (once the :additionalProperties TODO is resolved). I imagine there are many other providers using JSON schema for tool definitions. (Gemini uses OpenAPI schema so can't benefit from this helper)

If there were a gptel-utils, this is the sort of thing that could live there.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a fairly generic piece of functionality that would be useful across many backends:

Each backend uses a different JSON scheme. Compare this implementation of gptel--parse-tools with that in gptel-anthropic.el and gptel-gemini.el for example.

So this version of gptel--tool-args-to-json-schema is not useful when separated from its caller.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each backend uses a different JSON scheme

They differ in how the "top" of the tool spec is structured, but all except Gemini use the same format for the arguments. JSON schema is a standard independent of the LLM provider, which OpenAI, Ollama, Bedrock, and Anthropic have all chosen to use.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But anyway, this isn't the hill for me to die on. If you'd rather I just copy-paste the code I can do that. LMK what you prefer

Copy link
Owner

@karthink karthink Mar 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but all except Gemini use the same format for the arguments. JSON schema is a standard independent of the LLM provider, which OpenAI, Ollama, Bedrock, and Anthropic have all chosen to use.

I don't trust them to follow the standard. When I was adding tool support, I saw a large amount of ill-defined behavior and variance between the backends in what they accept as tool arguments. Tool use for every backend is an under-specified mess right now. So even though they look similar, each backend's tool argument parser is tested specifically for that backend type, and there will be many edge cases if we assume we can reuse gptel--tool-args-to-json-schema across backends.

So please leave the current version unaltered and copy what you need into gptel-bedrock's implementation of gptel--parse-tools. I will revisit this issue later this year after both the upstream tools API and gptel's tool support have had more time to stabilize.

@@ -151,7 +151,8 @@ Throw an error if there is no match."
(:copier gptel--copy-backend))
name host header protocol stream
endpoint key models url request-params
curl-args)
curl-args
(coding-system nil :documentation "Can be set to `binary' if the backend expects non UTF-8 output."))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation for this can be more general. Perhaps something like:

"The coding system used by the backend, defaults to utf-8-unix.  Does not apply to Windows systems."

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently we only handle the case where (eq (gptel-backend-coding-system backend) 'binary). If we want to support any coding system, do we want to support separate encoding and decoding systems? Or alternatively we could rename this field to something like use-binary-coding to keep the options as utf8 or binary

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. Let's leave it at coding-system and with your current documentation. Supporting any coding system is a messy affair because of Windows.

@karthink
Copy link
Owner

@felipeochoa Here's where we are on merging this:

  1. gptel--tool-args-to-json-schema: Extracting this out to a separate function is either a mistake, or I'm missing something. You'll have to explain it to me.
  2. gptel-curl-path: I would like to retain the name gptel-use-curl. Its value can be t or nil, or the Curl path. The name can be aliased to gptel-curl-path in the future if required.
  3. After resolving the above points, could you make a separate PR with just the changes to gptel? i.e. everything except gptel-bedrock.el itself. I'd like to merge and document those separately first.

@marcbowes
Copy link

I rebased this PR on master and things still work. While doing that, I also thought about gptel--tool-args-to-json-schema. I think the answer is just "here's some code that happens to exactly match what bedrock needs" (see https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_ToolSpecification.html).

fovc9 added 3 commits March 20, 2025 09:55
This is useful for the bedrock implementation since some distros' version of curl doesn't have
support for the --aws-sigv4 argument
This will allow the bedrock implementation to accept responses using vnd.amazon.event-stream
@felipeochoa
Copy link
Author

1. `gptel--tool-args-to-json-schema`: Extracting this out to a separate function is either a mistake, or I'm missing something.  You'll have to explain it to me.

Discussion here. LMK how you want to proceed

2. `gptel-curl-path`: I would like to retain the name `gptel-use-curl`.  Its value can be t or nil, or the Curl path.  The name can be aliased to `gptel-curl-path` in the future if required.

Done. I put in gptel-curl-path as a defsubst for now

3. After resolving the above points, could you make a separate PR with just the changes to gptel?  i.e. everything except `gptel-bedrock.el` itself.  I'd like to merge and document those separately first.

Will do as soon as we decide on point 1

(const t :tag "Use the system curl")
(string :tag "Path to the curl executable")))

(defsubst gptel-curl-path ()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename to gptel--curl-path to signal that this is internal.

:endpoint "" ; Url is dynamically constructed based on other args
:stream stream
:coding-system (and stream 'binary)
:curl-args (lambda () (gptel-bedrock--curl-args region))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will mean that the user can't add any more backend-specific Curl arguments. For example, if they want to specify --max-time or --keepalive-time time for this backend.

@karthink
Copy link
Owner

karthink commented Apr 7, 2025

@felipeochoa gentle ping about the last few remaining issues to merge this.

@felipeochoa
Copy link
Author

@felipeochoa gentle ping about the last few remaining issues to merge this.

Sorry been quite busy at work. I'll probably get to this later this month

@p-baleine
Copy link

p-baleine commented May 2, 2025

@karthink @felipeochoa I would like to thank gptel and this PR.

I think that gptel is the most emacs way client in various other clients for emacs.

I want to feed privacy information to gptel via AWS Bedrock, and this PR enables this.

A few things I noticed:

  • Same as mentioned by @marcbowes, I got invalidSignatureException, and I could fix this by updating curl (7.x -> 8.13)
  • I want to connect Bedrock via application inference profiles. If someone like me, you should add your application profile ARN url encoded, i.e.:
    (add-to-list 'gptel-bedrock-model-ids '(foo-claude-3-5-sonnet-20241022 . "arn%3Aaws%3Abedrock%3Aus-west-2%3AXXXX%3Aapplication-inference-profile%2FYYYY"))

@sjtechdev
Copy link

Thank you @karthink @felipeochoa for your efforts on adding AWS Bedrock support, this is very useful feature!

Something I thought I'd share, in case it's helpful for others too:

  1. I was also running into the InvalidSignatureException until I updated curl (8.13), as mentioned by couple of other users above.
  2. After that, I was getting an HTTP 400 error; it turns out that I had to change the modelId to include a region prefix e.g. us.anthropic.claude-3-5-haiku-20241022-v1:0 instead of anthropic.claude-3-5-haiku-20241022-v1:0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

AWS Bedrock support
7 participants