diff --git a/src/AnthropicClient/AnthropicApiClient.cs b/src/AnthropicClient/AnthropicApiClient.cs
index 836a6bd..3be31ee 100644
--- a/src/AnthropicClient/AnthropicApiClient.cs
+++ b/src/AnthropicClient/AnthropicApiClient.cs
@@ -49,6 +49,13 @@ public interface IAnthropicApiClient
/// An asynchronous enumerable that yields the response as an where T is where T is .
///
IAsyncEnumerable>> ListAllModelsAsync(int limit = 20);
+
+ ///
+ /// Gets a model by its ID asynchronously.
+ ///
+ /// The ID of the model to get.
+ /// A task that represents the asynchronous operation. The task result contains the response as an where T is .
+ Task> GetModelAsync(string modelId);
}
///
@@ -352,6 +359,24 @@ public class AnthropicApiClient : IAnthropicApiClient
} while (hasMore);
}
+ ///
+ public async Task> GetModelAsync(string modelId)
+ {
+ var endpoint = $"{ModelsEndpoint}/{modelId}";
+ var response = await SendRequestAsync(endpoint);
+ var anthropicHeaders = new AnthropicHeaders(response.Headers);
+ var responseContent = await response.Content.ReadAsStringAsync();
+
+ if (response.IsSuccessStatusCode is false)
+ {
+ var error = Deserialize(responseContent) ?? new AnthropicError();
+ return AnthropicResult.Failure(error, anthropicHeaders);
+ }
+
+ var model = Deserialize(responseContent) ?? new AnthropicModel();
+ return AnthropicResult.Success(model, anthropicHeaders);
+ }
+
private ToolCall? GetToolCall(MessageResponse response, List tools)
{
var toolUse = response.Content.OfType().FirstOrDefault();
diff --git a/tests/AnthropicClient.Tests/EndToEnd/AnthropicApiClientTests.cs b/tests/AnthropicClient.Tests/EndToEnd/AnthropicApiClientTests.cs
index e64893a..47ccadd 100644
--- a/tests/AnthropicClient.Tests/EndToEnd/AnthropicApiClientTests.cs
+++ b/tests/AnthropicClient.Tests/EndToEnd/AnthropicApiClientTests.cs
@@ -331,4 +331,14 @@ public class ClientTests(ConfigurationFixture configFixture) : EndToEndTest(conf
responses.Should().HaveCountGreaterThan(0);
}
+
+ [Fact]
+ public async Task GetModelAsync_WhenCalled_ItShouldReturnResponse()
+ {
+ var result = await _client.GetModelAsync(AnthropicModels.Claude3Haiku);
+
+ result.IsSuccess.Should().BeTrue();
+ result.Value.Should().BeOfType();
+ result.Value.Id.Should().Be(AnthropicModels.Claude3Haiku);
+ }
}
\ No newline at end of file
diff --git a/tests/AnthropicClient.Tests/Integration/AnthropicApiClientTests.cs b/tests/AnthropicClient.Tests/Integration/AnthropicApiClientTests.cs
index 795b132..2dcb1be 100644
--- a/tests/AnthropicClient.Tests/Integration/AnthropicApiClientTests.cs
+++ b/tests/AnthropicClient.Tests/Integration/AnthropicApiClientTests.cs
@@ -479,7 +479,7 @@ public class AnthropicApiClientTests : IntegrationTest
}
[Fact]
- public async Task CountMessageTokensAsync_WhenCalledRequestFailsAndCanNotSerializeError_ItShouldReturnUnknownError()
+ public async Task CountMessageTokensAsync_WhenCalledRequestFailsAndCanNotDeserializeError_ItShouldReturnUnknownError()
{
_mockHttpMessageHandler
.WhenCountMessageTokensRequest()
@@ -647,7 +647,7 @@ public class AnthropicApiClientTests : IntegrationTest
}
[Fact]
- public async Task ListModelsAsync_WhenCalledRequestFailsAndCanNotSerializeError_ItShouldReturnUnknownError()
+ public async Task ListModelsAsync_WhenCalledRequestFailsAndCanNotDeserializeError_ItShouldReturnUnknownError()
{
_mockHttpMessageHandler
.WhenListModelsRequest()
@@ -794,7 +794,7 @@ public class AnthropicApiClientTests : IntegrationTest
}
[Fact]
- public async Task ListAllModelsAsync_WhenCalledRequestFailsAndCanNotSerializeError_ItShouldReturnUnknownError()
+ public async Task ListAllModelsAsync_WhenCalledRequestFailsAndCanNotDeserializeError_ItShouldReturnUnknownError()
{
_mockHttpMessageHandler
.WhenListModelsRequest()
@@ -886,4 +886,97 @@ public class AnthropicApiClientTests : IntegrationTest
count.Should().Be(2);
}
+
+ [Fact]
+ public async Task GetModelAsync_WhenCalled_ItShouldReturnModel()
+ {
+ var modelId = "claude-3-5-sonnet-20241022";
+
+ _mockHttpMessageHandler
+ .WhenGetModelRequest(modelId)
+ .Respond(
+ HttpStatusCode.OK,
+ "application/json",
+ @"{
+ ""type"": ""model"",
+ ""id"": ""claude-3-5-sonnet-20241022"",
+ ""display_name"": ""Claude 3.5 Sonnet (New)"",
+ ""created_at"": ""2024-10-22T00:00:00Z""
+ }"
+ );
+
+ var result = await Client.GetModelAsync(modelId);
+
+ result.IsSuccess.Should().BeTrue();
+ result.Value.Should().BeOfType();
+ result.Value.Type.Should().Be("model");
+ result.Value.Id.Should().Be("claude-3-5-sonnet-20241022");
+ result.Value.DisplayName.Should().Be("Claude 3.5 Sonnet (New)");
+ result.Value.CreatedAt.Should().Be(DateTimeOffset.Parse("2024-10-22T00:00:00Z"));
+ }
+
+ [Fact]
+ public async Task GetModelAsync_WhenCalledAndErrorReturned_ItShouldHandleError()
+ {
+ var modelId = "claude-3-5-sonnet-20241022";
+
+ _mockHttpMessageHandler
+ .WhenGetModelRequest(modelId)
+ .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 result = await Client.GetModelAsync(modelId);
+
+ result.IsSuccess.Should().BeFalse();
+ result.Error.Should().BeOfType();
+ result.Error.Error.Should().BeOfType();
+ }
+
+ [Fact]
+ public async Task GetModelAsync_WhenCalledRequestFailsAndCanNotDeserializeError_ItShouldReturnUnknownError()
+ {
+ var modelId = "claude-3-5-sonnet-20241022";
+
+ _mockHttpMessageHandler
+ .WhenGetModelRequest(modelId)
+ .Respond(
+ HttpStatusCode.BadRequest,
+ "application/json",
+ @"{}"
+ );
+
+ var result = await Client.GetModelAsync(modelId);
+
+ result.IsSuccess.Should().BeFalse();
+ result.Error.Should().BeOfType();
+ result.Error.Error.Should().BeOfType();
+ }
+
+ [Fact]
+ public async Task GetModelAsync_WhenCalledAndCanNotDeserializeModel_ItShouldReturnEmptyModel()
+ {
+ var modelId = "claude-3-5-sonnet-20241022";
+
+ _mockHttpMessageHandler
+ .WhenGetModelRequest(modelId)
+ .Respond(
+ HttpStatusCode.OK,
+ "application/json",
+ @"{}"
+ );
+
+ var result = await Client.GetModelAsync(modelId);
+
+ result.IsSuccess.Should().BeFalse();
+ result.Error.Should().BeOfType();
+ }
}
\ No newline at end of file
diff --git a/tests/AnthropicClient.Tests/Integration/IntegrationTest.cs b/tests/AnthropicClient.Tests/Integration/IntegrationTest.cs
index 1d5b77a..6b6376d 100644
--- a/tests/AnthropicClient.Tests/Integration/IntegrationTest.cs
+++ b/tests/AnthropicClient.Tests/Integration/IntegrationTest.cs
@@ -58,4 +58,10 @@ public static class MockHttpMessageHandlerExtensions
return mockHttpMessageHandler
.SetupBaseRequest(HttpMethod.Get, ModelsEndpoint);
}
+
+ public static MockedRequest WhenGetModelRequest(this MockHttpMessageHandler mockHttpMessageHandler, string modelId)
+ {
+ return mockHttpMessageHandler
+ .SetupBaseRequest(HttpMethod.Get, $"{ModelsEndpoint}/{modelId}");
+ }
}
\ No newline at end of file