docs: work on README

This commit is contained in:
Stevan Freeborn
2024-07-06 11:31:55 -05:00
parent 5c0cad2545
commit 1d352a6f95
2 changed files with 237 additions and 2 deletions
+138 -2
View File
@@ -10,6 +10,18 @@ This SDK was developed independently using existing libraries and the Anthropic
> [!NOTE] > [!NOTE]
> This client library is heavily inspired by the [Anthropic.SDK](https://github.com/tghamm/Anthropic.SDK) library. I chose to create a new library because I wanted to handle streaming and tool calling differently as well as have control over the client library as I plan to use it to build a connector for [SemanticKernel](https://github.com/microsoft/semantic-kernel). However if you are looking for a client library the Anthropic.SDK is a great place to start. > This client library is heavily inspired by the [Anthropic.SDK](https://github.com/tghamm/Anthropic.SDK) library. I chose to create a new library because I wanted to handle streaming and tool calling differently as well as have control over the client library as I plan to use it to build a connector for [SemanticKernel](https://github.com/microsoft/semantic-kernel). However if you are looking for a client library the Anthropic.SDK is a great place to start.
## 📝 Issues
If you encounter any issues while using this library please open an issue [here](https://github.com/StevanFreeborn/anthropic-client/issues).
## 📜 License
This library is licensed under the [MIT License](https://choosealicense.com/licenses/mit/) and is free to use and modify.
## 📝 Contributing
If you would like to contribute to this library please open a pull request [here](https://github.com/StevanFreeborn/anthropic-client/pulls).
## 🛠️ Dependencies ## 🛠️ Dependencies
### [Microsoft.Bcl.AsyncInterfaces](https://www.nuget.org/packages/Microsoft.Bcl.AsyncInterfaces/) ### [Microsoft.Bcl.AsyncInterfaces](https://www.nuget.org/packages/Microsoft.Bcl.AsyncInterfaces/)
@@ -84,7 +96,7 @@ This library was developed to make using the Anthropic API easier within a .NET
The primary use case for working with the Anthropic API is to create a message in response to a request that includes one or more other messages. The created message can then be received either as a complete response or a stream of events. This can be used to create a conversation between the caller and Anthropic's AI models and/or to use Anthropic's AI models to perform a task. The primary use case for working with the Anthropic API is to create a message in response to a request that includes one or more other messages. The created message can then be received either as a complete response or a stream of events. This can be used to create a conversation between the caller and Anthropic's AI models and/or to use Anthropic's AI models to perform a task.
> [!NOTE] > [!NOTE]
> The following examples assume that you have already created an instance of the `AnthropicApiClient` class named `client`. > The following examples assume that you have already created an instance of the `AnthropicApiClient` class named `client`. You can also find these snippets in the [examples](./tests/AnthropicClient.Tests/Examples/) directory.
### Create a message ### Create a message
@@ -368,10 +380,27 @@ It is important to remember that while Anthropic's models do support tool use th
This library aims to make this process convenient by allowing you to simply provide the tools you want Anthropic's models to consider for use when creating a message, receive the response, check if the response contains a tool call, and if it does invoke the tool to get the result. This library aims to make this process convenient by allowing you to simply provide the tools you want Anthropic's models to consider for use when creating a message, receive the response, check if the response contains a tool call, and if it does invoke the tool to get the result.
> [!NOTE]
> Anthropic's API expects requests to contain messages that alternate between the user and the assistant. In addition if you receive a tool use from the model the API expects you to respond with a message that contains the result of the tool call. The tool use content will always be from the assistant while the tool result will always be from the user.
```csharp ```csharp
using AnthropicClient; using AnthropicClient;
using AnthropicClient.Models; using AnthropicClient.Models;
class GetWeatherTool : ITool
{
public string Name => "Get Weather";
public string Description => "Get the weather for a location in the specified units";
public MethodInfo Function => typeof(GetWeatherTool).GetMethod(nameof(GetWeather))!;
public static string GetWeather(string location, string units)
{
return $"The weather in {location} is 72 degrees {units}";
}
}
List<Message> messages = [ List<Message> messages = [
new( new(
MessageRole.User, MessageRole.User,
@@ -464,6 +493,113 @@ foreach (var content in finalResponse.Value.Content)
} }
``` ```
The `InvokeAsync` method does accept a generic type parameter that can be used to specify the type of the `Value` property of the `ToolCallResult`. If it is not specified it will be an `object`. If an exception is thrown while invoking the tool the `InvokeAsync` method will return a `ToolCallResult` with the exception contained in the `Error` property.
> [!NOTE]
> The `InvokeAsync` method does accept a generic type parameter that can be used to specify the type of the `Value` property of the `ToolCallResult`. If it is not specified it will be an `object`.
#### Call a tool in streamed message #### Call a tool in streamed message
Tool calling is also supported when streaming the message response. The following example demonstrates how you can handle a tool call in a streamed message response.
```csharp
using AnthropicClient;
using AnthropicClient.Models;
var tool = (string location, string units) => $"The weather in {location} is 72 degrees {units}";
var messages = [
new(
MessageRole.User,
[new TextContent("What is the weather in New York?")]
)
];
var tools = [Tool.CreateFromFunction(
"Get Weather",
"Get the weather for a location in the specified units",
tool
)];
var events = client.CreateMessageAsync(new StreamMessageRequest(
AnthropicModels.Claude3Haiku,
messages,
tools: tools
));
MessageResponse? response = null;
await foreach (var e in events)
{
switch (e.Data)
{
case var data when data is MessageCompleteEventData msgData:
response = msgData.Message;
break;
}
}
if (response is null)
{
Console.WriteLine("Failed to get message response");
return;
}
foreach (var content in response.Content)
{
messages.Add(new(MessageRole.Assistant, [content]));
}
if (response?.ToolCall is not null)
{
var toolCallResult = await response.ToolCall.InvokeAsync<string>();
string toolResultContent;
if (toolCallResult.IsSuccess && toolCallResult.Value is not null)
{
toolResultContent = toolCallResult.Value;
}
else
{
toolResultContent = toolCallResult.Error.Message;
}
messages.Add(
new(
MessageRole.User,
[
new ToolResultContent(
response.ToolCall.ToolUse.Id,
toolResultContent
)
]
)
);
}
var finalResponse = await client.CreateMessageAsync(new MessageRequest(
AnthropicModels.Claude3Haiku,
messages,
tools: tools
));
if (finalResponse.IsSuccess is false)
{
Console.WriteLine($"Failed to create message");
Console.WriteLine($"Error Type: {0}", finalResponse.Error.Error.Type);
Console.WriteLine($"Error Message: {0}", finalResponse.Error.Error.Message);
return;
}
foreach (var content in finalResponse.Value.Content)
{
switch (content)
{
case TextContent textContent:
Console.WriteLine(textContent.Text);
break;
}
}
```
If you do find that you need more control over how exactly provided tools are called and how the result of those tools are returned you can avoid using the `InvokeAsync` method and instead use the `Tool` and `ToolUse` properties of the `ToolCall` instance to implement your own solution.
@@ -465,6 +465,105 @@ public class Examples(ConfigurationFixture config, ITestOutputHelper console) :
} }
} }
} }
[Example]
public async Task CreateAMessageProvideAToolStreamTheResponseAndCallTheTool()
{
var tool = (string location, string units) => $"The weather in {location} is 72 degrees {units}";
List<Message> messages = [
new(
MessageRole.User,
[new TextContent("What is the weather in New York?")]
)
];
List<Tool> tools = [Tool.CreateFromFunction(
"Get Weather",
"Get the weather for a location in the specified units",
tool
)];
var events = _client.CreateMessageAsync(new StreamMessageRequest(
AnthropicModels.Claude3Haiku,
messages,
tools: tools
));
MessageResponse? response = null;
await foreach (var e in events)
{
switch (e.Data)
{
case var data when data is MessageCompleteEventData msgData:
response = msgData.Message;
break;
}
}
if (response is null)
{
_console.WriteLine("Failed to get message response");
return;
}
foreach (var content in response.Content)
{
messages.Add(new(MessageRole.Assistant, [content]));
}
if (response?.ToolCall is not null)
{
var toolCallResult = await response.ToolCall.InvokeAsync<string>();
string toolResultContent;
if (toolCallResult.IsSuccess && toolCallResult.Value is not null)
{
toolResultContent = toolCallResult.Value;
}
else
{
toolResultContent = toolCallResult.Error.Message;
}
messages.Add(
new(
MessageRole.User,
[
new ToolResultContent(
response.ToolCall.ToolUse.Id,
toolResultContent
)
]
)
);
}
var finalResponse = await _client.CreateMessageAsync(new MessageRequest(
AnthropicModels.Claude3Haiku,
messages,
tools: tools
));
if (finalResponse.IsSuccess is false)
{
_console.WriteLine($"Failed to create message");
_console.WriteLine($"Error Type: {0}", finalResponse.Error.Error.Type);
_console.WriteLine($"Error Message: {0}", finalResponse.Error.Error.Message);
return;
}
foreach (var content in finalResponse.Value.Content)
{
switch (content)
{
case TextContent textContent:
_console.WriteLine(textContent.Text);
break;
}
}
}
} }
class GetWeatherTool : ITool class GetWeatherTool : ITool