-
Notifications
You must be signed in to change notification settings - Fork 307
Closed
Labels
bugCategory: Something isn't working and appears to be a defect in the client library.Category: 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.Workflow: The OpenAI maintainers believe the issue to be addressed and ready to close.
Description
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
- 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"}
- 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.Category: 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.Workflow: The OpenAI maintainers believe the issue to be addressed and ready to close.