Skip to content
Draft
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
77 changes: 77 additions & 0 deletions src/xAI.Tests/ChatClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Text.Json.Nodes;
using Azure;
using Devlooped.Extensions.AI;
using Grpc.Core;
using Microsoft.Extensions.AI;
using Moq;
using Tests.Client.Helpers;
Expand Down Expand Up @@ -520,5 +521,81 @@ public async Task AskFiles()

}

[Fact]
public async Task GrokSetsToolCallIdFromFunctionResultContent()
{
GetCompletionsRequest? capturedRequest = null;
var client = new Mock<xAI.Protocol.Chat.ChatClient>(MockBehavior.Strict);
client.Setup(x => x.GetCompletionAsync(It.IsAny<GetCompletionsRequest>(), null, null, CancellationToken.None))
.Callback<GetCompletionsRequest, Metadata?, DateTime?, CancellationToken>((req, _, _, _) => capturedRequest = req)
.Returns(CallHelpers.CreateAsyncUnaryCall(new GetChatCompletionResponse
{
Outputs =
{
new CompletionOutput
{
Message = new CompletionMessage
{
Content = "Done"
}
}
}
}));

var grok = new GrokChatClient(client.Object, "grok-4-1-fast");
var messages = new List<ChatMessage>
{
new(ChatRole.User, "What's the time?"),
new(ChatRole.Assistant, [new FunctionCallContent("call-123", "get_time")]),
new(ChatRole.Tool, [new FunctionResultContent("call-123", "2024-01-01T00:00:00Z")]),
};

await grok.GetResponseAsync(messages);

Assert.NotNull(capturedRequest);
var toolMessage = capturedRequest.Messages.FirstOrDefault(m => m.Role == MessageRole.RoleTool);
Assert.NotNull(toolMessage);
Assert.Equal("call-123", toolMessage.ToolCallId);
}

[Fact]
public async Task GrokSetsToolCallIdOnlyWhenCallIdIsProvided()
{
GetCompletionsRequest? capturedRequest = null;
var client = new Mock<xAI.Protocol.Chat.ChatClient>(MockBehavior.Strict);
client.Setup(x => x.GetCompletionAsync(It.IsAny<GetCompletionsRequest>(), null, null, CancellationToken.None))
.Callback<GetCompletionsRequest, Metadata?, DateTime?, CancellationToken>((req, _, _, _) => capturedRequest = req)
.Returns(CallHelpers.CreateAsyncUnaryCall(new GetChatCompletionResponse
{
Outputs =
{
new CompletionOutput
{
Message = new CompletionMessage
{
Content = "Done"
}
}
}
}));

var grok = new GrokChatClient(client.Object, "grok-4-1-fast");
var messages = new List<ChatMessage>
{
new(ChatRole.User, "What's the time?"),
new(ChatRole.Assistant, [new FunctionCallContent("call-456", "get_time")]),
// FunctionResultContent with empty CallId
new(ChatRole.Tool, [new FunctionResultContent("", "2024-01-01T00:00:00Z")]),
};

await grok.GetResponseAsync(messages);

Assert.NotNull(capturedRequest);
var toolMessage = capturedRequest.Messages.FirstOrDefault(m => m.Role == MessageRole.RoleTool);
Assert.NotNull(toolMessage);
// ToolCallId should not be set if CallId is empty
Assert.False(toolMessage.HasToolCallId);
}

record Response(DateOnly Today, string Release, decimal Price);
}
7 changes: 5 additions & 2 deletions src/xAI/GrokChatClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,11 +176,14 @@ GetCompletionsRequest MapToRequest(IEnumerable<ChatMessage> messages, ChatOption
}
else if (content is FunctionResultContent resultContent)
{
request.Messages.Add(new Message
var msg = new Message
{
Role = MessageRole.RoleTool,
Content = { new Content { Text = JsonSerializer.Serialize(resultContent.Result) ?? "null" } }
});
};
if (resultContent.CallId is { Length: > 0 } callId)
msg.ToolCallId = callId;
request.Messages.Add(msg);
}
else if (content is McpServerToolResultContent mcpResult &&
mcpResult.RawRepresentation is ToolCall mcpToolCall &&
Expand Down