summaryrefslogtreecommitdiff
path: root/libpanto/src/anthropic_messages_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/anthropic_messages_json.zig
parent456d986be1357c247753d9dc21734bb898d5e78b (diff)
system prompt building and logging
Diffstat (limited to 'libpanto/src/anthropic_messages_json.zig')
-rw-r--r--libpanto/src/anthropic_messages_json.zig81
1 files changed, 71 insertions, 10 deletions
diff --git a/libpanto/src/anthropic_messages_json.zig b/libpanto/src/anthropic_messages_json.zig
index 5b65c2b..f8bfc2e 100644
--- a/libpanto/src/anthropic_messages_json.zig
+++ b/libpanto/src/anthropic_messages_json.zig
@@ -111,20 +111,26 @@ fn writeRawJson(s: *std.json.Stringify, raw: []const u8) !void {
try s.write(parsed.value);
}
+/// Build the top-level Anthropic `system` string. Applies the shared
+/// append/replace derivation (see `conversation.effectiveSystemBlocks`),
+/// strips trailing newlines from each surviving block, and joins them with
+/// a horizontal rule (`\n\n---\n\n`). Anthropic's wire format requires a
+/// single string, so this rule is its concession to that constraint.
fn collectSystemPrompt(
conv: *const conversation.Conversation,
out: *std.ArrayList(u8),
allocator: Allocator,
) !void {
+ var blocks = try conversation.effectiveSystemBlocks(allocator, conv.messages.items);
+ defer blocks.deinit(allocator);
+
+ const sep = "\n\n---\n\n";
var first = true;
- for (conv.messages.items) |msg| {
- if (msg.role != .system) continue;
- for (msg.content.items) |block| {
- if (block != .Text) continue;
- if (!first) try out.append(allocator, '\n');
- try out.appendSlice(allocator, block.Text.items);
- first = false;
- }
+ for (blocks.items) |text| {
+ const trimmed = std.mem.trimEnd(u8, text, "\n");
+ if (!first) try out.appendSlice(allocator, sep);
+ try out.appendSlice(allocator, trimmed);
+ first = false;
}
}
@@ -208,6 +214,11 @@ fn writeBlock(s: *std.json.Stringify, block: conversation.ContentBlock) !void {
try s.write(tr.content.items);
try s.endObject();
},
+ // System blocks never reach here: `serializeRequest` filters out
+ // `.system` messages before emitting the `messages` array, and the
+ // system text is hoisted into the top-level `system` string by
+ // `collectSystemPrompt`. Present only to keep the switch exhaustive.
+ .System => {},
}
}
@@ -526,7 +537,7 @@ test "serializeRequest - system extracted into top-level field" {
try testing.expectEqualStrings("Hello!", content[0].object.get("text").?.string);
}
-test "serializeRequest - multiple system messages concatenated with newlines" {
+test "serializeRequest - multiple system messages joined with horizontal rule" {
const allocator = testing.allocator;
var conv = conversation.Conversation.init(allocator);
@@ -545,7 +556,57 @@ test "serializeRequest - multiple system messages concatenated with newlines" {
var parsed = try std.json.parseFromSlice(std.json.Value, allocator, body, .{});
defer parsed.deinit();
try testing.expectEqualStrings(
- "Be terse.\nBe accurate.",
+ "Be terse.\n\n---\n\nBe accurate.",
+ parsed.value.object.get("system").?.string,
+ );
+}
+
+test "serializeRequest - replace-mode system block wipes prior system text" {
+ const allocator = testing.allocator;
+
+ var conv = conversation.Conversation.init(allocator);
+ defer conv.deinit();
+
+ try conv.addSystemMessage("original seed");
+ try conv.addSystemMessage("original append");
+ try conv.replaceSystemMessage("fresh seed");
+ try conv.addSystemMessage("fresh append");
+ try conv.addUserMessage("Hi");
+
+ const cfg = testConfig("claude-x");
+ var empty_tools = tool_registry_mod.ToolRegistry.init(allocator);
+ defer empty_tools.deinit();
+ const body = try serializeRequest(allocator, &cfg, &conv, &empty_tools);
+ defer allocator.free(body);
+
+ var parsed = try std.json.parseFromSlice(std.json.Value, allocator, body, .{});
+ defer parsed.deinit();
+ try testing.expectEqualStrings(
+ "fresh seed\n\n---\n\nfresh append",
+ parsed.value.object.get("system").?.string,
+ );
+}
+
+test "serializeRequest - trailing newlines stripped before the rule join" {
+ const allocator = testing.allocator;
+
+ var conv = conversation.Conversation.init(allocator);
+ defer conv.deinit();
+
+ try conv.addSystemMessage("line one\n\n");
+ try conv.addSystemMessage("line two\n");
+ try conv.addUserMessage("Hi");
+
+ const cfg = testConfig("claude-x");
+ var empty_tools = tool_registry_mod.ToolRegistry.init(allocator);
+ defer empty_tools.deinit();
+ const body = try serializeRequest(allocator, &cfg, &conv, &empty_tools);
+ defer allocator.free(body);
+
+ var parsed = try std.json.parseFromSlice(std.json.Value, allocator, body, .{});
+ defer parsed.deinit();
+ try testing.expectEqualStrings(
+ "line one\n\n---\n\nline two",
parsed.value.object.get("system").?.string,
);
}