Merge pull request #11 from StevanFreeborn/stevanfreeborn/fix/missing-type-property-and-response-handling
fix: address issues
This commit is contained in:
@@ -95,6 +95,14 @@ public class AnthropicApiClient : IAnthropicApiClient
|
|||||||
public async IAsyncEnumerable<AnthropicEvent> CreateMessageAsync(StreamMessageRequest request)
|
public async IAsyncEnumerable<AnthropicEvent> CreateMessageAsync(StreamMessageRequest request)
|
||||||
{
|
{
|
||||||
var response = await SendRequestAsync(request);
|
var response = await SendRequestAsync(request);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode is false)
|
||||||
|
{
|
||||||
|
var error = Deserialize<AnthropicError>(await response.Content.ReadAsStringAsync()) ?? new AnthropicError();
|
||||||
|
yield return new AnthropicEvent(EventType.Error, new ErrorEventData(error.Error));
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
var anthropicHeaders = new AnthropicHeaders(response.Headers);
|
var anthropicHeaders = new AnthropicHeaders(response.Headers);
|
||||||
|
|
||||||
using var responseContent = await response.Content.ReadAsStreamAsync();
|
using var responseContent = await response.Content.ReadAsStreamAsync();
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ public class ImageSource
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string Data { get; init; } = string.Empty;
|
public string Data { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type of encoding of the image data.
|
||||||
|
/// </summary>
|
||||||
|
public string Type { get; init; } = "base64";
|
||||||
|
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
internal ImageSource()
|
internal ImageSource()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
<IsTestProject>true</IsTestProject>
|
<IsTestProject>true</IsTestProject>
|
||||||
|
<NoWarn>$(NoWarn);IDE0039</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -54,4 +55,10 @@
|
|||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="Files/**">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
using Xunit.Abstractions;
|
||||||
|
using Xunit.Sdk;
|
||||||
|
|
||||||
namespace AnthropicClient.Tests.EndToEnd;
|
namespace AnthropicClient.Tests.EndToEnd;
|
||||||
|
|
||||||
public class ClientTests(ConfigurationFixture configFixture) : EndToEndTest(configFixture)
|
public class ClientTests(ConfigurationFixture configFixture) : EndToEndTest(configFixture)
|
||||||
@@ -56,4 +59,40 @@ public class ClientTests(ConfigurationFixture configFixture) : EndToEndTest(conf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateMessageAsync_WhenImageIsSent_ItShouldReturnResponse()
|
||||||
|
{
|
||||||
|
var imagePath = Path.Combine(Directory.GetCurrentDirectory(), "Files", "base64-elephant.txt");
|
||||||
|
var mediaType = "image/jpeg";
|
||||||
|
var base64Data = await File.ReadAllTextAsync(imagePath);
|
||||||
|
|
||||||
|
var request = new MessageRequest(
|
||||||
|
model: AnthropicModels.Claude3Haiku,
|
||||||
|
messages: [
|
||||||
|
new(MessageRole.User, [
|
||||||
|
new ImageContent(mediaType, base64Data),
|
||||||
|
new TextContent("What is in this image?")
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
var result = await _client.CreateMessageAsync(request);
|
||||||
|
|
||||||
|
result.IsSuccess.Should().BeTrue();
|
||||||
|
result.Value.Should().BeOfType<MessageResponse>();
|
||||||
|
result.Value.Content.Should().NotBeNullOrEmpty();
|
||||||
|
|
||||||
|
var text = result.Value.Content.Aggregate("", (acc, content) =>
|
||||||
|
{
|
||||||
|
if (content is TextContent textContent)
|
||||||
|
{
|
||||||
|
acc += textContent.Text;
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
});
|
||||||
|
|
||||||
|
text.Should().Contain("elephant");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
@@ -321,4 +321,42 @@ public class AnthropicApiClientTests : IntegrationTest
|
|||||||
toolCallResult.IsSuccess.Should().BeTrue();
|
toolCallResult.IsSuccess.Should().BeTrue();
|
||||||
toolCallResult.Value.Should().Be(getWeather("San Francisco, CA", "fahrenheit"));
|
toolCallResult.Value.Should().Be(getWeather("San Francisco, CA", "fahrenheit"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateMessageAsync_WhenCalledMessageIsStreamAndRequestFails_ItShouldReturnErrorEvent()
|
||||||
|
{
|
||||||
|
_mockHttpMessageHandler
|
||||||
|
.WhenCreateStreamMessageRequest()
|
||||||
|
.Respond(
|
||||||
|
HttpStatusCode.BadRequest,
|
||||||
|
"application/json",
|
||||||
|
@"{
|
||||||
|
""type"": ""error"",
|
||||||
|
""error"": {
|
||||||
|
""type"": ""invalid_request_error"",
|
||||||
|
""message"": ""messages: roles must alternate between user and assistant, but found multiple user roles in a row""
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
);
|
||||||
|
|
||||||
|
var request = new StreamMessageRequest(
|
||||||
|
model: AnthropicModels.Claude35Sonnet,
|
||||||
|
messages: [
|
||||||
|
new(MessageRole.User, [new TextContent("Hello!")]),
|
||||||
|
new(MessageRole.User, [new TextContent("Hello!")])
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
var result = Client.CreateMessageAsync(request);
|
||||||
|
var events = await result.ToListAsync();
|
||||||
|
|
||||||
|
events.Should().HaveCount(1);
|
||||||
|
events[0].Type.Should().Be(EventType.Error);
|
||||||
|
events[0].Data.Should().BeOfType<ErrorEventData>();
|
||||||
|
events[0].Data.Should().BeEquivalentTo(new ErrorEventData(
|
||||||
|
new InvalidRequestError(
|
||||||
|
"messages: roles must alternate between user and assistant, but found multiple user roles in a row"
|
||||||
|
)
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,8 @@ public class ImageContentTests : SerializationTest
|
|||||||
private readonly string _testJson = @"{
|
private readonly string _testJson = @"{
|
||||||
""source"": {
|
""source"": {
|
||||||
""media_type"": ""image/png"",
|
""media_type"": ""image/png"",
|
||||||
""data"": ""data""
|
""data"": ""data"",
|
||||||
|
""type"": ""base64""
|
||||||
},
|
},
|
||||||
""type"": ""image""
|
""type"": ""image""
|
||||||
}";
|
}";
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class ImageSourceTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledWithValidArguments_ItShouldSetProperties()
|
||||||
|
{
|
||||||
|
var mediaType = "image/jpeg";
|
||||||
|
var data = "base64data";
|
||||||
|
|
||||||
|
var imageSource = new ImageSource(mediaType, data);
|
||||||
|
|
||||||
|
imageSource.MediaType.Should().Be(mediaType);
|
||||||
|
imageSource.Data.Should().Be(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledWithInvalidMediaType_ItShouldThrowArgumentException()
|
||||||
|
{
|
||||||
|
var mediaType = "invalid";
|
||||||
|
var data = "base64data";
|
||||||
|
|
||||||
|
var action = () => new ImageSource(mediaType, data);
|
||||||
|
|
||||||
|
action.Should().Throw<ArgumentException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledWithNullMediaType_ItShouldThrowArgumentNullException()
|
||||||
|
{
|
||||||
|
string? mediaType = null;
|
||||||
|
var data = "base64data";
|
||||||
|
|
||||||
|
var action = () => new ImageSource(mediaType!, data);
|
||||||
|
|
||||||
|
action.Should().Throw<ArgumentNullException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledWithNullData_ItShouldThrowArgumentNullException()
|
||||||
|
{
|
||||||
|
var mediaType = "image/jpeg";
|
||||||
|
string? data = null;
|
||||||
|
|
||||||
|
var action = () => new ImageSource(mediaType, data!);
|
||||||
|
|
||||||
|
action.Should().Throw<ArgumentNullException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalled_ItShouldHaveTypePropertySetToBase64()
|
||||||
|
{
|
||||||
|
var mediaType = "image/jpeg";
|
||||||
|
var data = "base64data";
|
||||||
|
|
||||||
|
var imageSource = new ImageSource(mediaType, data);
|
||||||
|
|
||||||
|
imageSource.Type.Should().Be("base64");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user