Skip to content

Commit 6e98424

Browse files
renfredxhms-jpq
andauthored
feat: chat completion streaming helpers (#828)
* add chat completion streaming helpers * add basic sorbet types * temp: add big demo * simplify parsed fields for tool calls * refactor parse methods * remove anonymous tool default * rm ParsedChatCompletionMessage * update naming * fix docs * remove redundant filter * undo * update event methods to return values * style * remove big demo --------- Co-authored-by: dogisgreat <[email protected]>
1 parent 31470e6 commit 6e98424

34 files changed

+4758
-54
lines changed

examples/chat/streaming_basic.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
require_relative "../../lib/openai"
5+
6+
# gets API Key from environment variable `OPENAI_API_KEY`
7+
client = OpenAI::Client.new
8+
9+
stream = client.chat.completions.stream(
10+
model: "gpt-4o-mini",
11+
messages: [
12+
{role: :user, content: "Write a creative haiku about the ocean."}
13+
]
14+
)
15+
16+
stream.each do |event|
17+
case event
18+
when OpenAI::Streaming::ChatContentDeltaEvent
19+
print(event.delta)
20+
when OpenAI::Streaming::ChatContentDoneEvent
21+
puts
22+
end
23+
end
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
require_relative "../../lib/openai"
5+
6+
# gets API Key from environment variable `OPENAI_API_KEY`
7+
client = OpenAI::Client.new
8+
9+
# This example demonstrates how to start a new streamed chat completion that includes prior turns by
10+
# resending the conversation messages.
11+
#
12+
# 1. Start with an initial user turn and stream the assistant reply.
13+
messages = [
14+
{role: :user, content: "Tell me a short story about a robot. Stop after 2 sentences."}
15+
]
16+
17+
puts "First streamed completion:"
18+
assistant_text = ""
19+
20+
stream1 = client.chat.completions.stream(
21+
model: "gpt-4o-mini",
22+
messages: messages
23+
)
24+
25+
stream1.each do |event|
26+
case event
27+
when OpenAI::Streaming::ChatContentDeltaEvent
28+
assistant_text += event.delta
29+
print(event.delta)
30+
when OpenAI::Streaming::ChatContentDoneEvent
31+
puts
32+
end
33+
end
34+
35+
# 2. Start a new streamed completion that includes the prior assistant turn
36+
# and adds a follow-up user instruction.
37+
messages << {role: :assistant, content: assistant_text}
38+
messages << {role: :user, content: "Continue the story with 2 more sentences while keeping the same style."}
39+
40+
puts
41+
puts "Second streamed completion (with prior turns included):"
42+
43+
stream2 = client.chat.completions.stream(
44+
model: "gpt-4o-mini",
45+
messages: messages
46+
)
47+
48+
stream2.each do |event|
49+
case event
50+
when OpenAI::Streaming::ChatContentDeltaEvent
51+
print(event.delta)
52+
when OpenAI::Streaming::ChatContentDoneEvent
53+
puts
54+
end
55+
end
56+
57+
puts
58+
puts "Done. The second stream is a new completion that used the prior turns as context."
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
require_relative "../../lib/openai"
5+
6+
# gets API Key from environment variable `OPENAI_API_KEY`
7+
client = OpenAI::Client.new
8+
9+
stream = client.chat.completions.stream(
10+
model: "gpt-4o-mini",
11+
logprobs: true,
12+
top_logprobs: 3,
13+
messages: [
14+
{role: :user, content: "Finish the sentence: The capital of France is"}
15+
]
16+
)
17+
18+
stream.each do |event|
19+
case event
20+
when OpenAI::Streaming::ChatContentDeltaEvent
21+
print(event.delta)
22+
when OpenAI::Streaming::ChatLogprobsContentDeltaEvent
23+
# Print top logprobs for the last token in the delta
24+
tokens = event.content
25+
last = tokens.last
26+
next unless last
27+
alts = last.top_logprobs.map { |t| "#{t.token}=#{format('%.2f', t.logprob)}" }.join(", ")
28+
puts("\nlogprobs: [#{alts}]")
29+
when OpenAI::Streaming::ChatLogprobsContentDoneEvent
30+
puts("\n--- logprobs collection finished (#{event.content.length} tokens) ---")
31+
end
32+
end
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
require_relative "../../lib/openai"
5+
6+
# gets API Key from environment variable `OPENAI_API_KEY`
7+
client = OpenAI::Client.new
8+
9+
stream = client.chat.completions.stream(
10+
model: "gpt-4o-mini",
11+
n: 2,
12+
messages: [
13+
{role: :user, content: "Give me two short taglines for a beach resort."}
14+
]
15+
)
16+
17+
choice_contents = {}
18+
choice_finished = {}
19+
20+
stream.each do |event|
21+
case event
22+
when OpenAI::Streaming::ChatChunkEvent
23+
# Access the full snapshot with all choices:
24+
event.snapshot.choices.each_with_index do |choice, index|
25+
if choice.message.content
26+
choice_contents[index] = choice.message.content
27+
end
28+
29+
next unless choice.finish_reason && !choice_finished[index]
30+
choice_finished[index] = true
31+
# Print the complete content for this choice when it finishes:
32+
puts("[choice #{index}] complete:")
33+
puts(choice_contents[index])
34+
puts("--- choice #{index} done ---")
35+
puts
36+
end
37+
end
38+
end
39+
40+
puts("------ final choices ------")
41+
choice_contents.keys.sort.each do |i|
42+
puts("[#{i}] #{choice_contents[i]}")
43+
end
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
require_relative "../../lib/openai"
5+
6+
class Step < OpenAI::BaseModel
7+
required :explanation, String
8+
required :output, String
9+
end
10+
11+
class MathResponse < OpenAI::BaseModel
12+
required :steps, OpenAI::ArrayOf[Step]
13+
required :final_answer, String
14+
end
15+
16+
client = OpenAI::Client.new
17+
18+
stream = client.chat.completions.stream(
19+
model: "gpt-4o-mini",
20+
response_format: MathResponse,
21+
messages: [
22+
{role: :user, content: "solve 8x + 31 = 2, show all steps"}
23+
]
24+
)
25+
26+
stream.each do |event|
27+
case event
28+
when OpenAI::Streaming::ChatContentDeltaEvent
29+
print(event.delta)
30+
when OpenAI::Streaming::ChatContentDoneEvent
31+
puts
32+
puts("--- parsed object ---")
33+
pp(event.parsed)
34+
end
35+
end
36+
37+
response = stream.get_final_completion
38+
39+
puts
40+
puts("----- parsed outputs from final response -----")
41+
response
42+
.choices
43+
.each do |choice|
44+
# parsed is an instance of `MathResponse`
45+
pp(choice.message.parsed)
46+
end

examples/chat/streaming_text.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
require_relative "../../lib/openai"
5+
6+
# gets API Key from environment variable `OPENAI_API_KEY`
7+
client = OpenAI::Client.new
8+
9+
stream = client.chat.completions.stream(
10+
model: "gpt-4o-mini",
11+
messages: [
12+
{role: :user, content: "List three fun facts about dolphins."}
13+
]
14+
)
15+
16+
stream.text.each do |text|
17+
print(text)
18+
end
19+
puts

examples/chat/streaming_tools.rb

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
require_relative "../../lib/openai"
5+
6+
class GetWeather < OpenAI::BaseModel
7+
required :location, String
8+
end
9+
10+
# gets API Key from environment variable `OPENAI_API_KEY`
11+
client = OpenAI::Client.new
12+
13+
stream = client.chat.completions.stream(
14+
model: "gpt-4o-mini",
15+
tools: [GetWeather],
16+
messages: [
17+
{role: :user, content: "Call get_weather with location San Francisco in JSON."}
18+
]
19+
)
20+
21+
stream.each do |event|
22+
case event
23+
when OpenAI::Streaming::ChatFunctionToolCallArgumentsDeltaEvent
24+
puts("delta: #{event.arguments_delta}")
25+
pp(event.parsed)
26+
when OpenAI::Streaming::ChatFunctionToolCallArgumentsDoneEvent
27+
puts("--- Tool call finalized ---")
28+
puts("name: #{event.name}")
29+
puts("args: #{event.arguments}")
30+
pp(event.parsed)
31+
end
32+
end

0 commit comments

Comments
 (0)