summaryrefslogtreecommitdiff
path: root/libpanto/src/anthropic_messages_json.zig
diff options
context:
space:
mode:
Diffstat (limited to 'libpanto/src/anthropic_messages_json.zig')
-rw-r--r--libpanto/src/anthropic_messages_json.zig83
1 files changed, 77 insertions, 6 deletions
diff --git a/libpanto/src/anthropic_messages_json.zig b/libpanto/src/anthropic_messages_json.zig
index c54db86..8e33539 100644
--- a/libpanto/src/anthropic_messages_json.zig
+++ b/libpanto/src/anthropic_messages_json.zig
@@ -232,8 +232,39 @@ fn writeBlock(s: *std.json.Stringify, block: conversation.ContentBlock) !void {
try s.write("tool_result");
try s.objectField("tool_use_id");
try s.write(tr.tool_use_id);
+ // Anthropic accepts `content` as an array of typed blocks:
+ // text, image (base64 source), and document (PDF).
try s.objectField("content");
- try s.write(tr.content.items);
+ try s.beginArray();
+ for (tr.parts.items) |part| {
+ switch (part) {
+ .text => |tb| {
+ try s.beginObject();
+ try s.objectField("type");
+ try s.write("text");
+ try s.objectField("text");
+ try s.write(tb.items);
+ try s.endObject();
+ },
+ .media => |m| {
+ const is_pdf = std.mem.eql(u8, m.media_type, "application/pdf");
+ try s.beginObject();
+ try s.objectField("type");
+ try s.write(if (is_pdf) "document" else "image");
+ try s.objectField("source");
+ try s.beginObject();
+ try s.objectField("type");
+ try s.write("base64");
+ try s.objectField("media_type");
+ try s.write(m.media_type);
+ try s.objectField("data");
+ try s.write(m.data.items);
+ try s.endObject();
+ try s.endObject();
+ },
+ }
+ }
+ try s.endArray();
try s.endObject();
},
// System blocks never reach here: `serializeRequest` filters out
@@ -865,7 +896,7 @@ test "parseStreamEvent - unknown type" {
const tool_mod = @import("tool.zig");
const StaticToolVT = struct {
- fn invoke(_: *anyopaque, _: []const u8, _: Allocator) anyerror![]u8 {
+ fn invoke(_: *anyopaque, _: []const u8, _: Allocator) anyerror![]tool_mod.ResultPart {
return error.NotImplementedInTest;
}
fn deinit_(_: *anyopaque, _: Allocator) void {}
@@ -999,11 +1030,11 @@ test "serializeRequest - user ToolResult becomes tool_result content block" {
defer conv.deinit();
const id = try allocator.dupe(u8, "tu_1");
- var content: conversation.TextualBlock = .empty;
- try content.appendSlice(allocator, "the answer is 42");
+ var parts: std.ArrayList(conversation.ResultPartStored) = .empty;
+ try parts.append(allocator, .{ .text = try conversation.textualBlockFromSlice(allocator, "the answer is 42") });
var blocks: std.ArrayList(conversation.ContentBlock) = .empty;
- try blocks.append(allocator, .{ .ToolResult = .{ .tool_use_id = id, .content = content } });
+ try blocks.append(allocator, .{ .ToolResult = .{ .tool_use_id = id, .parts = parts } });
try conv.messages.append(allocator, .{ .role = .user, .content = blocks });
var empty_tools = tool_registry_mod.ToolRegistry.init(allocator);
@@ -1022,7 +1053,47 @@ test "serializeRequest - user ToolResult becomes tool_result content block" {
try testing.expectEqual(@as(usize, 1), arr.len);
try testing.expectEqualStrings("tool_result", arr[0].object.get("type").?.string);
try testing.expectEqualStrings("tu_1", arr[0].object.get("tool_use_id").?.string);
- try testing.expectEqualStrings("the answer is 42", arr[0].object.get("content").?.string);
+ const tr_content = arr[0].object.get("content").?.array.items;
+ try testing.expectEqual(@as(usize, 1), tr_content.len);
+ try testing.expectEqualStrings("text", tr_content[0].object.get("type").?.string);
+ try testing.expectEqualStrings("the answer is 42", tr_content[0].object.get("text").?.string);
+}
+
+test "serializeRequest - tool result with image part emits image block" {
+ const allocator = testing.allocator;
+
+ var conv = conversation.Conversation.init(allocator);
+ defer conv.deinit();
+
+ const id = try allocator.dupe(u8, "tu_img");
+ var parts: std.ArrayList(conversation.ResultPartStored) = .empty;
+ try parts.append(allocator, .{ .text = try conversation.textualBlockFromSlice(allocator, "the file:") });
+ try parts.append(allocator, .{ .media = .{
+ .media_type = try allocator.dupe(u8, "image/png"),
+ .data = try conversation.textualBlockFromSlice(allocator, "iVBOR=="),
+ } });
+ var blocks: std.ArrayList(conversation.ContentBlock) = .empty;
+ try blocks.append(allocator, .{ .ToolResult = .{ .tool_use_id = id, .parts = parts } });
+ try conv.messages.append(allocator, .{ .role = .user, .content = blocks });
+
+ var empty_tools = tool_registry_mod.ToolRegistry.init(allocator);
+ defer empty_tools.deinit();
+ const cfg = testConfig("claude-x");
+ 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();
+
+ const msg = parsed.value.object.get("messages").?.array.items[0].object;
+ const arr = msg.get("content").?.array.items[0].object.get("content").?.array.items;
+ try testing.expectEqual(@as(usize, 2), arr.len);
+ try testing.expectEqualStrings("text", arr[0].object.get("type").?.string);
+ try testing.expectEqualStrings("image", arr[1].object.get("type").?.string);
+ const src = arr[1].object.get("source").?.object;
+ try testing.expectEqualStrings("base64", src.get("type").?.string);
+ try testing.expectEqualStrings("image/png", src.get("media_type").?.string);
+ try testing.expectEqualStrings("iVBOR==", src.get("data").?.string);
}
test "serializeRequest - empty ToolUse input becomes {} not invalid JSON" {