Skip to content

Commit 956d133

Browse files
authored
Support DataContent and UriContent (images) in GrokChatClient messages (#76)
Fix GrokChatClient to map DataContent and UriContent (images) to ImageUrlContent
1 parent ff49e61 commit 956d133

2 files changed

Lines changed: 82 additions & 0 deletions

File tree

src/xAI.Tests/ChatClientTests.cs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,5 +597,79 @@ public async Task GrokSetsToolCallIdOnlyWhenCallIdIsProvided()
597597
Assert.False(toolMessage.HasToolCallId);
598598
}
599599

600+
[Fact]
601+
public async Task GrokSendsDataContentAsBase64ImageUrl()
602+
{
603+
GetCompletionsRequest? capturedRequest = null;
604+
var client = new Mock<xAI.Protocol.Chat.ChatClient>(MockBehavior.Strict);
605+
client.Setup(x => x.GetCompletionAsync(It.IsAny<GetCompletionsRequest>(), null, null, CancellationToken.None))
606+
.Callback<GetCompletionsRequest, Metadata?, DateTime?, CancellationToken>((req, _, _, _) => capturedRequest = req)
607+
.Returns(CallHelpers.CreateAsyncUnaryCall(new GetChatCompletionResponse
608+
{
609+
Outputs =
610+
{
611+
new CompletionOutput
612+
{
613+
Message = new CompletionMessage { Content = "I see an image." }
614+
}
615+
}
616+
}));
617+
618+
var imageBytes = new byte[] { 1, 2, 3, 4, 5 };
619+
var grok = new GrokChatClient(client.Object, "grok-4-1-fast");
620+
var messages = new List<ChatMessage>
621+
{
622+
new(ChatRole.User, [new TextContent("What do you see?"), new DataContent(imageBytes, "image/png")]),
623+
};
624+
625+
await grok.GetResponseAsync(messages);
626+
627+
Assert.NotNull(capturedRequest);
628+
var userMessage = capturedRequest.Messages.FirstOrDefault(m => m.Role == MessageRole.RoleUser);
629+
Assert.NotNull(userMessage);
630+
Assert.Equal(2, userMessage.Content.Count);
631+
Assert.Equal("What do you see?", userMessage.Content[0].Text);
632+
var imageContent = userMessage.Content[1].ImageUrl;
633+
Assert.NotNull(imageContent);
634+
Assert.Equal($"data:image/png;base64,{Convert.ToBase64String(imageBytes)}", imageContent.ImageUrl);
635+
}
636+
637+
[Fact]
638+
public async Task GrokSendsUriContentAsImageUrl()
639+
{
640+
GetCompletionsRequest? capturedRequest = null;
641+
var client = new Mock<xAI.Protocol.Chat.ChatClient>(MockBehavior.Strict);
642+
client.Setup(x => x.GetCompletionAsync(It.IsAny<GetCompletionsRequest>(), null, null, CancellationToken.None))
643+
.Callback<GetCompletionsRequest, Metadata?, DateTime?, CancellationToken>((req, _, _, _) => capturedRequest = req)
644+
.Returns(CallHelpers.CreateAsyncUnaryCall(new GetChatCompletionResponse
645+
{
646+
Outputs =
647+
{
648+
new CompletionOutput
649+
{
650+
Message = new CompletionMessage { Content = "I see an image." }
651+
}
652+
}
653+
}));
654+
655+
var imageUri = new Uri("https://example.com/photo.jpg");
656+
var grok = new GrokChatClient(client.Object, "grok-4-1-fast");
657+
var messages = new List<ChatMessage>
658+
{
659+
new(ChatRole.User, [new TextContent("What do you see?"), new UriContent(imageUri, "image/jpeg")]),
660+
};
661+
662+
await grok.GetResponseAsync(messages);
663+
664+
Assert.NotNull(capturedRequest);
665+
var userMessage = capturedRequest.Messages.FirstOrDefault(m => m.Role == MessageRole.RoleUser);
666+
Assert.NotNull(userMessage);
667+
Assert.Equal(2, userMessage.Content.Count);
668+
Assert.Equal("What do you see?", userMessage.Content[0].Text);
669+
var imageContent = userMessage.Content[1].ImageUrl;
670+
Assert.NotNull(imageContent);
671+
Assert.Equal(imageUri.ToString(), imageContent.ImageUrl);
672+
}
673+
600674
record Response(DateOnly Today, string Release, decimal Price);
601675
}

src/xAI/GrokChatClient.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,14 @@ GetCompletionsRequest MapToRequest(IEnumerable<ChatMessage> messages, ChatOption
157157
{
158158
gmsg.Content.Add(new Content { Text = textContent.Text });
159159
}
160+
else if (content is DataContent dataContent && dataContent.HasTopLevelMediaType("image"))
161+
{
162+
gmsg.Content.Add(new Content { ImageUrl = new ImageUrlContent { ImageUrl = $"data:{dataContent.MediaType};base64,{Convert.ToBase64String(dataContent.Data.Span)}" } });
163+
}
164+
else if (content is UriContent uriContent && uriContent.HasTopLevelMediaType("image"))
165+
{
166+
gmsg.Content.Add(new Content { ImageUrl = new ImageUrlContent { ImageUrl = uriContent.Uri.ToString() } });
167+
}
160168
else if (content.RawRepresentation is ToolCall toolCall)
161169
{
162170
gmsg.ToolCalls.Add(toolCall);

0 commit comments

Comments
 (0)