diff --git a/src/Mscc.GenerativeAI/Enums/DynamicRetrievalConfigMode.cs b/src/Mscc.GenerativeAI/Enums/DynamicRetrievalConfigMode.cs new file mode 100644 index 0000000..efcf6b1 --- /dev/null +++ b/src/Mscc.GenerativeAI/Enums/DynamicRetrievalConfigMode.cs @@ -0,0 +1,22 @@ +#if NET472_OR_GREATER || NETSTANDARD2_0 +using System.Text.Json.Serialization; +#endif + +namespace Mscc.GenerativeAI +{ + /// + /// The mode of the predictor to be used in dynamic retrieval. + /// + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum DynamicRetrievalConfigMode + { + /// + /// Always trigger retrieval. + /// + ModeUnspecified = 0, + /// + /// Run retrieval only when system decides it is necessary. + /// + ModeDynamic + } +} \ No newline at end of file diff --git a/src/Mscc.GenerativeAI/Types/Generative/Candidate.cs b/src/Mscc.GenerativeAI/Types/Generative/Candidate.cs index 9f92315..9bacb38 100644 --- a/src/Mscc.GenerativeAI/Types/Generative/Candidate.cs +++ b/src/Mscc.GenerativeAI/Types/Generative/Candidate.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +#if NET472_OR_GREATER || NETSTANDARD2_0 +using System.Collections.Generic; +#endif namespace Mscc.GenerativeAI { diff --git a/src/Mscc.GenerativeAI/Types/Generative/DynamicRetrievalConfig.cs b/src/Mscc.GenerativeAI/Types/Generative/DynamicRetrievalConfig.cs new file mode 100644 index 0000000..ffc590c --- /dev/null +++ b/src/Mscc.GenerativeAI/Types/Generative/DynamicRetrievalConfig.cs @@ -0,0 +1,17 @@ +namespace Mscc.GenerativeAI +{ + /// + /// Describes the options to customize dynamic retrieval. + /// + public class DynamicRetrievalConfig + { + /// + /// The mode of the predictor to be used in dynamic retrieval. + /// + public DynamicRetrievalConfigMode? Mode { get; set; } + /// + /// The threshold to be used in dynamic retrieval. If not set, a system default value is used. + /// + public float? DynamicThreshold { get; set; } + } +} \ No newline at end of file diff --git a/src/Mscc.GenerativeAI/Types/Generative/GoogleSearchRetrieval.cs b/src/Mscc.GenerativeAI/Types/Generative/GoogleSearchRetrieval.cs index b810c9f..f69b717 100644 --- a/src/Mscc.GenerativeAI/Types/Generative/GoogleSearchRetrieval.cs +++ b/src/Mscc.GenerativeAI/Types/Generative/GoogleSearchRetrieval.cs @@ -5,10 +5,33 @@ namespace Mscc.GenerativeAI /// public class GoogleSearchRetrieval { + /// + /// Specifies the dynamic retrieval configuration for the given source. + /// + public DynamicRetrievalConfig? DynamicRetrievalConfig { get; set; } /// /// Optional. Disable using the result from this tool in detecting grounding attribution. /// /// This does not affect how the result is given to the model for generation. public bool? DisableAttribution { get; set; } + + /// + /// Creates an instance of + /// + public GoogleSearchRetrieval() { } + + /// + /// Creates an instance of with Mode and DynamicThreshold. + /// + /// The mode of the predictor to be used in dynamic retrieval. + /// The threshold to be used in dynamic retrieval. If not set, a system default value is used. + public GoogleSearchRetrieval(DynamicRetrievalConfigMode mode, float dynamicThreshold) + { + DynamicRetrievalConfig = new DynamicRetrievalConfig + { + Mode = mode, + DynamicThreshold = dynamicThreshold + }; + } } } \ No newline at end of file diff --git a/src/Mscc.GenerativeAI/Types/Generative/GroundingChunk.cs b/src/Mscc.GenerativeAI/Types/Generative/GroundingChunk.cs new file mode 100644 index 0000000..5e488b3 --- /dev/null +++ b/src/Mscc.GenerativeAI/Types/Generative/GroundingChunk.cs @@ -0,0 +1,13 @@ +namespace Mscc.GenerativeAI +{ + /// + /// Grounding chunk. + /// + public class GroundingChunk + { + /// + /// Grounding chunk from the web. + /// + public Web? Web { get; set; } + } +} \ No newline at end of file diff --git a/src/Mscc.GenerativeAI/Types/Generative/GroundingMetadata.cs b/src/Mscc.GenerativeAI/Types/Generative/GroundingMetadata.cs index 985a308..2e4102f 100644 --- a/src/Mscc.GenerativeAI/Types/Generative/GroundingMetadata.cs +++ b/src/Mscc.GenerativeAI/Types/Generative/GroundingMetadata.cs @@ -1,10 +1,37 @@ -using System.Collections.Generic; +#if NET472_OR_GREATER || NETSTANDARD2_0 +using System.Collections.Generic; +#endif namespace Mscc.GenerativeAI { + /// + /// Metadata returned to client when grounding is enabled. + /// public class GroundingMetadata { + /// + /// Optional. Google search entry for the following-up web searches. + /// + public SearchEntryPoint? SearchEntryPoint { get; set; } + /// + /// + /// public List? GroundingAttributions { get; set; } + /// + /// Web search queries for the following-up web search. + /// public List? WebSearchQueries { get; set; } + /// + /// List of grounding support. + /// + public List? GroundingSupports { get; set; } + /// + /// Metadata related to retrieval in the grounding flow. + /// + public RetrievalMetadata? RetrievalMetadata { get; set; } + /// + /// List of supporting references retrieved from specified grounding source. + /// + public List? GroundingChunks { get; set; } } } \ No newline at end of file diff --git a/src/Mscc.GenerativeAI/Types/Generative/GroundingSupport.cs b/src/Mscc.GenerativeAI/Types/Generative/GroundingSupport.cs new file mode 100644 index 0000000..0221c44 --- /dev/null +++ b/src/Mscc.GenerativeAI/Types/Generative/GroundingSupport.cs @@ -0,0 +1,29 @@ +#if NET472_OR_GREATER || NETSTANDARD2_0 +using System.Collections.Generic; +#endif + +namespace Mscc.GenerativeAI +{ + /// + /// Grounding support. + /// + public class GroundingSupport + { + /// + /// Segment of the content this support belongs to. + /// + public Segment? Segment { get; set; } + /// + /// A list of indices (into 'grounding_chunk') specifying the citations associated with the claim. + /// + /// + /// For instance [1,3,4] means that grounding_chunk[1], grounding_chunk[3], grounding_chunk[4] are the retrieved content attributed to the claim. + /// + public List? GroundingChunkIndices { get; set; } + /// + /// Confidence score of the support references. Ranges from 0 to 1. 1 is the most confident. + /// This list must have the same size as the grounding_chunk_indices. + /// + public List? ConfidenceScores { get; set; } + } +} \ No newline at end of file diff --git a/src/Mscc.GenerativeAI/Types/Generative/RetrievalMetadata.cs b/src/Mscc.GenerativeAI/Types/Generative/RetrievalMetadata.cs new file mode 100644 index 0000000..6176b6b --- /dev/null +++ b/src/Mscc.GenerativeAI/Types/Generative/RetrievalMetadata.cs @@ -0,0 +1,18 @@ +namespace Mscc.GenerativeAI +{ + /// + /// Metadata related to retrieval in the grounding flow. + /// + public class RetrievalMetadata + { + /// + /// Optional. Score indicating how likely information from google search could help answer the prompt. + /// + /// + /// The score is in the range [0, 1], where 0 is the least likely and 1 is the most likely. + /// This score is only populated when google search grounding and dynamic retrieval is enabled. + /// It will be compared to the threshold to determine whether to trigger google search. + /// + public float? GoogleSearchDynamicRetrievalScore { get; set; } + } +} \ No newline at end of file diff --git a/src/Mscc.GenerativeAI/Types/Generative/SearchEntryPoint.cs b/src/Mscc.GenerativeAI/Types/Generative/SearchEntryPoint.cs new file mode 100644 index 0000000..e6e7217 --- /dev/null +++ b/src/Mscc.GenerativeAI/Types/Generative/SearchEntryPoint.cs @@ -0,0 +1,17 @@ +namespace Mscc.GenerativeAI +{ + /// + /// Google search entry point. + /// + public class SearchEntryPoint + { + /// + /// Optional. Web content snippet that can be embedded in a web page or an app webview. + /// + public string? RenderedContent { get; set; } + /// + /// Optional. Base64 encoded JSON representing array of tuple. + /// + public byte[]? SdkBlob { get; set; } + } +} \ No newline at end of file diff --git a/src/Mscc.GenerativeAI/Types/Generative/Segment.cs b/src/Mscc.GenerativeAI/Types/Generative/Segment.cs new file mode 100644 index 0000000..a40f3ff --- /dev/null +++ b/src/Mscc.GenerativeAI/Types/Generative/Segment.cs @@ -0,0 +1,25 @@ +namespace Mscc.GenerativeAI +{ + /// + /// Segment of the content. + /// + public class Segment + { + /// + /// Output only. The text corresponding to the segment from the response. + /// + public string? Text { get; set; } + /// + /// Output only. Start index in the given Part, measured in bytes. Offset from the start of the Part, inclusive, starting at zero. + /// + public int? StartIndex { get; set; } + /// + /// Output only. The index of a Part object within its parent Content object. + /// + public int? PartIndex { get; set; } + /// + /// Output only. End index in the given Part, measured in bytes. Offset from the start of the Part, exclusive, starting at zero. + /// + public int? EndIndex { get; set; } + } +} \ No newline at end of file diff --git a/src/Mscc.GenerativeAI/Types/Generative/Web.cs b/src/Mscc.GenerativeAI/Types/Generative/Web.cs new file mode 100644 index 0000000..7cba083 --- /dev/null +++ b/src/Mscc.GenerativeAI/Types/Generative/Web.cs @@ -0,0 +1,17 @@ +namespace Mscc.GenerativeAI +{ + /// + /// Chunk from the web. + /// + public class Web + { + /// + /// URI reference of the chunk. + /// + public string? Uri { get; set; } + /// + /// Title of the chunk. + /// + public string? Title { get; set; } + } +} \ No newline at end of file diff --git a/tests/Mscc.GenerativeAI/GoogleAi_GeminiPro_Should.cs b/tests/Mscc.GenerativeAI/GoogleAi_GeminiPro_Should.cs index fc17648..7429d0c 100644 --- a/tests/Mscc.GenerativeAI/GoogleAi_GeminiPro_Should.cs +++ b/tests/Mscc.GenerativeAI/GoogleAi_GeminiPro_Should.cs @@ -771,7 +771,7 @@ public async Task Start_Chat_Streaming() [Fact] // Ref: https://ai.google.dev/api/generate-content#code-execution - public async Task Code_Execution() + public async Task Generate_Content_Code_Execution() { // Arrange var prompt = "What is the sum of the first 50 prime numbers?"; @@ -791,6 +791,72 @@ public async Task Code_Execution() // .Where(t => !string.IsNullOrEmpty(t)) .ToArray())); } + + [Fact] + // Ref: https://ai.google.dev/gemini-api/docs/grounding + public async Task Generate_Content_Grounding_Search() + { + // Arrange + var prompt = "Who won Wimbledon this year?"; + var genAi = new GoogleAI(_fixture.ApiKey); + var model = genAi.GenerativeModel("gemini-1.5-pro-002", + tools: [new Tool { GoogleSearchRetrieval = new() }]); + + // Act + var response = await model.GenerateContent(prompt); + + // Assert + response.Should().NotBeNull(); + response.Candidates.Should().NotBeNull().And.HaveCount(1); + response.Candidates![0].GroundingMetadata.Should().NotBeNull(); + response.Candidates![0].GroundingMetadata!.SearchEntryPoint.Should().NotBeNull(); + response.Candidates![0].GroundingMetadata!.WebSearchQueries.Should().NotBeNull(); + _output.WriteLine(string.Join(Environment.NewLine, + response.Candidates![0].Content!.Parts + .Select(x => x.Text) +// .Where(t => !string.IsNullOrEmpty(t)) + .ToArray())); + response.Candidates![0].GroundingMetadata!.GroundingChunks! + .ForEach(c => + _output.WriteLine($"{c!.Web!.Title} - {c!.Web!.Uri}")); + _output.WriteLine(string.Join(Environment.NewLine, + response.Candidates![0].GroundingMetadata!.WebSearchQueries! + .Select(w => w) + .ToArray())); + _output.WriteLine(response.Candidates![0].GroundingMetadata!.SearchEntryPoint!.RenderedContent); + } + + [Fact] + // Ref: https://ai.google.dev/gemini-api/docs/grounding + public async Task Generate_Content_Grounding_Search_Dictionary() + { + // Arrange + var prompt = "Who won Wimbledon this year?"; + var genAi = new GoogleAI(_fixture.ApiKey); + var model = genAi.GenerativeModel("gemini-1.5-pro-002", + tools: [new Tool { GoogleSearchRetrieval = + new(DynamicRetrievalConfigMode.ModeUnspecified, 0.06f) }]); + + // Act + var response = await model.GenerateContent(prompt); + + // Assert + response.Should().NotBeNull(); + response.Candidates.Should().NotBeNull().And.HaveCount(1); + _output.WriteLine(string.Join(Environment.NewLine, + response.Candidates![0].Content!.Parts + .Select(x => x.Text) +// .Where(t => !string.IsNullOrEmpty(t)) + .ToArray())); + response.Candidates![0].GroundingMetadata!.GroundingChunks! + .ForEach(c => + _output.WriteLine($"{c!.Web!.Title} - {c!.Web!.Uri}")); + _output.WriteLine(string.Join(Environment.NewLine, + response.Candidates![0].GroundingMetadata!.WebSearchQueries! + .Select(w => w) + .ToArray())); + _output.WriteLine(response.Candidates![0].GroundingMetadata!.SearchEntryPoint!.RenderedContent); + } [Fact] // Ref: https://ai.google.dev/docs/function_calling