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 ContentDeltaConverter(),
|
||||||
new JsonStringEnumConverter(),
|
new JsonStringEnumConverter(),
|
||||||
new MessageBatchResultConverter(),
|
new MessageBatchResultConverter(),
|
||||||
|
new CitationConverter(),
|
||||||
},
|
},
|
||||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
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>
|
/// </summary>
|
||||||
public DocumentSource Source { get; init; } = new();
|
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]
|
[JsonConstructor]
|
||||||
internal DocumentContent()
|
internal DocumentContent()
|
||||||
{
|
{
|
||||||
@@ -53,4 +59,24 @@ public class DocumentContent : Content
|
|||||||
|
|
||||||
Source = new(mediaType, data);
|
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>
|
/// </summary>
|
||||||
public string Text { get; init; } = string.Empty;
|
public string Text { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
public Citation[] Citations { get; init; } = [];
|
||||||
|
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
internal TextContent() : base(ContentType.Text)
|
internal TextContent() : base(ContentType.Text)
|
||||||
{
|
{
|
||||||
@@ -50,4 +52,45 @@ public class TextContent : Content
|
|||||||
|
|
||||||
Text = text;
|
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);
|
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]
|
[Fact]
|
||||||
public async Task CountMessageTokensAsync_WhenCalled_ItShouldReturnResponse()
|
public async Task CountMessageTokensAsync_WhenCalled_ItShouldReturnResponse()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user