Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -399,4 +399,78 @@ private void AddMessagesWithFunctionCallsWithValidFunctionName()
}), false);
}
}

[Fact]
public void NonInvocableToolHasValidParametersSchema()
{
// Arrange & Act
// Access the NonInvocableTool through reflection since it's protected
var clientCoreType = typeof(ClientCore);
var nonInvocableToolField = clientCoreType.GetField("s_nonInvocableFunctionTool",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);

Assert.NotNull(nonInvocableToolField);

var nonInvocableTool = (ChatTool)nonInvocableToolField.GetValue(null)!;

// Assert
Assert.NotNull(nonInvocableTool);
Assert.Equal("NonInvocableTool", nonInvocableTool.FunctionName);
Assert.Equal("A placeholder tool used when no real tools are available", nonInvocableTool.FunctionDescription);

// Verify that parameters are not null (this is the key fix for Mistral compatibility)
Assert.NotNull(nonInvocableTool.FunctionParameters);

// Verify the parameters contain a valid JSON schema
var parametersJson = nonInvocableTool.FunctionParameters.ToString();
Assert.Contains("\"type\":\"object\"", parametersJson);
Assert.Contains("\"required\":[]", parametersJson);
Assert.Contains("\"properties\":{}", parametersJson);

// Verify it's valid JSON
var parsedJson = JsonSerializer.Deserialize<JsonElement>(parametersJson);
Assert.Equal(JsonValueKind.Object, parsedJson.ValueKind);
Assert.True(parsedJson.TryGetProperty("type", out var typeProperty));
Assert.Equal("object", typeProperty.GetString());
Assert.True(parsedJson.TryGetProperty("required", out var requiredProperty));
Assert.Equal(JsonValueKind.Array, requiredProperty.ValueKind);
Assert.Equal(0, requiredProperty.GetArrayLength());
Assert.True(parsedJson.TryGetProperty("properties", out var propertiesProperty));
Assert.Equal(JsonValueKind.Object, propertiesProperty.ValueKind);
}

[Fact]
public void NonInvocableToolSchemaIsCompatibleWithMistral()
{
// Arrange & Act
var clientCoreType = typeof(ClientCore);
var nonInvocableToolField = clientCoreType.GetField("s_nonInvocableFunctionTool",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);

var nonInvocableTool = (ChatTool)nonInvocableToolField!.GetValue(null)!;

// Assert
// This test verifies that the tool schema meets Mistral's requirements:
// 1. Has a parameters field (not null)
// 2. Parameters field contains valid JSON schema
// 3. Schema has required type, properties, and required fields

Assert.NotNull(nonInvocableTool.FunctionParameters);

var parametersJson = nonInvocableTool.FunctionParameters.ToString();
var schema = JsonSerializer.Deserialize<JsonElement>(parametersJson);

// Verify all required fields for Mistral compatibility
Assert.True(schema.TryGetProperty("type", out _), "Schema must have 'type' field");
Assert.True(schema.TryGetProperty("properties", out _), "Schema must have 'properties' field");
Assert.True(schema.TryGetProperty("required", out _), "Schema must have 'required' field");

// Verify the schema structure matches what Mistral expects
Assert.Equal("object", schema.GetProperty("type").GetString());
Assert.Equal(JsonValueKind.Object, schema.GetProperty("properties").ValueKind);
Assert.Equal(JsonValueKind.Array, schema.GetProperty("required").ValueKind);

// This ensures the tool won't cause 422 errors with Mistral APIs
// as described in GitHub issue #13232
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ protected record ToolCallingConfig(IList<ChatTool>? Tools, ChatToolChoice? Choic
protected const int MaxInflightAutoInvokes = 128;

/// <summary>Singleton tool used when tool call count drops to 0 but we need to supply tools to keep the service happy.</summary>
protected static readonly ChatTool s_nonInvocableFunctionTool = ChatTool.CreateFunctionTool("NonInvocableTool");
protected static readonly ChatTool s_nonInvocableFunctionTool = ChatTool.CreateFunctionTool(
functionName: "NonInvocableTool",
functionDescription: "A placeholder tool used when no real tools are available",
functionParameters: BinaryData.FromString("""{"type":"object","required":[],"properties":{}}"""));

/// <summary>
/// Instance of <see cref="Meter"/> for metrics.
Expand Down
Loading