Skip to content

Commit fa79186

Browse files
committed
Add first-class support for Grok unique features
* Live search support: On/Off/Auto setting * Reasoning effort: for grok-3-mini Example: ```csharp var messages = new Chat() { { "system", "You are a highly intelligent AI assistant." }, { "user", "What is 101*3?" }, }; var grok = new GrokClient(Env.Get("XAI_API_KEY")!); var options = new GrokChatOptions { ModelId = "grok-3-mini", // or "grok-3-mini-fast" Temperature = 0.7f, ReasoningEffort = GrokReasoningEffort.High, // or GrokReasoningEffort.Low Search = GrokSearch.Auto, // or GrokSearch.On or GrokSearch.Off }; var response = await grok.GetResponseAsync(messages, options); AnsiConsole.MarkupLine($":robot: {response.Text}"); ```
1 parent 2143eae commit fa79186

File tree

10 files changed

+165
-9
lines changed

10 files changed

+165
-9
lines changed

.netconfig

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,7 @@
9696
[file "Directory.Build.rsp"]
9797
url = https://github.com/devlooped/oss/blob/main/Directory.Build.rsp
9898
sha = 0f7f7f7e8a29de9b535676f75fe7c67e629a5e8c
99-
100-
etag = 0ccae83fc51f400bfd7058170bfec7aba11455e24a46a0d7e6a358da6486e255
101-
weak
99+
skip
102100
[file "_config.yml"]
103101
url = https://github.com/devlooped/oss/blob/main/_config.yml
104102
sha = 68b409c486842062e0de0e5b11e6fdb7cd12d6e2

Directory.Build.rsp

Lines changed: 0 additions & 5 deletions
This file was deleted.

src/AI/ChatExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,4 @@ public static class ChatExtensions
2020
public Task<ChatResponse> GetResponseAsync(Chat chat, ChatOptions? options = null, CancellationToken cancellation = default)
2121
=> client.GetResponseAsync((IEnumerable<ChatMessage>)chat, options, cancellation);
2222
}
23-
}
23+
}

src/AI/Grok/GrokChatOptions.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using Microsoft.Extensions.AI;
2+
3+
namespace Devlooped.Extensions.AI;
4+
5+
/// <summary>
6+
/// Grok-specific chat options that extend the base <see cref="ChatOptions"/>
7+
/// with <see cref="Search"/> and <see cref="ReasoningEffort"/> properties.
8+
/// </summary>
9+
public class GrokChatOptions : ChatOptions
10+
{
11+
/// <summary>
12+
/// Configures Grok's live search capabilities.
13+
/// See https://docs.x.ai/docs/guides/live-search.
14+
/// </summary>
15+
public GrokSearch Search { get; set; } = GrokSearch.Auto;
16+
17+
/// <summary>
18+
/// Configures the reasoning effort level for Grok's responses.
19+
/// See https://docs.x.ai/docs/guides/reasoning.
20+
/// </summary>
21+
public GrokReasoningEffort? ReasoningEffort { get; set; }
22+
}

src/AI/Grok/GrokClient.cs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
using System.ClientModel;
2+
using System.ClientModel.Primitives;
3+
using System.Collections.Concurrent;
4+
using System.Text.Json;
5+
using Microsoft.Extensions.AI;
6+
using OpenAI;
7+
8+
namespace Devlooped.Extensions.AI;
9+
10+
public class GrokClient(string apiKey, GrokClientOptions options)
11+
: OpenAIClient(new ApiKeyCredential(apiKey), options), IChatClient
12+
{
13+
readonly GrokClientOptions clientOptions = options;
14+
readonly ConcurrentDictionary<string, IChatClient> clients = new();
15+
16+
public GrokClient(string apiKey)
17+
: this(apiKey, new())
18+
{
19+
}
20+
21+
void IDisposable.Dispose() { }
22+
object? IChatClient.GetService(Type serviceType, object? serviceKey) => default;
23+
24+
async Task<ChatResponse> IChatClient.GetResponseAsync(IEnumerable<ChatMessage> messages, ChatOptions? options, CancellationToken cancellation)
25+
=> SetResponse(await GetClient(options).GetResponseAsync(messages, SetOptions(options), cancellation));
26+
27+
IAsyncEnumerable<ChatResponseUpdate> IChatClient.GetStreamingResponseAsync(IEnumerable<ChatMessage> messages, ChatOptions? options, CancellationToken cancellation)
28+
=> GetClient(options).GetStreamingResponseAsync(messages, SetOptions(options), cancellation);
29+
30+
IChatClient GetClient(ChatOptions? options) => clients.GetOrAdd(
31+
options?.ModelId ?? clientOptions.Model,
32+
model => base.GetChatClient(model).AsIChatClient());
33+
34+
ChatResponse SetResponse(ChatResponse response)
35+
{
36+
return response;
37+
}
38+
39+
ChatOptions? SetOptions(ChatOptions? options)
40+
{
41+
if (options is null || options is not GrokChatOptions grok)
42+
return null;
43+
44+
options.RawRepresentationFactory = _ =>
45+
{
46+
var result = new GrokCompletionOptions
47+
{
48+
Search = grok.Search
49+
};
50+
51+
if (grok.ReasoningEffort != null)
52+
{
53+
result.ReasoningEffortLevel = grok.ReasoningEffort switch
54+
{
55+
GrokReasoningEffort.Low => OpenAI.Chat.ChatReasoningEffortLevel.Low,
56+
GrokReasoningEffort.High => OpenAI.Chat.ChatReasoningEffortLevel.High,
57+
_ => throw new ArgumentException($"Unsupported reasoning effort {grok.ReasoningEffort}")
58+
};
59+
}
60+
61+
return result;
62+
};
63+
64+
return options;
65+
}
66+
67+
class SearchParameters
68+
{
69+
public GrokSearch Mode { get; set; } = GrokSearch.Auto;
70+
}
71+
72+
class GrokCompletionOptions : OpenAI.Chat.ChatCompletionOptions
73+
{
74+
public GrokSearch Search { get; set; } = GrokSearch.Auto;
75+
76+
protected override void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options)
77+
{
78+
base.JsonModelWriteCore(writer, options);
79+
80+
// "search_parameters": { "mode": "auto" }
81+
writer.WritePropertyName("search_parameters");
82+
writer.WriteStartObject();
83+
writer.WriteString("mode", Search.ToString().ToLowerInvariant());
84+
writer.WriteEndObject();
85+
}
86+
}
87+
}
88+

src/AI/Grok/GrokClientOptions.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using OpenAI;
2+
3+
namespace Devlooped.Extensions.AI;
4+
5+
public class GrokClientOptions : OpenAIClientOptions
6+
{
7+
public GrokClientOptions() : this("grok-3") { }
8+
9+
public GrokClientOptions(string model)
10+
{
11+
Endpoint = new Uri("https://api.x.ai/v1");
12+
Model = Throw.IfNullOrEmpty(model);
13+
}
14+
15+
public string Model { get; }
16+
}

src/AI/Grok/GrokReasoningEffort.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
namespace Devlooped.Extensions.AI;
2+
3+
public enum GrokReasoningEffort { Low, High }

src/AI/Grok/GrokSearch.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
namespace Devlooped.Extensions.AI;
2+
3+
public enum GrokSearch { Auto, On, Off }
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System.ComponentModel;
2+
using Devlooped.Extensions.AI;
3+
using Microsoft.Extensions.AI;
4+
using Microsoft.Extensions.Configuration;
5+
using Microsoft.Extensions.DependencyInjection;
6+
7+
namespace Devlooped.Extensions.AI;
8+
9+
/// <summary>
10+
/// Extensions for registering the <see cref="GrokClient"/> as a chat client in the service collection.
11+
/// </summary>
12+
[EditorBrowsable(EditorBrowsableState.Never)]
13+
public static class GrokServiceCollectionExtensions
14+
{
15+
extension(IServiceCollection services)
16+
{
17+
/// <summary>
18+
/// Registers the <see cref="GrokClient"/> as a chat client in the service collection.
19+
/// </summary>
20+
/// <param name="factory">The factory to create the Grok client.</param>
21+
/// <param name="lifetime">The optional service lifetime.</param>
22+
/// <returns>The <see cref="ChatClientBuilder"/> to further build the pipeline.</returns>
23+
public ChatClientBuilder AddGrok(Func<IConfiguration, GrokClient> factory, ServiceLifetime lifetime = ServiceLifetime.Singleton)
24+
=> services.AddChatClient(services
25+
=> factory(services.GetRequiredService<IConfiguration>()), lifetime);
26+
}
27+
}

src/Directory.targets

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
<Project InitialTargets="SetLocalVersion">
22

3+
<PropertyGroup>
4+
<PackOnBuild>true</PackOnBuild>
5+
</PropertyGroup>
6+
37
<Target Name="SetLocalVersion" Condition="!$(CI)">
48
<GetVersion>
59
<Output TaskParameter="Version" PropertyName="Version" />

0 commit comments

Comments
 (0)