summaryrefslogtreecommitdiff
path: root/libpanto/src/openai_chat_json.zig
diff options
context:
space:
mode:
authort <t@tjp.lol>2026-06-02 09:02:49 -0600
committert <t@tjp.lol>2026-06-02 09:49:18 -0600
commit9c64a7d4462a11674e2dea481b037b5f5d9c62fc (patch)
treea4f0a493a9b7558d40f5eb79a0eeae46ecc23515 /libpanto/src/openai_chat_json.zig
parent456d986be1357c247753d9dc21734bb898d5e78b (diff)
system prompt building and logging
Diffstat (limited to 'libpanto/src/openai_chat_json.zig')
-rw-r--r--libpanto/src/openai_chat_json.zig79
1 files changed, 79 insertions, 0 deletions
diff --git a/libpanto/src/openai_chat_json.zig b/libpanto/src/openai_chat_json.zig
index 6991559..3b81c41 100644
--- a/libpanto/src/openai_chat_json.zig
+++ b/libpanto/src/openai_chat_json.zig
@@ -140,7 +140,24 @@ pub fn serializeRequest(
try s.objectField("messages");
try s.beginArray();
+ // Hoist the effective system prompt to the front as separate,
+ // individually-positioned `system` messages (one per surviving block,
+ // in derivation order). Keeping them distinct preserves block-level
+ // addressability for `/tree`-style truncation — we deliberately do NOT
+ // concatenate them the way Anthropic's single-string format forces.
+ var sys_blocks = try conversation.effectiveSystemBlocks(allocator, conv.messages.items);
+ defer sys_blocks.deinit(allocator);
+ for (sys_blocks.items) |text| {
+ try s.beginObject();
+ try s.objectField("role");
+ try s.write("system");
+ try s.objectField("content");
+ try s.write(text);
+ try s.endObject();
+ }
+ // Then every non-system message, in its original order.
for (conv.messages.items) |msg| {
+ if (msg.role == .system) continue;
try writeMessage(&s, msg, allocator);
}
try s.endArray();
@@ -497,6 +514,68 @@ test "serializeRequest - system + user" {
try testing.expectEqualStrings("Hello!", msgs[1].object.get("content").?.string);
}
+test "serializeRequest - multiple system blocks hoisted as separate leading messages" {
+ const allocator = testing.allocator;
+
+ var conv = conversation.Conversation.init(allocator);
+ defer conv.deinit();
+
+ try conv.addSystemMessage("seed");
+ try conv.addUserMessage("Hello!");
+ try conv.addSystemMessage("mid-conversation append");
+ try conv.addUserMessage("again");
+
+ const cfg = testConfig("gpt-4o");
+ var tools = emptyTools();
+ defer tools.deinit();
+ const body = try serializeRequest(allocator, &cfg, &conv, &tools);
+ defer allocator.free(body);
+
+ var parsed = try std.json.parseFromSlice(std.json.Value, allocator, body, .{});
+ defer parsed.deinit();
+
+ const msgs = parsed.value.object.get("messages").?.array.items;
+ // Two system messages first (in derivation order), then the two users.
+ try testing.expectEqual(@as(usize, 4), msgs.len);
+ try testing.expectEqualStrings("system", msgs[0].object.get("role").?.string);
+ try testing.expectEqualStrings("seed", msgs[0].object.get("content").?.string);
+ try testing.expectEqualStrings("system", msgs[1].object.get("role").?.string);
+ try testing.expectEqualStrings("mid-conversation append", msgs[1].object.get("content").?.string);
+ try testing.expectEqualStrings("user", msgs[2].object.get("role").?.string);
+ try testing.expectEqualStrings("Hello!", msgs[2].object.get("content").?.string);
+ try testing.expectEqualStrings("user", msgs[3].object.get("role").?.string);
+ try testing.expectEqualStrings("again", msgs[3].object.get("content").?.string);
+}
+
+test "serializeRequest - replace-mode system block wipes prior system messages" {
+ const allocator = testing.allocator;
+
+ var conv = conversation.Conversation.init(allocator);
+ defer conv.deinit();
+
+ try conv.addSystemMessage("original");
+ try conv.replaceSystemMessage("fresh seed");
+ try conv.addSystemMessage("fresh append");
+ try conv.addUserMessage("Hi");
+
+ const cfg = testConfig("gpt-4o");
+ var tools = emptyTools();
+ defer tools.deinit();
+ const body = try serializeRequest(allocator, &cfg, &conv, &tools);
+ defer allocator.free(body);
+
+ var parsed = try std.json.parseFromSlice(std.json.Value, allocator, body, .{});
+ defer parsed.deinit();
+
+ const msgs = parsed.value.object.get("messages").?.array.items;
+ try testing.expectEqual(@as(usize, 3), msgs.len);
+ try testing.expectEqualStrings("system", msgs[0].object.get("role").?.string);
+ try testing.expectEqualStrings("fresh seed", msgs[0].object.get("content").?.string);
+ try testing.expectEqualStrings("system", msgs[1].object.get("role").?.string);
+ try testing.expectEqualStrings("fresh append", msgs[1].object.get("content").?.string);
+ try testing.expectEqualStrings("user", msgs[2].object.get("role").?.string);
+}
+
test "serializeRequest - assistant Thinking blocks are stripped from outbound history" {
const allocator = testing.allocator;