Skip to content

Commit 6f94b0a

Browse files
committed
Add Grok search option to avoid citations
Citations are returned from search by default, but now you can turn them off as documented at https://docs.x.ai/docs/guides/live-search#returning-citations
1 parent bf3a238 commit 6f94b0a

File tree

3 files changed

+87
-67
lines changed

3 files changed

+87
-67
lines changed

src/Extensions/Grok/GrokChatClient.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ IChatClient GetChatClient(string modelId) => clients.GetOrAdd(modelId, model
8989
FromDate = tool.FromDate,
9090
ToDate = tool.ToDate,
9191
MaxSearchResults = tool.MaxSearchResults,
92-
Sources = tool.Sources
92+
Sources = tool.Sources,
93+
ReturnCitations = tool.ReturnCitations
9394
};
9495
}
9596

@@ -133,6 +134,7 @@ class GrokChatWebSearchOptions : global::OpenAI.Chat.ChatWebSearchOptions
133134
public DateOnly? ToDate { get; set; }
134135
public int? MaxSearchResults { get; set; }
135136
public IList<GrokSource>? Sources { get; set; }
137+
public bool? ReturnCitations { get; set; }
136138
}
137139

138140
[JsonSourceGenerationOptions(JsonSerializerDefaults.Web,

src/Extensions/Grok/GrokSearchTool.cs

Lines changed: 23 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -23,118 +23,77 @@ public enum GrokSearch
2323
Off
2424
}
2525

26-
/// <summary>
27-
/// Configures Grok's live search capabilities.
28-
/// See https://docs.x.ai/docs/guides/live-search.
29-
/// </summary>
26+
/// <summary>Configures Grok's live search capabilities. See https://docs.x.ai/docs/guides/live-search.</summary>
3027
public class GrokSearchTool(GrokSearch mode) : HostedWebSearchTool
3128
{
32-
/// <summary>
33-
/// Sets the search mode for Grok's live search capabilities.
34-
/// </summary>
29+
/// <summary>Sets the search mode for Grok's live search capabilities.</summary>
3530
public GrokSearch Mode { get; } = mode;
3631
/// <inheritdoc/>
3732
public override string Name => "Live Search";
3833
/// <inheritdoc/>
3934
public override string Description => "Performs live search using X.AI";
40-
/// <summary>
41-
/// See https://docs.x.ai/docs/guides/live-search#set-date-range-of-the-search-data
42-
/// </summary>
35+
/// <summary>See https://docs.x.ai/docs/guides/live-search#set-date-range-of-the-search-data</summary>
4336
public DateOnly? FromDate { get; set; }
44-
/// <summary>
45-
/// See https://docs.x.ai/docs/guides/live-search#set-date-range-of-the-search-data
46-
/// </summary>
37+
/// <summary>See https://docs.x.ai/docs/guides/live-search#set-date-range-of-the-search-data</summary>
4738
public DateOnly? ToDate { get; set; }
48-
/// <summary>
49-
/// See https://docs.x.ai/docs/guides/live-search#limit-the-maximum-amount-of-data-sources
50-
/// </summary>
39+
/// <summary>See https://docs.x.ai/docs/guides/live-search#limit-the-maximum-amount-of-data-sources</summary>
5140
public int? MaxSearchResults { get; set; }
52-
/// <summary>
53-
/// See https://docs.x.ai/docs/guides/live-search#data-sources-and-parameters
54-
/// </summary>
41+
/// <summary>See https://docs.x.ai/docs/guides/live-search#data-sources-and-parameters</summary>
5542
public IList<GrokSource>? Sources { get; set; }
43+
/// <summary>See https://docs.x.ai/docs/guides/live-search#returning-citations</summary>
44+
public bool? ReturnCitations { get; set; }
5645
}
5746

58-
/// <summary>
59-
/// Grok Live Search data source base type.
60-
/// </summary>
47+
/// <summary>Grok Live Search data source base type.</summary>
6148
[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")]
6249
[JsonDerivedType(typeof(GrokWebSource), "web")]
6350
[JsonDerivedType(typeof(GrokNewsSource), "news")]
6451
[JsonDerivedType(typeof(GrokRssSource), "rss")]
6552
[JsonDerivedType(typeof(GrokXSource), "x")]
6653
public abstract class GrokSource { }
6754

68-
/// <summary>
69-
/// Search-based data source base class providing common properties for `web` and `news` sources.
70-
/// </summary>
55+
/// <summary>Search-based data source base class providing common properties for `web` and `news` sources.</summary>
7156
public abstract class GrokSearchSource : GrokSource
7257
{
73-
/// <summary>
74-
/// Include data from a specific country/region by specifying the ISO alpha-2 code of the country.
75-
/// </summary>
58+
/// <summary>Include data from a specific country/region by specifying the ISO alpha-2 code of the country.</summary>
7659
public string? Country { get; set; }
77-
/// <summary>
78-
/// See https://docs.x.ai/docs/guides/live-search#parameter-safe_search-supported-by-web-and-news
79-
/// </summary>
60+
/// <summary>See https://docs.x.ai/docs/guides/live-search#parameter-safe_search-supported-by-web-and-news</summary>
8061
public bool? SafeSearch { get; set; }
81-
/// <summary>
82-
/// See https://docs.x.ai/docs/guides/live-search#parameter-excluded_websites-supported-by-web-and-news
83-
/// </summary>
62+
/// <summary>See https://docs.x.ai/docs/guides/live-search#parameter-excluded_websites-supported-by-web-and-news</summary>
8463
public IList<string>? ExcludedWebsites { get; set; }
8564
}
8665

87-
/// <summary>
88-
/// Web live search source.
89-
/// </summary>
66+
/// <summary>Web live search source.</summary>
9067
public class GrokWebSource : GrokSearchSource
9168
{
92-
/// <summary>
93-
/// See https://docs.x.ai/docs/guides/live-search#parameter-allowed_websites-supported-by-web
94-
/// </summary>
69+
/// <summary>See https://docs.x.ai/docs/guides/live-search#parameter-allowed_websites-supported-by-web</summary>
9570
public IList<string>? AllowedWebsites { get; set; }
9671
}
9772

98-
/// <summary>
99-
/// News live search source.
100-
/// </summary>
73+
/// <summary>News live search source.</summary>
10174
public class GrokNewsSource : GrokSearchSource { }
10275

103-
/// <summary>
104-
/// RSS live search source.
105-
/// </summary>
76+
/// <summary>RSS live search source.</summary>
10677
/// <param name="rss">The RSS feed to search.</param>
10778
public class GrokRssSource(string rss) : GrokSource
10879
{
109-
/// <summary>
110-
/// See https://docs.x.ai/docs/guides/live-search#parameter-link-supported-by-rss
111-
/// </summary>
80+
/// <summary>See https://docs.x.ai/docs/guides/live-search#parameter-link-supported-by-rss</summary>
11281
public IList<string>? Links { get; set; } = [rss];
11382
}
11483

115-
/// <summary>
116-
/// X live search source.
117-
/// </summary>
84+
/// <summary>X live search source./summary>
11885
public class GrokXSource : GrokSearchSource
11986
{
120-
/// <summary>
121-
/// See https://docs.x.ai/docs/guides/live-search#parameter-excluded_x_handles-supported-by-x
122-
/// </summary>
87+
/// <summary>See https://docs.x.ai/docs/guides/live-search#parameter-excluded_x_handles-supported-by-x</summary>
12388
[JsonPropertyName("excluded_x_handles")]
12489
public IList<string>? ExcludedHandles { get; set; }
125-
/// <summary>
126-
/// See https://docs.x.ai/docs/guides/live-search#parameter-included_x_handles-supported-by-x
127-
/// </summary>
90+
/// <summary>See https://docs.x.ai/docs/guides/live-search#parameter-included_x_handles-supported-by-x</summary>
12891
[JsonPropertyName("included_x_handles")]
12992
public IList<string>? IncludedHandles { get; set; }
130-
/// <summary>
131-
/// See https://docs.x.ai/docs/guides/live-search#parameters-post_favorite_count-and-post_view_count-supported-by-x
132-
/// </summary>
93+
/// <summary>See https://docs.x.ai/docs/guides/live-search#parameters-post_favorite_count-and-post_view_count-supported-by-x</summary>
13394
[JsonPropertyName("post_favorite_count")]
13495
public int? FavoriteCount { get; set; }
135-
/// <summary>
136-
/// See https://docs.x.ai/docs/guides/live-search#parameters-post_favorite_count-and-post_view_count-supported-by-x
137-
/// </summary>
96+
/// <summary>See https://docs.x.ai/docs/guides/live-search#parameters-post_favorite_count-and-post_view_count-supported-by-x</summary>
13897
[JsonPropertyName("post_view_count")]
13998
public int? ViewCount { get; set; }
14099
}

src/Tests/GrokTests.cs

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ public async Task GrokInvokesSpecificSearchUrl()
187187
var requests = new List<JsonNode>();
188188
var responses = new List<JsonNode>();
189189

190-
var grok = new GrokChatClient(Configuration["XAI_API_KEY"]!, "grok-3", OpenAIClientOptions
190+
var grok = new GrokChatClient(Configuration["XAI_API_KEY"]!, "grok-4-fast-non-reasoning", OpenAIClientOptions
191191
.Observable(requests.Add, responses.Add)
192192
.WriteTo(output));
193193

@@ -236,6 +236,65 @@ public async Task GrokInvokesSpecificSearchUrl()
236236
Assert.True(catedral, "Expected at least one citation to catedralaltapatagonia.com");
237237

238238
// Uses the default model set by the client when we asked for it
239-
Assert.Equal("grok-3", response.ModelId);
239+
Assert.Equal("grok-4-fast-non-reasoning", response.ModelId);
240+
}
241+
242+
[SecretsFact("XAI_API_KEY")]
243+
public async Task CanAvoidCitations()
244+
{
245+
var messages = new Chat()
246+
{
247+
{ "system", "Sos un asistente del Cerro Catedral, usas la funcionalidad de Live Search en el sitio oficial." },
248+
{ "system", $"Hoy es {DateTime.Now.ToString("o")}" },
249+
{ "user", "Que calidad de nieve hay hoy?" },
250+
};
251+
252+
var requests = new List<JsonNode>();
253+
var responses = new List<JsonNode>();
254+
255+
var grok = new GrokChatClient(Configuration["XAI_API_KEY"]!, "grok-4-fast-non-reasoning", OpenAIClientOptions
256+
.Observable(requests.Add, responses.Add)
257+
.WriteTo(output));
258+
259+
var options = new ChatOptions
260+
{
261+
Tools = [new GrokSearchTool(GrokSearch.On)
262+
{
263+
ReturnCitations = false,
264+
Sources =
265+
[
266+
new GrokWebSource
267+
{
268+
AllowedWebsites =
269+
[
270+
"https://catedralaltapatagonia.com",
271+
"https://catedralaltapatagonia.com/parte-de-nieve/",
272+
"https://catedralaltapatagonia.com/tarifas/"
273+
]
274+
},
275+
]
276+
}]
277+
};
278+
279+
var response = await grok.GetResponseAsync(messages, options);
280+
var text = response.Text;
281+
282+
// assert that the request contains the following node
283+
// "search_parameters": {
284+
// "mode": "auto"
285+
// "return_citations": "false"
286+
//}
287+
Assert.All(requests, x =>
288+
{
289+
var search = Assert.IsType<JsonObject>(x["search_parameters"]);
290+
Assert.Equal("on", search["mode"]?.GetValue<string>());
291+
Assert.False(search["return_citations"]?.GetValue<bool>());
292+
});
293+
294+
// Citations are not included
295+
Assert.Single(responses);
296+
var node = responses[0];
297+
Assert.NotNull(node);
298+
Assert.Null(node["citations"]);
240299
}
241300
}

0 commit comments

Comments
 (0)