feat: initial pass at citations in request and response
This commit is contained in:
@@ -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 content type: {type}")
|
||||
};
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, Citation value, JsonSerializerOptions options)
|
||||
{
|
||||
JsonSerializer.Serialize(writer, value, value.GetType(), options);
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ static class JsonSerializationOptions
|
||||
new ContentDeltaConverter(),
|
||||
new JsonStringEnumConverter(),
|
||||
new MessageBatchResultConverter(),
|
||||
new CitationConverter(),
|
||||
},
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace AnthropicClient.Models;
|
||||
|
||||
public static class CitationType
|
||||
{
|
||||
public const string CharacterLocation = "char_location";
|
||||
public const string PageLocation = "page_location";
|
||||
public const string ContentBlockLocation = "content_block_location";
|
||||
}
|
||||
@@ -14,6 +14,12 @@ public class DocumentContent : Content
|
||||
/// </summary>
|
||||
public DocumentSource Source { get; init; } = new();
|
||||
|
||||
public string Title { get; init; } = string.Empty;
|
||||
|
||||
public string Context { get; init; } = string.Empty;
|
||||
|
||||
public CitationOption Citations { get; init; } = new CitationOption();
|
||||
|
||||
[JsonConstructor]
|
||||
internal DocumentContent()
|
||||
{
|
||||
@@ -53,4 +59,24 @@ public class DocumentContent : Content
|
||||
|
||||
Source = new(mediaType, data);
|
||||
}
|
||||
|
||||
public DocumentContent(DocumentSource source) : base(ContentType.Document)
|
||||
{
|
||||
ArgumentValidator.ThrowIfNull(source, nameof(source));
|
||||
|
||||
Source = source;
|
||||
}
|
||||
|
||||
public DocumentContent(DocumentSource source, CacheControl cacheControl) : base(ContentType.Document, cacheControl)
|
||||
{
|
||||
ArgumentValidator.ThrowIfNull(source, nameof(source));
|
||||
|
||||
Source = source;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class CitationOption
|
||||
{
|
||||
public bool Enabled { get; init; }
|
||||
}
|
||||
@@ -14,6 +14,8 @@ public class TextContent : Content
|
||||
/// </summary>
|
||||
public string Text { get; init; } = string.Empty;
|
||||
|
||||
public Citation[] Citations { get; init; } = [];
|
||||
|
||||
[JsonConstructor]
|
||||
internal TextContent() : base(ContentType.Text)
|
||||
{
|
||||
@@ -51,3 +53,44 @@ public class TextContent : Content
|
||||
Text = text;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class Citation
|
||||
{
|
||||
public string Type { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("cited_text")]
|
||||
public string CitedText { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("document_index")]
|
||||
public int DocumentIndex { get; init; }
|
||||
|
||||
[JsonPropertyName("document_title")]
|
||||
public string DocumentTitle { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
public class CharacterLocationCitation : Citation
|
||||
{
|
||||
[JsonPropertyName("start_char_index")]
|
||||
public int StartCharIndex { get; init; }
|
||||
|
||||
[JsonPropertyName("end_char_index")]
|
||||
public int EndCharIndex { get; init; }
|
||||
}
|
||||
|
||||
public class PageLocationCitation : Citation
|
||||
{
|
||||
[JsonPropertyName("start_page_number")]
|
||||
public int StartPageNumber { get; init; }
|
||||
|
||||
[JsonPropertyName("end_page_number")]
|
||||
public int EndPageNumber { get; init; }
|
||||
}
|
||||
|
||||
public class ContentBlockLocationCitation : Citation
|
||||
{
|
||||
[JsonPropertyName("start_block_index")]
|
||||
public int StartBlockIndex { get; init; }
|
||||
|
||||
[JsonPropertyName("end_block_index")]
|
||||
public int EndBlockIndex { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace AnthropicClient.Models;
|
||||
|
||||
public class TextDocumentSource : DocumentSource
|
||||
{
|
||||
public TextDocumentSource(string data) : base("text/plain", data)
|
||||
{
|
||||
Type = "text";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -286,6 +286,35 @@ public class AnthropicApiClientTests(ConfigurationFixture configFixture) : EndTo
|
||||
resultTwo.Value.Usage.CacheReadInputTokens.Should().BeGreaterThan(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateMessageAsync_WhenCitationsAreEnabled_ItShouldReturnCitationsInResponse()
|
||||
{
|
||||
var request = new MessageRequest(
|
||||
model: AnthropicModels.Claude3Haiku,
|
||||
messages: [
|
||||
new(
|
||||
MessageRole.User,
|
||||
[
|
||||
new DocumentContent(
|
||||
new TextDocumentSource("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();
|
||||
result.Value.Content.OfType<TextContent>().SelectMany(c => c.Citations).Should().NotBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CountMessageTokensAsync_WhenCalled_ItShouldReturnResponse()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user