chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
* Copyright 2024 CloudWeGo Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cloudwego/eino/schema"
|
||||
)
|
||||
|
||||
// NewAgenticToolsNode creates a new AgenticToolsNode.
|
||||
// e.g.
|
||||
//
|
||||
// conf := &ToolsNodeConfig{
|
||||
// Tools: []tool.BaseTool{invokableTool1, streamableTool2},
|
||||
// }
|
||||
// toolsNode, err := NewAgenticToolsNode(ctx, conf)
|
||||
func NewAgenticToolsNode(ctx context.Context, conf *ToolsNodeConfig) (*AgenticToolsNode, error) {
|
||||
tn, err := NewToolNode(ctx, conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &AgenticToolsNode{inner: tn}, nil
|
||||
}
|
||||
|
||||
type AgenticToolsNode struct {
|
||||
inner *ToolsNode
|
||||
}
|
||||
|
||||
func (a *AgenticToolsNode) Invoke(ctx context.Context, input *schema.AgenticMessage, opts ...ToolsNodeOption) ([]*schema.AgenticMessage, error) {
|
||||
result, err := a.inner.Invoke(ctx, agenticMessageToToolCallMessage(input), opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toolMessageToAgenticMessage(result), nil
|
||||
}
|
||||
|
||||
func (a *AgenticToolsNode) Stream(ctx context.Context, input *schema.AgenticMessage,
|
||||
opts ...ToolsNodeOption) (*schema.StreamReader[[]*schema.AgenticMessage], error) {
|
||||
result, err := a.inner.Stream(ctx, agenticMessageToToolCallMessage(input), opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return streamToolMessageToAgenticMessage(result), nil
|
||||
}
|
||||
|
||||
func agenticMessageToToolCallMessage(input *schema.AgenticMessage) *schema.Message {
|
||||
var tc []schema.ToolCall
|
||||
for _, block := range input.ContentBlocks {
|
||||
if block.Type != schema.ContentBlockTypeFunctionToolCall || block.FunctionToolCall == nil {
|
||||
continue
|
||||
}
|
||||
tc = append(tc, schema.ToolCall{
|
||||
ID: block.FunctionToolCall.CallID,
|
||||
Function: schema.FunctionCall{
|
||||
Name: block.FunctionToolCall.Name,
|
||||
Arguments: block.FunctionToolCall.Arguments,
|
||||
},
|
||||
Extra: block.Extra,
|
||||
})
|
||||
}
|
||||
return &schema.Message{
|
||||
Role: schema.Assistant,
|
||||
ToolCalls: tc,
|
||||
}
|
||||
}
|
||||
|
||||
func toolMessageToAgenticMessage(input []*schema.Message) []*schema.AgenticMessage {
|
||||
results := make([]*schema.AgenticMessage, len(input))
|
||||
for i, m := range input {
|
||||
if msg, ok := toolSearchResultMessageToAgenticMessage(m, nil); ok {
|
||||
results[i] = msg
|
||||
continue
|
||||
}
|
||||
|
||||
ftr := &schema.FunctionToolResult{
|
||||
CallID: m.ToolCallID,
|
||||
Name: m.ToolName,
|
||||
}
|
||||
if len(m.UserInputMultiContent) > 0 {
|
||||
ftr.Content = messageInputPartsToFunctionToolBlocks(m.UserInputMultiContent)
|
||||
} else if m.Content != "" {
|
||||
ftr.Content = []*schema.FunctionToolResultContentBlock{
|
||||
newFuncToolResultContentBlock(&schema.UserInputText{Text: m.Content}),
|
||||
}
|
||||
}
|
||||
results[i] = &schema.AgenticMessage{
|
||||
Role: schema.AgenticRoleTypeUser,
|
||||
ContentBlocks: []*schema.ContentBlock{{
|
||||
Type: schema.ContentBlockTypeFunctionToolResult,
|
||||
FunctionToolResult: ftr,
|
||||
Extra: m.Extra,
|
||||
}},
|
||||
Extra: m.Extra,
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func streamToolMessageToAgenticMessage(input *schema.StreamReader[[]*schema.Message]) *schema.StreamReader[[]*schema.AgenticMessage] {
|
||||
return schema.StreamReaderWithConvert(input, func(t []*schema.Message) ([]*schema.AgenticMessage, error) {
|
||||
results := make([]*schema.AgenticMessage, len(t))
|
||||
for i, m := range t {
|
||||
if m == nil {
|
||||
continue
|
||||
}
|
||||
if msg, ok := toolSearchResultMessageToAgenticMessage(m, &schema.StreamingMeta{Index: i}); ok {
|
||||
results[i] = msg
|
||||
continue
|
||||
}
|
||||
|
||||
ftr := &schema.FunctionToolResult{
|
||||
CallID: m.ToolCallID,
|
||||
Name: m.ToolName,
|
||||
}
|
||||
if len(m.UserInputMultiContent) > 0 {
|
||||
ftr.Content = messageInputPartsToFunctionToolBlocks(m.UserInputMultiContent)
|
||||
} else if m.Content != "" {
|
||||
ftr.Content = []*schema.FunctionToolResultContentBlock{
|
||||
newFuncToolResultContentBlock(&schema.UserInputText{Text: m.Content}),
|
||||
}
|
||||
}
|
||||
results[i] = &schema.AgenticMessage{
|
||||
Role: schema.AgenticRoleTypeUser,
|
||||
ContentBlocks: []*schema.ContentBlock{{
|
||||
Type: schema.ContentBlockTypeFunctionToolResult,
|
||||
FunctionToolResult: ftr,
|
||||
StreamingMeta: &schema.StreamingMeta{Index: i},
|
||||
Extra: m.Extra,
|
||||
}},
|
||||
Extra: m.Extra,
|
||||
}
|
||||
}
|
||||
return results, nil
|
||||
})
|
||||
}
|
||||
|
||||
func toolSearchResultMessageToAgenticMessage(m *schema.Message, meta *schema.StreamingMeta) (*schema.AgenticMessage, bool) {
|
||||
if m == nil || len(m.UserInputMultiContent) != 1 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
part := m.UserInputMultiContent[0]
|
||||
if part.Type != schema.ChatMessagePartTypeToolSearchResult || part.ToolSearchResult == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
block := schema.NewContentBlock(&schema.ToolSearchFunctionToolResult{
|
||||
CallID: m.ToolCallID,
|
||||
Name: m.ToolName,
|
||||
Result: part.ToolSearchResult,
|
||||
})
|
||||
block.StreamingMeta = meta
|
||||
block.Extra = m.Extra
|
||||
|
||||
return &schema.AgenticMessage{
|
||||
Role: schema.AgenticRoleTypeUser,
|
||||
ContentBlocks: []*schema.ContentBlock{block},
|
||||
Extra: m.Extra,
|
||||
}, true
|
||||
}
|
||||
|
||||
func messageInputPartsToFunctionToolBlocks(parts []schema.MessageInputPart) []*schema.FunctionToolResultContentBlock {
|
||||
blocks := make([]*schema.FunctionToolResultContentBlock, 0, len(parts))
|
||||
for _, p := range parts {
|
||||
var block *schema.FunctionToolResultContentBlock
|
||||
switch p.Type {
|
||||
case schema.ChatMessagePartTypeText:
|
||||
block = newFuncToolResultContentBlock(&schema.UserInputText{Text: p.Text})
|
||||
block.Extra = p.Extra
|
||||
case schema.ChatMessagePartTypeImageURL:
|
||||
if p.Image != nil {
|
||||
block = newFuncToolResultContentBlock(&schema.UserInputImage{
|
||||
URL: derefString(p.Image.URL),
|
||||
Base64Data: derefString(p.Image.Base64Data),
|
||||
MIMEType: p.Image.MIMEType,
|
||||
Detail: p.Image.Detail,
|
||||
})
|
||||
block.Extra = p.Extra
|
||||
}
|
||||
case schema.ChatMessagePartTypeAudioURL:
|
||||
if p.Audio != nil {
|
||||
block = newFuncToolResultContentBlock(&schema.UserInputAudio{
|
||||
URL: derefString(p.Audio.URL),
|
||||
Base64Data: derefString(p.Audio.Base64Data),
|
||||
MIMEType: p.Audio.MIMEType,
|
||||
})
|
||||
block.Extra = p.Extra
|
||||
}
|
||||
case schema.ChatMessagePartTypeVideoURL:
|
||||
if p.Video != nil {
|
||||
block = newFuncToolResultContentBlock(&schema.UserInputVideo{
|
||||
URL: derefString(p.Video.URL),
|
||||
Base64Data: derefString(p.Video.Base64Data),
|
||||
MIMEType: p.Video.MIMEType,
|
||||
})
|
||||
block.Extra = p.Extra
|
||||
}
|
||||
case schema.ChatMessagePartTypeFileURL:
|
||||
if p.File != nil {
|
||||
block = newFuncToolResultContentBlock(&schema.UserInputFile{
|
||||
URL: derefString(p.File.URL),
|
||||
Base64Data: derefString(p.File.Base64Data),
|
||||
Name: p.File.Name,
|
||||
MIMEType: p.File.MIMEType,
|
||||
})
|
||||
block.Extra = p.Extra
|
||||
}
|
||||
}
|
||||
if block != nil {
|
||||
blocks = append(blocks, block)
|
||||
}
|
||||
}
|
||||
return blocks
|
||||
}
|
||||
|
||||
type userInputVariant interface {
|
||||
schema.UserInputText | schema.UserInputImage | schema.UserInputAudio | schema.UserInputVideo | schema.UserInputFile
|
||||
}
|
||||
|
||||
// newFuncToolResultContentBlock creates a FunctionToolResultContentBlock from a typed content pointer.
|
||||
func newFuncToolResultContentBlock[T userInputVariant](content *T) *schema.FunctionToolResultContentBlock {
|
||||
switch c := any(content).(type) {
|
||||
case *schema.UserInputText:
|
||||
return &schema.FunctionToolResultContentBlock{Type: schema.FunctionToolResultContentBlockTypeText, Text: c}
|
||||
case *schema.UserInputImage:
|
||||
return &schema.FunctionToolResultContentBlock{Type: schema.FunctionToolResultContentBlockTypeImage, Image: c}
|
||||
case *schema.UserInputAudio:
|
||||
return &schema.FunctionToolResultContentBlock{Type: schema.FunctionToolResultContentBlockTypeAudio, Audio: c}
|
||||
case *schema.UserInputVideo:
|
||||
return &schema.FunctionToolResultContentBlock{Type: schema.FunctionToolResultContentBlockTypeVideo, Video: c}
|
||||
case *schema.UserInputFile:
|
||||
return &schema.FunctionToolResultContentBlock{Type: schema.FunctionToolResultContentBlockTypeFile, File: c}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func derefString(s *string) string {
|
||||
if s == nil {
|
||||
return ""
|
||||
}
|
||||
return *s
|
||||
}
|
||||
|
||||
func (a *AgenticToolsNode) GetType() string { return "" }
|
||||
Reference in New Issue
Block a user