diff --git a/NEWS.md b/NEWS.md index 701b3f7b..56cd2132 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,6 +5,7 @@ `gen_ai.output.messages`, and `gen_ai.system_instructions`. This is opt-in via the `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` environment variable (set to `"true"`), since these payloads may contain user data. * The `input` column of `token_usage()` and `parallel_chat()` no longer includes a hidden 1.25x pricing weight for Anthropic cache-creation tokens; the column now reports raw integer token counts. Cost calculations are unchanged. +* `chat_openai()` now uses the appropriate prices when a non-default service tier (e.g. `"priority"`) is used (@trangdata, #903). * `chat_aws_bedrock()` now supports reasoning/thinking content. To enable thinking in Anthropic Claude models, see the `api_args` argument in `?chat_aws_bedrock` for an example (#964). * New `chat_lmstudio()` and `models_lmstudio()` provide support for [LM Studio](https://lmstudio.ai), a local model server with an OpenAI-compatible API (#963). * Fixed three bugs that caused errors when streaming web search results: Claude's `citations_delta` events were mishandled, `server_tool_use` input wasn't parsed from JSON during streaming, and OpenAI's `web_search_call` failed for non-search action types like `open_page` (#941). diff --git a/R/provider-openai.R b/R/provider-openai.R index fd00b374..5a83b8d7 100644 --- a/R/provider-openai.R +++ b/R/provider-openai.R @@ -333,7 +333,8 @@ method(value_turn, ProviderOpenAI) <- function( }) tokens <- value_tokens(provider, result) - cost <- get_token_cost(provider, tokens, variant = result$service_tier) + variant <- result$service_tier %||% "default" + cost <- get_token_cost(provider, tokens, variant = variant) AssistantTurn( contents = contents, json = result, diff --git a/R/tokens.R b/R/tokens.R index 2fcbb3aa..7c39f1e9 100644 --- a/R/tokens.R +++ b/R/tokens.R @@ -118,6 +118,8 @@ has_cost <- function(provider, model) { } get_token_cost <- function(provider, tokens, variant = "") { + check_string(variant, .internal = TRUE) + needle <- data.frame( provider = provider@name, model = provider@model, diff --git a/tests/testthat/_snaps/tokens.md b/tests/testthat/_snaps/tokens.md index 0ca4974f..f72623eb 100644 --- a/tests/testthat/_snaps/tokens.md +++ b/tests/testthat/_snaps/tokens.md @@ -13,6 +13,16 @@ provider model input output cached_input price 1 testprovider test 1 1 1 $0.00 +# informative internal error if variant is missing + + Code + get_token_cost(provider, tokens(), variant = NULL) + Condition + Error in `get_token_cost()`: + ! `variant` must be a single string, not `NULL`. + i This is an internal error that was detected in the ellmer package. + Please report it at with a reprex () and the full backtrace. + # token_usage() shows price if available Code diff --git a/tests/testthat/test-provider-openai.R b/tests/testthat/test-provider-openai.R index 70fe201a..81bed532 100644 --- a/tests/testthat/test-provider-openai.R +++ b/tests/testthat/test-provider-openai.R @@ -143,6 +143,20 @@ test_that("can extract dummy response from malformed JSON", { ) }) +test_that("value_turn handles NULL service_tier gracefully", { + provider <- chat_openai_test()$get_provider() + + result <- list( + output = list( + list(type = "message", content = list(list(text = "Hello"))) + ), + usage = NULL, + service_tier = NULL + ) + + expect_no_error(value_turn(provider, result)) +}) + test_that("value_turn() handles web_search_call action types", { provider <- chat_openai_test()$get_provider() diff --git a/tests/testthat/test-tokens.R b/tests/testthat/test-tokens.R index 0f71790f..7d214a6e 100644 --- a/tests/testthat/test-tokens.R +++ b/tests/testthat/test-tokens.R @@ -55,6 +55,15 @@ test_that("can compute price of tokens with a variant", { ) }) +test_that("informative internal error if variant is missing", { + provider <- test_provider("OpenAI", "gpt-4o") + expect_snapshot( + get_token_cost(provider, tokens(), variant = NULL), + error = TRUE + ) +}) + + test_that("price is NA if we don't have the data for it", { provider <- test_provider("ClosedAI", "gpt-4o") expect_equal(