Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields
#pragma warning disable S3254 // Default parameter values should not be passed as arguments
#pragma warning disable SA1204 // Static elements should appear before instance elements
#pragma warning disable IL2075 // 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicProperties' in call to 'System.Type.GetProperty(String)'
Comment thread
stephentoub marked this conversation as resolved.
Outdated

namespace Microsoft.Extensions.AI;

Expand Down Expand Up @@ -1196,6 +1197,11 @@ private static List<AIContent> ToAIContents(IEnumerable<ResponseContentPart> con
!string.IsNullOrWhiteSpace(part.InputImageFileId) ? new HostedFileContent(part.InputImageFileId) { MediaType = "image/*" } :
!string.IsNullOrWhiteSpace(part.InputFileId) ? new HostedFileContent(part.InputFileId) { Name = part.InputFilename } :
part.InputFileBytes is not null ? new DataContent(part.InputFileBytes, part.InputFileBytesMediaType ?? "application/octet-stream") { Name = part.InputFilename } :

// Workaround for https://github.com/openai/openai-dotnet/pull/874.
// Replace with the public property once it's available (e.g., part.InputImageUrl).
part.GetType().GetProperty("ImageUrl")?.GetValue(part, null) is string inputImageUrl && !string.IsNullOrWhiteSpace(inputImageUrl) ?
new UriContent(new Uri(inputImageUrl), "image/*") :
Comment thread
stephentoub marked this conversation as resolved.
Outdated
null;
break;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5198,6 +5198,72 @@ public async Task ResponseWithRefusalContent_ParsesCorrectly()
Assert.Equal("Refusal", errorContent.ErrorCode);
}

[Fact]
public async Task ResponseWithInputImageUrl_ParsesCorrectly()
{
const string Input = """
{
"model":"gpt-4o-mini",
"input":[{"type":"message","role":"user","content":[{"type":"input_text","text":"What is in this image?"}]}]
}
""";

// The output includes a message with input_image content that has an image_url property.
// This simulates a response that echoes back the image URL that was sent as part of the request.
const string Output = """
{
"id":"resp_001",
"object":"response",
"created_at":1741892091,
"status":"completed",
"model":"gpt-4o-mini",
"output":[
{
"type":"message",
"id":"msg_001",
"status":"completed",
"role":"user",
"content":[
{"type":"input_image","image_url":"https://example.com/image.png"}
]
},
{
"type":"message",
"id":"msg_002",
"status":"completed",
"role":"assistant",
"content":[
{"type":"output_text","text":"This is a cat.","annotations":[]}
]
}
]
}
""";

using VerbatimHttpHandler handler = new(Input, Output);
using HttpClient httpClient = new(handler);
using IChatClient client = CreateResponseClient(httpClient, "gpt-4o-mini");

var response = await client.GetResponseAsync("What is in this image?");

Assert.NotNull(response);

// Check what contents we have in the first message
var userMessage = response.Messages.FirstOrDefault(m => m.Role == ChatRole.User);
Assert.NotNull(userMessage);

// The image content should be returned as a UriContent with the URL from the response
var imageContent = userMessage.Contents.OfType<UriContent>().FirstOrDefault();
Assert.NotNull(imageContent);
Assert.Equal("https://example.com/image.png", imageContent.Uri.ToString());
Assert.Equal("image/*", imageContent.MediaType);

// The second message should contain the assistant's response
var assistantMessage = response.Messages.LastOrDefault(m => m.Role == ChatRole.Assistant);
Assert.NotNull(assistantMessage);
Assert.Equal("This is a cat.", assistantMessage.Text);
}

[Fact]
public async Task HostedImageGenerationTool_NonStreaming()
{
Expand Down
Loading