13 Commits
11 changed files with 182 additions and 2 deletions
@@ -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();
+1 -1
View File
@@ -4,7 +4,7 @@
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<PackageId>AnthropicClient</PackageId> <PackageId>AnthropicClient</PackageId>
<Version>0.0.3</Version> <Version>0.0.4</Version>
<Authors>Stevan Freeborn</Authors> <Authors>Stevan Freeborn</Authors>
<Description>Anthropic Client Library</Description> <Description>Anthropic Client Library</Description>
<PackageProjectUrl>https://anthropicclient.stevanfreeborn.com/</PackageProjectUrl> <PackageProjectUrl>https://anthropicclient.stevanfreeborn.com/</PackageProjectUrl>
+21
View File
@@ -2,6 +2,27 @@
All notable changes to this project will be documented in this file. See [versionize](https://github.com/versionize/versionize) for commit guidelines. All notable changes to this project will be documented in this file. See [versionize](https://github.com/versionize/versionize) for commit guidelines.
<a name="0.0.4"></a>
## [0.0.4](https://www.github.com/StevanFreeborn/anthropic-client/releases/tag/v0.0.4) (2024-07-20)
### Bug Fixes
* add end to end test for vision ([b3d4ac3](https://www.github.com/StevanFreeborn/anthropic-client/commit/b3d4ac3faa94086369a02b0a47a19db9441b274e))
* add missing type property to image source model ([0a692d1](https://www.github.com/StevanFreeborn/anthropic-client/commit/0a692d1b9a5f39869aef05892f0801fefe31f50e))
* yield error when initial stream response is not successful ([3b47ed3](https://www.github.com/StevanFreeborn/anthropic-client/commit/3b47ed3fb4e8b30a73a8297e6485985fee7c129a))
### Other
* Merge pull request #11 from StevanFreeborn/stevanfreeborn/fix/missing-type-property-and-response-handling ([d27538b](https://www.github.com/StevanFreeborn/anthropic-client/commit/d27538b65620333ade65e920110a21acd829123f))
* add missing type property ([f970b07](https://www.github.com/StevanFreeborn/anthropic-client/commit/f970b07ed79979b94961c93a192b57fc12a81097))
* debug ci failure ([641eaf3](https://www.github.com/StevanFreeborn/anthropic-client/commit/641eaf30a476cece3cc8bb37f147aa42625992d2))
* debug ci failure ([30c0116](https://www.github.com/StevanFreeborn/anthropic-client/commit/30c01161136949471d661284fa86838ac663ec87))
* debug CI failure ([48b2716](https://www.github.com/StevanFreeborn/anthropic-client/commit/48b2716a61894c471bd2b703254ceb7126354f5e))
* debug CI failure ([9b714df](https://www.github.com/StevanFreeborn/anthropic-client/commit/9b714df12f1d7a6e2e500726ac10a345e4c66054))
* debug test failure in CI ([f39e76c](https://www.github.com/StevanFreeborn/anthropic-client/commit/f39e76cfa64f649e9297bab319ef6ddd58995e0e))
* run format ([93761b8](https://www.github.com/StevanFreeborn/anthropic-client/commit/93761b8e7e0ac67f205abd93f6e9db9465924b02))
* use base64 text file ([a16b64d](https://www.github.com/StevanFreeborn/anthropic-client/commit/a16b64d5a079f65884a8f3a2cf15be4da06f73d3))
<a name="0.0.3"></a> <a name="0.0.3"></a>
## [0.0.3](https://www.github.com/StevanFreeborn/anthropic-client/releases/tag/v0.0.3) (2024-07-11) ## [0.0.3](https://www.github.com/StevanFreeborn/anthropic-client/releases/tag/v0.0.3) (2024-07-11)
@@ -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>
@@ -53,5 +54,11 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</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");
}
}