Skip to content

Commit d04f908

Browse files
committed
Refactor ChatTemplateFormatter to support structured message content, including text, reasoning, tool calls, and tool results. Update tests to reflect new content format and ensure compatibility with unknown types.
1 parent f74f0d8 commit d04f908

2 files changed

Lines changed: 92 additions & 93 deletions

File tree

packages/torque/src/formatter.test.ts

Lines changed: 51 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ describe("ChatTemplateFormatter", () => {
6666
expect(result.messages).toHaveLength(1);
6767
expect(result.messages[0]).toEqual({
6868
role: "user",
69-
content: "Hello",
69+
content: [{ type: "text", text: "Hello" }],
7070
});
7171
});
7272

@@ -93,15 +93,13 @@ describe("ChatTemplateFormatter", () => {
9393
expect(result.messages).toHaveLength(1);
9494
expect(result.messages[0]).toEqual({
9595
role: "assistant",
96-
content: "Thinking...",
97-
tool_calls: [
96+
content: [
97+
{ type: "text", text: "Thinking..." },
9898
{
99+
type: "tool_call",
99100
id: "call_1",
100-
type: "function",
101-
function: {
102-
name: "calc",
103-
arguments: { a: 1 },
104-
},
101+
name: "calc",
102+
arguments: { a: 1 },
105103
},
106104
],
107105
});
@@ -125,12 +123,14 @@ describe("ChatTemplateFormatter", () => {
125123
expect(result.messages).toHaveLength(1);
126124
expect(result.messages[0]).toEqual({
127125
role: "assistant",
128-
content: "Checking weather...",
129-
reasoning: "I should check the weather.",
126+
content: [
127+
{ type: "reasoning", text: "I should check the weather." },
128+
{ type: "text", text: "Checking weather..." },
129+
],
130130
});
131131
});
132132

133-
it("should flatten tool result messages", () => {
133+
it("should transform tool result messages", () => {
134134
const row = createMockRow({
135135
messages: [
136136
{
@@ -155,18 +155,49 @@ describe("ChatTemplateFormatter", () => {
155155
});
156156

157157
const result = formatter.format(row);
158-
expect(result.messages).toHaveLength(2);
158+
expect(result.messages).toHaveLength(1);
159159
expect(result.messages[0]).toEqual({
160160
role: "tool",
161-
tool_call_id: "call_1",
162-
name: "calc",
163-
content: "2",
161+
content: [
162+
{
163+
type: "tool_result",
164+
tool_call_id: "call_1",
165+
name: "calc",
166+
content: { type: "text", value: "2" },
167+
},
168+
{
169+
type: "tool_result",
170+
tool_call_id: "call_2",
171+
name: "calc",
172+
content: { type: "text", value: "4" },
173+
},
174+
],
164175
});
165-
expect(result.messages[1]).toEqual({
166-
role: "tool",
167-
tool_call_id: "call_2",
168-
name: "calc",
169-
content: "4",
176+
});
177+
178+
it("should passthrough unknown types", () => {
179+
const videoObject = { id: "vid_1" };
180+
const row = createMockRow({
181+
messages: [
182+
{
183+
role: "user",
184+
content: [
185+
{ type: "video", video: videoObject } as any,
186+
{ type: "text", text: "What do you see?" },
187+
],
188+
generationId: "1",
189+
},
190+
],
191+
});
192+
193+
const result = formatter.format(row);
194+
expect(result.messages).toHaveLength(1);
195+
expect(result.messages[0]).toEqual({
196+
role: "user",
197+
content: [
198+
{ type: "video", video: videoObject },
199+
{ type: "text", text: "What do you see?" },
200+
],
170201
});
171202
});
172203
});

packages/torque/src/formatter.ts

Lines changed: 41 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -34,83 +34,51 @@ export class ChatTemplateFormatter implements IDatasetFormatter {
3434
},
3535
}));
3636

37-
const messages = row.messages.flatMap((msg) => {
38-
if (msg.role === "tool") {
39-
// Flatten tool results
40-
if (Array.isArray(msg.content)) {
41-
return msg.content
42-
.map((part: any) => {
43-
if (part.type === "tool-result") {
44-
return {
45-
role: "tool",
46-
tool_call_id: part.toolCallId,
47-
name: part.toolName,
48-
content: JSON.stringify(part.output),
49-
};
50-
}
51-
return null;
52-
})
53-
.filter(Boolean);
54-
}
55-
return [];
56-
}
57-
58-
if (msg.role === "assistant") {
59-
const toolCalls: any[] = [];
60-
let contentString = "";
61-
let reasoningString = "";
37+
const messages = row.messages.map((msg) => {
38+
let contentParts: any[] = [];
6239

63-
if (Array.isArray(msg.content)) {
64-
for (const part of msg.content) {
65-
if (part.type === "tool-call") {
66-
toolCalls.push({
67-
id: part.toolCallId,
68-
type: "function",
69-
function: {
70-
name: part.toolName,
71-
arguments: part.input,
72-
},
73-
});
74-
} else if (part.type === "text") {
75-
contentString += part.text;
76-
} else if (part.type === "reasoning") {
77-
// ai sdk types might vary but usually it's text or reasoning
78-
reasoningString +=
79-
(part as any).text || (part as any).reasoning || "";
80-
}
40+
if (typeof msg.content === "string") {
41+
contentParts = [{ type: "text", text: msg.content }];
42+
} else if (Array.isArray(msg.content)) {
43+
contentParts = msg.content.map((part: any) => {
44+
if (part.type === "text") {
45+
return { type: "text", text: part.text };
8146
}
82-
} else if (typeof msg.content === "string") {
83-
contentString = msg.content;
84-
}
85-
86-
const newMsg: any = {
87-
role: "assistant",
88-
content: contentString || null,
89-
};
90-
if (toolCalls.length > 0) {
91-
newMsg.tool_calls = toolCalls;
92-
}
93-
if (reasoningString) {
94-
newMsg.reasoning = reasoningString;
95-
}
96-
return [newMsg];
97-
}
98-
99-
// User / System
100-
let content = msg.content;
101-
if (Array.isArray(content)) {
102-
// Ensure content parts are compatible
103-
// OpenAI accepts array of text/image parts.
104-
// We'll assume they are compatible or simplify if needed.
105-
// For now, pass through.
47+
if (part.type === "image") {
48+
// Pass through image parts
49+
return { ...part };
50+
}
51+
if (part.type === "reasoning") {
52+
return {
53+
type: "reasoning",
54+
text: part.text || part.reasoning || "",
55+
};
56+
}
57+
if (part.type === "tool-call") {
58+
return {
59+
type: "tool_call",
60+
id: part.toolCallId,
61+
name: part.toolName,
62+
arguments: part.input, // Assuming input is the arguments object
63+
};
64+
}
65+
if (part.type === "tool-result") {
66+
return {
67+
type: "tool_result",
68+
tool_call_id: part.toolCallId,
69+
name: part.toolName,
70+
content: part.output || part.result,
71+
};
72+
}
73+
// Pass through any other parts (e.g. video)
74+
return part;
75+
});
10676
}
10777

108-
return [
109-
{
110-
role: msg.role,
111-
content,
112-
},
113-
];
78+
return {
79+
role: msg.role,
80+
content: contentParts,
81+
};
11482
});
11583

11684
return { tools, messages };

0 commit comments

Comments
 (0)