diff --git a/src/Extensions/Grok/GrokChatClient.cs b/src/Extensions/Grok/GrokChatClient.cs index d657320..0a1c63c 100644 --- a/src/Extensions/Grok/GrokChatClient.cs +++ b/src/Extensions/Grok/GrokChatClient.cs @@ -89,7 +89,8 @@ IChatClient GetChatClient(string modelId) => clients.GetOrAdd(modelId, model FromDate = tool.FromDate, ToDate = tool.ToDate, MaxSearchResults = tool.MaxSearchResults, - Sources = tool.Sources + Sources = tool.Sources, + ReturnCitations = tool.ReturnCitations }; } @@ -133,6 +134,7 @@ class GrokChatWebSearchOptions : global::OpenAI.Chat.ChatWebSearchOptions public DateOnly? ToDate { get; set; } public int? MaxSearchResults { get; set; } public IList? Sources { get; set; } + public bool? ReturnCitations { get; set; } } [JsonSourceGenerationOptions(JsonSerializerDefaults.Web, diff --git a/src/Extensions/Grok/GrokSearchTool.cs b/src/Extensions/Grok/GrokSearchTool.cs index 7bad648..82cc445 100644 --- a/src/Extensions/Grok/GrokSearchTool.cs +++ b/src/Extensions/Grok/GrokSearchTool.cs @@ -23,41 +23,28 @@ public enum GrokSearch Off } -/// -/// Configures Grok's live search capabilities. -/// See https://docs.x.ai/docs/guides/live-search. -/// +/// Configures Grok's live search capabilities. See https://docs.x.ai/docs/guides/live-search. public class GrokSearchTool(GrokSearch mode) : HostedWebSearchTool { - /// - /// Sets the search mode for Grok's live search capabilities. - /// + /// Sets the search mode for Grok's live search capabilities. public GrokSearch Mode { get; } = mode; /// public override string Name => "Live Search"; /// public override string Description => "Performs live search using X.AI"; - /// - /// See https://docs.x.ai/docs/guides/live-search#set-date-range-of-the-search-data - /// + /// See https://docs.x.ai/docs/guides/live-search#set-date-range-of-the-search-data public DateOnly? FromDate { get; set; } - /// - /// See https://docs.x.ai/docs/guides/live-search#set-date-range-of-the-search-data - /// + /// See https://docs.x.ai/docs/guides/live-search#set-date-range-of-the-search-data public DateOnly? ToDate { get; set; } - /// - /// See https://docs.x.ai/docs/guides/live-search#limit-the-maximum-amount-of-data-sources - /// + /// See https://docs.x.ai/docs/guides/live-search#limit-the-maximum-amount-of-data-sources public int? MaxSearchResults { get; set; } - /// - /// See https://docs.x.ai/docs/guides/live-search#data-sources-and-parameters - /// + /// See https://docs.x.ai/docs/guides/live-search#data-sources-and-parameters public IList? Sources { get; set; } + /// See https://docs.x.ai/docs/guides/live-search#returning-citations + public bool? ReturnCitations { get; set; } } -/// -/// Grok Live Search data source base type. -/// +/// Grok Live Search data source base type. [JsonPolymorphic(TypeDiscriminatorPropertyName = "type")] [JsonDerivedType(typeof(GrokWebSource), "web")] [JsonDerivedType(typeof(GrokNewsSource), "news")] @@ -65,76 +52,48 @@ public class GrokSearchTool(GrokSearch mode) : HostedWebSearchTool [JsonDerivedType(typeof(GrokXSource), "x")] public abstract class GrokSource { } -/// -/// Search-based data source base class providing common properties for `web` and `news` sources. -/// +/// Search-based data source base class providing common properties for `web` and `news` sources. public abstract class GrokSearchSource : GrokSource { - /// - /// Include data from a specific country/region by specifying the ISO alpha-2 code of the country. - /// + /// Include data from a specific country/region by specifying the ISO alpha-2 code of the country. public string? Country { get; set; } - /// - /// See https://docs.x.ai/docs/guides/live-search#parameter-safe_search-supported-by-web-and-news - /// + /// See https://docs.x.ai/docs/guides/live-search#parameter-safe_search-supported-by-web-and-news public bool? SafeSearch { get; set; } - /// - /// See https://docs.x.ai/docs/guides/live-search#parameter-excluded_websites-supported-by-web-and-news - /// + /// See https://docs.x.ai/docs/guides/live-search#parameter-excluded_websites-supported-by-web-and-news public IList? ExcludedWebsites { get; set; } } -/// -/// Web live search source. -/// +/// Web live search source. public class GrokWebSource : GrokSearchSource { - /// - /// See https://docs.x.ai/docs/guides/live-search#parameter-allowed_websites-supported-by-web - /// + /// See https://docs.x.ai/docs/guides/live-search#parameter-allowed_websites-supported-by-web public IList? AllowedWebsites { get; set; } } -/// -/// News live search source. -/// +/// News live search source. public class GrokNewsSource : GrokSearchSource { } -/// -/// RSS live search source. -/// +/// RSS live search source. /// The RSS feed to search. public class GrokRssSource(string rss) : GrokSource { - /// - /// See https://docs.x.ai/docs/guides/live-search#parameter-link-supported-by-rss - /// + /// See https://docs.x.ai/docs/guides/live-search#parameter-link-supported-by-rss public IList? Links { get; set; } = [rss]; } -/// -/// X live search source. -/// +/// X live search source./summary> public class GrokXSource : GrokSearchSource { - /// - /// See https://docs.x.ai/docs/guides/live-search#parameter-excluded_x_handles-supported-by-x - /// + /// See https://docs.x.ai/docs/guides/live-search#parameter-excluded_x_handles-supported-by-x [JsonPropertyName("excluded_x_handles")] public IList? ExcludedHandles { get; set; } - /// - /// See https://docs.x.ai/docs/guides/live-search#parameter-included_x_handles-supported-by-x - /// + /// See https://docs.x.ai/docs/guides/live-search#parameter-included_x_handles-supported-by-x [JsonPropertyName("included_x_handles")] public IList? IncludedHandles { get; set; } - /// - /// See https://docs.x.ai/docs/guides/live-search#parameters-post_favorite_count-and-post_view_count-supported-by-x - /// + /// See https://docs.x.ai/docs/guides/live-search#parameters-post_favorite_count-and-post_view_count-supported-by-x [JsonPropertyName("post_favorite_count")] public int? FavoriteCount { get; set; } - /// - /// See https://docs.x.ai/docs/guides/live-search#parameters-post_favorite_count-and-post_view_count-supported-by-x - /// + /// See https://docs.x.ai/docs/guides/live-search#parameters-post_favorite_count-and-post_view_count-supported-by-x [JsonPropertyName("post_view_count")] public int? ViewCount { get; set; } } \ No newline at end of file diff --git a/src/Tests/GrokTests.cs b/src/Tests/GrokTests.cs index 9927099..f60fbe5 100644 --- a/src/Tests/GrokTests.cs +++ b/src/Tests/GrokTests.cs @@ -187,7 +187,7 @@ public async Task GrokInvokesSpecificSearchUrl() var requests = new List(); var responses = new List(); - var grok = new GrokChatClient(Configuration["XAI_API_KEY"]!, "grok-3", OpenAIClientOptions + var grok = new GrokChatClient(Configuration["XAI_API_KEY"]!, "grok-4-fast-non-reasoning", OpenAIClientOptions .Observable(requests.Add, responses.Add) .WriteTo(output)); @@ -236,6 +236,65 @@ public async Task GrokInvokesSpecificSearchUrl() Assert.True(catedral, "Expected at least one citation to catedralaltapatagonia.com"); // Uses the default model set by the client when we asked for it - Assert.Equal("grok-3", response.ModelId); + Assert.Equal("grok-4-fast-non-reasoning", response.ModelId); + } + + [SecretsFact("XAI_API_KEY")] + public async Task CanAvoidCitations() + { + var messages = new Chat() + { + { "system", "Sos un asistente del Cerro Catedral, usas la funcionalidad de Live Search en el sitio oficial." }, + { "system", $"Hoy es {DateTime.Now.ToString("o")}" }, + { "user", "Que calidad de nieve hay hoy?" }, + }; + + var requests = new List(); + var responses = new List(); + + var grok = new GrokChatClient(Configuration["XAI_API_KEY"]!, "grok-4-fast-non-reasoning", OpenAIClientOptions + .Observable(requests.Add, responses.Add) + .WriteTo(output)); + + var options = new ChatOptions + { + Tools = [new GrokSearchTool(GrokSearch.On) + { + ReturnCitations = false, + Sources = + [ + new GrokWebSource + { + AllowedWebsites = + [ + "https://catedralaltapatagonia.com", + "https://catedralaltapatagonia.com/parte-de-nieve/", + "https://catedralaltapatagonia.com/tarifas/" + ] + }, + ] + }] + }; + + var response = await grok.GetResponseAsync(messages, options); + var text = response.Text; + + // assert that the request contains the following node + // "search_parameters": { + // "mode": "auto" + // "return_citations": "false" + //} + Assert.All(requests, x => + { + var search = Assert.IsType(x["search_parameters"]); + Assert.Equal("on", search["mode"]?.GetValue()); + Assert.False(search["return_citations"]?.GetValue()); + }); + + // Citations are not included + Assert.Single(responses); + var node = responses[0]; + Assert.NotNull(node); + Assert.Null(node["citations"]); } } \ No newline at end of file