Skip to content

Malformed tool call request format #440

@ForkBug

Description

@ForkBug

Service

OpenAI

Describe the bug

When send a request with tool calls, the request json format is malformed json, And the json schema doesn't follow OpenAI doc

Steps to reproduce

  1. Send request by the following code:
        private static string GetCurrentLocation()
        {
            // Call the location API here.
            return "San Francisco";
        }

        private static string GetCurrentWeather(string location, string unit = "celsius")
        {
            // Call the weather API here.
            return $"31 {unit}";
        }

        private static readonly ChatTool getCurrentLocationTool = ChatTool.CreateFunctionTool(
            functionName: nameof(GetCurrentLocation),
            functionDescription: "Get the user's current location"
        );

        private static readonly ChatTool getCurrentWeatherTool = ChatTool.CreateFunctionTool(
            functionName: nameof(GetCurrentWeather),
            functionDescription: "Get the current weather in a given location",
            functionParameters: BinaryData.FromBytes("""
                {                
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "The city and state, e.g. Boston, MA"
                    },
                    "unit": {
                        "type": "string",
                        "enum": [ "celsius", "fahrenheit" ],
                        "description": "The temperature unit to use. Infer this from the specified location."
                    }
                },
                "required": [ "location" ]
            }
            """u8.ToArray())
        );
static async Task Main(string[] args)
        {
            // Initialize logger factory with console and file output
            // Configure Serilog
            var serilogLogger = new LoggerConfiguration()
                .MinimumLevel.Debug()
                .WriteTo.Console()
                .WriteTo.File("logs/api-calls.log", rollingInterval: RollingInterval.Day)
                .CreateLogger();

            // Create logger factory with Serilog
            var loggerFactory = new SerilogLoggerFactory(serilogLogger);
            
            // Create a logger for the main program
            var logger = loggerFactory.CreateLogger("Program");
            logger.LogInformation("Application started");
            
            // Create a logger for HTTP client messages
            var httpLogger = loggerFactory.CreateLogger("HttpClient.RawMessages");
            var innerHandler = new HttpClientHandler();
            var loggingHandler = new LoggingHttpMessageHandler(innerHandler, httpLogger, logHeaders: true, logContent: true);
            var httpClient = new HttpClient(loggingHandler);
            
            try
            {
                logger.LogDebug("Configuring OpenAI client");
                var opt = new OpenAIClientOptions
                {
                    Endpoint = new Uri(@"https://openrouter.ai/api/v1"),
                    Transport = new HttpClientPipelineTransport(httpClient, true, loggerFactory)
                };

                string? apiKey = @"Your key";
                if (string.IsNullOrEmpty(apiKey))
                {
                    logger.LogError("OPENAI_API_KEY environment variable is not set");
                    Console.WriteLine("Error: OPENAI_API_KEY environment variable is not set");
                    return;
                }

                logger.LogInformation("Creating ChatClient");
                var openaic = new OpenAIClient(new ApiKeyCredential(apiKey), opt);
                ChatClient chatClient = openaic.GetChatClient("gpt-4o");

                // Create a list of messages for the conversation
                List<ChatMessage> messages = 
                [
                    new UserChatMessage("What's the weather like today?"),
                ];

                // Create options with tools
                ChatCompletionOptions options = new()
                {
                    ToolChoice= ChatToolChoice.CreateRequiredChoice(),
                    Tools = { getCurrentLocationTool, getCurrentWeatherTool },
                };

                logger.LogInformation("Starting chat completion with tool calls");
                
                bool requiresAction;

                do
                {
                    requiresAction = false;
                    logger.LogInformation("Sending chat completion request with streaming");
                    
                    // Using synchronous streaming approach
                    Console.WriteLine("\n[USING SYNCHRONOUS STREAMING]\n");
                    var completionUpdates = chatClient.CompleteChatStreamingAsync(messages, options);
                    
                    Console.Write($"[ASSISTANT]: ");
                    string fullContent = "";
                    List<StreamingChatToolCallUpdate> toolCalls = new ();
                    ChatFinishReason finishReason = ChatFinishReason.Stop;
                    
                    await foreach (var completionUpdate in completionUpdates)
                    {
                        // Process content updates
                        if (completionUpdate.ContentUpdate.Count > 0)
                        {
                            Console.Write(completionUpdate.ContentUpdate[0].Text);
                            fullContent += completionUpdate.ContentUpdate[0].Text;
                        }
                        
                        // Process tool calls
                        if (completionUpdate.ToolCallUpdates!= null)
                        {
                            foreach (var toolCallUpdate in completionUpdate.ToolCallUpdates)
                            {
                                // Add or update tool calls as they come in
                                var existingToolCall = toolCalls.FirstOrDefault(tc => tc.ToolCallId == toolCallUpdate.ToolCallId);
                                if (existingToolCall == null)
                                {
                                    toolCalls.Add(toolCallUpdate);
                                }
                            }
                        }
                        
                        // Update finish reason if provided
                        if (completionUpdate.FinishReason.HasValue)
                        {
                            finishReason = completionUpdate.FinishReason.Value;
                        }
                    }
                    Console.WriteLine(); // Add a newline after streaming content
                    
                } while (requiresAction);
                
                // Demonstrate the async streaming approach (without tool calls for simplicity)
                Console.WriteLine("\n[USING ASYNCHRONOUS STREAMING]\n");
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "An error occurred while processing the request");
                Console.WriteLine($"Error: {ex.Message}");
            }
            
            logger.LogInformation("Application completed");
        }

LoggingHttpMessageHandler is not included. Any LLM generated class will work.

 /// <summary>
 /// A delegating handler that logs HTTP requests and responses
 /// </summary>
 public class LoggingHttpMessageHandler : DelegatingHandler `

Replace apiKey
2. We got this log:

HTTP Request: ec5c888e-2e27-453e-8c21-23a70f315f7d
Method: POST
Uri: https://openrouter.ai/api/v1/chat/completions
Headers:
  Accept: application/json
  OpenAI-Beta: assistants=v2
  User-Agent: OpenAI/2.1.0, (.NET 9.0.5; Microsoft Windows 10.0)
  Authorization: Bearer key
  Content-Type: application/json
Content:
{"messages":[{"role":"user","content":"What\u0027s the weather like today?"}],"model":"gpt-4o","stream":true,"stream_options":{"include_usage":true},"tools":[{"type":"function","function":{"description":"Get the user\u0027s current location","name":"GetCurrentLocation"}},{"type":"function","function":{"description":"Get the current weather in a given location","name":"GetCurrentWeather","parameters":    {                
    "type": "object",
    "properties": {
        "location": {
            "type": "string",
            "description": "The city and state, e.g. Boston, MA"
        },
        "unit": {
            "type": "string",
            "enum": [ "celsius", "fahrenheit" ],
            "description": "The temperature unit to use. Infer this from the specified location."
        }
    },
    "required": [ "location" ]
}}],"tool_choice":"required"}

  1. Formated Json:
{
    "messages": [
        {
            "role": "user",
            "content": "What\u0027s the weather like today?"
        }
    ],
    "model": "gpt-4o",
    "stream": true,
    "stream_options": {
        "include_usage": true
    },
    "tools": [
        {
            "type": "function",
            "function": {
                "description": "Get the user\u0027s current location",
                "name": "GetCurrentLocation"
            }
        },
        {
            "type": "function",
            "function": {
                "description": "Get the current weather in a given location",
                "name": "GetCurrentWeather",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "location": {
                            "type": "string",
                            "description": "The city and state, e.g. Boston, MA"
                        },
                        "unit": {
                            "type": "string",
                            "enum": [
                                "celsius",
                                "fahrenheit"
                            ],
                            "description": "The temperature unit to use. Infer this from the specified location."
                        }
                    },
                    "required": [
                        "location"
                    ]
                }
            }
        ],
        "tool_choice": "required"
    }

There is an extra "function": { in the description of functions, according to https://platform.openai.com/docs/api-reference/responses/create
And the json braces are uneven.

OS

Win 10

.NET version

9.0.5

Library version

2.1.0. But 2.2.0-beta.4 would have same issue

Metadata

Metadata

Assignees

Labels

bugCategory: Something isn't working and appears to be a defect in the client library.issue-addressedWorkflow: The OpenAI maintainers believe the issue to be addressed and ready to close.

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions