tests/testthat/test-openai_request.R

# tests/testthat/test-openai-real.R

# Here we test some functions from R/internal_request_llm_provider.R,
#   to verify that the OpenAI API can be used correctly with both
#   the responses and the chat completions endpoints

testthat::skip_if_not_installed("httr2")

suppressWarnings(library(testthat))
suppressWarnings(library(httr2))

# ---- Config ------------------------------------------------------------------
openai_key <- Sys.getenv("OPENAI_API_KEY", unset = "")
skip_if(openai_key == "", "OPENAI_API_KEY not set; skipping OpenAI tests")

model_chat <- Sys.getenv("TIDYPROMPT_OPENAI_CHAT_MODEL", unset = "gpt-4o-mini")
model_resp <- Sys.getenv(
  "TIDYPROMPT_OPENAI_RESPONSES_MODEL",
  unset = "gpt-4o-mini"
)

dummy_history <- function() {
  data.frame(role = "user", content = "ping", stringsAsFactors = FALSE)
}

openai_headers <- function(req) {
  req |>
    req_headers(
      Authorization = paste("Bearer", openai_key),
      `Content-Type` = "application/json"
    )
}

# ---- Chat Completions (non-stream) -------------------------------------------
test_that("OpenAI Chat Completions (non-stream) returns content", {
  req <- request("https://api.openai.com/v1/chat/completions") |>
    openai_headers() |>
    req_body_json(
      list(
        model = model_chat,
        temperature = 0,
        max_tokens = 10,
        messages = list(
          list(
            role = "system",
            content = "Reply with exactly the single word: pong"
          ),
          list(role = "user", content = "Say it.")
        )
      ),
      auto_unbox = TRUE
    )

  out <- request_llm_provider(
    chat_history = dummy_history(),
    request = req,
    stream = FALSE,
    api_type = "openai"
  )

  expect_s3_class(out$completed, "data.frame")
  assistant <- tail(out$completed$content, 1)
  expect_true(is.character(assistant) && nchar(assistant) > 0)
  expect_true(grepl("pong", assistant, ignore.case = TRUE))
})

# ---- Responses API (non-stream) ----------------------------------------------
test_that("OpenAI Responses API (non-stream) returns content", {
  req <- request("https://api.openai.com/v1/responses") |>
    openai_headers() |>
    req_body_json(
      list(
        model = model_resp,
        temperature = 0,
        max_output_tokens = 32,
        input = "Reply with exactly the single word: pong"
      ),
      auto_unbox = TRUE
    )

  out <- request_llm_provider(
    chat_history = dummy_history(),
    request = req,
    stream = FALSE,
    api_type = "openai"
  )

  assistant <- tail(out$completed$content, 1)
  expect_true(is.character(assistant) && nchar(assistant) > 0)
  expect_true(grepl("pong", assistant, ignore.case = TRUE))
})

# ---- Chat Completions (stream) -----------------------------------------------
test_that("OpenAI Chat Completions (stream) accumulates deltas", {
  req <- request("https://api.openai.com/v1/chat/completions") |>
    openai_headers() |>
    req_body_json(
      list(
        model = model_chat,
        temperature = 0,
        max_tokens = 32,
        stream = TRUE,
        messages = list(
          list(
            role = "system",
            content = "Reply with exactly the single word: pong"
          ),
          list(role = "user", content = "Say it.")
        )
      ),
      auto_unbox = TRUE
    )

  out <- request_llm_provider(
    chat_history = dummy_history(),
    request = req,
    stream = TRUE, # ensure SSE path
    verbose = FALSE, # silence token-by-token printing in tests
    api_type = "openai"
  )

  assistant <- tail(out$completed$content, 1)
  reply <- tolower(trimws(assistant))

  expect_true(nchar(reply) > 0)
  expect_true(grepl("\\bpong\\b", reply, ignore.case = FALSE))

  # If you want to be strict:
  # expect_equal(reply, "pong")
})

# ---- Responses API (stream) ---------------------------------------------------
test_that("OpenAI Responses API (stream) accumulates output_text deltas", {
  req <- request("https://api.openai.com/v1/responses") |>
    openai_headers() |>
    req_body_json(
      list(
        model = model_resp,
        temperature = 0,
        max_output_tokens = 32,
        stream = TRUE,
        input = "Reply with exactly the single word: pong"
      ),
      auto_unbox = TRUE
    )

  out <- request_llm_provider(
    chat_history = dummy_history(),
    request = req,
    stream = TRUE, # ensure we take the SSE path
    verbose = FALSE, # the new impl can print token deltas; silence in tests
    api_type = "openai"
  )

  # Grab the assistant's final message from the completed history
  assistant <- tail(out$completed$content, 1)
  reply <- tolower(trimws(assistant))

  # Basic sanity checks
  expect_true(nchar(reply) > 0)
  expect_true(grepl("\\bpong\\b", reply, ignore.case = FALSE))

  # (Optional) If you want to be stricter, uncomment:
  # expect_equal(reply, "pong")
})

Try the tidyprompt package in your browser

Any scripts or data that you put into this service are public.

tidyprompt documentation built on Aug. 25, 2025, 5:11 p.m.