Skip to content

Commit d4a9e3b

Browse files
authored
feat(compose): unify FunctionToolResult with Blocks (#968)
Replace FunctionToolResult.Result string field with Blocks []*FunctionToolResultBlock to uniformly represent all tool results (text-only and multimodal) as structured content blocks. - Add FunctionToolResultBlock type supporting text, image, audio, video, and file content with String() method - Remove FunctionToolResult.Result field; text results are now wrapped as FunctionToolResultBlock{Text: ...} - Update FunctionToolResultAgenticMessage to accept blocks parameter - Convert MessageInputPart to FunctionToolResultBlock in compose layer - Update concatFunctionToolResults to merge via Blocks append - Add comprehensive tests for multimodal and streaming tool results
1 parent 96357c3 commit d4a9e3b

6 files changed

Lines changed: 687 additions & 282 deletions

File tree

compose/agentic_tools_node.go

Lines changed: 100 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,21 @@ func agenticMessageToToolCallMessage(input *schema.AgenticMessage) *schema.Messa
8282
func toolMessageToAgenticMessage(input []*schema.Message) []*schema.AgenticMessage {
8383
var results []*schema.ContentBlock
8484
for _, m := range input {
85+
ftr := &schema.FunctionToolResult{
86+
CallID: m.ToolCallID,
87+
Name: m.ToolName,
88+
}
89+
if len(m.UserInputMultiContent) > 0 {
90+
ftr.Blocks = messageInputPartsToFunctionToolBlocks(m.UserInputMultiContent)
91+
} else if m.Content != "" {
92+
ftr.Blocks = []*schema.FunctionToolResultBlock{
93+
{Text: &schema.UserInputText{Text: m.Content}},
94+
}
95+
}
8596
results = append(results, &schema.ContentBlock{
86-
Type: schema.ContentBlockTypeFunctionToolResult,
87-
FunctionToolResult: &schema.FunctionToolResult{
88-
CallID: m.ToolCallID,
89-
Name: m.ToolName,
90-
Result: m.Content,
91-
},
92-
Extra: m.Extra,
97+
Type: schema.ContentBlockTypeFunctionToolResult,
98+
FunctionToolResult: ftr,
99+
Extra: m.Extra,
93100
})
94101
}
95102
return []*schema.AgenticMessage{{
@@ -105,15 +112,22 @@ func streamToolMessageToAgenticMessage(input *schema.StreamReader[[]*schema.Mess
105112
if m == nil {
106113
continue
107114
}
115+
ftr := &schema.FunctionToolResult{
116+
CallID: m.ToolCallID,
117+
Name: m.ToolName,
118+
}
119+
if len(m.UserInputMultiContent) > 0 {
120+
ftr.Blocks = messageInputPartsToFunctionToolBlocks(m.UserInputMultiContent)
121+
} else if m.Content != "" {
122+
ftr.Blocks = []*schema.FunctionToolResultBlock{
123+
{Text: &schema.UserInputText{Text: m.Content}},
124+
}
125+
}
108126
results = append(results, &schema.ContentBlock{
109-
Type: schema.ContentBlockTypeFunctionToolResult,
110-
FunctionToolResult: &schema.FunctionToolResult{
111-
CallID: m.ToolCallID,
112-
Name: m.ToolName,
113-
Result: m.Content,
114-
},
115-
StreamingMeta: &schema.StreamingMeta{Index: i},
116-
Extra: m.Extra,
127+
Type: schema.ContentBlockTypeFunctionToolResult,
128+
FunctionToolResult: ftr,
129+
StreamingMeta: &schema.StreamingMeta{Index: i},
130+
Extra: m.Extra,
117131
})
118132
}
119133
return []*schema.AgenticMessage{{
@@ -123,4 +137,75 @@ func streamToolMessageToAgenticMessage(input *schema.StreamReader[[]*schema.Mess
123137
})
124138
}
125139

140+
func messageInputPartsToFunctionToolBlocks(parts []schema.MessageInputPart) []*schema.FunctionToolResultBlock {
141+
blocks := make([]*schema.FunctionToolResultBlock, 0, len(parts))
142+
for _, p := range parts {
143+
var block *schema.FunctionToolResultBlock
144+
switch p.Type {
145+
case schema.ChatMessagePartTypeText:
146+
block = &schema.FunctionToolResultBlock{
147+
Text: &schema.UserInputText{Text: p.Text},
148+
Extra: p.Extra,
149+
}
150+
case schema.ChatMessagePartTypeImageURL:
151+
if p.Image != nil {
152+
block = &schema.FunctionToolResultBlock{
153+
Image: &schema.UserInputImage{
154+
URL: derefString(p.Image.URL),
155+
Base64Data: derefString(p.Image.Base64Data),
156+
MIMEType: p.Image.MIMEType,
157+
Detail: p.Image.Detail,
158+
},
159+
Extra: p.Extra,
160+
}
161+
}
162+
case schema.ChatMessagePartTypeAudioURL:
163+
if p.Audio != nil {
164+
block = &schema.FunctionToolResultBlock{
165+
Audio: &schema.UserInputAudio{
166+
URL: derefString(p.Audio.URL),
167+
Base64Data: derefString(p.Audio.Base64Data),
168+
MIMEType: p.Audio.MIMEType,
169+
},
170+
Extra: p.Extra,
171+
}
172+
}
173+
case schema.ChatMessagePartTypeVideoURL:
174+
if p.Video != nil {
175+
block = &schema.FunctionToolResultBlock{
176+
Video: &schema.UserInputVideo{
177+
URL: derefString(p.Video.URL),
178+
Base64Data: derefString(p.Video.Base64Data),
179+
MIMEType: p.Video.MIMEType,
180+
},
181+
Extra: p.Extra,
182+
}
183+
}
184+
case schema.ChatMessagePartTypeFileURL:
185+
if p.File != nil {
186+
block = &schema.FunctionToolResultBlock{
187+
File: &schema.UserInputFile{
188+
URL: derefString(p.File.URL),
189+
Base64Data: derefString(p.File.Base64Data),
190+
Name: p.File.Name,
191+
MIMEType: p.File.MIMEType,
192+
},
193+
Extra: p.Extra,
194+
}
195+
}
196+
}
197+
if block != nil {
198+
blocks = append(blocks, block)
199+
}
200+
}
201+
return blocks
202+
}
203+
204+
func derefString(s *string) string {
205+
if s == nil {
206+
return ""
207+
}
208+
return *s
209+
}
210+
126211
func (a *AgenticToolsNode) GetType() string { return "" }

0 commit comments

Comments
 (0)