From 9c64a7d4462a11674e2dea481b037b5f5d9c62fc Mon Sep 17 00:00:00 2001 From: t Date: Tue, 2 Jun 2026 09:02:49 -0600 Subject: system prompt building and logging --- libpanto/src/openai_chat_json.zig | 79 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) (limited to 'libpanto/src/openai_chat_json.zig') 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; -- cgit v1.3