feat: implement tool call
This commit is contained in:
@@ -82,6 +82,12 @@ public class AnthropicApiClient : IAnthropicApiClient
|
||||
}
|
||||
|
||||
var chatResponse = Deserialize<ChatResponse>(responseContent) ?? new ChatResponse();
|
||||
|
||||
if (request.Tools is not null && request.Tools.Count > 0)
|
||||
{
|
||||
chatResponse.ToolCall = GetToolCall(chatResponse, request.Tools);
|
||||
}
|
||||
|
||||
return AnthropicResult<ChatResponse>.Success(chatResponse, anthropicHeaders);
|
||||
}
|
||||
|
||||
@@ -190,6 +196,11 @@ public class AnthropicApiClient : IAnthropicApiClient
|
||||
Usage = newUsage,
|
||||
Content = chatResponse.Content,
|
||||
};
|
||||
|
||||
if (request.Tools is not null && request.Tools.Count > 0)
|
||||
{
|
||||
chatResponse.ToolCall = GetToolCall(chatResponse, request.Tools);
|
||||
}
|
||||
}
|
||||
|
||||
// yield chat response on message stop
|
||||
@@ -228,6 +239,25 @@ public class AnthropicApiClient : IAnthropicApiClient
|
||||
} while (true);
|
||||
}
|
||||
|
||||
private ToolCall? GetToolCall(ChatResponse response, List<Tool> tools)
|
||||
{
|
||||
var toolUse = response.Content.OfType<ToolUseContent>().FirstOrDefault();
|
||||
|
||||
if (toolUse is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var tool = tools.FirstOrDefault(t => t.Name == toolUse.Name);
|
||||
|
||||
if (tool is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ToolCall(tool, toolUse);
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage> SendRequestAsync(MessageRequest request)
|
||||
{
|
||||
var requestContent = new StringContent(Serialize(request), Encoding.UTF8, JsonContentType);
|
||||
|
||||
@@ -48,4 +48,10 @@ public class ChatResponse
|
||||
/// Gets the contents of the chat response.
|
||||
/// </summary>
|
||||
public List<Content> Content { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tool call of the chat response. If the chat response does not contain a tool call, this property is null.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public ToolCall? ToolCall { get; set; } = null;
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
|
||||
using AnthropicClient.Json;
|
||||
|
||||
namespace AnthropicClient.Models;
|
||||
|
||||
/// <summary>
|
||||
@@ -5,13 +10,112 @@ namespace AnthropicClient.Models;
|
||||
/// </summary>
|
||||
public class ToolCall
|
||||
{
|
||||
// TODO: Implementation
|
||||
// - each tool call needs parameters
|
||||
// - each tool call needs a tool name
|
||||
// - each tool needs function to call
|
||||
// - tool may or may not need an instance
|
||||
// - tool call will be given input from tool use
|
||||
// - tool call should have list of known functions
|
||||
// - tool call should be able to find function by name
|
||||
// - tool call should be able to translate input to function parameters
|
||||
/// <summary>
|
||||
/// Gets the tool of the tool call.
|
||||
/// </summary>
|
||||
public Tool Tool { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tool use of the tool call.
|
||||
/// </summary>
|
||||
public ToolUseContent ToolUse { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ToolCall"/> class.
|
||||
/// </summary>
|
||||
/// <param name="tool">The tool of the tool call.</param>
|
||||
/// <param name="toolUse">The tool use of the tool call.</param>
|
||||
/// <returns>A new instance of the <see cref="ToolCall"/> class.</returns>
|
||||
public ToolCall(Tool tool, ToolUseContent toolUse)
|
||||
{
|
||||
Tool = tool;
|
||||
ToolUse = toolUse;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the tool call.
|
||||
/// </summary>
|
||||
/// <returns>The result of the tool call as an instance of <see cref="ToolCallResult{T}"/>.</returns>
|
||||
public async Task<ToolCallResult<object>> InvokeAsync()
|
||||
{
|
||||
return await InvokeAsync<object>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the tool call.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the result value.</typeparam>
|
||||
/// <returns>The result of the tool call as an instance of <see cref="ToolCallResult{T}"/>.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown when a parameter name is not found.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown when an argument is missing.</exception>
|
||||
public async Task<ToolCallResult<T>> InvokeAsync<T>()
|
||||
{
|
||||
try
|
||||
{
|
||||
T? result = default;
|
||||
|
||||
var arguments = GetArguments();
|
||||
var isAwaitable = Tool.Function.Method.ReturnType.GetMethod(nameof(Task.GetAwaiter)) is not null;
|
||||
|
||||
if (isAwaitable)
|
||||
{
|
||||
var task = (Task)Tool.Function.Method.Invoke(Tool.Function.Instance, arguments);
|
||||
|
||||
await task;
|
||||
|
||||
const string resultPropertyName = "Result";
|
||||
var resultProperty = task.GetType().GetProperty(resultPropertyName);
|
||||
result = resultProperty is not null ? (T)resultProperty.GetValue(task) : default;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = (T)Tool.Function.Method.Invoke(Tool.Function.Instance, arguments);
|
||||
}
|
||||
|
||||
return ToolCallResult<T>.Success(result);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return ToolCallResult<T>.Failure(e);
|
||||
}
|
||||
}
|
||||
|
||||
private object?[] GetArguments()
|
||||
{
|
||||
var parameters = Tool.Function.Method.GetParameters();
|
||||
var arguments = new object?[parameters.Length];
|
||||
|
||||
for (var i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
var parameter = parameters[i];
|
||||
var attribute = parameter.GetCustomAttribute<FunctionParameterAttribute>();
|
||||
var parameterName = attribute is not null
|
||||
? string.IsNullOrWhiteSpace(attribute.Name)
|
||||
? parameter.Name
|
||||
: attribute.Name
|
||||
: parameter.Name;
|
||||
|
||||
if (parameterName == null)
|
||||
{
|
||||
throw new ArgumentException($"Failed to find a valid parameter name for {Tool.Function.Method.DeclaringType}.{Tool.Function.Method.Name}()");
|
||||
}
|
||||
|
||||
if (ToolUse.Input.TryGetValue(parameterName, out var value))
|
||||
{
|
||||
arguments[i] = value is string s && parameter.ParameterType.IsEnum
|
||||
? Enum.Parse(parameter.ParameterType, s, true)
|
||||
: value is JsonElement element
|
||||
? JsonSerializer.Deserialize(element.GetRawText(), parameter.ParameterType, JsonSerializationOptions.DefaultOptions)
|
||||
: value;
|
||||
}
|
||||
else
|
||||
{
|
||||
arguments[i] = parameter.HasDefaultValue
|
||||
? parameter.DefaultValue
|
||||
: throw new ArgumentException($"Missing argument for parameter '{parameter.Name}'");
|
||||
}
|
||||
}
|
||||
|
||||
return arguments;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
namespace AnthropicClient.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a tool call result.
|
||||
/// </summary>
|
||||
public class ToolCallResult<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// The value of the tool call result.
|
||||
/// </summary>
|
||||
public T? Value { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The error of the tool call result.
|
||||
/// </summary>
|
||||
public Exception Error { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the tool call was successful.
|
||||
/// </summary>
|
||||
public bool IsSuccess { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ToolCallResult{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="value">The value of the tool call result.</param>
|
||||
/// <param name="error">The error of the tool call result.</param>
|
||||
/// <param name="isSuccess">Indicates whether the tool call was successful.</param>
|
||||
/// <returns>A new instance of the <see cref="ToolCallResult{T}"/> class.</returns>
|
||||
protected ToolCallResult(T? value, Exception error, bool isSuccess)
|
||||
{
|
||||
Value = value;
|
||||
Error = error;
|
||||
IsSuccess = isSuccess;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a successful tool call result.
|
||||
/// </summary>
|
||||
/// <param name="value">The value of the tool call result.</param>
|
||||
/// <returns>A new instance of the <see cref="ToolCallResult{T}"/> class.</returns>
|
||||
public static ToolCallResult<T> Success(T? value)
|
||||
{
|
||||
return new ToolCallResult<T>(value, null!, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a failed ool call result.
|
||||
/// </summary>
|
||||
/// <param name="error">The error of the tool call result.</param>
|
||||
/// <returns>A new instance of the <see cref="ToolCallResult{T}"/> class.</returns>
|
||||
public static ToolCallResult<T> Failure(Exception error)
|
||||
{
|
||||
return new ToolCallResult<T>(default!, error, false);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user