Merge pull request #37 from StevanFreeborn/stevanfreeborn/feat/add-citations
feat: add citations support
This commit is contained in:
+11
-4
@@ -24,6 +24,10 @@ insert_final_newline = false
|
|||||||
#### .NET Coding Conventions ####
|
#### .NET Coding Conventions ####
|
||||||
[*.{cs,vb}]
|
[*.{cs,vb}]
|
||||||
|
|
||||||
|
# diagnostics
|
||||||
|
dotnet_diagnostic.IDE0058.severity = none
|
||||||
|
dotnet_diagnostic.CA1707.severity = none
|
||||||
|
|
||||||
# Organize usings
|
# Organize usings
|
||||||
dotnet_separate_import_directive_groups = true
|
dotnet_separate_import_directive_groups = true
|
||||||
dotnet_sort_system_directives_first = true
|
dotnet_sort_system_directives_first = true
|
||||||
@@ -77,10 +81,13 @@ dotnet_remove_unnecessary_suppression_exclusions = none
|
|||||||
#### C# Coding Conventions ####
|
#### C# Coding Conventions ####
|
||||||
[*.cs]
|
[*.cs]
|
||||||
|
|
||||||
|
# namespace preferences
|
||||||
|
csharp_style_namespace_declarations = file_scoped:suggestion
|
||||||
|
|
||||||
# var preferences
|
# var preferences
|
||||||
csharp_style_var_elsewhere = false:silent
|
csharp_style_var_elsewhere = true:suggestion
|
||||||
csharp_style_var_for_built_in_types = false:silent
|
csharp_style_var_for_built_in_types = true:suggestion
|
||||||
csharp_style_var_when_type_is_apparent = false:silent
|
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||||
|
|
||||||
# Expression-bodied members
|
# Expression-bodied members
|
||||||
csharp_style_expression_bodied_accessors = true:silent
|
csharp_style_expression_bodied_accessors = true:silent
|
||||||
@@ -118,7 +125,7 @@ csharp_style_pattern_local_over_anonymous_function = true:suggestion
|
|||||||
csharp_style_prefer_index_operator = true:suggestion
|
csharp_style_prefer_index_operator = true:suggestion
|
||||||
csharp_style_prefer_range_operator = true:suggestion
|
csharp_style_prefer_range_operator = true:suggestion
|
||||||
csharp_style_throw_expression = true:suggestion
|
csharp_style_throw_expression = true:suggestion
|
||||||
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
|
csharp_style_unused_value_assignment_preference = discard_variable:silent
|
||||||
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
|
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
|
||||||
|
|
||||||
# 'using' directive preferences
|
# 'using' directive preferences
|
||||||
|
|||||||
Vendored
+4
-1
@@ -17,5 +17,8 @@
|
|||||||
"targetdir",
|
"targetdir",
|
||||||
"typeof"
|
"typeof"
|
||||||
],
|
],
|
||||||
"dotnet.unitTests.runSettingsPath": "./tests/AnthropicClient.Tests/.runsettings"
|
"dotnet.unitTests.runSettingsPath": "./tests/AnthropicClient.Tests/.runsettings",
|
||||||
|
"search.exclude": {
|
||||||
|
"**/docs": true,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -972,6 +972,192 @@ foreach (var content in response.Value.Content)
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Citations
|
||||||
|
|
||||||
|
Anthropic provides a feature called [Citations](https://docs.anthropic.com/en/docs/build-with-claude/citations) that allows Claude to provide citations for information extracted from documents. This feature enables Claude to reference specific parts of the source material when answering questions, making it easier to verify information and understand the context of responses.
|
||||||
|
|
||||||
|
Citations can be enabled for documents and will return references to the specific locations in the source material where information was found. This library provides comprehensive support for citations through strongly-typed models that represent different types of citation locations.
|
||||||
|
|
||||||
|
#### Enabling Citations for Documents
|
||||||
|
|
||||||
|
You can enable citations for documents by setting the `Citations` property on `DocumentContent` instances:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using AnthropicClient;
|
||||||
|
using AnthropicClient.Models;
|
||||||
|
|
||||||
|
var request = new MessageRequest(
|
||||||
|
model: AnthropicModels.Claude35Sonnet,
|
||||||
|
messages: [
|
||||||
|
new(MessageRole.User, [
|
||||||
|
new DocumentContent(new TextSource("The grass is green. The sky is blue."))
|
||||||
|
{
|
||||||
|
Title = "My Document",
|
||||||
|
Context = "This is a trustworthy document.",
|
||||||
|
Citations = new() { Enabled = true }
|
||||||
|
},
|
||||||
|
new TextContent("What color is the grass and sky?")
|
||||||
|
])
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
var response = await client.CreateMessageAsync(request);
|
||||||
|
|
||||||
|
if (response.IsSuccess is false)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Failed to create message");
|
||||||
|
Console.WriteLine("Error Type: {0}", response.Error.Error.Type);
|
||||||
|
Console.WriteLine("Error Message: {0}", response.Error.Error.Message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var content in response.Value.Content)
|
||||||
|
{
|
||||||
|
switch (content)
|
||||||
|
{
|
||||||
|
case TextContent textContent:
|
||||||
|
Console.WriteLine("Response: {0}", textContent.Text);
|
||||||
|
|
||||||
|
if (textContent.Citations is not null)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Citations:");
|
||||||
|
foreach (var citation in textContent.Citations)
|
||||||
|
{
|
||||||
|
Console.WriteLine(" - Cited Text: {0}", citation.CitedText);
|
||||||
|
Console.WriteLine(" Document: {0}", citation.DocumentTitle);
|
||||||
|
Console.WriteLine(" Type: {0}", citation.Type);
|
||||||
|
|
||||||
|
switch (citation)
|
||||||
|
{
|
||||||
|
case CharacterLocationCitation charCitation:
|
||||||
|
Console.WriteLine(
|
||||||
|
" Character Range: {0}-{1}",
|
||||||
|
charCitation.StartCharIndex, charCitation.EndCharIndex
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case PageLocationCitation pageCitation:
|
||||||
|
Console.WriteLine(
|
||||||
|
" Page Range: {0}-{1}",
|
||||||
|
pageCitation.StartPageNumber, pageCitation.EndPageNumber
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case ContentBlockLocationCitation blockCitation:
|
||||||
|
Console.WriteLine(
|
||||||
|
" Block Range: {0}-{1}",
|
||||||
|
blockCitation.StartBlockIndex, blockCitation.EndBlockIndex
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Citations with PDF Documents
|
||||||
|
|
||||||
|
Citations work particularly well with PDF documents, providing page-level references:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using AnthropicClient;
|
||||||
|
using AnthropicClient.Models;
|
||||||
|
|
||||||
|
var pdfBytes = await File.ReadAllBytesAsync("document.pdf");
|
||||||
|
var base64Data = Convert.ToBase64String(pdfBytes);
|
||||||
|
|
||||||
|
var request = new MessageRequest(
|
||||||
|
model: AnthropicModels.Claude35Sonnet,
|
||||||
|
messages: [
|
||||||
|
new(MessageRole.User, [
|
||||||
|
new DocumentContent("application/pdf", base64Data)
|
||||||
|
{
|
||||||
|
Title = "Research Paper",
|
||||||
|
Citations = new() { Enabled = true }
|
||||||
|
},
|
||||||
|
new TextContent("Summarize the key findings from this research paper.")
|
||||||
|
])
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
var response = await client.CreateMessageAsync(request);
|
||||||
|
|
||||||
|
if (response.IsSuccess is false)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Failed to create message");
|
||||||
|
Console.WriteLine("Error Type: {0}", response.Error.Error.Type);
|
||||||
|
Console.WriteLine("Error Message: {0}", response.Error.Error.Message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var content in response.Value.Content)
|
||||||
|
{
|
||||||
|
switch (content)
|
||||||
|
{
|
||||||
|
case TextContent textContent:
|
||||||
|
Console.WriteLine("Summary: {0}", textContent.Text);
|
||||||
|
|
||||||
|
if (textContent.Citations is not null)
|
||||||
|
{
|
||||||
|
Console.WriteLine("\nCitations:");
|
||||||
|
foreach (var citation in textContent.Citations.OfType<PageLocationCitation>())
|
||||||
|
{
|
||||||
|
Console.WriteLine(
|
||||||
|
" - \"{0}\" (Pages {1}-{2})",
|
||||||
|
citation.CitedText,
|
||||||
|
citation.StartPageNumber,
|
||||||
|
citation.EndPageNumber
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Citations in Streaming Responses
|
||||||
|
|
||||||
|
Citations are also supported in streaming responses through the `CitationDelta` events:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using AnthropicClient;
|
||||||
|
using AnthropicClient.Models;
|
||||||
|
|
||||||
|
var request = new StreamMessageRequest(
|
||||||
|
model: AnthropicModels.Claude35Sonnet,
|
||||||
|
messages: [
|
||||||
|
new(MessageRole.User, [
|
||||||
|
new DocumentContent(new TextSource("The grass is green. The sky is blue."))
|
||||||
|
{
|
||||||
|
Citations = new() { Enabled = true }
|
||||||
|
},
|
||||||
|
new TextContent("What color is the grass?")
|
||||||
|
])
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
var events = client.CreateMessageAsync(request);
|
||||||
|
|
||||||
|
await foreach (var e in events)
|
||||||
|
{
|
||||||
|
switch (e.Data)
|
||||||
|
{
|
||||||
|
case ContentDeltaEventData contentData:
|
||||||
|
switch (contentData.Delta)
|
||||||
|
{
|
||||||
|
case CitationDelta citationDelta:
|
||||||
|
Console.WriteLine("Citation: {0}", citationDelta.Citation.CitedText);
|
||||||
|
Console.WriteLine("Type: {0}", citationDelta.Citation.Type);
|
||||||
|
break;
|
||||||
|
case TextDelta textDelta:
|
||||||
|
Console.Write(textDelta.Text);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Message Batches
|
### Message Batches
|
||||||
|
|
||||||
Anthropic provides a feature called [Message Batches](https://docs.anthropic.com/en/docs/build-with-claude/message-batches) that allows you to send multiple messages in a single request. This feature is covered in depth in [Anthropic's API Documentation](https://docs.anthropic.com/en/docs/build-with-claude/message-batches).
|
Anthropic provides a feature called [Message Batches](https://docs.anthropic.com/en/docs/build-with-claude/message-batches) that allows you to send multiple messages in a single request. This feature is covered in depth in [Anthropic's API Documentation](https://docs.anthropic.com/en/docs/build-with-claude/message-batches).
|
||||||
|
|||||||
@@ -124,10 +124,37 @@ public class AnthropicApiClient : IAnthropicApiClient
|
|||||||
// current content type and delta type
|
// current content type and delta type
|
||||||
if (currentEvent.Type is EventType.ContentBlockDelta && currentEvent.Data is ContentDeltaEventData contentDeltaData)
|
if (currentEvent.Type is EventType.ContentBlockDelta && currentEvent.Data is ContentDeltaEventData contentDeltaData)
|
||||||
{
|
{
|
||||||
if (content is TextContent textContent && contentDeltaData.Delta is TextDelta textDelta)
|
if (content is TextContent textContent)
|
||||||
|
{
|
||||||
|
if (contentDeltaData.Delta is TextDelta textDelta)
|
||||||
{
|
{
|
||||||
var newText = textContent.Text + textDelta.Text;
|
var newText = textContent.Text + textDelta.Text;
|
||||||
content = new TextContent(newText);
|
|
||||||
|
content = new TextContent(newText)
|
||||||
|
{
|
||||||
|
Citations = textContent.Citations,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contentDeltaData.Delta is CitationDelta citationDelta)
|
||||||
|
{
|
||||||
|
var citations = new List<Citation>()
|
||||||
|
{
|
||||||
|
citationDelta.Citation,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (textContent.Citations is not null)
|
||||||
|
{
|
||||||
|
citations.AddRange(textContent.Citations);
|
||||||
|
}
|
||||||
|
|
||||||
|
var newContent = new TextContent(textContent.Text)
|
||||||
|
{
|
||||||
|
Citations = [.. citations],
|
||||||
|
};
|
||||||
|
|
||||||
|
content = newContent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (content is ToolUseContent toolUseContent && contentDeltaData.Delta is JsonDelta jsonDelta)
|
if (content is ToolUseContent toolUseContent && contentDeltaData.Delta is JsonDelta jsonDelta)
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
using AnthropicClient.Models;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Json;
|
||||||
|
|
||||||
|
class CitationConverter : JsonConverter<Citation>
|
||||||
|
{
|
||||||
|
public override Citation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
using var jsonDocument = JsonDocument.ParseValue(ref reader);
|
||||||
|
var root = jsonDocument.RootElement;
|
||||||
|
var type = root.GetProperty("type").GetString();
|
||||||
|
return type switch
|
||||||
|
{
|
||||||
|
CitationType.CharacterLocation => JsonSerializer.Deserialize<CharacterLocationCitation>(root.GetRawText(), options)!,
|
||||||
|
CitationType.PageLocation => JsonSerializer.Deserialize<PageLocationCitation>(root.GetRawText(), options)!,
|
||||||
|
CitationType.ContentBlockLocation => JsonSerializer.Deserialize<ContentBlockLocationCitation>(root.GetRawText(), options)!,
|
||||||
|
_ => throw new JsonException($"Unknown citation type: {type}")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(Utf8JsonWriter writer, Citation value, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
JsonSerializer.Serialize(writer, value, value.GetType(), options);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ class ContentDeltaConverter : JsonConverter<ContentDelta>
|
|||||||
{
|
{
|
||||||
ContentDeltaType.TextDelta => JsonSerializer.Deserialize<TextDelta>(root.GetRawText(), options)!,
|
ContentDeltaType.TextDelta => JsonSerializer.Deserialize<TextDelta>(root.GetRawText(), options)!,
|
||||||
ContentDeltaType.JsonDelta => JsonSerializer.Deserialize<JsonDelta>(root.GetRawText(), options)!,
|
ContentDeltaType.JsonDelta => JsonSerializer.Deserialize<JsonDelta>(root.GetRawText(), options)!,
|
||||||
|
ContentDeltaType.CitationDelta => JsonSerializer.Deserialize<CitationDelta>(root.GetRawText(), options)!,
|
||||||
_ => throw new JsonException($"Unknown content type: {type}")
|
_ => throw new JsonException($"Unknown content type: {type}")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ static class JsonSerializationOptions
|
|||||||
new ContentDeltaConverter(),
|
new ContentDeltaConverter(),
|
||||||
new JsonStringEnumConverter(),
|
new JsonStringEnumConverter(),
|
||||||
new MessageBatchResultConverter(),
|
new MessageBatchResultConverter(),
|
||||||
|
new CitationConverter(),
|
||||||
|
new SourceConverter(),
|
||||||
},
|
},
|
||||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
using AnthropicClient.Models;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Json;
|
||||||
|
|
||||||
|
class SourceConverter : JsonConverter<Source>
|
||||||
|
{
|
||||||
|
public override Source Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
using var jsonDocument = JsonDocument.ParseValue(ref reader);
|
||||||
|
var root = jsonDocument.RootElement;
|
||||||
|
var type = root.GetProperty("type").GetString();
|
||||||
|
return type switch
|
||||||
|
{
|
||||||
|
SourceType.Text => JsonSerializer.Deserialize<TextSource>(root.GetRawText(), options)!,
|
||||||
|
SourceType.Content => JsonSerializer.Deserialize<CustomSource>(root.GetRawText(), options)!,
|
||||||
|
SourceType.Base64 => DeserializeBase64Source(root, options),
|
||||||
|
_ => throw new JsonException($"Unknown source type: {type}")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Source DeserializeBase64Source(JsonElement root, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
var mediaType = root.TryGetProperty("media_type", out var mediaTypeElement)
|
||||||
|
? mediaTypeElement.GetString() ?? throw new JsonException("Missing 'media_type' property")
|
||||||
|
: throw new JsonException("Missing 'media_type' property");
|
||||||
|
|
||||||
|
var isImage = ImageType.IsValidImageType(mediaType);
|
||||||
|
|
||||||
|
return isImage
|
||||||
|
? JsonSerializer.Deserialize<ImageSource>(root.GetRawText(), options)!
|
||||||
|
: JsonSerializer.Deserialize<DocumentSource>(root.GetRawText(), options)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(Utf8JsonWriter writer, Source value, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
if (value is TextSource textSource)
|
||||||
|
{
|
||||||
|
JsonSerializer.Serialize(writer, textSource, options);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value is CustomSource customSource)
|
||||||
|
{
|
||||||
|
JsonSerializer.Serialize(writer, customSource, options);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value is Base64Source base64Source)
|
||||||
|
{
|
||||||
|
JsonSerializer.Serialize(writer, base64Source, options);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonSerializer.Serialize(writer, value, value.GetType(), options);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
using AnthropicClient.Utils;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a base64 source.
|
||||||
|
/// </summary>
|
||||||
|
public class Base64Source : Source
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the media type of the source.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("media_type")]
|
||||||
|
public string MediaType { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the data of the source.
|
||||||
|
/// </summary>
|
||||||
|
public string Data { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Base64Source"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mediaType">The media type of the source.</param>
|
||||||
|
/// <param name="data">The data of the source.</param>
|
||||||
|
/// <exception cref="ArgumentException">Thrown when the media type is invalid.</exception>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown when the media type or data is null.</exception>
|
||||||
|
/// <returns>A new instance of the <see cref="Base64Source"/> class.</returns>
|
||||||
|
public Base64Source(string mediaType, string data) : base(SourceType.Base64)
|
||||||
|
{
|
||||||
|
ArgumentValidator.ThrowIfNull(mediaType, nameof(mediaType));
|
||||||
|
ArgumentValidator.ThrowIfNull(data, nameof(data));
|
||||||
|
|
||||||
|
MediaType = mediaType;
|
||||||
|
Data = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a citation for specific locations within text content.
|
||||||
|
/// </summary>
|
||||||
|
public class CharacterLocationCitation : Citation
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the start character index of the citation.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("start_char_index")]
|
||||||
|
public int StartCharIndex { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the end character index of the citation.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("end_char_index")]
|
||||||
|
public int EndCharIndex { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CharacterLocationCitation"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A new instance of <see cref="CharacterLocationCitation"/>.</returns>
|
||||||
|
public CharacterLocationCitation() : base(CitationType.CharacterLocation)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a citation
|
||||||
|
/// </summary>
|
||||||
|
public abstract class Citation
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type of the citation.
|
||||||
|
/// </summary>
|
||||||
|
public string Type { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the text that is cited.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("cited_text")]
|
||||||
|
public string CitedText { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the document index of the citation.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("document_index")]
|
||||||
|
public int DocumentIndex { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the title of the document from which the citation is made.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("document_title")]
|
||||||
|
public string DocumentTitle { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Citation"/> class with a specified type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type of the citation.</param>
|
||||||
|
/// <returns>A new instance of <see cref="Citation"/>.</returns>
|
||||||
|
protected Citation(string type)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
using AnthropicClient.Utils;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a citation delta.
|
||||||
|
/// </summary>
|
||||||
|
public class CitationDelta : ContentDelta
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the citation associated with this delta.
|
||||||
|
/// </summary>
|
||||||
|
public Citation Citation { get; init; } = new CharacterLocationCitation();
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
internal CitationDelta() : base(ContentDeltaType.CitationDelta)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CitationDelta"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="citation">The citation to associate with this delta.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown when <paramref name="citation"/> is null.</exception>
|
||||||
|
/// <returns>A new instance of <see cref="CitationDelta"/>.</returns>
|
||||||
|
public CitationDelta(Citation citation) : base(ContentDeltaType.CitationDelta)
|
||||||
|
{
|
||||||
|
ArgumentValidator.ThrowIfNull(citation, nameof(citation));
|
||||||
|
Citation = citation;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents whether citations are enabled for a document.
|
||||||
|
/// </summary>
|
||||||
|
public class CitationOption
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether citations are enabled for the document.
|
||||||
|
/// </summary>
|
||||||
|
public bool Enabled { get; init; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The types of citations that can be returned by the Anthropic API.
|
||||||
|
/// </summary>
|
||||||
|
public static class CitationType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A citation that refers to a specific character in the text.
|
||||||
|
/// </summary>
|
||||||
|
public const string CharacterLocation = "char_location";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A citation that refers to a specific page in the text.
|
||||||
|
/// </summary>
|
||||||
|
public const string PageLocation = "page_location";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A citation that refers to a specific section in the text.
|
||||||
|
/// </summary>
|
||||||
|
public const string ContentBlockLocation = "content_block_location";
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a citation for content blocks within custom content.
|
||||||
|
/// </summary>
|
||||||
|
public class ContentBlockLocationCitation : Citation
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the start block index of the citation.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("start_block_index")]
|
||||||
|
public int StartBlockIndex { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the end block index of the citation.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("end_block_index")]
|
||||||
|
public int EndBlockIndex { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ContentBlockLocationCitation"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A new instance of <see cref="ContentBlockLocationCitation"/>.</returns>
|
||||||
|
public ContentBlockLocationCitation() : base(CitationType.ContentBlockLocation)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,4 +14,9 @@ public static class ContentDeltaType
|
|||||||
/// The input_json_delta.
|
/// The input_json_delta.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string JsonDelta = "input_json_delta";
|
public const string JsonDelta = "input_json_delta";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The citation_delta.
|
||||||
|
/// </summary>
|
||||||
|
public const string CitationDelta = "citations_delta";
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
using AnthropicClient.Utils;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a custom source that contains a list of text content.
|
||||||
|
/// </summary>
|
||||||
|
public class CustomSource : Source
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the list of text content that makes up the custom source.
|
||||||
|
/// </summary>
|
||||||
|
public List<TextContent> Content { get; init; } = [];
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
internal CustomSource() : base(SourceType.Content)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CustomSource"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A new instance of the <see cref="CustomSource"/> class.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown when the content is null.</exception>
|
||||||
|
public CustomSource(List<TextContent> content) : base(SourceType.Content)
|
||||||
|
{
|
||||||
|
ArgumentValidator.ThrowIfNull(content, nameof(content));
|
||||||
|
|
||||||
|
Content = content;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,22 @@ public class DocumentContent : Content
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the source of the document.
|
/// Gets the source of the document.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DocumentSource Source { get; init; } = new();
|
public Source Source { get; init; } = new DocumentSource();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the title of the document.
|
||||||
|
/// </summary>
|
||||||
|
public string? Title { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the context of the document.
|
||||||
|
/// </summary>
|
||||||
|
public string? Context { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether citations are enabled for the document.
|
||||||
|
/// </summary>
|
||||||
|
public CitationOption? Citations { get; init; }
|
||||||
|
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
internal DocumentContent()
|
internal DocumentContent()
|
||||||
@@ -36,7 +51,7 @@ public class DocumentContent : Content
|
|||||||
{
|
{
|
||||||
Validate(mediaType, data);
|
Validate(mediaType, data);
|
||||||
|
|
||||||
Source = new(mediaType, data);
|
Source = new DocumentSource(mediaType, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -51,6 +66,33 @@ public class DocumentContent : Content
|
|||||||
{
|
{
|
||||||
Validate(mediaType, data);
|
Validate(mediaType, data);
|
||||||
|
|
||||||
Source = new(mediaType, data);
|
Source = new DocumentSource(mediaType, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="DocumentContent"/> class with a document source.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">The document source.</param>
|
||||||
|
/// <returns>A new instance of the <see cref="DocumentContent"/> class.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown when the source is null.</exception>
|
||||||
|
public DocumentContent(Source source) : base(ContentType.Document)
|
||||||
|
{
|
||||||
|
ArgumentValidator.ThrowIfNull(source, nameof(source));
|
||||||
|
|
||||||
|
Source = source;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="DocumentContent"/> class with a document source and cache control.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">The document source.</param>
|
||||||
|
/// <param name="cacheControl">The cache control to be used for the content.</param>
|
||||||
|
/// <returns>A new instance of the <see cref="DocumentContent"/> class.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown when the source is null.</exception>
|
||||||
|
public DocumentContent(Source source, CacheControl cacheControl) : base(ContentType.Document, cacheControl)
|
||||||
|
{
|
||||||
|
ArgumentValidator.ThrowIfNull(source, nameof(source));
|
||||||
|
|
||||||
|
Source = source;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,26 +7,10 @@ namespace AnthropicClient.Models;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a document source.
|
/// Represents a document source.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DocumentSource
|
public class DocumentSource : Base64Source
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Gets the media type of the document.
|
|
||||||
/// </summary>
|
|
||||||
[JsonPropertyName("media_type")]
|
|
||||||
public string MediaType { get; init; } = string.Empty;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the data of the document.
|
|
||||||
/// </summary>
|
|
||||||
public string Data { get; init; } = string.Empty;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the type of encoding of the document data.
|
|
||||||
/// </summary>
|
|
||||||
public string Type { get; init; } = "base64";
|
|
||||||
|
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
internal DocumentSource()
|
internal DocumentSource() : base(string.Empty, string.Empty)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,12 +22,7 @@ public class DocumentSource
|
|||||||
/// <exception cref="ArgumentException">Thrown when the media type is invalid.</exception>
|
/// <exception cref="ArgumentException">Thrown when the media type is invalid.</exception>
|
||||||
/// <exception cref="ArgumentNullException">Thrown when the media type or data is null.</exception>
|
/// <exception cref="ArgumentNullException">Thrown when the media type or data is null.</exception>
|
||||||
/// <returns>A new instance of the <see cref="DocumentSource"/> class.</returns>
|
/// <returns>A new instance of the <see cref="DocumentSource"/> class.</returns>
|
||||||
public DocumentSource(string mediaType, string data)
|
public DocumentSource(string mediaType, string data) : base(mediaType, data)
|
||||||
{
|
{
|
||||||
ArgumentValidator.ThrowIfNull(mediaType, nameof(mediaType));
|
|
||||||
ArgumentValidator.ThrowIfNull(data, nameof(data));
|
|
||||||
|
|
||||||
MediaType = mediaType;
|
|
||||||
Data = data;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,7 @@ public class ImageContent : Content
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the source of the image.
|
/// Gets the source of the image.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ImageSource Source { get; init; } = new();
|
public Source Source { get; init; } = new ImageSource();
|
||||||
|
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
internal ImageContent()
|
internal ImageContent()
|
||||||
@@ -36,7 +36,7 @@ public class ImageContent : Content
|
|||||||
{
|
{
|
||||||
Validate(mediaType, data);
|
Validate(mediaType, data);
|
||||||
|
|
||||||
Source = new(mediaType, data);
|
Source = new ImageSource(mediaType, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -51,6 +51,6 @@ public class ImageContent : Content
|
|||||||
{
|
{
|
||||||
Validate(mediaType, data);
|
Validate(mediaType, data);
|
||||||
|
|
||||||
Source = new(mediaType, data);
|
Source = new ImageSource(mediaType, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,26 +7,10 @@ namespace AnthropicClient.Models;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents an image source.
|
/// Represents an image source.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ImageSource
|
public class ImageSource : Base64Source
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Gets the media type of the image.
|
|
||||||
/// </summary>
|
|
||||||
[JsonPropertyName("media_type")]
|
|
||||||
public string MediaType { get; init; } = string.Empty;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the data of the image.
|
|
||||||
/// </summary>
|
|
||||||
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() : base(string.Empty, string.Empty)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,17 +22,13 @@ public class ImageSource
|
|||||||
/// <exception cref="ArgumentException">Thrown when the media type is invalid.</exception>
|
/// <exception cref="ArgumentException">Thrown when the media type is invalid.</exception>
|
||||||
/// <exception cref="ArgumentNullException">Thrown when the media type or data is null.</exception>
|
/// <exception cref="ArgumentNullException">Thrown when the media type or data is null.</exception>
|
||||||
/// <returns>A new instance of the <see cref="ImageSource"/> class.</returns>
|
/// <returns>A new instance of the <see cref="ImageSource"/> class.</returns>
|
||||||
public ImageSource(string mediaType, string data)
|
public ImageSource(string mediaType, string data) : base(mediaType, data)
|
||||||
{
|
{
|
||||||
ArgumentValidator.ThrowIfNull(mediaType, nameof(mediaType));
|
|
||||||
|
|
||||||
if (ImageType.IsValidImageType(mediaType) is false)
|
if (ImageType.IsValidImageType(mediaType) is false)
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"Invalid media type: {mediaType}");
|
throw new ArgumentException($"Invalid media type: {mediaType}");
|
||||||
}
|
}
|
||||||
|
|
||||||
ArgumentValidator.ThrowIfNull(data, nameof(data));
|
|
||||||
|
|
||||||
MediaType = mediaType;
|
MediaType = mediaType;
|
||||||
Data = data;
|
Data = data;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a citation for text within a page of a document.
|
||||||
|
/// </summary>
|
||||||
|
public class PageLocationCitation : Citation
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the start page number of the citation.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("start_page_number")]
|
||||||
|
public int StartPageNumber { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the end page number of the citation.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("end_page_number")]
|
||||||
|
public int EndPageNumber { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="PageLocationCitation"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A new instance of <see cref="PageLocationCitation"/>.</returns>
|
||||||
|
public PageLocationCitation() : base(CitationType.PageLocation)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a base class for sources.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class Source
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type of the source.
|
||||||
|
/// </summary>
|
||||||
|
public string Type { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Source"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type of the source.</param>
|
||||||
|
/// <returns>A new instance of the <see cref="Source"/> class.</returns>
|
||||||
|
protected Source(string type)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the types of document sources that can be used in the Anthropic API.
|
||||||
|
/// </summary>
|
||||||
|
public static class SourceType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The base64 encoded document source type.
|
||||||
|
/// </summary>
|
||||||
|
public const string Base64 = "base64";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The custom content document source type.
|
||||||
|
/// </summary>
|
||||||
|
public const string Content = "content";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The text document source type.
|
||||||
|
/// </summary>
|
||||||
|
public const string Text = "text";
|
||||||
|
}
|
||||||
@@ -14,6 +14,11 @@ public class TextContent : Content
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string Text { get; init; } = string.Empty;
|
public string Text { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the citations associated with the text content.
|
||||||
|
/// </summary>
|
||||||
|
public Citation[]? Citations { get; init; }
|
||||||
|
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
internal TextContent() : base(ContentType.Text)
|
internal TextContent() : base(ContentType.Text)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a text document source.
|
||||||
|
/// </summary>
|
||||||
|
public class TextSource : Source
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the media type of the source.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("media_type")]
|
||||||
|
public string MediaType { get; } = "text/plain";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the data of the source.
|
||||||
|
/// </summary>
|
||||||
|
public string Data { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="TextSource"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">The data of the document.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown when the data is null.</exception>
|
||||||
|
/// <returns>A new instance of the <see cref="TextSource"/> class.</returns>
|
||||||
|
public TextSource(string data) : base(SourceType.Text)
|
||||||
|
{
|
||||||
|
Data = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,7 +24,13 @@ public class EventTestData : IEnumerable<object[]>
|
|||||||
Usage = new Usage { InputTokens = 472, OutputTokens = 91 },
|
Usage = new Usage { InputTokens = 472, OutputTokens = 91 },
|
||||||
StopReason = "tool_use",
|
StopReason = "tool_use",
|
||||||
Content = [
|
Content = [
|
||||||
new TextContent("Okay, let's check the weather for San Francisco, CA:"),
|
new TextContent("Okay, let's check the weather for San Francisco, CA:")
|
||||||
|
{
|
||||||
|
Citations = [
|
||||||
|
new CharacterLocationCitation(),
|
||||||
|
new CharacterLocationCitation(),
|
||||||
|
]
|
||||||
|
},
|
||||||
new ToolUseContent()
|
new ToolUseContent()
|
||||||
{
|
{
|
||||||
Id = "toolu_01T1x1fJ34qAmk2tNTrN7Up6",
|
Id = "toolu_01T1x1fJ34qAmk2tNTrN7Up6",
|
||||||
@@ -380,6 +386,46 @@ public class EventTestData : IEnumerable<object[]>
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
"""
|
||||||
|
event: content_block_delta
|
||||||
|
data: {"type":"content_block_delta","index":0,"delta":{"type":"citations_delta","citation": {"type":"char_location","start_char_index":0,"end_char_index":0, "cited_text":"","document_index":0,"document_title":""}}}
|
||||||
|
""",
|
||||||
|
new AnthropicEvent()
|
||||||
|
{
|
||||||
|
Type = EventType.ContentBlockDelta,
|
||||||
|
Data = new ContentDeltaEventData()
|
||||||
|
{
|
||||||
|
Index = 0,
|
||||||
|
Delta = new CitationDelta()
|
||||||
|
{
|
||||||
|
Citation = new CharacterLocationCitation()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
"""
|
||||||
|
event: content_block_delta
|
||||||
|
data: {"type":"content_block_delta","index":0,"delta":{"type":"citations_delta","citation":{"type":"char_location","start_char_index":0,"end_char_index":0,"cited_text":"","document_index":0,"document_title":""}}}
|
||||||
|
""",
|
||||||
|
new AnthropicEvent()
|
||||||
|
{
|
||||||
|
Type = EventType.ContentBlockDelta,
|
||||||
|
Data = new ContentDeltaEventData()
|
||||||
|
{
|
||||||
|
Index = 0,
|
||||||
|
Delta = new CitationDelta()
|
||||||
|
{
|
||||||
|
Citation = new CharacterLocationCitation()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
yield return new object[]
|
yield return new object[]
|
||||||
{
|
{
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ public class AnthropicApiClientTests(ConfigurationFixture configFixture) : EndTo
|
|||||||
result.Value.Should().BeOfType<MessageResponse>();
|
result.Value.Should().BeOfType<MessageResponse>();
|
||||||
result.Value.Content.Should().NotBeNullOrEmpty();
|
result.Value.Content.Should().NotBeNullOrEmpty();
|
||||||
|
|
||||||
var text = result.Value.Content.Aggregate("", (acc, content) =>
|
var text = result.Value.Content.Aggregate("", static (acc, content) =>
|
||||||
{
|
{
|
||||||
if (content is TextContent textContent)
|
if (content is TextContent textContent)
|
||||||
{
|
{
|
||||||
@@ -122,7 +122,7 @@ public class AnthropicApiClientTests(ConfigurationFixture configFixture) : EndTo
|
|||||||
resultOne.IsSuccess.Should().BeTrue();
|
resultOne.IsSuccess.Should().BeTrue();
|
||||||
resultOne.Value.Should().BeOfType<MessageResponse>();
|
resultOne.Value.Should().BeOfType<MessageResponse>();
|
||||||
resultOne.Value.Content.Should().NotBeNullOrEmpty();
|
resultOne.Value.Content.Should().NotBeNullOrEmpty();
|
||||||
resultOne.Value.Usage.Should().Match<Usage>(u => u.CacheCreationInputTokens > 0 || u.CacheReadInputTokens > 0);
|
resultOne.Value.Usage.Should().Match<Usage>(static u => u.CacheCreationInputTokens > 0 || u.CacheReadInputTokens > 0);
|
||||||
|
|
||||||
request.Messages.Add(new(MessageRole.Assistant, resultOne.Value.Content));
|
request.Messages.Add(new(MessageRole.Assistant, resultOne.Value.Content));
|
||||||
request.Messages.Add(new(MessageRole.User, [new TextContent("What is the main theme of this story?")]));
|
request.Messages.Add(new(MessageRole.User, [new TextContent("What is the main theme of this story?")]));
|
||||||
@@ -158,7 +158,7 @@ public class AnthropicApiClientTests(ConfigurationFixture configFixture) : EndTo
|
|||||||
resultOne.IsSuccess.Should().BeTrue();
|
resultOne.IsSuccess.Should().BeTrue();
|
||||||
resultOne.Value.Should().BeOfType<MessageResponse>();
|
resultOne.Value.Should().BeOfType<MessageResponse>();
|
||||||
resultOne.Value.Content.Should().NotBeNullOrEmpty();
|
resultOne.Value.Content.Should().NotBeNullOrEmpty();
|
||||||
resultOne.Value.Usage.Should().Match<Usage>(u => u.CacheCreationInputTokens > 0 || u.CacheReadInputTokens > 0);
|
resultOne.Value.Usage.Should().Match<Usage>(static u => u.CacheCreationInputTokens > 0 || u.CacheReadInputTokens > 0);
|
||||||
|
|
||||||
request.Messages.Add(new(MessageRole.Assistant, resultOne.Value.Content));
|
request.Messages.Add(new(MessageRole.Assistant, resultOne.Value.Content));
|
||||||
request.Messages.Add(new(MessageRole.User, [new TextContent("What is the main theme of this story?")]));
|
request.Messages.Add(new(MessageRole.User, [new TextContent("What is the main theme of this story?")]));
|
||||||
@@ -236,7 +236,7 @@ public class AnthropicApiClientTests(ConfigurationFixture configFixture) : EndTo
|
|||||||
result.Value.Should().BeOfType<MessageResponse>();
|
result.Value.Should().BeOfType<MessageResponse>();
|
||||||
result.Value.Content.Should().NotBeNullOrEmpty();
|
result.Value.Content.Should().NotBeNullOrEmpty();
|
||||||
|
|
||||||
var text = result.Value.Content.Aggregate("", (acc, content) =>
|
var text = result.Value.Content.Aggregate("", static (acc, content) =>
|
||||||
{
|
{
|
||||||
if (content is TextContent textContent)
|
if (content is TextContent textContent)
|
||||||
{
|
{
|
||||||
@@ -273,7 +273,7 @@ public class AnthropicApiClientTests(ConfigurationFixture configFixture) : EndTo
|
|||||||
resultOne.IsSuccess.Should().BeTrue();
|
resultOne.IsSuccess.Should().BeTrue();
|
||||||
resultOne.Value.Should().BeOfType<MessageResponse>();
|
resultOne.Value.Should().BeOfType<MessageResponse>();
|
||||||
resultOne.Value.Content.Should().NotBeNullOrEmpty();
|
resultOne.Value.Content.Should().NotBeNullOrEmpty();
|
||||||
resultOne.Value.Usage.Should().Match<Usage>(u => u.CacheCreationInputTokens > 0 || u.CacheReadInputTokens > 0);
|
resultOne.Value.Usage.Should().Match<Usage>(static u => u.CacheCreationInputTokens > 0 || u.CacheReadInputTokens > 0);
|
||||||
|
|
||||||
request.Messages.Add(new(MessageRole.Assistant, resultOne.Value.Content));
|
request.Messages.Add(new(MessageRole.Assistant, resultOne.Value.Content));
|
||||||
request.Messages.Add(new(MessageRole.User, [new TextContent("What is the main theme of this paper?")]));
|
request.Messages.Add(new(MessageRole.User, [new TextContent("What is the main theme of this paper?")]));
|
||||||
@@ -286,6 +286,236 @@ public class AnthropicApiClientTests(ConfigurationFixture configFixture) : EndTo
|
|||||||
resultTwo.Value.Usage.CacheReadInputTokens.Should().BeGreaterThan(0);
|
resultTwo.Value.Usage.CacheReadInputTokens.Should().BeGreaterThan(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateMessageAsync_WhenCitationsAreEnabledForTextDocumentSource_ItShouldReturnCitationsInResponse()
|
||||||
|
{
|
||||||
|
var request = new MessageRequest(
|
||||||
|
model: AnthropicModels.Claude35HaikuLatest,
|
||||||
|
messages: [
|
||||||
|
new(
|
||||||
|
MessageRole.User,
|
||||||
|
[
|
||||||
|
new DocumentContent(
|
||||||
|
new TextSource("The grass is green. The sky is blue.")
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Title = "My Document",
|
||||||
|
Context = "This is a trustworthy document.",
|
||||||
|
Citations = new() { Enabled = true }
|
||||||
|
},
|
||||||
|
new TextContent("What color is the grass and sky?"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
var result = await _client.CreateMessageAsync(request);
|
||||||
|
|
||||||
|
result.IsSuccess.Should().BeTrue();
|
||||||
|
|
||||||
|
var citations = result.Value
|
||||||
|
.Content
|
||||||
|
.OfType<TextContent>()
|
||||||
|
.SelectMany(static c => c.Citations is null ? [] : c.Citations);
|
||||||
|
|
||||||
|
citations.OfType<CharacterLocationCitation>().Should().NotBeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateMessageAsync_WhenCitationsAreEnabledForPDFDocumentSource_ItShouldReturnCitationsInResponse()
|
||||||
|
{
|
||||||
|
var pdfPath = TestFileHelper.GetTestFilePath("addendum.pdf");
|
||||||
|
var bytes = await File.ReadAllBytesAsync(pdfPath);
|
||||||
|
var base64Data = Convert.ToBase64String(bytes);
|
||||||
|
|
||||||
|
var request = new MessageRequest(
|
||||||
|
model: AnthropicModels.Claude35HaikuLatest,
|
||||||
|
messages: [
|
||||||
|
new(
|
||||||
|
MessageRole.User,
|
||||||
|
[
|
||||||
|
new DocumentContent("application/pdf", base64Data)
|
||||||
|
{
|
||||||
|
Title = "My PDF Document",
|
||||||
|
Context = "This is a trustworthy document.",
|
||||||
|
Citations = new() { Enabled = true }
|
||||||
|
},
|
||||||
|
new TextContent("What is the title of this paper?"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
var result = await _client.CreateMessageAsync(request);
|
||||||
|
|
||||||
|
result.IsSuccess.Should().BeTrue();
|
||||||
|
|
||||||
|
var citations = result.Value
|
||||||
|
.Content
|
||||||
|
.OfType<TextContent>()
|
||||||
|
.SelectMany(static c => c.Citations is null ? [] : c.Citations);
|
||||||
|
|
||||||
|
citations.OfType<PageLocationCitation>().Should().NotBeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateMessageAsync_WhenCitationsAreEnabledForCustomDocumentSource_ItShouldReturnCitationsInResponse()
|
||||||
|
{
|
||||||
|
var request = new MessageRequest(
|
||||||
|
model: AnthropicModels.Claude35HaikuLatest,
|
||||||
|
messages: [
|
||||||
|
new(
|
||||||
|
MessageRole.User,
|
||||||
|
[
|
||||||
|
new DocumentContent(
|
||||||
|
new CustomSource([
|
||||||
|
new TextContent("The grass is green. The sky is blue.")
|
||||||
|
])
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Title = "My Custom Document",
|
||||||
|
Context = "This is a trustworthy document.",
|
||||||
|
Citations = new() { Enabled = true }
|
||||||
|
},
|
||||||
|
new TextContent("What color is the grass and sky?"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
var result = await _client.CreateMessageAsync(request);
|
||||||
|
|
||||||
|
result.IsSuccess.Should().BeTrue();
|
||||||
|
|
||||||
|
var citations = result.Value
|
||||||
|
.Content
|
||||||
|
.OfType<TextContent>()
|
||||||
|
.SelectMany(static c => c.Citations is null ? [] : c.Citations);
|
||||||
|
|
||||||
|
citations.OfType<ContentBlockLocationCitation>().Should().NotBeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateMessageAsync_WhenStreamingAndCitationsAreEnabledForTextDocumentSource_ItShouldReturnCitationsInResponse()
|
||||||
|
{
|
||||||
|
var request = new StreamMessageRequest(
|
||||||
|
model: AnthropicModels.Claude35HaikuLatest,
|
||||||
|
messages: [
|
||||||
|
new(
|
||||||
|
MessageRole.User,
|
||||||
|
[
|
||||||
|
new DocumentContent(
|
||||||
|
new TextSource("The grass is green. The sky is blue.")
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Title = "My Document",
|
||||||
|
Context = "This is a trustworthy document.",
|
||||||
|
Citations = new() { Enabled = true }
|
||||||
|
},
|
||||||
|
new TextContent("What color is the grass and sky?"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
var result = _client.CreateMessageAsync(request);
|
||||||
|
|
||||||
|
var messageCompleteEvent = await result
|
||||||
|
.Where(e => e.Type is EventType.MessageComplete)
|
||||||
|
.FirstAsync();
|
||||||
|
|
||||||
|
var citations = messageCompleteEvent.Data
|
||||||
|
.As<MessageCompleteEventData>()
|
||||||
|
.Message
|
||||||
|
.Content
|
||||||
|
.OfType<TextContent>()
|
||||||
|
.SelectMany(static c => c.Citations is null ? [] : c.Citations);
|
||||||
|
|
||||||
|
citations.OfType<CharacterLocationCitation>().Should().NotBeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateMessageAsync_WhenStreamingAndCitationsAreEnabledForPDFDocumentSource_ItShouldReturnCitationsInResponse()
|
||||||
|
{
|
||||||
|
var pdfPath = TestFileHelper.GetTestFilePath("addendum.pdf");
|
||||||
|
var bytes = await File.ReadAllBytesAsync(pdfPath);
|
||||||
|
var base64Data = Convert.ToBase64String(bytes);
|
||||||
|
|
||||||
|
var request = new StreamMessageRequest(
|
||||||
|
model: AnthropicModels.Claude35HaikuLatest,
|
||||||
|
messages: [
|
||||||
|
new(
|
||||||
|
MessageRole.User,
|
||||||
|
[
|
||||||
|
new DocumentContent("application/pdf", base64Data)
|
||||||
|
{
|
||||||
|
Title = "My PDF Document",
|
||||||
|
Context = "This is a trustworthy document.",
|
||||||
|
Citations = new() { Enabled = true }
|
||||||
|
},
|
||||||
|
new TextContent("What is the title of this paper?"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
var result = _client.CreateMessageAsync(request);
|
||||||
|
|
||||||
|
var messageCompleteEvent = await result
|
||||||
|
.Where(e => e.Type is EventType.MessageComplete)
|
||||||
|
.FirstAsync();
|
||||||
|
|
||||||
|
var citations = messageCompleteEvent.Data
|
||||||
|
.As<MessageCompleteEventData>()
|
||||||
|
.Message
|
||||||
|
.Content
|
||||||
|
.OfType<TextContent>()
|
||||||
|
.SelectMany(static c => c.Citations is null ? [] : c.Citations);
|
||||||
|
|
||||||
|
citations.OfType<PageLocationCitation>().Should().NotBeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateMessageAsync_WhenStreamingAndCitationsAreEnabledForCustomDocumentSource_ItShouldReturnCitationsInResponse()
|
||||||
|
{
|
||||||
|
var request = new StreamMessageRequest(
|
||||||
|
model: AnthropicModels.Claude35HaikuLatest,
|
||||||
|
messages: [
|
||||||
|
new(
|
||||||
|
MessageRole.User,
|
||||||
|
[
|
||||||
|
new DocumentContent(
|
||||||
|
new CustomSource([
|
||||||
|
new TextContent("The grass is green. The sky is blue.")
|
||||||
|
])
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Title = "My Custom Document",
|
||||||
|
Context = "This is a trustworthy document.",
|
||||||
|
Citations = new() { Enabled = true }
|
||||||
|
},
|
||||||
|
new TextContent("What color is the grass and sky?"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
var result = _client.CreateMessageAsync(request);
|
||||||
|
|
||||||
|
var messageCompleteEvent = await result
|
||||||
|
.Where(e => e.Type is EventType.MessageComplete)
|
||||||
|
.FirstAsync();
|
||||||
|
|
||||||
|
var citations = messageCompleteEvent.Data
|
||||||
|
.As<MessageCompleteEventData>()
|
||||||
|
.Message
|
||||||
|
.Content
|
||||||
|
.OfType<TextContent>()
|
||||||
|
.SelectMany(static c => c.Citations is null ? [] : c.Citations);
|
||||||
|
|
||||||
|
citations.OfType<ContentBlockLocationCitation>().Should().NotBeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CountMessageTokensAsync_WhenCalled_ItShouldReturnResponse()
|
public async Task CountMessageTokensAsync_WhenCalled_ItShouldReturnResponse()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Json;
|
||||||
|
|
||||||
|
public class CitationConverterTests : SerializationTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenTypeIsUnknown_ItThrowException()
|
||||||
|
{
|
||||||
|
var json = @"{ ""type"": ""unknown"" }";
|
||||||
|
|
||||||
|
var action = () => Deserialize<Citation>(json);
|
||||||
|
|
||||||
|
action.Should().Throw<JsonException>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Json;
|
||||||
|
|
||||||
|
public class SourceConverterTests : SerializationTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenTypeIsUnknown_ItThrowsException()
|
||||||
|
{
|
||||||
|
var json = @"{ ""type"": ""unknown"" }";
|
||||||
|
|
||||||
|
var action = () => Deserialize<Source>(json);
|
||||||
|
|
||||||
|
action.Should().Throw<JsonException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonSerialization_WhenSourceIsNotKnown_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var source = new TestSource();
|
||||||
|
|
||||||
|
var result = Serialize<Source>(source);
|
||||||
|
|
||||||
|
JsonAssert.Equal(@"{ ""type"": ""test"" }", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestSource : Source
|
||||||
|
{
|
||||||
|
public TestSource() : base("test")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class Base64SourceTests : SerializationTest
|
||||||
|
{
|
||||||
|
private readonly string _testJson = @"{
|
||||||
|
""type"": ""base64"",
|
||||||
|
""media_type"": ""application/pdf"",
|
||||||
|
""data"": ""base64data""
|
||||||
|
}";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledWithValidArguments_ItShouldSetProperties()
|
||||||
|
{
|
||||||
|
var mediaType = "application/pdf";
|
||||||
|
var data = "base64data";
|
||||||
|
|
||||||
|
var source = new Base64Source(mediaType, data);
|
||||||
|
|
||||||
|
source.MediaType.Should().Be(mediaType);
|
||||||
|
source.Data.Should().Be(data);
|
||||||
|
source.Type.Should().Be("base64");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledWithNullMediaType_ItShouldThrowArgumentNullException()
|
||||||
|
{
|
||||||
|
string? mediaType = null;
|
||||||
|
var data = "base64data";
|
||||||
|
|
||||||
|
var action = () => new Base64Source(mediaType!, data);
|
||||||
|
|
||||||
|
action.Should().Throw<ArgumentNullException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledWithNullData_ItShouldThrowArgumentNullException()
|
||||||
|
{
|
||||||
|
var mediaType = "application/pdf";
|
||||||
|
string? data = null;
|
||||||
|
|
||||||
|
var action = () => new Base64Source(mediaType, data!);
|
||||||
|
|
||||||
|
action.Should().Throw<ArgumentNullException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var mediaType = "application/pdf";
|
||||||
|
var data = "base64data";
|
||||||
|
var source = new Base64Source(mediaType, data);
|
||||||
|
|
||||||
|
var result = Serialize<Source>(source);
|
||||||
|
|
||||||
|
JsonAssert.Equal(_testJson, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var source = Deserialize<Source>(_testJson);
|
||||||
|
|
||||||
|
var base64Source = source.As<Base64Source>();
|
||||||
|
base64Source!.Type.Should().Be("base64");
|
||||||
|
base64Source.MediaType.Should().Be("application/pdf");
|
||||||
|
base64Source.Data.Should().Be("base64data");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenMediaTypeIsMissing_ItShouldThrowException()
|
||||||
|
{
|
||||||
|
var json = @"{ ""type"": ""base64"", ""data"": ""base64data"" }";
|
||||||
|
|
||||||
|
var action = () => Deserialize<Source>(json);
|
||||||
|
|
||||||
|
action.Should().Throw<JsonException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenMediaTypeIsNull_ItShouldThrowException()
|
||||||
|
{
|
||||||
|
var json = @"{ ""type"": ""base64"", ""media_type"": null, ""data"": ""base64data"" }";
|
||||||
|
|
||||||
|
var action = () => Deserialize<Source>(json);
|
||||||
|
|
||||||
|
action.Should().Throw<JsonException>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class CharacterLocationCitationTests : SerializationTest
|
||||||
|
{
|
||||||
|
private readonly string _testJson = @"{
|
||||||
|
""type"": ""char_location"",
|
||||||
|
""cited_text"": ""cited text"",
|
||||||
|
""document_index"": 1,
|
||||||
|
""document_title"": ""document title"",
|
||||||
|
""start_char_index"": 2,
|
||||||
|
""end_char_index"": 3
|
||||||
|
}";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalled_ItShouldSetProperties()
|
||||||
|
{
|
||||||
|
var result = new CharacterLocationCitation();
|
||||||
|
|
||||||
|
result.Type.Should().Be("char_location");
|
||||||
|
result.CitedText.Should().BeEmpty();
|
||||||
|
result.DocumentIndex.Should().Be(0);
|
||||||
|
result.DocumentTitle.Should().BeEmpty();
|
||||||
|
result.StartCharIndex.Should().Be(0);
|
||||||
|
result.EndCharIndex.Should().Be(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var citation = new CharacterLocationCitation
|
||||||
|
{
|
||||||
|
Type = "char_location",
|
||||||
|
CitedText = "cited text",
|
||||||
|
DocumentIndex = 1,
|
||||||
|
DocumentTitle = "document title",
|
||||||
|
StartCharIndex = 2,
|
||||||
|
EndCharIndex = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = Serialize<Citation>(citation);
|
||||||
|
|
||||||
|
JsonAssert.Equal(_testJson, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var citation = Deserialize<Citation>(_testJson);
|
||||||
|
|
||||||
|
var characterLocationCitation = citation.As<CharacterLocationCitation>();
|
||||||
|
characterLocationCitation!.Type.Should().Be("char_location");
|
||||||
|
characterLocationCitation.CitedText.Should().Be("cited text");
|
||||||
|
characterLocationCitation.DocumentIndex.Should().Be(1);
|
||||||
|
characterLocationCitation.DocumentTitle.Should().Be("document title");
|
||||||
|
characterLocationCitation.StartCharIndex.Should().Be(2);
|
||||||
|
characterLocationCitation.EndCharIndex.Should().Be(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class CitationDeltaTests : SerializationTest
|
||||||
|
{
|
||||||
|
private readonly string _testJson = @"{
|
||||||
|
""type"": ""citations_delta"",
|
||||||
|
""citation"": {
|
||||||
|
""type"": ""content_block_location"",
|
||||||
|
""cited_text"": ""cited text"",
|
||||||
|
""document_index"": 1,
|
||||||
|
""document_title"": ""document title"",
|
||||||
|
""start_block_index"": 2,
|
||||||
|
""end_block_index"": 3
|
||||||
|
}
|
||||||
|
}";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalled_ItShouldSetProperties()
|
||||||
|
{
|
||||||
|
var result = new CitationDelta();
|
||||||
|
|
||||||
|
result.Type.Should().Be("citations_delta");
|
||||||
|
result.Citation.Should().BeEquivalentTo(new CharacterLocationCitation());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledWithCitation_ItShouldSetProperties()
|
||||||
|
{
|
||||||
|
var citation = new ContentBlockLocationCitation();
|
||||||
|
var result = new CitationDelta(citation);
|
||||||
|
|
||||||
|
result.Type.Should().Be("citations_delta");
|
||||||
|
result.Citation.Should().BeSameAs(citation);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledWithNullCitation_ItShouldThrowException()
|
||||||
|
{
|
||||||
|
var act = () => new CitationDelta(null!);
|
||||||
|
|
||||||
|
act.Should().Throw<ArgumentNullException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var citation = new ContentBlockLocationCitation
|
||||||
|
{
|
||||||
|
Type = "content_block_location",
|
||||||
|
CitedText = "cited text",
|
||||||
|
DocumentIndex = 1,
|
||||||
|
DocumentTitle = "document title",
|
||||||
|
StartBlockIndex = 2,
|
||||||
|
EndBlockIndex = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
var citationDelta = new CitationDelta(citation);
|
||||||
|
var result = Serialize<ContentDelta>(citationDelta);
|
||||||
|
|
||||||
|
JsonAssert.Equal(_testJson, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var citation = Deserialize<ContentDelta>(_testJson);
|
||||||
|
|
||||||
|
var citationDelta = citation.As<CitationDelta>();
|
||||||
|
citationDelta!.Type.Should().Be("citations_delta");
|
||||||
|
|
||||||
|
var contentBlockLocationCitation = citationDelta.Citation.As<ContentBlockLocationCitation>();
|
||||||
|
contentBlockLocationCitation!.CitedText.Should().Be("cited text");
|
||||||
|
contentBlockLocationCitation.DocumentIndex.Should().Be(1);
|
||||||
|
contentBlockLocationCitation.DocumentTitle.Should().Be("document title");
|
||||||
|
contentBlockLocationCitation.StartBlockIndex.Should().Be(2);
|
||||||
|
contentBlockLocationCitation.EndBlockIndex.Should().Be(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class CitationOptionTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalled_ItShouldSetProperties()
|
||||||
|
{
|
||||||
|
var result = new CitationOption();
|
||||||
|
|
||||||
|
result.Enabled.Should().BeFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class CitationTypeTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void ContentBlockLocation_WhenCalled_ItShouldReturnCorrectValue()
|
||||||
|
{
|
||||||
|
CitationType.ContentBlockLocation.Should().Be("content_block_location");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CharacterLocation_WhenCalled_ItShouldReturnCorrectValue()
|
||||||
|
{
|
||||||
|
CitationType.CharacterLocation.Should().Be("char_location");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PageLocation_WhenCalled_ItShouldReturnCorrectValue()
|
||||||
|
{
|
||||||
|
CitationType.PageLocation.Should().Be("page_location");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class ContentBlockLocationCitationTests : SerializationTest
|
||||||
|
{
|
||||||
|
private readonly string _testJson = @"{
|
||||||
|
""type"": ""content_block_location"",
|
||||||
|
""cited_text"": ""cited text"",
|
||||||
|
""document_index"": 1,
|
||||||
|
""document_title"": ""document title"",
|
||||||
|
""start_block_index"": 2,
|
||||||
|
""end_block_index"": 3
|
||||||
|
}";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalled_ItShouldSetProperties()
|
||||||
|
{
|
||||||
|
var result = new ContentBlockLocationCitation();
|
||||||
|
|
||||||
|
result.Type.Should().Be("content_block_location");
|
||||||
|
result.CitedText.Should().BeEmpty();
|
||||||
|
result.DocumentIndex.Should().Be(0);
|
||||||
|
result.DocumentTitle.Should().BeEmpty();
|
||||||
|
result.StartBlockIndex.Should().Be(0);
|
||||||
|
result.EndBlockIndex.Should().Be(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var citation = new ContentBlockLocationCitation
|
||||||
|
{
|
||||||
|
Type = "content_block_location",
|
||||||
|
CitedText = "cited text",
|
||||||
|
DocumentIndex = 1,
|
||||||
|
DocumentTitle = "document title",
|
||||||
|
StartBlockIndex = 2,
|
||||||
|
EndBlockIndex = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = Serialize<Citation>(citation);
|
||||||
|
|
||||||
|
JsonAssert.Equal(_testJson, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var citation = Deserialize<Citation>(_testJson);
|
||||||
|
|
||||||
|
var contentBlockLocationCitation = citation.As<ContentBlockLocationCitation>();
|
||||||
|
contentBlockLocationCitation!.Type.Should().Be("content_block_location");
|
||||||
|
contentBlockLocationCitation.CitedText.Should().Be("cited text");
|
||||||
|
contentBlockLocationCitation.DocumentIndex.Should().Be(1);
|
||||||
|
contentBlockLocationCitation.DocumentTitle.Should().Be("document title");
|
||||||
|
contentBlockLocationCitation.StartBlockIndex.Should().Be(2);
|
||||||
|
contentBlockLocationCitation.EndBlockIndex.Should().Be(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,4 +21,14 @@ public class ContentDeltaTypeTests
|
|||||||
|
|
||||||
actual.Should().Be(expected);
|
actual.Should().Be(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CitationDelta_WhenCalled_ItShouldReturnExpectedValue()
|
||||||
|
{
|
||||||
|
var expected = "citations_delta";
|
||||||
|
|
||||||
|
var actual = ContentDeltaType.CitationDelta;
|
||||||
|
|
||||||
|
actual.Should().Be(expected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class CustomSourceTests : SerializationTest
|
||||||
|
{
|
||||||
|
private readonly string _testJson = @"{
|
||||||
|
""type"": ""content"",
|
||||||
|
""content"": [
|
||||||
|
{
|
||||||
|
""type"": ""text"",
|
||||||
|
""text"": ""Sample text""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalled_ItShouldSetProperties()
|
||||||
|
{
|
||||||
|
var result = new CustomSource();
|
||||||
|
|
||||||
|
result.Content.Should().BeEmpty();
|
||||||
|
result.Type.Should().Be("content");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledWithContent_ItShouldSetProperties()
|
||||||
|
{
|
||||||
|
var content = new List<TextContent>
|
||||||
|
{
|
||||||
|
new("Sample text")
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = new CustomSource(content);
|
||||||
|
|
||||||
|
result.Content.Should().BeSameAs(content);
|
||||||
|
result.Type.Should().Be("content");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledWithNullContent_ItShouldThrowArgumentNullException()
|
||||||
|
{
|
||||||
|
var act = () => new CustomSource(null!);
|
||||||
|
|
||||||
|
act.Should().Throw<ArgumentNullException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var content = new List<TextContent>
|
||||||
|
{
|
||||||
|
new("Sample text")
|
||||||
|
};
|
||||||
|
var source = new CustomSource(content);
|
||||||
|
|
||||||
|
var result = Serialize<Source>(source);
|
||||||
|
|
||||||
|
JsonAssert.Equal(_testJson, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var source = Deserialize<Source>(_testJson);
|
||||||
|
|
||||||
|
var customSource = source.As<CustomSource>();
|
||||||
|
customSource!.Type.Should().Be("content");
|
||||||
|
customSource.Content.Should().HaveCount(1);
|
||||||
|
customSource.Content[0].Type.Should().Be("text");
|
||||||
|
customSource.Content[0].Text.Should().Be("Sample text");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -87,6 +87,28 @@ public class DocumentContentTests : SerializationTest
|
|||||||
action.Should().Throw<ArgumentNullException>();
|
action.Should().Throw<ArgumentNullException>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledWithSource_ItShouldInitializeSource()
|
||||||
|
{
|
||||||
|
var source = new DocumentSource("application/pdf", "data");
|
||||||
|
|
||||||
|
var result = new DocumentContent(source);
|
||||||
|
|
||||||
|
result.Source.Should().BeSameAs(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledWithSourceAndCacheControl_ItShouldInitializeSourceAndCacheControl()
|
||||||
|
{
|
||||||
|
var source = new DocumentSource("application/pdf", "data");
|
||||||
|
var cacheControl = new EphemeralCacheControl();
|
||||||
|
|
||||||
|
var result = new DocumentContent(source, cacheControl);
|
||||||
|
|
||||||
|
result.Source.Should().BeSameAs(source);
|
||||||
|
result.CacheControl.Should().BeSameAs(cacheControl);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
|
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ public class MessageRequestTests : SerializationTest
|
|||||||
""content"": [
|
""content"": [
|
||||||
{
|
{
|
||||||
""type"": ""image"",
|
""type"": ""image"",
|
||||||
""source"": { ""media_type"": ""image/jpeg"", ""data"": ""data"" }
|
""source"": { ""type"": ""base64"", ""media_type"": ""image/jpeg"", ""data"": ""data"" }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -558,8 +558,9 @@ public class MessageRequestTests : SerializationTest
|
|||||||
|
|
||||||
var imageContent = messageRequest.Messages[0].Content[0] as ImageContent;
|
var imageContent = messageRequest.Messages[0].Content[0] as ImageContent;
|
||||||
imageContent!.Type.Should().Be("image");
|
imageContent!.Type.Should().Be("image");
|
||||||
imageContent.Source.MediaType.Should().Be("image/jpeg");
|
|
||||||
imageContent.Source.Data.Should().Be("data");
|
imageContent.Source.As<ImageSource>().MediaType.Should().Be("image/jpeg");
|
||||||
|
imageContent.Source.As<ImageSource>().Data.Should().Be("data");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class PageLocationCitationTests : SerializationTest
|
||||||
|
{
|
||||||
|
private readonly string _testJson = @"{
|
||||||
|
""type"": ""page_location"",
|
||||||
|
""cited_text"": ""cited text"",
|
||||||
|
""document_index"": 1,
|
||||||
|
""document_title"": ""document title"",
|
||||||
|
""start_page_number"": 2,
|
||||||
|
""end_page_number"": 3
|
||||||
|
}";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalled_ItShouldSetProperties()
|
||||||
|
{
|
||||||
|
var result = new PageLocationCitation();
|
||||||
|
|
||||||
|
result.Type.Should().Be("page_location");
|
||||||
|
result.CitedText.Should().BeEmpty();
|
||||||
|
result.DocumentIndex.Should().Be(0);
|
||||||
|
result.DocumentTitle.Should().BeEmpty();
|
||||||
|
result.StartPageNumber.Should().Be(0);
|
||||||
|
result.EndPageNumber.Should().Be(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var citation = new PageLocationCitation
|
||||||
|
{
|
||||||
|
Type = "page_location",
|
||||||
|
CitedText = "cited text",
|
||||||
|
DocumentIndex = 1,
|
||||||
|
DocumentTitle = "document title",
|
||||||
|
StartPageNumber = 2,
|
||||||
|
EndPageNumber = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = Serialize<Citation>(citation);
|
||||||
|
|
||||||
|
JsonAssert.Equal(_testJson, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var citation = Deserialize<Citation>(_testJson);
|
||||||
|
|
||||||
|
var pageLocationCitation = citation.As<PageLocationCitation>();
|
||||||
|
pageLocationCitation!.Type.Should().Be("page_location");
|
||||||
|
pageLocationCitation.CitedText.Should().Be("cited text");
|
||||||
|
pageLocationCitation.DocumentIndex.Should().Be(1);
|
||||||
|
pageLocationCitation.DocumentTitle.Should().Be("document title");
|
||||||
|
pageLocationCitation.StartPageNumber.Should().Be(2);
|
||||||
|
pageLocationCitation.EndPageNumber.Should().Be(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class SourceTypeTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Base64_WhenCalled_ItShouldReturnCorrectValue()
|
||||||
|
{
|
||||||
|
SourceType.Base64.Should().Be("base64");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Content_WhenCalled_ItShouldReturnCorrectValue()
|
||||||
|
{
|
||||||
|
SourceType.Content.Should().Be("content");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Text_WhenCalled_ItShouldReturnCorrectValue()
|
||||||
|
{
|
||||||
|
SourceType.Text.Should().Be("text");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class TextSourceTests : SerializationTest
|
||||||
|
{
|
||||||
|
private readonly string _testJson = @"{
|
||||||
|
""type"": ""text"",
|
||||||
|
""media_type"": ""text/plain"",
|
||||||
|
""data"": ""data""
|
||||||
|
}";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalled_ItShouldSetProperties()
|
||||||
|
{
|
||||||
|
var result = new TextSource("data");
|
||||||
|
|
||||||
|
result.Type.Should().Be("text");
|
||||||
|
result.MediaType.Should().Be("text/plain");
|
||||||
|
result.Data.Should().Be("data");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var source = new TextSource("data");
|
||||||
|
|
||||||
|
var result = Serialize<Source>(source);
|
||||||
|
|
||||||
|
JsonAssert.Equal(_testJson, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
|
||||||
|
{
|
||||||
|
var source = Deserialize<Source>(_testJson);
|
||||||
|
|
||||||
|
var textSource = source.As<TextSource>();
|
||||||
|
textSource!.Type.Should().Be("text");
|
||||||
|
textSource.MediaType.Should().Be("text/plain");
|
||||||
|
textSource.Data.Should().Be("data");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user