first commit
This commit is contained in:
584
core/mcp/agentadaptors.go
Normal file
584
core/mcp/agentadaptors.go
Normal file
@@ -0,0 +1,584 @@
|
||||
package mcp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/maximhq/bifrost/core/schemas"
|
||||
)
|
||||
|
||||
// agentAPIAdapter defines the interface for API-specific operations in agent mode.
|
||||
// This adapter pattern allows the agent execution logic to work with both Chat Completions
|
||||
// and Responses APIs without requiring API-specific code in the agent loop.
|
||||
//
|
||||
// The adapter handles format conversions at the boundaries:
|
||||
// - Responses API requests/responses are converted to/from Chat API format
|
||||
// - Tool calls are extracted in Chat format for uniform processing
|
||||
// - Results are converted back to the original API format for the response
|
||||
//
|
||||
// This design ensures that:
|
||||
// 1. Tool execution logic is format-agnostic
|
||||
// 2. Both APIs have feature parity
|
||||
// 3. Conversions are localized to adapters
|
||||
// 4. The agent loop remains API-neutral
|
||||
type agentAPIAdapter interface {
|
||||
// Extract conversation history from the original request
|
||||
getConversationHistory() []interface{}
|
||||
|
||||
// Get original request
|
||||
getOriginalRequest() interface{}
|
||||
|
||||
// Get initial response
|
||||
getInitialResponse() interface{}
|
||||
|
||||
// Check if response has tool calls
|
||||
hasToolCalls(response interface{}) bool
|
||||
|
||||
// Extract tool calls from response.
|
||||
// For Chat API: Returns tool calls directly from the response.
|
||||
// For Responses API: Converts ResponsesMessage tool calls to ChatAssistantMessageToolCall for processing.
|
||||
extractToolCalls(response interface{}) []schemas.ChatAssistantMessageToolCall
|
||||
|
||||
// Add assistant message with tool calls to conversation
|
||||
addAssistantMessage(conversation []interface{}, response interface{}) []interface{}
|
||||
|
||||
// Add tool results to conversation.
|
||||
// For Chat API: Adds ChatMessage results directly.
|
||||
// For Responses API: Converts ChatMessage results to ResponsesMessage via ToResponsesToolMessage().
|
||||
addToolResults(conversation []interface{}, toolResults []*schemas.ChatMessage) []interface{}
|
||||
|
||||
// Create new request with updated conversation
|
||||
createNewRequest(conversation []interface{}) interface{}
|
||||
|
||||
// Make LLM call
|
||||
makeLLMCall(ctx *schemas.BifrostContext, request interface{}) (interface{}, *schemas.BifrostError)
|
||||
|
||||
// Create response with executed tools and non-auto-executable calls
|
||||
createResponseWithExecutedTools(
|
||||
response interface{},
|
||||
executedToolResults []*schemas.ChatMessage,
|
||||
executedToolCalls []schemas.ChatAssistantMessageToolCall,
|
||||
nonAutoExecutableToolCalls []schemas.ChatAssistantMessageToolCall,
|
||||
) interface{}
|
||||
|
||||
// extractUsage returns the token usage from a response as BifrostLLMUsage.
|
||||
extractUsage(response interface{}) *schemas.BifrostLLMUsage
|
||||
|
||||
// applyUsage sets accumulated usage on the response in place.
|
||||
applyUsage(response interface{}, usage *schemas.BifrostLLMUsage)
|
||||
}
|
||||
|
||||
// chatAPIAdapter implements agentAPIAdapter for Chat API
|
||||
type chatAPIAdapter struct {
|
||||
originalReq *schemas.BifrostChatRequest
|
||||
initialResponse *schemas.BifrostChatResponse
|
||||
makeReq func(ctx *schemas.BifrostContext, req *schemas.BifrostChatRequest) (*schemas.BifrostChatResponse, *schemas.BifrostError)
|
||||
}
|
||||
|
||||
// responsesAPIAdapter implements agentAPIAdapter for Responses API.
|
||||
// It enables the agent mode execution loop to work with Responses API requests and responses
|
||||
// by handling format conversions transparently.
|
||||
//
|
||||
// Key conversions performed:
|
||||
// - extractToolCalls(): Converts ResponsesMessage tool calls to ChatAssistantMessageToolCall
|
||||
// via BifrostResponsesResponse.ToBifrostChatResponse() and existing extraction logic
|
||||
// - addToolResults(): Converts ChatMessage tool results back to ResponsesMessage
|
||||
// via ChatMessage.ToResponsesMessages() and ToResponsesToolMessage()
|
||||
// - createNewRequest(): Builds a new BifrostResponsesRequest from converted conversation
|
||||
// - createResponseWithExecutedTools(): Creates a Responses response with results and pending tools
|
||||
//
|
||||
// This adapter enables full feature parity between Chat Completions and Responses APIs
|
||||
// for tool execution in agent mode.
|
||||
type responsesAPIAdapter struct {
|
||||
originalReq *schemas.BifrostResponsesRequest
|
||||
initialResponse *schemas.BifrostResponsesResponse
|
||||
makeReq func(ctx *schemas.BifrostContext, req *schemas.BifrostResponsesRequest) (*schemas.BifrostResponsesResponse, *schemas.BifrostError)
|
||||
}
|
||||
|
||||
// Chat API adapter implementations
|
||||
func (c *chatAPIAdapter) getConversationHistory() []interface{} {
|
||||
history := make([]interface{}, 0)
|
||||
if c.originalReq.Input != nil {
|
||||
for _, msg := range c.originalReq.Input {
|
||||
history = append(history, msg)
|
||||
}
|
||||
}
|
||||
return history
|
||||
}
|
||||
|
||||
func (c *chatAPIAdapter) getOriginalRequest() interface{} {
|
||||
return c.originalReq
|
||||
}
|
||||
|
||||
func (c *chatAPIAdapter) getInitialResponse() interface{} {
|
||||
return c.initialResponse
|
||||
}
|
||||
|
||||
func (c *chatAPIAdapter) hasToolCalls(response interface{}) bool {
|
||||
chatResponse := response.(*schemas.BifrostChatResponse)
|
||||
return hasToolCallsForChatResponse(chatResponse)
|
||||
}
|
||||
|
||||
func (c *chatAPIAdapter) extractToolCalls(response interface{}) []schemas.ChatAssistantMessageToolCall {
|
||||
chatResponse := response.(*schemas.BifrostChatResponse)
|
||||
return extractToolCalls(chatResponse)
|
||||
}
|
||||
|
||||
func (c *chatAPIAdapter) addAssistantMessage(conversation []interface{}, response interface{}) []interface{} {
|
||||
chatResponse := response.(*schemas.BifrostChatResponse)
|
||||
for _, choice := range chatResponse.Choices {
|
||||
if choice.ChatNonStreamResponseChoice != nil && choice.ChatNonStreamResponseChoice.Message != nil {
|
||||
conversation = append(conversation, *choice.ChatNonStreamResponseChoice.Message)
|
||||
}
|
||||
}
|
||||
return conversation
|
||||
}
|
||||
|
||||
func (c *chatAPIAdapter) addToolResults(conversation []interface{}, toolResults []*schemas.ChatMessage) []interface{} {
|
||||
for _, toolResult := range toolResults {
|
||||
conversation = append(conversation, *toolResult)
|
||||
}
|
||||
return conversation
|
||||
}
|
||||
|
||||
func (c *chatAPIAdapter) createNewRequest(conversation []interface{}) interface{} {
|
||||
// Convert conversation back to ChatMessage slice
|
||||
chatMessages := make([]schemas.ChatMessage, 0, len(conversation))
|
||||
for _, msg := range conversation {
|
||||
if msg == nil {
|
||||
continue
|
||||
}
|
||||
if chatMessage, ok := msg.(schemas.ChatMessage); ok {
|
||||
chatMessages = append(chatMessages, chatMessage)
|
||||
}
|
||||
}
|
||||
|
||||
return &schemas.BifrostChatRequest{
|
||||
Provider: c.originalReq.Provider,
|
||||
Model: c.originalReq.Model,
|
||||
Fallbacks: c.originalReq.Fallbacks,
|
||||
Params: c.originalReq.Params,
|
||||
Input: chatMessages,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *chatAPIAdapter) makeLLMCall(ctx *schemas.BifrostContext, request interface{}) (interface{}, *schemas.BifrostError) {
|
||||
chatRequest := request.(*schemas.BifrostChatRequest)
|
||||
return c.makeReq(ctx, chatRequest)
|
||||
}
|
||||
|
||||
func (c *chatAPIAdapter) createResponseWithExecutedTools(
|
||||
response interface{},
|
||||
executedToolResults []*schemas.ChatMessage,
|
||||
executedToolCalls []schemas.ChatAssistantMessageToolCall,
|
||||
nonAutoExecutableToolCalls []schemas.ChatAssistantMessageToolCall,
|
||||
) interface{} {
|
||||
chatResponse := response.(*schemas.BifrostChatResponse)
|
||||
return createChatResponseWithExecutedToolsAndNonAutoExecutableCalls(
|
||||
chatResponse,
|
||||
executedToolResults,
|
||||
executedToolCalls,
|
||||
nonAutoExecutableToolCalls,
|
||||
)
|
||||
}
|
||||
|
||||
func (c *chatAPIAdapter) extractUsage(response interface{}) *schemas.BifrostLLMUsage {
|
||||
return response.(*schemas.BifrostChatResponse).Usage
|
||||
}
|
||||
|
||||
func (c *chatAPIAdapter) applyUsage(response interface{}, usage *schemas.BifrostLLMUsage) {
|
||||
response.(*schemas.BifrostChatResponse).Usage = usage
|
||||
}
|
||||
|
||||
// createChatResponseWithExecutedToolsAndNonAutoExecutableCalls creates a chat response
|
||||
// that includes executed tool results and non-auto-executable tool calls. The response
|
||||
// contains a formatted text summary of executed tool results and includes the non-auto-executable
|
||||
// tool calls for the caller to handle. The finish reason is set to "stop" to prevent
|
||||
// further agent loop iterations.
|
||||
//
|
||||
// Parameters:
|
||||
// - originalResponse: The original chat response to copy metadata from
|
||||
// - executedToolResults: List of tool execution results from auto-executable tools
|
||||
// - executedToolCalls: List of tool calls that were executed
|
||||
// - nonAutoExecutableToolCalls: List of tool calls that require manual execution
|
||||
//
|
||||
// Returns:
|
||||
// - *schemas.BifrostChatResponse: A new chat response with executed results and pending tool calls
|
||||
func createChatResponseWithExecutedToolsAndNonAutoExecutableCalls(
|
||||
originalResponse *schemas.BifrostChatResponse,
|
||||
executedToolResults []*schemas.ChatMessage,
|
||||
executedToolCalls []schemas.ChatAssistantMessageToolCall,
|
||||
nonAutoExecutableToolCalls []schemas.ChatAssistantMessageToolCall,
|
||||
) *schemas.BifrostChatResponse {
|
||||
// Start with a copy of the original response metadata
|
||||
response := &schemas.BifrostChatResponse{
|
||||
ID: originalResponse.ID,
|
||||
Object: originalResponse.Object,
|
||||
Created: originalResponse.Created,
|
||||
Model: originalResponse.Model,
|
||||
Choices: make([]schemas.BifrostResponseChoice, 0),
|
||||
ServiceTier: originalResponse.ServiceTier,
|
||||
SystemFingerprint: originalResponse.SystemFingerprint,
|
||||
Usage: originalResponse.Usage,
|
||||
ExtraFields: originalResponse.ExtraFields,
|
||||
SearchResults: originalResponse.SearchResults,
|
||||
Videos: originalResponse.Videos,
|
||||
Citations: originalResponse.Citations,
|
||||
}
|
||||
|
||||
// Build a map from tool call ID to tool name for easy lookup
|
||||
toolCallIDToName := make(map[string]string)
|
||||
for _, toolCall := range executedToolCalls {
|
||||
if toolCall.ID != nil && toolCall.Function.Name != nil {
|
||||
toolCallIDToName[*toolCall.ID] = *toolCall.Function.Name
|
||||
}
|
||||
}
|
||||
|
||||
// Build content text showing executed tool results
|
||||
var contentText string
|
||||
if len(executedToolResults) > 0 {
|
||||
// Format tool results as JSON-like structure
|
||||
toolResultsMap := make(map[string]interface{})
|
||||
for _, toolResult := range executedToolResults {
|
||||
// Get tool name from tool call ID mapping
|
||||
var toolName string
|
||||
if toolResult.ChatToolMessage != nil && toolResult.ChatToolMessage.ToolCallID != nil {
|
||||
toolCallID := *toolResult.ChatToolMessage.ToolCallID
|
||||
if name, ok := toolCallIDToName[toolCallID]; ok {
|
||||
toolName = name
|
||||
} else {
|
||||
toolName = toolCallID // Fallback to tool call ID if name not found
|
||||
}
|
||||
} else {
|
||||
toolName = "unknown_tool"
|
||||
}
|
||||
|
||||
// Extract output from tool result
|
||||
var output interface{}
|
||||
if toolResult.Content != nil {
|
||||
if toolResult.Content.ContentStr != nil {
|
||||
output = *toolResult.Content.ContentStr
|
||||
} else if toolResult.Content.ContentBlocks != nil {
|
||||
// Convert content blocks to a readable format
|
||||
blocks := make([]map[string]interface{}, 0)
|
||||
for _, block := range toolResult.Content.ContentBlocks {
|
||||
blockMap := make(map[string]interface{})
|
||||
blockMap["type"] = string(block.Type)
|
||||
if block.Text != nil {
|
||||
blockMap["text"] = *block.Text
|
||||
}
|
||||
blocks = append(blocks, blockMap)
|
||||
}
|
||||
output = blocks
|
||||
}
|
||||
}
|
||||
toolResultsMap[toolName] = output
|
||||
}
|
||||
|
||||
// Convert to JSON string for display
|
||||
jsonBytes, err := schemas.MarshalSorted(toolResultsMap)
|
||||
if err != nil {
|
||||
// Fallback to simple string representation
|
||||
contentText = fmt.Sprintf("The Output from allowed tools calls is - %v\n\nNow I shall call these tools next...", toolResultsMap)
|
||||
} else {
|
||||
contentText = fmt.Sprintf("The Output from allowed tools calls is - %s\n\nNow I shall call these tools next...", string(jsonBytes))
|
||||
}
|
||||
} else {
|
||||
contentText = "Now I shall call these tools next..."
|
||||
}
|
||||
|
||||
// Create content with the formatted text
|
||||
content := &schemas.ChatMessageContent{
|
||||
ContentStr: &contentText,
|
||||
}
|
||||
|
||||
// Determine finish reason
|
||||
// Note: We set finish_reason to "stop" (not "tool_calls") for non-auto-executable tools
|
||||
// to prevent the agent loop from retrying. The tool calls are still included in the response
|
||||
// for the caller to handle, but setting finish_reason to "stop" ensures hasToolCalls returns false
|
||||
// and the agent loop exits properly.
|
||||
finishReason := "stop"
|
||||
|
||||
// Create a single choice with the formatted content and non-auto-executable tool calls
|
||||
response.Choices = append(response.Choices, schemas.BifrostResponseChoice{
|
||||
Index: 0,
|
||||
FinishReason: &finishReason,
|
||||
ChatNonStreamResponseChoice: &schemas.ChatNonStreamResponseChoice{
|
||||
Message: &schemas.ChatMessage{
|
||||
Role: schemas.ChatMessageRoleAssistant,
|
||||
Content: content,
|
||||
ChatAssistantMessage: &schemas.ChatAssistantMessage{
|
||||
ToolCalls: nonAutoExecutableToolCalls,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
// Responses API adapter implementations
|
||||
func (r *responsesAPIAdapter) getConversationHistory() []interface{} {
|
||||
history := make([]interface{}, 0)
|
||||
if r.originalReq.Input != nil {
|
||||
for _, msg := range r.originalReq.Input {
|
||||
history = append(history, msg)
|
||||
}
|
||||
}
|
||||
return history
|
||||
}
|
||||
|
||||
func (r *responsesAPIAdapter) getOriginalRequest() interface{} {
|
||||
return r.originalReq
|
||||
}
|
||||
|
||||
func (r *responsesAPIAdapter) getInitialResponse() interface{} {
|
||||
return r.initialResponse
|
||||
}
|
||||
|
||||
func (r *responsesAPIAdapter) hasToolCalls(response interface{}) bool {
|
||||
responsesResponse := response.(*schemas.BifrostResponsesResponse)
|
||||
return hasToolCallsForResponsesResponse(responsesResponse)
|
||||
}
|
||||
|
||||
func (r *responsesAPIAdapter) extractToolCalls(response interface{}) []schemas.ChatAssistantMessageToolCall {
|
||||
responsesResponse := response.(*schemas.BifrostResponsesResponse)
|
||||
// Convert to Chat format and extract tool calls using existing logic
|
||||
chatResponse := responsesResponse.ToBifrostChatResponse()
|
||||
return extractToolCalls(chatResponse)
|
||||
}
|
||||
|
||||
func (r *responsesAPIAdapter) addAssistantMessage(conversation []interface{}, response interface{}) []interface{} {
|
||||
responsesResponse := response.(*schemas.BifrostResponsesResponse)
|
||||
for _, output := range responsesResponse.Output {
|
||||
conversation = append(conversation, output)
|
||||
}
|
||||
return conversation
|
||||
}
|
||||
|
||||
func (r *responsesAPIAdapter) addToolResults(conversation []interface{}, toolResults []*schemas.ChatMessage) []interface{} {
|
||||
for _, toolResult := range toolResults {
|
||||
// Convert using existing converter
|
||||
responsesMessages := toolResult.ToResponsesMessages()
|
||||
for _, respMsg := range responsesMessages {
|
||||
conversation = append(conversation, respMsg)
|
||||
}
|
||||
}
|
||||
return conversation
|
||||
}
|
||||
|
||||
func (r *responsesAPIAdapter) createNewRequest(conversation []interface{}) interface{} {
|
||||
// Convert conversation back to ResponsesMessage slice
|
||||
responsesMessages := make([]schemas.ResponsesMessage, 0, len(conversation))
|
||||
for _, msg := range conversation {
|
||||
responsesMessages = append(responsesMessages, msg.(schemas.ResponsesMessage))
|
||||
}
|
||||
|
||||
return &schemas.BifrostResponsesRequest{
|
||||
Provider: r.originalReq.Provider,
|
||||
Model: r.originalReq.Model,
|
||||
Fallbacks: r.originalReq.Fallbacks,
|
||||
Params: r.originalReq.Params,
|
||||
Input: responsesMessages,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *responsesAPIAdapter) makeLLMCall(ctx *schemas.BifrostContext, request interface{}) (interface{}, *schemas.BifrostError) {
|
||||
responsesRequest := request.(*schemas.BifrostResponsesRequest)
|
||||
return r.makeReq(ctx, responsesRequest)
|
||||
}
|
||||
|
||||
func (r *responsesAPIAdapter) createResponseWithExecutedTools(
|
||||
response interface{},
|
||||
executedToolResults []*schemas.ChatMessage,
|
||||
executedToolCalls []schemas.ChatAssistantMessageToolCall,
|
||||
nonAutoExecutableToolCalls []schemas.ChatAssistantMessageToolCall,
|
||||
) interface{} {
|
||||
responsesResponse := response.(*schemas.BifrostResponsesResponse)
|
||||
|
||||
// Create response with executed tools directly on Responses schema
|
||||
return createResponsesResponseWithExecutedToolsAndNonAutoExecutableCalls(
|
||||
responsesResponse,
|
||||
executedToolResults,
|
||||
executedToolCalls,
|
||||
nonAutoExecutableToolCalls,
|
||||
)
|
||||
}
|
||||
|
||||
func (r *responsesAPIAdapter) extractUsage(response interface{}) *schemas.BifrostLLMUsage {
|
||||
return response.(*schemas.BifrostResponsesResponse).Usage.ToBifrostLLMUsage()
|
||||
}
|
||||
|
||||
func (r *responsesAPIAdapter) applyUsage(response interface{}, usage *schemas.BifrostLLMUsage) {
|
||||
response.(*schemas.BifrostResponsesResponse).Usage = usage.ToResponsesResponseUsage()
|
||||
}
|
||||
|
||||
// createResponsesResponseWithExecutedToolsAndNonAutoExecutableCalls creates a responses response
|
||||
// that includes executed tool results and non-auto-executable tool calls. The response
|
||||
// contains a formatted text summary of executed tool results and includes the non-auto-executable
|
||||
// tool calls for the caller to handle. All Response-specific fields are preserved.
|
||||
//
|
||||
// Parameters:
|
||||
// - originalResponse: The original responses response to copy metadata from
|
||||
// - executedToolResults: List of tool execution results from auto-executable tools
|
||||
// - executedToolCalls: List of tool calls that were executed
|
||||
// - nonAutoExecutableToolCalls: List of tool calls that require manual execution
|
||||
//
|
||||
// Returns:
|
||||
// - *schemas.BifrostResponsesResponse: A new responses response with executed results and pending tool calls
|
||||
func createResponsesResponseWithExecutedToolsAndNonAutoExecutableCalls(
|
||||
originalResponse *schemas.BifrostResponsesResponse,
|
||||
executedToolResults []*schemas.ChatMessage,
|
||||
executedToolCalls []schemas.ChatAssistantMessageToolCall,
|
||||
nonAutoExecutableToolCalls []schemas.ChatAssistantMessageToolCall,
|
||||
) *schemas.BifrostResponsesResponse {
|
||||
// Start with a copy of the original response, preserving all Response-specific fields
|
||||
response := &schemas.BifrostResponsesResponse{
|
||||
ID: originalResponse.ID,
|
||||
Background: originalResponse.Background,
|
||||
Conversation: originalResponse.Conversation,
|
||||
CreatedAt: originalResponse.CreatedAt,
|
||||
Error: originalResponse.Error,
|
||||
Include: originalResponse.Include,
|
||||
IncompleteDetails: originalResponse.IncompleteDetails,
|
||||
Instructions: originalResponse.Instructions,
|
||||
MaxOutputTokens: originalResponse.MaxOutputTokens,
|
||||
MaxToolCalls: originalResponse.MaxToolCalls,
|
||||
Metadata: originalResponse.Metadata,
|
||||
ParallelToolCalls: originalResponse.ParallelToolCalls,
|
||||
PreviousResponseID: originalResponse.PreviousResponseID,
|
||||
Prompt: originalResponse.Prompt,
|
||||
PromptCacheKey: originalResponse.PromptCacheKey,
|
||||
Reasoning: originalResponse.Reasoning,
|
||||
SafetyIdentifier: originalResponse.SafetyIdentifier,
|
||||
ServiceTier: originalResponse.ServiceTier,
|
||||
StreamOptions: originalResponse.StreamOptions,
|
||||
Store: originalResponse.Store,
|
||||
Temperature: originalResponse.Temperature,
|
||||
Text: originalResponse.Text,
|
||||
TopLogProbs: originalResponse.TopLogProbs,
|
||||
TopP: originalResponse.TopP,
|
||||
ToolChoice: originalResponse.ToolChoice,
|
||||
Tools: originalResponse.Tools,
|
||||
Truncation: originalResponse.Truncation,
|
||||
Usage: originalResponse.Usage,
|
||||
ExtraFields: originalResponse.ExtraFields,
|
||||
// Perplexity-specific fields
|
||||
SearchResults: originalResponse.SearchResults,
|
||||
Videos: originalResponse.Videos,
|
||||
Citations: originalResponse.Citations,
|
||||
Output: make([]schemas.ResponsesMessage, 0),
|
||||
}
|
||||
|
||||
// Build a map from tool call ID to tool name for easy lookup
|
||||
toolCallIDToName := make(map[string]string)
|
||||
for _, toolCall := range executedToolCalls {
|
||||
if toolCall.ID != nil && toolCall.Function.Name != nil {
|
||||
toolCallIDToName[*toolCall.ID] = *toolCall.Function.Name
|
||||
}
|
||||
}
|
||||
|
||||
// Build content text showing executed tool results
|
||||
var contentText string
|
||||
if len(executedToolResults) > 0 {
|
||||
// Format tool results as JSON-like structure
|
||||
toolResultsMap := make(map[string]interface{})
|
||||
for _, toolResult := range executedToolResults {
|
||||
// Get tool name from tool call ID mapping
|
||||
var toolName string
|
||||
if toolResult.ChatToolMessage != nil && toolResult.ChatToolMessage.ToolCallID != nil {
|
||||
toolCallID := *toolResult.ChatToolMessage.ToolCallID
|
||||
if name, ok := toolCallIDToName[toolCallID]; ok {
|
||||
toolName = name
|
||||
} else {
|
||||
toolName = toolCallID // Fallback to tool call ID if name not found
|
||||
}
|
||||
} else {
|
||||
toolName = "unknown_tool"
|
||||
}
|
||||
|
||||
// Extract output from tool result
|
||||
var output interface{}
|
||||
if toolResult.Content != nil {
|
||||
if toolResult.Content.ContentStr != nil {
|
||||
output = *toolResult.Content.ContentStr
|
||||
} else if toolResult.Content.ContentBlocks != nil {
|
||||
// Convert content blocks to a readable format
|
||||
blocks := make([]map[string]interface{}, 0)
|
||||
for _, block := range toolResult.Content.ContentBlocks {
|
||||
blockMap := make(map[string]interface{})
|
||||
blockMap["type"] = string(block.Type)
|
||||
if block.Text != nil {
|
||||
blockMap["text"] = *block.Text
|
||||
}
|
||||
blocks = append(blocks, blockMap)
|
||||
}
|
||||
output = blocks
|
||||
}
|
||||
}
|
||||
toolResultsMap[toolName] = output
|
||||
}
|
||||
|
||||
// Convert to JSON string for display
|
||||
jsonBytes, err := schemas.MarshalSorted(toolResultsMap)
|
||||
if err != nil {
|
||||
// Fallback to simple string representation
|
||||
contentText = fmt.Sprintf("The Output from allowed tools calls is - %v\n\nNow I shall call these tools next...", toolResultsMap)
|
||||
} else {
|
||||
contentText = fmt.Sprintf("The Output from allowed tools calls is - %s\n\nNow I shall call these tools next...", string(jsonBytes))
|
||||
}
|
||||
} else {
|
||||
contentText = "Now I shall call these tools next..."
|
||||
}
|
||||
|
||||
// Create assistant message with the formatted text content
|
||||
messageType := schemas.ResponsesMessageTypeMessage
|
||||
role := schemas.ResponsesInputMessageRoleAssistant
|
||||
assistantMessage := schemas.ResponsesMessage{
|
||||
Type: &messageType,
|
||||
Role: &role,
|
||||
Content: &schemas.ResponsesMessageContent{
|
||||
ContentBlocks: []schemas.ResponsesMessageContentBlock{
|
||||
{
|
||||
Type: schemas.ResponsesOutputMessageContentTypeText,
|
||||
Text: &contentText,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
response.Output = append(response.Output, assistantMessage)
|
||||
|
||||
// Add non-auto-executable tool calls as separate function_call messages
|
||||
for _, toolCall := range nonAutoExecutableToolCalls {
|
||||
functionCallType := schemas.ResponsesMessageTypeFunctionCall
|
||||
assistantRole := schemas.ResponsesInputMessageRoleAssistant
|
||||
|
||||
var callID *string
|
||||
if toolCall.ID != nil && *toolCall.ID != "" {
|
||||
callID = toolCall.ID
|
||||
}
|
||||
|
||||
var namePtr *string
|
||||
if toolCall.Function.Name != nil && *toolCall.Function.Name != "" {
|
||||
namePtr = toolCall.Function.Name
|
||||
}
|
||||
|
||||
var argumentsPtr *string
|
||||
if toolCall.Function.Arguments != "" {
|
||||
argumentsPtr = &toolCall.Function.Arguments
|
||||
}
|
||||
|
||||
toolCallMessage := schemas.ResponsesMessage{
|
||||
Type: &functionCallType,
|
||||
Role: &assistantRole,
|
||||
ResponsesToolMessage: &schemas.ResponsesToolMessage{
|
||||
CallID: callID,
|
||||
Name: namePtr,
|
||||
Arguments: argumentsPtr,
|
||||
},
|
||||
}
|
||||
|
||||
response.Output = append(response.Output, toolCallMessage)
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
Reference in New Issue
Block a user