diff --git a/src/Cellm/AddIn/CellmAddIn.cs b/src/Cellm/AddIn/CellmAddIn.cs index 859a3832..5c333c5e 100644 --- a/src/Cellm/AddIn/CellmAddIn.cs +++ b/src/Cellm/AddIn/CellmAddIn.cs @@ -16,6 +16,7 @@ using Cellm.Models.Providers.OpenAi; using Cellm.Models.Providers.OpenAiCompatible; using Cellm.Models.Providers.OpenRouter; +using Cellm.Models.Providers.Vertex; using Cellm.Models.Resilience; using Cellm.Tools; using Cellm.Tools.FileReader; @@ -84,6 +85,7 @@ private static ServiceCollection ConfigureServices(ServiceCollection services) .Configure(configuration.GetRequiredSection(nameof(OllamaConfiguration))) .Configure(configuration.GetRequiredSection(nameof(OpenAiConfiguration))) .Configure(configuration.GetRequiredSection(nameof(OpenAiCompatibleConfiguration))) + .Configure(configuration.GetRequiredSection(nameof(VertexConfiguration))) .Configure(configuration.GetRequiredSection(nameof(OpenRouterConfiguration))) .Configure(configuration.GetRequiredSection(nameof(ResilienceConfiguration))) .Configure(configuration.GetRequiredSection(nameof(SentryConfiguration))); @@ -149,7 +151,7 @@ private static ServiceCollection ConfigureServices(ServiceCollection services) cfg.AddBehavior>(ServiceLifetime.Singleton); }) .AddSingleton() - .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton(); @@ -169,6 +171,7 @@ private static ServiceCollection ConfigureServices(ServiceCollection services) .AddResilientHttpClient(resilienceConfiguration, cellmAddInConfiguration, Provider.Gemini) .AddResilientHttpClient(resilienceConfiguration, cellmAddInConfiguration, Provider.Mistral) .AddResilientHttpClient(resilienceConfiguration, cellmAddInConfiguration, Provider.OpenAiCompatible) + .AddResilientHttpClient(resilienceConfiguration, cellmAddInConfiguration, Provider.Vertex) .AddResilientHttpClient(resilienceConfiguration, cellmAddInConfiguration, Provider.OpenRouter); #pragma warning disable EXTEXP0018 // Type is for evaluation purposes only and is subject to change or removal in future updates. @@ -189,6 +192,7 @@ private static ServiceCollection ConfigureServices(ServiceCollection services) .AddOllamaChatClient() .AddOpenAiChatClient() .AddOpenAiCompatibleChatClient() + .AddVertexChatClient() .AddOpenRouterChatClient(); // Add tools @@ -237,6 +241,7 @@ internal static IEnumerable GetProviderConfigurations() Services.GetRequiredService>().CurrentValue, Services.GetRequiredService>().CurrentValue, Services.GetRequiredService>().CurrentValue, + Services.GetRequiredService>().CurrentValue, Services.GetRequiredService>().CurrentValue ]; } diff --git a/src/Cellm/AddIn/UserInterface/Resources/Vertex.png b/src/Cellm/AddIn/UserInterface/Resources/Vertex.png new file mode 100644 index 00000000..a4a9918d Binary files /dev/null and b/src/Cellm/AddIn/UserInterface/Resources/Vertex.png differ diff --git a/src/Cellm/AddIn/UserInterface/Ribbon/RibbonModelGroup.cs b/src/Cellm/AddIn/UserInterface/Ribbon/RibbonModelGroup.cs index d1572248..6df9f025 100644 --- a/src/Cellm/AddIn/UserInterface/Ribbon/RibbonModelGroup.cs +++ b/src/Cellm/AddIn/UserInterface/Ribbon/RibbonModelGroup.cs @@ -12,6 +12,7 @@ using Cellm.Models.Providers.Ollama; using Cellm.Models.Providers.OpenAi; using Cellm.Models.Providers.OpenAiCompatible; +using Cellm.Models.Providers.Vertex; using Cellm.Models.Providers.OpenRouter; using Cellm.Users; using ExcelDna.Integration.CustomUI; @@ -495,6 +496,8 @@ public void ShowProviderSettingsForm(IRibbonControl control) case Provider.OpenAiCompatible: currentBaseAddress = GetProviderConfiguration()?.BaseAddress?.ToString() ?? ""; break; + case Provider.Vertex: + currentBaseAddress = GetProviderConfiguration()?.BaseAddress?.ToString() ?? ""; case Provider.OpenRouter: currentBaseAddress = GetProviderConfiguration()?.BaseAddress?.ToString() ?? ""; break; @@ -551,7 +554,7 @@ static internal bool IsBaseAddressEditable(Provider provider) { return provider switch { - Provider.Azure or Provider.Aws or Provider.OpenAiCompatible => true, + Provider.Azure or Provider.Aws or Provider.OpenAiCompatible or Provider.Vertex => true, _ => false }; } diff --git a/src/Cellm/Cellm.csproj b/src/Cellm/Cellm.csproj index 7aeeb012..42f090eb 100644 --- a/src/Cellm/Cellm.csproj +++ b/src/Cellm/Cellm.csproj @@ -92,5 +92,6 @@ + diff --git a/src/Cellm/Models/Providers/Behaviors/GeminiTemperatureBehavior.cs b/src/Cellm/Models/Providers/Behaviors/GoogleTemperatureBehavior.cs similarity index 75% rename from src/Cellm/Models/Providers/Behaviors/GeminiTemperatureBehavior.cs rename to src/Cellm/Models/Providers/Behaviors/GoogleTemperatureBehavior.cs index 409d9098..e6db285c 100644 --- a/src/Cellm/Models/Providers/Behaviors/GeminiTemperatureBehavior.cs +++ b/src/Cellm/Models/Providers/Behaviors/GoogleTemperatureBehavior.cs @@ -4,15 +4,15 @@ namespace Cellm.Models.Providers.Behaviors; -internal class GeminiTemperatureBehavior(IOptionsMonitor cellmAddinConfiguration) : IProviderBehavior +internal class GoogleTemperatureBehavior(IOptionsMonitor cellmAddinConfiguration) : IProviderBehavior { private const float DefaultMinTemp = 0.0f; private const float DefaultMaxTemp = 1.0f; - private const float GeminiMaxTemperature = 2.0f; + private const float GoogleMaxTemperature = 2.0f; public bool IsEnabled(Provider provider) { - return provider == Provider.Gemini; + return provider == Provider.Gemini || provider == Provider.Vertex; } public void Before(Provider provider, Prompt prompt) @@ -20,8 +20,8 @@ public void Before(Provider provider, Prompt prompt) var temperature = prompt.Options.Temperature ?? (float)cellmAddinConfiguration.CurrentValue.DefaultTemperature; // Scale temperature from [0;1] to [0;2] - temperature = (temperature / DefaultMaxTemp) * GeminiMaxTemperature; - prompt.Options.Temperature = Math.Clamp(temperature, DefaultMinTemp, GeminiMaxTemperature); + temperature = (temperature / DefaultMaxTemp) * GoogleMaxTemperature; + prompt.Options.Temperature = Math.Clamp(temperature, DefaultMinTemp, GoogleMaxTemperature); } public void After(Provider Provider, Prompt prompt) @@ -31,7 +31,7 @@ public void After(Provider Provider, Prompt prompt) var temperature = prompt.Options.Temperature.Value; // Scale temperature back from [0;2] to [0;1] - temperature = (temperature / GeminiMaxTemperature) * DefaultMaxTemp; + temperature = (temperature / GoogleMaxTemperature) * DefaultMaxTemp; prompt.Options.Temperature = Math.Clamp(temperature, DefaultMinTemp, DefaultMaxTemp); } } diff --git a/src/Cellm/Models/Providers/Provider.cs b/src/Cellm/Models/Providers/Provider.cs index f6b5f7b4..87209ee5 100644 --- a/src/Cellm/Models/Providers/Provider.cs +++ b/src/Cellm/Models/Providers/Provider.cs @@ -12,5 +12,6 @@ public enum Provider Ollama, OpenAi, OpenAiCompatible, - OpenRouter + OpenRouter, + Vertex } diff --git a/src/Cellm/Models/Providers/Vertex/VertexConfiguration.cs b/src/Cellm/Models/Providers/Vertex/VertexConfiguration.cs new file mode 100644 index 00000000..6d700e89 --- /dev/null +++ b/src/Cellm/Models/Providers/Vertex/VertexConfiguration.cs @@ -0,0 +1,35 @@ +using Cellm.Users; +using Microsoft.Extensions.AI; + +namespace Cellm.Models.Providers.Vertex; + +internal class VertexConfiguration : IProviderConfiguration +{ + public Provider Id { get => Provider.Vertex; } + + public string Name { get => "Vertex AI"; } + + public Entitlement Entitlement { get => Entitlement.EnableVertexProvider; } + + public string Icon { get => $"AddIn/UserInterface/Resources/{nameof(Provider.Vertex)}.png"; } + + public Uri BaseAddress { get; init; } = new Uri("https://us-central1-aiplatform.googleapis.com/v1beta1/projects/YOUR_PROJECT_ID/locations/us-central1/endpoints/openapi"); + + public string DefaultModel { get; init; } = string.Empty; + + public string ApiKey { get; init; } = string.Empty; + + public string SmallModel { get; init; } = string.Empty; + + public string MediumModel { get; init; } = string.Empty; + + public string LargeModel { get; init; } = string.Empty; + + public AdditionalPropertiesDictionary? AdditionalProperties { get; init; } = []; + + public bool SupportsJsonSchemaResponses { get; init; } = true; + + public bool SupportsStructuredOutputWithTools { get; init; } = false; + + public bool IsEnabled { get; init; } = false; +} diff --git a/src/Cellm/Models/ServiceCollectionExtensions.cs b/src/Cellm/Models/ServiceCollectionExtensions.cs index 13addf00..6768d025 100644 --- a/src/Cellm/Models/ServiceCollectionExtensions.cs +++ b/src/Cellm/Models/ServiceCollectionExtensions.cs @@ -22,6 +22,7 @@ using Cellm.Models.Providers.OpenAi; using Cellm.Models.Providers.OpenAiCompatible; using Cellm.Models.Providers.OpenRouter; +using Cellm.Models.Providers.Vertex; using Cellm.Models.Resilience; using Cellm.Users; using Microsoft.Extensions.AI; @@ -427,6 +428,34 @@ public static IServiceCollection AddOpenAiCompatibleChatClient(this IServiceColl return services; } + public static IServiceCollection AddVertexChatClient(this IServiceCollection services) + { + services + .AddKeyedChatClient(Provider.Vertex, serviceProvider => + { + var account = serviceProvider.GetRequiredService(); + account.ThrowIfNotEntitled(Entitlement.EnableVertexProvider); + + var vertexConfiguration = serviceProvider.GetRequiredService>(); + var resilientHttpClient = serviceProvider.GetResilientHttpClient(Provider.Vertex); + + if (string.IsNullOrWhiteSpace(vertexConfiguration.CurrentValue.ApiKey)) + { + throw new CellmException($"Empty {nameof(VertexConfiguration.ApiKey)} for {Provider.Vertex}. Please set your API key."); + } + + var openAiClient = new OpenAIClient( + new ApiKeyCredential(vertexConfiguration.CurrentValue.ApiKey), + new OpenAIClientOptions + { + Transport = new HttpClientPipelineTransport(resilientHttpClient), + Endpoint = vertexConfiguration.CurrentValue.BaseAddress + }); + + return openAiClient.GetChatClient(vertexConfiguration.CurrentValue.DefaultModel).AsIChatClient(); + }); + } + public static IServiceCollection AddOpenRouterChatClient(this IServiceCollection services) { services diff --git a/src/Cellm/Users/Entitlement.cs b/src/Cellm/Users/Entitlement.cs index 173fa722..774a4cdc 100644 --- a/src/Cellm/Users/Entitlement.cs +++ b/src/Cellm/Users/Entitlement.cs @@ -15,6 +15,7 @@ public enum Entitlement EnableOpenAiCompatibleProviderLocalModels, EnableOpenAiCompatibleProviderHostedModels, EnableOpenRouterProvider, + EnableVertexProvider, EnableModelContextProtocol, DisableTelemetry } diff --git a/src/Cellm/Users/Models/Entitlements.cs b/src/Cellm/Users/Models/Entitlements.cs index 3ff22fed..90bac4ba 100644 --- a/src/Cellm/Users/Models/Entitlements.cs +++ b/src/Cellm/Users/Models/Entitlements.cs @@ -19,6 +19,7 @@ internal class Entitlements() Entitlement.EnableDeepSeekProvider, Entitlement.EnableGeminiProvider, Entitlement.EnableMistralProvider, + Entitlement.EnableModelContextProtocol, Entitlement.EnableOllamaProvider, Entitlement.EnableOpenAiProvider, Entitlement.EnableOpenAiCompatibleProvider, diff --git a/src/Cellm/appsettings.json b/src/Cellm/appsettings.json index dfdc5dbe..214d787d 100644 --- a/src/Cellm/appsettings.json +++ b/src/Cellm/appsettings.json @@ -110,6 +110,14 @@ "LargeModel": "anthropic/claude-opus-4-5-20251101", "IsEnabled": true }, + "VertexConfiguration": { + "BaseAddress": "https://us-central1-aiplatform.googleapis.com/v1beta1/projects/YOUR_PROJECT_ID/locations/us-central1/endpoints/openapi", + "DefaultModel": "gemini-2.5-flash", + "ApiKey": "", + "SmallModel": "gemini-2.5-flash-lite", + "MediumModel": "gemini-2.5-flash", + "LargeModel": "gemini-2.5-pro", + }, "ModelContextProtocolConfiguration": { "StdioServers": [ {