Skip to content

Streaming breaks with OpenAI-compatible APIs that omit usage data #706

Description

@stevegbrooks

Problem

I'm using ellmer within my company (Boehringer Ingelheim). We have an OpenAI-compatible API service called Apollo. The output of Apollo's chat.completion when stream = TRUE doesn't include usage information in the streaming chunks. This causes ellmer to crash during token logging.

I've found that the error happens in tokens_row() when ellmer tries to create a data frame. Some fields have data but the usage fields are NULL, so you get mismatched row counts. Specifically, it throws this error: Error in data.frame: arguments imply differing number of rows: 1, 0.

Here's what a chat completion chunk looks like from Apollo when streaming (omitting the non-relevant parts):

{
  "choices": [{"delta": {"content": "Hello"}}],
  "usage": null
}

Compare this to OpenAI which sends usage data in the final chunk (this is also what our Apollo service looks like when giving non-streaming responses):

{
  "choices": [{"delta": {"content": ""}}],
  "usage": {"completion_tokens": 5, "prompt_tokens": 10, ...}
}

Steps to reproduce

Unfortunately, I can't give you a Boehringer Ingelheim account for you to test this with, so you're just gonna have to trust me :)

Expected behavior

This shouldn't break streaming. Either skip token logging when usage is missing, or use 0/NA as defaults.

Impact

This makes ellmer incompatible with our internal OpenAI-compatible service. The streaming content works fine - it's just the token logging that fails at the end.

Currently I have to catch this error and fall back to non-streaming mode, which defeats the purpose of streaming, and makes the usage of ellmer burdensome for users, especially when using tool calls and the wait time becomes longer.

Problematic code

I believe the issue occurs in /R/provider-openai.R at lines 272-277 in the value_turn() method:

cached_tokens <- result$usage$prompt_tokens_details$cached_tokens %||% 0
tokens <- tokens_log(
  provider,
  input = result$usage$prompt_tokens - cached_tokens,
  output = result$usage$completion_tokens,
  cached_input = cached_tokens
)

When result$usage is NULL (as with Apollo), attempting to access result$usage$prompt_tokens throws an error before the null-coalescing operator %||% and the existing null handling in tokens_log() can take effect.

Solution

I'm more than happy to submit the PR here, because I think I've found a good solution and its pretty easy to implement (at least for when the provider is "OpenAI"):

if (is.null(result$usage)) {
  tokens <- tokens_log(provider, input = NULL, output = NULL, cached_input = NULL)
} else {
  cached_tokens <- result$usage$prompt_tokens_details$cached_tokens %||% 0
  tokens <- tokens_log(
    provider,
    input = result$usage$prompt_tokens - cached_tokens,
    output = result$usage$completion_tokens,
    cached_input = cached_tokens
  )
}

When testing this, the symptoms I mentioned above go away, and I can happily use streaming w/ {ellmer} + Apollo.

The proposed fix would make the OpenAI provider more robust and consistent with how usage should be handled when it's missing from API responses. Not sure if there are any knock-on effects of this change, however. I imagine it would impact the token usage and cost reporting functionality, but since our service doesn't even provide token usage there's no way we can use it anyway.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions