Skip to content

Conversation

@hadley
Copy link
Member

@hadley hadley commented Oct 21, 2025

credentials is a zero-arg callback that returns the API key (or list of other headers). It is called dynamically so that secrets are never stored in the provider object and hence can't accidentally be serialised to disk.

Includes fixes for batch_*() to improve provider hashing and provide an escape hatch if I get the hashing wrong.

Fixes #613

To do:

  • Extract out as_credentials(api_key, credentials, function_name)
  • Call credentials once to check that it returns the right type of thing
  • Use ellmer_req_credentials() in all providers and extend it to optionally take just a string too (need to supply header name in argument)
  • Update once Improve provider hashing for batch_chat_*() #802 is merged

hadley added a commit that referenced this pull request Oct 22, 2025
Rather than hashing the complete provider object, we should just hash the most critical components. This trades a little safety for convenience; I don't think there are likely to be too many cases where you re-call `batch_chat()` with a subtly different provider object and this makes it more likely that you can retrieve results stored with a different version of ellmer. I've also added an escape hatch so you can use chats saved by ellmer 0.3.0 in ellmer 0.4.0

Extracted out from #799.
hadley added a commit that referenced this pull request Oct 23, 2025
Rather than hashing the complete provider object, we should just hash the most critical components. This trades a little safety for convenience; I don't think there are likely to be too many cases where you re-call `batch_chat()` with a subtly different provider object and this makes it more likely that you can retrieve results stored with a different version of ellmer. I've also added an escape hatch so you can use chats saved by ellmer 0.3.0 in ellmer 0.4.0

Extracted out from #799.
@hadley hadley requested a review from gadenbuie October 24, 2025 18:32
@hadley
Copy link
Member Author

hadley commented Oct 24, 2025

@gadenbuie this is a massive PR but you don't need to review it in detail. I think what would be most useful is if you looked at the changes to a couple of chat_ functions and read through the new utils-auth.R to see if it makes sense. I'd also appreciate your comments on whether or not I've motivated the change well, i.e. do you understand the benefits of this approach? (I haven't documented it in huge detail mostly because I don't think many users will need this, but it's very useful to have an as escape hatch we can suggest as needed.)

Copy link
Collaborator

@gadenbuie gadenbuie left a comment

Choose a reason for hiding this comment

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

Looks great! I think the reason for credentials is well-motivated and clear and the docs all hit the right balance for this feature (agreed that most people don't need to know much, but there's a good escape hatch for edge-cases).

Just a few small comments...

#' @param model Name of the model.
#' @param base_url The base URL for the API.
#' @param params A list of standard parameters created by [params()].
#' @param credentials A zero-argument function that returns credential to use
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
#' @param credentials A zero-argument function that returns credential to use
#' @param credentials A zero-argument function that returns the credentials to use

@@ -1,5 +1,6 @@
# ellmer (development version)

* `chat_*()` functions now use a credentials functions instead of an `api_key` (#613). This means that the `api_key` is never stored in the provider object (which might be saved to disk), but is instead retrieved on demand as needed. You generally shouldn't need to use the `credentials` argument, but when you do, you should use it to dynamically retrieve the API key from some other source (i.e. never inline a secret directly into a function call).
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
* `chat_*()` functions now use a credentials functions instead of an `api_key` (#613). This means that the `api_key` is never stored in the provider object (which might be saved to disk), but is instead retrieved on demand as needed. You generally shouldn't need to use the `credentials` argument, but when you do, you should use it to dynamically retrieve the API key from some other source (i.e. never inline a secret directly into a function call).
* `chat_*()` functions now use a `credentials` function instead of an `api_key` (#613). This means that API keys are never stored in the chat object (which might be saved to disk), but is instead retrieved on demand as needed. You generally shouldn't need to use the `credentials` argument, but when you do, you should use it to dynamically retrieve the API key from some other source (i.e. never inline a secret directly into a function call).

chat_openai_test(credentials = function() 1)
Condition
Error in `chat_openai()`:
! `credentials()` must be a string or a named list, not the number 1.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't know if you have much control over this, but given that we often reference functions like across() or pivot_wider(), it's easy to miss that in this error message credentials() means the result of evaluating credentials.

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.

Deprecate api_key arguments

3 participants