summaryrefslogtreecommitdiff
path: root/libpanto/src/provider_openai_chat.zig
diff options
context:
space:
mode:
Diffstat (limited to 'libpanto/src/provider_openai_chat.zig')
-rw-r--r--libpanto/src/provider_openai_chat.zig52
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