diff options
Diffstat (limited to 'libpanto/src/provider_openai_chat.zig')
| -rw-r--r-- | libpanto/src/provider_openai_chat.zig | 52 |
1 files changed, 48 insertions, 4 deletions
diff --git a/libpanto/src/provider_openai_chat.zig b/libpanto/src/provider_openai_chat.zig index f653ff6..a05c7ba 100644 --- a/libpanto/src/provider_openai_chat.zig +++ b/libpanto/src/provider_openai_chat.zig @@ -674,23 +674,23 @@ fn handleEvent( try out.push(.{ .message_start = .assistant }); } - if (d.reasoning_content) |rc| { + if (d.reasoning_content) |rc| if (rc.len > 0) { if (!state.started) { state.started = true; try out.push(.{ .message_start = .assistant }); } try state.openBlock(.thinking, out); try state.appendDelta(out, rc); - } + }; - if (d.content) |c| { + if (d.content) |c| if (c.len > 0) { if (!state.started) { state.started = true; try out.push(.{ .message_start = .assistant }); } try state.openBlock(.text, out); try state.appendDelta(out, c); - } + }; if (d.tool_calls.len > 0) { if (!state.started) { @@ -909,6 +909,50 @@ test "openai_chat: omitted stream usage yields null on message_complete" { try testing.expectEqualStrings("msg_complete[usage:null]", found.?); } +test "openai_chat: empty content alongside reasoning does not split thinking block" { + const allocator = testing.allocator; + + var conv = conversation.Conversation.init(allocator); + defer conv.deinit(); + try addUserText(&conv, "think"); + + var rec = EventRecorder{ .allocator = allocator }; + defer rec.deinit(); + + const events = [_][]const u8{ + \\{"choices":[{"delta":{"role":"assistant"}}]} + , + \\{"choices":[{"delta":{"reasoning_content":"rea","content":""}}]} + , + \\{"choices":[{"delta":{"reasoning_content":"son","content":""}}]} + , + \\{"choices":[{"delta":{"reasoning_content":"ing","content":""}}]} + , + \\{"choices":[{"delta":{},"finish_reason":"stop"}]} + , + "[DONE]", + }; + try runStreamedTurn(allocator, &conv, &rec, &events); + + const expected = [_][]const u8{ + "msg_start", + "block_start[0]:Thinking", + "delta[0]:rea", + "delta[0]:son", + "delta[0]:ing", + "block_complete[0]", + "msg_complete[usage:null]", + }; + try testing.expectEqual(expected.len, rec.events.items.len); + for (expected, rec.events.items) |want, got| { + try testing.expectEqualStrings(want, got); + } + + const asst = conv.messages.items[1]; + try testing.expectEqual(@as(usize, 1), asst.content.items.len); + try testing.expectEqualStrings("reasoning", asst.content.items[0].Thinking.text.items); +} + test "fragmented tool_call id and name are reassembled" { // Lenient OpenAI-compatible providers occasionally split `id` and // `function.name` across multiple deltas instead of sending them whole |
