diff --git a/pom.xml b/pom.xml index 0ce8c9f..ba2570a 100644 --- a/pom.xml +++ b/pom.xml @@ -51,6 +51,7 @@ 0.0.0-SNAPSHOT + 0.12.0 true 3.16.0 diff --git a/src/main/java/tech/illuin/wombat/engine/module/extension/CoreExtensionLoader.java b/src/main/java/tech/illuin/wombat/engine/module/extension/CoreExtensionLoader.java index 4b3501a..57ae374 100644 --- a/src/main/java/tech/illuin/wombat/engine/module/extension/CoreExtensionLoader.java +++ b/src/main/java/tech/illuin/wombat/engine/module/extension/CoreExtensionLoader.java @@ -3,6 +3,7 @@ import tech.illuin.wombat.core.module.WombatModule; import tech.illuin.wombat.module.kubernetes_api.KubernetesAPIModule; import tech.illuin.wombat.module.kubernetes_simulated.KubernetesSimulatedModule; +import tech.illuin.wombat.module.llm_google.LLMGoogleModule; import tech.illuin.wombat.module.llm_prometheus.LLMPrometheusModule; import tech.illuin.wombat.module.llm_simulated.LLMSimulatedModule; import tech.illuin.wombat.module.llm_static.LLMStaticModule; @@ -33,6 +34,8 @@ public List load() modules.add(new LLMStaticModule()); if (this.properties.kubernetesApi().enabled()) modules.add(new KubernetesAPIModule()); + if (this.properties.llmGoogle().enabled()) + modules.add(new LLMGoogleModule()); return modules; } diff --git a/src/main/java/tech/illuin/wombat/engine/module/extension/WombatModuleProperties.java b/src/main/java/tech/illuin/wombat/engine/module/extension/WombatModuleProperties.java index 5ff4902..e34c92e 100644 --- a/src/main/java/tech/illuin/wombat/engine/module/extension/WombatModuleProperties.java +++ b/src/main/java/tech/illuin/wombat/engine/module/extension/WombatModuleProperties.java @@ -18,6 +18,9 @@ public interface WombatModuleProperties @WithName("llm-static") LlmStaticProperties llmStatic(); + @WithName("llm-google") + LlmGoogleProperties llmGoogle(); + @WithName("extension") ExtensionProperties extension(); @@ -39,6 +42,12 @@ interface LlmStaticProperties boolean enabled(); } + interface LlmGoogleProperties + { + @WithDefault("true") + boolean enabled(); + } + interface ExtensionProperties { @WithName("path") diff --git a/src/main/resources/monitored/monitored-environments-dev.yaml.dist b/src/main/resources/monitored/monitored-environments-dev.yaml.dist index c1710c2..5eb315f 100644 --- a/src/main/resources/monitored/monitored-environments-dev.yaml.dist +++ b/src/main/resources/monitored/monitored-environments-dev.yaml.dist @@ -49,3 +49,25 @@ environments: # location: FRA # dynamic-profile: # query: 'sum(increase(generation_tokens_total{model_name="mistral-large-latest"}[10s]))' +# - type: tech.illuin.wombat-module.llm-google +# id: my-gemini-llm +# environment-id: preprod +# name: My Gemini LLM (preprod) +# google-url: https://monitoring.googleapis.com +# project-id: my-gcp-project-id +# heartbeat-skip: 1 +# # Authentication is optional. Without it, Application Default Credentials are used. `credentials-key` names +# # the environment variable holding the service-account key JSON. The service account needs the +# # `roles/monitoring.viewer` role. +# credentials-key: WOMBAT_GOOGLE_CREDENTIALS +# profile: +# provider: google_genai +# model: gemini-2.5-flash +# location: USA +# dynamic-profile: +# filter: >- +# metric.type="aiplatform.googleapis.com/publisher/online_serving/token_count" +# AND metric.labels.type="output" +# AND resource.labels.model_user_id="gemini-2.5-flash" +# # Optional, ISO-8601, defaults to PT1M. Keep it equal to the sampling period. +# window: PT1M diff --git a/src/test/java/tech/illuin/wombat/engine/module/WombatModuleConfigTest.java b/src/test/java/tech/illuin/wombat/engine/module/WombatModuleConfigTest.java index a2c76ca..dccaf70 100644 --- a/src/test/java/tech/illuin/wombat/engine/module/WombatModuleConfigTest.java +++ b/src/test/java/tech/illuin/wombat/engine/module/WombatModuleConfigTest.java @@ -14,6 +14,7 @@ import tech.illuin.wombat.engine.module.extension.ExtensionLoader; import tech.illuin.wombat.engine.module.extension.WombatModuleProperties; import tech.illuin.wombat.module.kubernetes_api.KubernetesAPIModule; +import tech.illuin.wombat.module.llm_google.LLMGoogleModule; import tech.illuin.wombat.module.llm_prometheus.LLMPrometheusModule; import tech.illuin.wombat.module.llm_static.LLMStaticModule; @@ -39,7 +40,7 @@ class WombatModuleConfigTest @Test void provideWombatExtensionLoader_returnsCompositeExtensionLoader() { - WombatModuleProperties properties = properties(true, true, true); + WombatModuleProperties properties = properties(true, true, true, true); ExtensionLoader loader = this.config.provideWombatExtensionLoader(properties); assertInstanceOf(CompositeExtensionLoader.class, loader); @@ -49,7 +50,7 @@ void provideWombatExtensionLoader_returnsCompositeExtensionLoader() void provideWombatModules_loadsModulesFromExtensionLoader() { ExtensionLoader loader = mock(ExtensionLoader.class); - List expected = List.of(new KubernetesAPIModule(), new LLMStaticModule()); + List expected = List.of(new KubernetesAPIModule(), new LLMStaticModule(), new LLMGoogleModule()); when(loader.load()).thenReturn(expected); List result = this.config.provideWombatModules(loader); @@ -64,7 +65,8 @@ void provideMappers_registerSubtypesForGivenModules() List modules = List.of( new KubernetesAPIModule(), new LLMPrometheusModule(), - new LLMStaticModule() + new LLMStaticModule(), + new LLMGoogleModule() ); JsonMapper jsonMapper = this.config.provideJsonMapper(modules); @@ -96,7 +98,7 @@ void provideDefaults_createsHandlersForFamilies() assertNotNull(llmHandlers.costResolver()); } - private static WombatModuleProperties properties(boolean kubernetesApi, boolean llmPrometheus, boolean llmStatic) + private static WombatModuleProperties properties(boolean kubernetesApi, boolean llmPrometheus, boolean llmStatic, boolean llmGoogle) { return new WombatModuleProperties() { @@ -118,6 +120,12 @@ public LlmStaticProperties llmStatic() return () -> llmStatic; } + @Override + public LlmGoogleProperties llmGoogle() + { + return () -> llmGoogle; + } + @Override public ExtensionProperties extension() { diff --git a/src/test/java/tech/illuin/wombat/engine/module/extension/CoreExtensionLoaderTest.java b/src/test/java/tech/illuin/wombat/engine/module/extension/CoreExtensionLoaderTest.java index e34326c..683c6cc 100644 --- a/src/test/java/tech/illuin/wombat/engine/module/extension/CoreExtensionLoaderTest.java +++ b/src/test/java/tech/illuin/wombat/engine/module/extension/CoreExtensionLoaderTest.java @@ -6,6 +6,7 @@ import tech.illuin.wombat.engine.module.extension.WombatModuleProperties; import tech.illuin.wombat.module.kubernetes_api.KubernetesAPIModule; import tech.illuin.wombat.module.kubernetes_simulated.KubernetesSimulatedModule; +import tech.illuin.wombat.module.llm_google.LLMGoogleModule; import tech.illuin.wombat.module.llm_prometheus.LLMPrometheusModule; import tech.illuin.wombat.module.llm_simulated.LLMSimulatedModule; import tech.illuin.wombat.module.llm_static.LLMStaticModule; @@ -21,7 +22,7 @@ class CoreExtensionLoaderTest @Test void alwaysLoadsSimulatedModulesEvenWhenAllCoreModulesDisabled() { - WombatModuleProperties properties = properties(false, false, false); + WombatModuleProperties properties = properties(false, false, false, false); try (CoreExtensionLoader loader = new CoreExtensionLoader(properties)) { List modules = loader.load(); @@ -32,6 +33,7 @@ void alwaysLoadsSimulatedModulesEvenWhenAllCoreModulesDisabled() assertFalse(modules.stream().anyMatch(KubernetesAPIModule.class::isInstance)); assertFalse(modules.stream().anyMatch(LLMPrometheusModule.class::isInstance)); assertFalse(modules.stream().anyMatch(LLMStaticModule.class::isInstance)); + assertFalse(modules.stream().anyMatch(LLMGoogleModule.class::isInstance)); } catch (IOException e) { fail(e); @@ -41,17 +43,18 @@ void alwaysLoadsSimulatedModulesEvenWhenAllCoreModulesDisabled() @Test void loadsAllModulesWhenAllTogglesEnabled() { - WombatModuleProperties properties = properties(true, true, true); + WombatModuleProperties properties = properties(true, true, true, true); try (CoreExtensionLoader loader = new CoreExtensionLoader(properties)) { List modules = loader.load(); - assertEquals(5, modules.size()); + assertEquals(6, modules.size()); assertTrue(modules.stream().anyMatch(KubernetesSimulatedModule.class::isInstance)); assertTrue(modules.stream().anyMatch(LLMSimulatedModule.class::isInstance)); assertTrue(modules.stream().anyMatch(KubernetesAPIModule.class::isInstance)); assertTrue(modules.stream().anyMatch(LLMPrometheusModule.class::isInstance)); assertTrue(modules.stream().anyMatch(LLMStaticModule.class::isInstance)); + assertTrue(modules.stream().anyMatch(LLMGoogleModule.class::isInstance)); } catch (IOException e) { fail(e); @@ -61,7 +64,7 @@ void loadsAllModulesWhenAllTogglesEnabled() @Test void loadsOnlySpecificallyEnabledModules() { - WombatModuleProperties properties = properties(true, false, false); + WombatModuleProperties properties = properties(true, false, false, false); try (CoreExtensionLoader loader = new CoreExtensionLoader(properties)) { List modules = loader.load(); @@ -72,13 +75,35 @@ void loadsOnlySpecificallyEnabledModules() assertTrue(modules.stream().anyMatch(KubernetesAPIModule.class::isInstance)); assertFalse(modules.stream().anyMatch(LLMPrometheusModule.class::isInstance)); assertFalse(modules.stream().anyMatch(LLMStaticModule.class::isInstance)); + assertFalse(modules.stream().anyMatch(LLMGoogleModule.class::isInstance)); } catch (IOException e) { fail(e); } } - private static WombatModuleProperties properties(boolean kubernetesApi, boolean llmPrometheus, boolean llmStatic) + @Test + void loadsGoogleModuleWhenOnlyGoogleToggleEnabled() + { + WombatModuleProperties properties = properties(false, false, false, true); + try (CoreExtensionLoader loader = new CoreExtensionLoader(properties)) + { + List modules = loader.load(); + + assertEquals(3, modules.size()); + assertTrue(modules.stream().anyMatch(KubernetesSimulatedModule.class::isInstance)); + assertTrue(modules.stream().anyMatch(LLMSimulatedModule.class::isInstance)); + assertTrue(modules.stream().anyMatch(LLMGoogleModule.class::isInstance)); + assertFalse(modules.stream().anyMatch(KubernetesAPIModule.class::isInstance)); + assertFalse(modules.stream().anyMatch(LLMPrometheusModule.class::isInstance)); + assertFalse(modules.stream().anyMatch(LLMStaticModule.class::isInstance)); + } + catch (IOException e) { + fail(e); + } + } + + private static WombatModuleProperties properties(boolean kubernetesApi, boolean llmPrometheus, boolean llmStatic, boolean llmGoogle) { return new WombatModuleProperties() { @@ -100,6 +125,12 @@ public LlmStaticProperties llmStatic() return () -> llmStatic; } + @Override + public LlmGoogleProperties llmGoogle() + { + return () -> llmGoogle; + } + @Override public ExtensionProperties extension() {