fix(openai): implement parsing for real-time Responses API streaming format
This commit is contained in:
@@ -474,8 +474,30 @@ impl super::Provider for OpenAIProvider {
|
|||||||
} else {
|
} else {
|
||||||
// Responses API specific parsing for streaming
|
// Responses API specific parsing for streaming
|
||||||
let mut content = String::new();
|
let mut content = String::new();
|
||||||
|
let mut finish_reason = None;
|
||||||
|
|
||||||
// Check for output[0].content[0].text (similar to non-stream)
|
let event_type = chunk.get("type").and_then(|v| v.as_str()).unwrap_or("");
|
||||||
|
|
||||||
|
match event_type {
|
||||||
|
"response.output_text.delta" => {
|
||||||
|
if let Some(delta) = chunk.get("delta").and_then(|v| v.as_str()) {
|
||||||
|
content.push_str(delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"response.output_text.done" => {
|
||||||
|
if let Some(text) = chunk.get("text").and_then(|v| v.as_str()) {
|
||||||
|
// Some implementations send the full text at the end
|
||||||
|
// We usually prefer deltas, but if we haven't seen them, this is the fallback.
|
||||||
|
// However, if we're already yielding deltas, we might not want this.
|
||||||
|
// For now, let's just use it as a signal that we're done.
|
||||||
|
finish_reason = Some("stop".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"response.done" => {
|
||||||
|
finish_reason = Some("stop".to_string());
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Fallback to older nested structure if present
|
||||||
if let Some(output) = chunk.get("output").and_then(|o| o.as_array()) {
|
if let Some(output) = chunk.get("output").and_then(|o| o.as_array()) {
|
||||||
for out in output {
|
for out in output {
|
||||||
if let Some(contents) = out.get("content").and_then(|c| c.as_array()) {
|
if let Some(contents) = out.get("content").and_then(|c| c.as_array()) {
|
||||||
@@ -489,29 +511,14 @@ impl super::Provider for OpenAIProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for candidates[0].content.parts[0].text
|
|
||||||
if content.is_empty() {
|
|
||||||
if let Some(cands) = chunk.get("candidates").and_then(|c| c.as_array()) {
|
|
||||||
for c in cands {
|
|
||||||
if let Some(content_obj) = c.get("content") {
|
|
||||||
if let Some(parts) = content_obj.get("parts").and_then(|p| p.as_array()) {
|
|
||||||
for p in parts {
|
|
||||||
if let Some(t) = p.get("text").and_then(|v| v.as_str()) {
|
|
||||||
content.push_str(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !content.is_empty() {
|
if !content.is_empty() || finish_reason.is_some() {
|
||||||
yield ProviderStreamChunk {
|
yield ProviderStreamChunk {
|
||||||
content,
|
content,
|
||||||
reasoning_content: None,
|
reasoning_content: None,
|
||||||
finish_reason: None,
|
finish_reason,
|
||||||
tool_calls: None,
|
tool_calls: None,
|
||||||
model: model.clone(),
|
model: model.clone(),
|
||||||
usage: None,
|
usage: None,
|
||||||
|
|||||||
Reference in New Issue
Block a user