Skip to content

Commit 841dd68

Browse files
authored
feat: Vertex AI integration (#128)
* Add Vertex AI backend support Introduce Vertex AI (Google) backend integration: add Vertex-specific settings, models, and services to enable using Vertex AI models. Key changes: - Add VertexInferenceParams and GoogleServiceAccountAuth to domain configuration; register BackendType.Vertex and include GoogleServiceAccountAuth in MaINSettings. - Add Vertex model constants and cloud model records (Gemini/Veo) and register Vertex in LLMApiRegistry. - Implement VertexService (LLM service) with request parameter mapping, model/endpoint URL construction, and validation of service-account config. - Add VertexTokenProvider to create and exchange JWT for OAuth2 access tokens using a service account private key. - Wire Vertex into factory, bootstrapper, HTTP clients, and service constants so it can be resolved and used. - Add ChatExampleVertex example and register it in the examples program. - Small ChatService fix: ensure BackendParams are created from model backend and message types set using resolved backend. - Add local claude settings file used by the infer page tooling. This commit enables selecting BackendType.Vertex and calling Vertex-hosted models via service-account authentication and token exchange. * Add shared token cache and refresh lock for Vertex token Introduce a static ConcurrentDictionary cache keyed by ClientEmail and a SemaphoreSlim to prevent concurrent token refreshes in VertexTokenProvider. Replace per-instance _cachedToken/_tokenExpiry with a CachedToken record, double-check cache after acquiring the lock, and store refreshed tokens with a buffered expiry. Also apply minor formatting/inline null-coalescing cleanup in VertexService getters. * Add Vertex AI backend support and UI Introduce Vertex AI support across the InferPage UI and settings plumbing. Changes include: - Add a new BackendOption (id 9) for Vertex AI and corresponding selection/lookup logic in Settings.razor and Program.cs; Program warns that Vertex requires service account credentials. - Extend the settings UI to collect Vertex auth fields (Project ID, Client Email, Private Key, Location), with toggleable key visibility and required-field validation to enable saving. - Persist Vertex auth separately via SettingsService.SaveVertexAuthAsync/GetVertexAuthAsync and a VertexAuthStorage record; private_key is stored outside general settings. Also add VertexLocation to InferPageSettings. - Pass a GoogleServiceAccountConfig (vertexAuth) through Utils.ApplySettings so MaINSettings.GoogleServiceAccountAuth can be set when selecting Vertex. - Add model identifier constant for gemini-2.5-pro and fix a CloudModel reference to use Models.Gemini.Gemini2_5Pro. These changes wire up UI, storage, and application settings so Vertex can be configured from the Settings page and applied at runtime. * Fix vertex file send for pdf files (without ocr) Expose CreateMemoryWithVertex on IMemoryFactory and implement it in MemoryFactory to build a KernelMemory configured for Vertex (Gemini) text generation and embeddings using a bearer token provider, location and projectId. Add PDF MIME detection in ChatHelper. Extend VertexService.AskMemory to bypass KernelMemory for multimodal requests: collect inline images/PDFs, convert non-native files to text via DocumentProcessor, aggregate text/context/memory, inject optional grammar JSON, and send combined content to Gemini; includes helper methods for file/stream processing and a list of Gemini-native extensions. Also add necessary usings and temporary file handling for stream processing. * fix: Use model backend when creating BackendParams Assign the backend from the chat model before creating BackendParams and use it to initialize BackendParams and to set message types. This ensures message Type mapping is based on model.Backend (and avoids relying on BackendParams.Backend prior to initialization), clarifying initialization order and avoiding potential mismatches. * final improvements Add a VertexExample.Setup helper and update examples to call it for Google service account configuration. Refactor GoogleServiceAccountTokenProvider: use a per-client-email ConcurrentDictionary cache and per-email SemaphoreSlim locks, parse token responses via HttpClient.Json APIs, target oauth2.googleapis.com/token, add IDisposable to dispose RSA, improve error messages and token expiry handling. Refactor VertexService: centralize GoogleServiceAccountConfig access via Auth property, build endpoints from Auth.ProjectId, streamline GetApiKey/GetApiName/ValidateApiKey, and reorganize multimodal file processing by introducing AppendDocument, BuildQuery and MergeInlineContent helpers, renaming native extensions and cleaning up temp file handling. Small logging and clarity improvements throughout. * Add Vertex Imagen image generation service Introduce support for Google Vertex Imagen image generation: add VertexImageGenService to call Vertex AI predict endpoints (uses service account token, default model/location, decodes base64 image into ChatResult), register BackendType.Vertex in ImageGenServiceFactory, add model constant (Imagen4_0_Generate) and CloudModel record (VertexImagen4_0Generate). Also adjust Home.razor injections and minor backendKey formatting. Includes basic response/error handling and model name extraction for publisher prefixes. * Refactor: replace hardcoded image gen model constants with central ModelRegistry Introduce new image-generation models and centralize image-gen logic: added Models constants (Imagen4_0_FastGenerate, Flux1Shnell) and new Cloud/Local model records for those models. Replace ad-hoc FLUX checks with ModelRegistry.TryGetById(...).HasImageGeneration across ChatMapper, AgentService, ChatService, and AgentStateManager. Update image generation services (Gemini, OpenAI, Vertex, Xai) to resolve default model IDs from Models, pass the resolved model through to ChatResult, and remove per-service hardcoded model constants. Also add necessary using/import adjustments and improve chat model availability handling in ChatService. * Add MCP for vertex Add support for Vertex AI Gemini chat completions and expose location on Mcp. Mcp.cs: introduce a new Location property with default "us-central1". McpService.cs: refactor InitializeChatCompletions to accept the Mcp config (and derive backend and model from it), add a using for LLMService.Auth, and wire up a Vertex backend case that creates a Google service-account token provider, builds a bearer token delegate, normalizes the model name, and registers the VertexAIGemini chat completion with the kernel using config.Location and the project ID. Also adjust the promptSettings call site to pass the config. Includes an auth presence check that throws if Vertex credentials are not configured. * versioning * Add new cloud models and XAI reasoning support Introduce multiple new cloud model definitions and update Groq model constants, plus implement streaming parsing in XaiService. Changes include: - Added Gemini NanoBanana and several xAI models (Grok 4.20/4.1 variants, GrokImagine image/pro) and new Groq models (Llama3_3_70b, GptOss120b). Renamed constant Llama3_1_8bInstant -> Llama3_1_8b and added DeepSeek Chat; increased DeepSeek Reasoner token limit. - Updated example and integration tests to use the renamed Groq model constant. - Added ProcessChatCompletionChunk in XaiService to handle streaming content, incremental reasoning deltas, and encrypted reasoning blobs; added JSON helper types and System.Text.Json imports to support parsing. These changes add support for new backends/models and improve handling of xAI streaming/ reasoning responses.
1 parent 4734cb4 commit 841dd68

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1068
-75
lines changed

Examples/Examples/Chat/ChatExampleGroqCloud.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public async Task Start()
1212
Console.WriteLine("(GroqCloud) ChatExample is running!");
1313

1414
await AIHub.Chat()
15-
.WithModel(Models.Groq.Llama3_1_8bInstant)
15+
.WithModel(Models.Groq.Llama3_1_8b)
1616
.WithMessage("Which color do people like the most?")
1717
.CompleteAsync(interactive: true);
1818
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using Examples.Utils;
2+
using MaIN.Core.Hub;
3+
using MaIN.Domain.Configuration.BackendInferenceParams;
4+
using MaIN.Domain.Models;
5+
6+
namespace Examples.Chat;
7+
8+
public class ChatExampleVertex : IExample
9+
{
10+
public async Task Start()
11+
{
12+
VertexExample.Setup(); //We need to provide Google service account config
13+
Console.WriteLine("(Vertex AI) ChatExample is running!");
14+
15+
await AIHub.Chat()
16+
.WithModel(Models.Vertex.Gemini2_5Pro)
17+
.WithMessage("Is the killer whale the smartest animal?")
18+
.WithInferenceParams(new VertexInferenceParams
19+
{
20+
Location = "europe-central2"
21+
})
22+
.CompleteAsync(interactive: true);
23+
}
24+
}

Examples/Examples/Program.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ static void RegisterExamples(IServiceCollection services)
7373
services.AddTransient<ChatGrammarExampleGemini>();
7474
services.AddTransient<ChatWithImageGenGeminiExample>();
7575
services.AddTransient<ChatWithFilesExampleGemini>();
76+
services.AddTransient<ChatExampleVertex>();
7677
services.AddTransient<ChatWithReasoningDeepSeekExample>();
7778
services.AddTransient<ChatWithTextToSpeechExample>();
7879
services.AddTransient<ChatExampleGroqCloud>();
@@ -186,6 +187,7 @@ public class ExampleRegistry(IServiceProvider serviceProvider)
186187
("\u25a0 Gemini Chat with grammar", serviceProvider.GetRequiredService<ChatGrammarExampleGemini>()),
187188
("\u25a0 Gemini Chat with image", serviceProvider.GetRequiredService<ChatWithImageGenGeminiExample>()),
188189
("\u25a0 Gemini Chat with files", serviceProvider.GetRequiredService<ChatWithFilesExampleGemini>()),
190+
("\u25a0 Vertex Chat", serviceProvider.GetRequiredService<ChatExampleVertex>()),
189191
("\u25a0 DeepSeek Chat with reasoning", serviceProvider.GetRequiredService<ChatWithReasoningDeepSeekExample>()),
190192
("\u25a0 GroqCloud Chat", serviceProvider.GetRequiredService<ChatExampleGroqCloud>()),
191193
("\u25a0 Anthropic Chat", serviceProvider.GetRequiredService<ChatExampleAnthropic>()),
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using MaIN.Core;
2+
using MaIN.Domain.Configuration;
3+
using MaIN.Domain.Configuration.Vertex;
4+
5+
namespace Examples.Utils;
6+
7+
public class VertexExample
8+
{
9+
public static void Setup()
10+
{
11+
MaINBootstrapper.Initialize(configureSettings: options =>
12+
{
13+
options.BackendType = BackendType.Vertex;
14+
options.GoogleServiceAccountAuth = new GoogleServiceAccountConfig
15+
{
16+
ProjectId = "<YOUR_GCP_PROJECT_ID>",
17+
ClientEmail = "<YOUR_SERVICE_ACCOUNT_EMAIL>",
18+
PrivateKey = @"<YOUR_PRIVATE_KEY>"
19+
};
20+
});
21+
}
22+
}

MaIN.Core.E2ETests/BackendParamsTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ public async Task GroqCloud_Should_RespondWithParams()
107107
SkipIfMissingKey(LLMApiRegistry.GetEntry(BackendType.GroqCloud)?.ApiKeyEnvName!);
108108

109109
var result = await AIHub.Chat()
110-
.WithModel(Models.Groq.Llama3_1_8bInstant)
110+
.WithModel(Models.Groq.Llama3_1_8b)
111111
.WithMessage(TestQuestion)
112112
.WithInferenceParams(new GroqCloudInferenceParams
113113
{
@@ -279,7 +279,7 @@ public async Task GroqCloud_Should_ThrowWhenGivenWrongParams()
279279
{
280280
await Assert.ThrowsAsync<InvalidBackendParamsException>(() =>
281281
AIHub.Chat()
282-
.WithModel(Models.Groq.Llama3_1_8bInstant)
282+
.WithModel(Models.Groq.Llama3_1_8b)
283283
.WithMessage(TestQuestion)
284284
.WithInferenceParams(new OpenAiInferenceParams())
285285
.CompleteAsync());

Releases/0.10.4.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# 0.10.4 release
2+
3+
Adds Google Vertex AI as a backend with authentication, MCP support, and new models including image generation, along with UI configuration and example usage.

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.10.3</version>
5+
<version>0.10.4</version>
66
<authors>Wisedev</authors>
77
<owners>Wisedev</owners>
88
<icon>favicon.png</icon>

src/MaIN.Domain/Configuration/BackendInferenceParams/BackendParamsFactory.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public static class BackendParamsFactory
1414
BackendType.Gemini => new GeminiInferenceParams(),
1515
BackendType.Anthropic => new AnthropicInferenceParams(),
1616
BackendType.Ollama => new OllamaInferenceParams(),
17+
BackendType.Vertex => new VertexInferenceParams(),
1718
_ => new LocalInferenceParams()
1819
};
1920
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using MaIN.Domain.Entities;
2+
using Grammar = MaIN.Domain.Models.Grammar;
3+
4+
namespace MaIN.Domain.Configuration.BackendInferenceParams;
5+
6+
public class VertexInferenceParams : IBackendInferenceParams
7+
{
8+
public BackendType Backend => BackendType.Vertex;
9+
10+
public string Location { get; init; } = "us-central1";
11+
12+
public float? Temperature { get; init; }
13+
public int? MaxTokens { get; init; }
14+
public float? TopP { get; init; }
15+
public string[]? StopSequences { get; init; }
16+
public Grammar? Grammar { get; set; }
17+
public Dictionary<string, object>? AdditionalParams { get; init; }
18+
}

src/MaIN.Domain/Configuration/MaINSettings.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using MaIN.Domain.Configuration.Vertex;
12

23
namespace MaIN.Domain.Configuration;
34

@@ -18,6 +19,7 @@ public class MaINSettings
1819
public SqliteSettings? SqliteSettings { get; set; }
1920
public SqlSettings? SqlSettings { get; set; }
2021
public string? VoicesPath { get; set; }
22+
public GoogleServiceAccountConfig? GoogleServiceAccountAuth { get; set; }
2123
}
2224

2325
public enum BackendType
@@ -30,4 +32,5 @@ public enum BackendType
3032
Anthropic = 5,
3133
Xai = 6,
3234
Ollama = 7,
35+
Vertex = 8,
3336
}

0 commit comments

Comments
 (0)