Skip to content

Commit c93cc5a

Browse files
authored
xAI integration (#102)
* xAi setup * AskMemory in XaiService * Add XaiImageGenService * Add Xai backend support to McpService
1 parent 4adaf07 commit c93cc5a

File tree

14 files changed

+257
-4
lines changed

14 files changed

+257
-4
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using Examples.Utils;
2+
using MaIN.Core.Hub;
3+
4+
namespace Examples;
5+
6+
public class ChatExampleXai : IExample
7+
{
8+
public async Task Start()
9+
{
10+
XaiExample.Setup(); //We need to provide xAI API key
11+
Console.WriteLine("(xAI) ChatExample is running!");
12+
13+
await AIHub.Chat()
14+
.WithModel("grok-3-beta")
15+
.WithMessage("Is the killer whale cute?")
16+
.CompleteAsync(interactive: true);
17+
}
18+
}

Examples/Examples/Program.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ static void RegisterExamples(IServiceCollection services)
7878
services.AddTransient<ChatWithTextToSpeechExample>();
7979
services.AddTransient<ChatExampleGroqCloud>();
8080
services.AddTransient<ChatExampleAnthropic>();
81+
services.AddTransient<ChatExampleXai>();
8182
}
8283

8384
async Task RunSelectedExample(IServiceProvider serviceProvider)
@@ -172,6 +173,7 @@ public class ExampleRegistry(IServiceProvider serviceProvider)
172173
("\u25a0 DeepSeek Chat with reasoning", serviceProvider.GetRequiredService<ChatWithReasoningDeepSeekExample>()),
173174
("\u25a0 GroqCloud Chat", serviceProvider.GetRequiredService<ChatExampleGroqCloud>()),
174175
("\u25a0 Anthropic Chat", serviceProvider.GetRequiredService<ChatExampleAnthropic>()),
176+
("\u25a0 xAI Chat", serviceProvider.GetRequiredService<ChatExampleXai>()),
175177
("\u25a0 McpClient example", serviceProvider.GetRequiredService<McpExample>()),
176178
("\u25a0 McpAgent example", serviceProvider.GetRequiredService<McpAgentsExample>()),
177179
("\u25a0 Chat with TTS example", serviceProvider.GetRequiredService<ChatWithTextToSpeechExample>()),
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using MaIN.Core;
2+
using MaIN.Domain.Configuration;
3+
4+
namespace Examples.Utils;
5+
6+
public class XaiExample
7+
{
8+
public static void Setup()
9+
{
10+
MaINBootstrapper.Initialize(configureSettings: (options) =>
11+
{
12+
options.BackendType = BackendType.Xai;
13+
options.XaiKey = "<YOUR_XAI_KEY>";
14+
});
15+
}
16+
}

Releases/0.7.9.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# 0.7.9 release
2+
3+
xAi integration has been added.

src/MaIN.Core/.nuspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<package>
33
<metadata>
44
<id>MaIN.NET</id>
5-
<version>0.7.8</version>
5+
<version>0.7.9</version>
66
<authors>Wisedev</authors>
77
<owners>Wisedev</owners>
88
<icon>favicon.png</icon>

src/MaIN.Domain/Configuration/MaINSettings.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public class MaINSettings
1313
public string? DeepSeekKey { get; set; }
1414
public string? AnthropicKey { get; set; }
1515
public string? GroqCloudKey { get; set; }
16+
public string? XaiKey { get; set; }
1617
public MongoDbSettings? MongoDbSettings { get; set; }
1718
public FileSystemSettings? FileSystemSettings { get; set; }
1819
public SqliteSettings? SqliteSettings { get; set; }
@@ -28,4 +29,5 @@ public enum BackendType
2829
DeepSeek = 3,
2930
GroqCloud = 4,
3031
Anthropic = 5,
32+
Xai = 6,
3133
}

src/MaIN.Services/Constants/ServiceConstants.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ public static class HttpClients
1010
public const string DeepSeekClient = "DeepSeekClient";
1111
public const string GroqCloudClient = "GroqCloudClient";
1212
public const string AnthropicClient = "AnthropicClient";
13+
public const string XaiClient = "XaiClient";
1314
public const string ImageDownloadClient = "ImageDownloadClient";
1415
public const string ModelContextDownloadClient = "ModelContextDownloadClient";
1516
}
@@ -36,6 +37,10 @@ public static class ApiUrls
3637

3738
public const string AnthropicChatMessages = "https://api.anthropic.com/v1/messages";
3839
public const string AnthropicModels = "https://api.anthropic.com/v1/models";
40+
41+
public const string XaiImageGenerations = "https://api.x.ai/v1/images/generations";
42+
public const string XaiOpenAiChatCompletions = "https://api.x.ai/v1/chat/completions";
43+
public const string XaiModels = "https://api.x.ai/v1/models";
3944
}
4045

4146
public static class Messages
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
using MaIN.Domain.Configuration;
2+
using MaIN.Domain.Entities;
3+
using MaIN.Services.Constants;
4+
using MaIN.Services.Services.Abstract;
5+
using MaIN.Services.Services.Models;
6+
using System.Net.Http.Headers;
7+
using System.Net.Http.Json;
8+
using System.Text.Json;
9+
10+
namespace MaIN.Services.Services.ImageGenServices;
11+
12+
public class XaiImageGenService(
13+
IHttpClientFactory httpClientFactory,
14+
MaINSettings settings)
15+
: IImageGenService
16+
{
17+
private readonly IHttpClientFactory _httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
18+
private readonly MaINSettings _settings = settings ?? throw new ArgumentNullException(nameof(settings));
19+
20+
public async Task<ChatResult?> Send(Chat chat)
21+
{
22+
var client = _httpClientFactory.CreateClient(ServiceConstants.HttpClients.XaiClient);
23+
string apiKey = _settings.XaiKey ?? Environment.GetEnvironmentVariable("XAI_API_KEY") ??
24+
throw new InvalidOperationException("xAI Key not configured");
25+
26+
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
27+
var requestBody = new
28+
{
29+
model = string.IsNullOrWhiteSpace(chat.Model) ? Models.GROK_IMAGE : chat.Model,
30+
prompt = BuildPromptFromChat(chat),
31+
n = 1,
32+
response_format = "b64_json" //or "url"
33+
};
34+
35+
using var response = await client.PostAsJsonAsync(ServiceConstants.ApiUrls.XaiImageGenerations, requestBody);
36+
var imageBytes = await ProcessXaiResponse(response);
37+
return CreateChatResult(imageBytes);
38+
}
39+
40+
private static string BuildPromptFromChat(Chat chat)
41+
{
42+
return chat.Messages
43+
.Select((msg, index) => index == 0 ? msg.Content : $"&& {msg.Content}")
44+
.Aggregate((current, next) => $"{current} {next}");
45+
}
46+
47+
private async Task<byte[]> ProcessXaiResponse(HttpResponseMessage response)
48+
{
49+
response.EnsureSuccessStatusCode();
50+
var responseData = await response.Content.ReadFromJsonAsync<XaiImageResponse>(new JsonSerializerOptions{ PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower });
51+
52+
var first = responseData?.Data.FirstOrDefault()
53+
?? throw new InvalidOperationException("No image data returned from xAI");
54+
55+
if (!string.IsNullOrEmpty(first.B64Json))
56+
{
57+
return Convert.FromBase64String(first.B64Json);
58+
}
59+
60+
if (!string.IsNullOrEmpty(first.Url))
61+
{
62+
return await DownloadImageAsync(first.Url);
63+
}
64+
65+
throw new InvalidOperationException("No image content returned from xAI");
66+
}
67+
68+
private async Task<byte[]> DownloadImageAsync(string imageUrl)
69+
{
70+
var imageClient = _httpClientFactory.CreateClient(ServiceConstants.HttpClients.ImageDownloadClient);
71+
72+
using var imageResponse = await imageClient.GetAsync(imageUrl);
73+
imageResponse.EnsureSuccessStatusCode();
74+
75+
return await imageResponse.Content.ReadAsByteArrayAsync();
76+
}
77+
78+
private static ChatResult CreateChatResult(byte[] imageBytes)
79+
{
80+
return new ChatResult
81+
{
82+
Done = true,
83+
Message = new Message
84+
{
85+
Content = ServiceConstants.Messages.GeneratedImageContent,
86+
Role = ServiceConstants.Roles.Assistant,
87+
Image = imageBytes,
88+
Type = MessageType.Image
89+
},
90+
Model = Models.GROK_IMAGE,
91+
CreatedAt = DateTime.UtcNow
92+
};
93+
}
94+
95+
private struct Models
96+
{
97+
public const string GROK_IMAGE = "grok-2-image";
98+
}
99+
}
100+
101+
102+
file class XaiImageResponse
103+
{
104+
public XaiImageData[] Data { get; set; } = [];
105+
}
106+
107+
file class XaiImageData
108+
{
109+
public string? Url { get; set; }
110+
public string? B64Json { get; set; }
111+
public string? RevisedPrompt { get; set; }
112+
}

src/MaIN.Services/Services/LLMService/Factory/ImageGenServiceFactory.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ public class ImageGenServiceFactory(IServiceProvider serviceProvider) : IImageGe
1818
BackendType.DeepSeek => null,
1919
BackendType.GroqCloud => null,
2020
BackendType.Anthropic => null,
21+
BackendType.Xai => new XaiImageGenService(serviceProvider.GetRequiredService<IHttpClientFactory>(),
22+
serviceProvider.GetRequiredService<MaINSettings>()),
2123
BackendType.Self => new ImageGenService(serviceProvider.GetRequiredService<IHttpClientFactory>(),
2224
serviceProvider.GetRequiredService<MaINSettings>()),
2325

src/MaIN.Services/Services/LLMService/Factory/LLMServiceFactory.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ public ILLMService CreateService(BackendType backendType)
3939
serviceProvider.GetRequiredService<IMemoryFactory>(),
4040
serviceProvider.GetRequiredService<IMemoryService>()),
4141

42+
BackendType.Xai => new XaiService(
43+
serviceProvider.GetRequiredService<MaINSettings>(),
44+
serviceProvider.GetRequiredService<INotificationService>(),
45+
serviceProvider.GetRequiredService<IHttpClientFactory>(),
46+
serviceProvider.GetRequiredService<IMemoryFactory>(),
47+
serviceProvider.GetRequiredService<IMemoryService>()),
48+
4249
BackendType.Anthropic => new AnthropicService(
4350
serviceProvider.GetRequiredService<MaINSettings>(),
4451
serviceProvider.GetRequiredService<INotificationService>(),

0 commit comments

Comments
 (0)