[BUG] Streaming transcription spans omit the stream flag
Maintainers usually reply within 1 day
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 78/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- ruby
- Domain
- observability
Research direction
Start with the transcription payload construction used by RubyLLM.transcribe and compare it with the existing streaming payload paths for speech and chat. Run CI=1 SKIP_COVERAGE=1 bundle exec rspec /tmp/transcription_stream_span_spec.rb; the streamed cases should include gen_ai.request.stream: true, while the buffered case should omit it and preserve span context and error behavior.
Written by the indexing model from the issue text.
Description
Basic checks
- I searched existing issues - this hasn't been reported
- I can reproduce this consistently
- This is a RubyLLM bug, not my application code
What's broken?
RubyLLM.transcribe streams correctly when given a block, but its OpenTelemetry span omits gen_ai.request.stream. This happens on a successful transcription and when the callback raises. The transcription payload never sets the existing streaming flag, unlike speech and chat.
Applications filtering spans by streaming mode cannot identify these transcription requests.
How to reproduce
From the repository root, save this as /tmp/transcription_stream_span_spec.rb. Run CI=1 SKIP_COVERAGE=1 bundle exec rspec /tmp/transcription_stream_span_spec.rb. The HTTP response is stubbed; no provider credentials or external request are needed.
# frozen_string_literal: true
require 'spec_helper'
require File.expand_path('spec/support/open_telemetry', Dir.pwd)
RSpec.describe 'Transcription streaming span reproduction' do
include_context 'with configured RubyLLM'
include_context 'with OpenTelemetry tracing'
describe 'transcription spans' do
let(:audio) { File.expand_path('spec/fixtures/ruby.wav', Dir.pwd) }
def stub_transcription_stream
events = [
{ type: 'transcript.text.delta', delta: 'private transcript' },
{ type: 'transcript.text.done', text: 'private transcript', usage: { input_tokens: 3, output_tokens: 1 } }
].map { |event| "data: #{event.to_json}\n\n" }.join
stub_request(:post, 'https://api.openai.com/v1/audio/transcriptions')
.to_return(body: events, headers: { 'Content-Type' => 'text/event-stream' })
end
it 'marks a streamed transcription and keeps the span active through its callbacks' do
stub_transcription_stream
chunks = []
result = RubyLLM.transcribe(audio, model: model_for(:openai, :streaming_transcription), provider: :openai) do |chunk|
chunks << chunk
expect(spans).to be_empty
expect(OpenTelemetry::Trace.current_span.recording?).to be(true)
end
expect(result.text).to eq('private transcript')
expect(chunks.last).to be_done
expect(spans.sole.attributes).to include('gen_ai.operation.name' => 'transcription', 'gen_ai.request.stream' => true)
expect(spans.sole.attributes.to_s).not_to include('private')
end
it 'keeps the streaming flag when the transcription callback raises' do
stub_transcription_stream
previous = OpenTelemetry::Context.current
expect do
RubyLLM.transcribe(audio, model: model_for(:openai, :streaming_transcription), provider: :openai) do
raise 'private callback error'
end
end.to raise_error(RuntimeError, 'private callback error')
expect(OpenTelemetry::Context.current).to equal(previous)
expect(spans.sole.status.code).to eq(OpenTelemetry::Trace::Status::ERROR)
expect(spans.sole.attributes).to include('gen_ai.request.stream' => true, 'error.type' => 'RuntimeError')
expect(spans.sole.attributes.to_s).not_to include('private')
end
it 'omits the streaming flag from a buffered transcription' do
stub_request(:post, 'https://api.openai.com/v1/audio/transcriptions')
.to_return_json(body: { text: 'private transcript', usage: { input_tokens: 3, output_tokens: 1 } })
result = RubyLLM.transcribe(audio, model: model_for(:openai, :streaming_transcription), provider: :openai)
expect(result.text).to eq('private transcript')
expect(spans.sole.attributes).not_to have_key('gen_ai.request.stream')
end
end
end
Expected behavior
Streamed transcription spans include gen_ai.request.stream: true, including when a callback raises. A buffered transcription keeps the attribute omitted. Return values, callback errors, and span context remain unchanged.
What actually happened
On upstream main, the two streaming examples fail because the attribute is missing. The buffered guard already passes. The operation still delivers the transcript, and the raising callback still produces an error span.
I have a one-line local payload correction with regression specs for all three paths.
Environment
- Ruby: 3.3.5
- RubyLLM: upstream main
a32aa84ba9bc387580598c555ab068c6ccd0f56c - Provider: OpenAI transcription, using stubbed SSE replies
- OS: macOS
- OpenTelemetry: repository development SDK and in-memory exporter
- Dominant language
- Ruby
- Stars
- 4.5k
- Forks
- 523
- Avg merge
- 9h 26m
- Merged PRs (30d)
- 213
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from crmne/ruby_llm
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
crmne/ruby_llm#1281 · 2 reactions ·
Maintainers usually reply within 1 day
-
[BUG] Transcription spans omit the requested temperaturePossibly taken @yorzi claimed this 2 days ago. Openbug
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
crmne/ruby_llm#1276 · 1 reaction ·
Maintainers usually reply within 1 day
-
[BUG] embed(array) returns one vector for many texts with Bedrock Titan behind an OpenAI-compatible gatewayPossibly taken @DevKaurav-14 claimed this 2 days ago. Openbug
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
crmne/ruby_llm#1269 · 1 comment · 1 reaction ·
Maintainers usually reply within 1 day
-
[BUG] Chat Completions reports vLLM cache writes as input tokensPossibly taken @merlin-croain claimed this 3 days ago. Openbug
Difficulty 2/5 1-3 hours Newbie friendliness 95/100
crmne/ruby_llm#1265 · 1 reaction ·
Maintainers usually reply within 1 day
-
[BUG] `as_json` on an acts_as_chat record renders an array instead of the chatPossibly taken @monsterlag claimed this 4 days ago. Openbug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
crmne/ruby_llm#1244 · 1 reaction ·
Maintainers usually reply within 1 day
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
FinanceFlash/unvibecode#273 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
-
[Bug Report] Concurrent runs sharing a cold cache fail with JSON::ParserErrorPossibly taken @sarankumar1325 claimed this today. Openbug
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
we-promise/sure#4481 ·
Maintainers usually reply within 1 day