diff --git a/spring-ai-spring-cloud-bindings/src/main/java/org/springframework/ai/bindings/TanzuBindingsPropertiesProcessor.java b/spring-ai-spring-cloud-bindings/src/main/java/org/springframework/ai/bindings/TanzuBindingsPropertiesProcessor.java new file mode 100644 index 000000000..ec13445ed --- /dev/null +++ b/spring-ai-spring-cloud-bindings/src/main/java/org/springframework/ai/bindings/TanzuBindingsPropertiesProcessor.java @@ -0,0 +1,47 @@ +package org.springframework.ai.bindings; + +import org.springframework.cloud.bindings.Binding; +import org.springframework.cloud.bindings.Bindings; +import org.springframework.cloud.bindings.boot.BindingsPropertiesProcessor; +import org.springframework.core.env.Environment; + +import java.util.Arrays; +import java.util.Map; + +/** + * An implementation of {@link BindingsPropertiesProcessor} that detects {@link Binding}s + * of type: {@value TYPE}. + * + * @author Stuart Charlton + */ +public class TanzuBindingsPropertiesProcessor implements BindingsPropertiesProcessor { + + /** + * The {@link Binding} type that this processor is interested in: {@value}. + **/ + public static final String TYPE = "genai"; + + @Override + public void process(Environment environment, Bindings bindings, Map properties) { + if (!BindingsValidator.isTypeEnabled(environment, TYPE)) { + return; + } + + bindings.filterBindings(TYPE).forEach(binding -> { + if (binding.getSecret().get("model-capabilities") != null) { + String[] capabilities = binding.getSecret().get("model-capabilities").trim().split("\\s*,\\s*"); + if (Arrays.stream(capabilities).anyMatch("chat"::equals)) { + properties.put("spring.ai.openai.chat.api-key", binding.getSecret().get("api-key")); + properties.put("spring.ai.openai.chat.base-url", binding.getSecret().get("uri")); + properties.put("spring.ai.openai.chat.options.model", binding.getSecret().get("model-name")); + } + if (Arrays.stream(capabilities).anyMatch("embedding"::equals)) { + properties.put("spring.ai.openai.embedding.api-key", binding.getSecret().get("api-key")); + properties.put("spring.ai.openai.embedding.base-url", binding.getSecret().get("uri")); + properties.put("spring.ai.openai.embedding.options.model", binding.getSecret().get("model-name")); + } + } + }); + } + +} diff --git a/spring-ai-spring-cloud-bindings/src/main/resources/META-INF/spring.factories b/spring-ai-spring-cloud-bindings/src/main/resources/META-INF/spring.factories index 72df6ee85..d8eef2b6c 100644 --- a/spring-ai-spring-cloud-bindings/src/main/resources/META-INF/spring.factories +++ b/spring-ai-spring-cloud-bindings/src/main/resources/META-INF/spring.factories @@ -3,4 +3,5 @@ org.springframework.cloud.bindings.boot.BindingsPropertiesProcessor=\ org.springframework.ai.bindings.ChromaBindingsPropertiesProcessor,\ org.springframework.ai.bindings.OllamaBindingsPropertiesProcessor,\ org.springframework.ai.bindings.OpenAiBindingsPropertiesProcessor,\ +org.springframework.ai.bindings.TanzuBindingsPropertiesProcessor,\ org.springframework.ai.bindings.WeaviateBindingsPropertiesProcessor diff --git a/spring-ai-spring-cloud-bindings/src/test/java/org/springframework/ai/bindings/TanzuBindingsPropertiesProcessorTests.java b/spring-ai-spring-cloud-bindings/src/test/java/org/springframework/ai/bindings/TanzuBindingsPropertiesProcessorTests.java new file mode 100644 index 000000000..bfa009dd0 --- /dev/null +++ b/spring-ai-spring-cloud-bindings/src/test/java/org/springframework/ai/bindings/TanzuBindingsPropertiesProcessorTests.java @@ -0,0 +1,79 @@ +package org.springframework.ai.bindings; + +import org.junit.jupiter.api.Test; +import org.springframework.cloud.bindings.Binding; +import org.springframework.cloud.bindings.Bindings; +import org.springframework.mock.env.MockEnvironment; + +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.ai.bindings.BindingsValidator.CONFIG_PATH; + +/** + * Unit tests for {@link TanzuBindingsPropertiesProcessor}. + * + * @author Stuart Charlton + */ +class TanzuBindingsPropertiesProcessorTests { + + private final Bindings bindings = new Bindings(new Binding("test-name", Paths.get("test-path"), + // @formatter:off + Map.of( + Binding.TYPE, TanzuBindingsPropertiesProcessor.TYPE, + "api-key", "demo", + "uri", "https://my.openai.example.net", + "model-name", "llava1.6", + "model-capabilities", " chat , vision " + )), + new Binding("test-name2", Paths.get("test-path2"), + Map.of( + Binding.TYPE, TanzuBindingsPropertiesProcessor.TYPE, + "api-key", "demo2", + "uri", "https://my.openai2.example.net", + "model-name", "text-embed-large", + "model-capabilities", "embedding"))); + // @formatter:on + + private final Bindings bindingsMissingModelCapabilities = new Bindings( + new Binding("test-name", Paths.get("test-path"), + // @formatter:off + Map.of( + Binding.TYPE, TanzuBindingsPropertiesProcessor.TYPE, + "api-key", "demo", + "uri", "https://my.openai.example.net" + ))); + // @formatter:on + + private final MockEnvironment environment = new MockEnvironment(); + + private final Map properties = new HashMap<>(); + + @Test + void propertiesAreContributed() { + new TanzuBindingsPropertiesProcessor().process(environment, bindings, properties); + assertThat(properties).containsEntry("spring.ai.openai.chat.api-key", "demo"); + assertThat(properties).containsEntry("spring.ai.openai.chat.base-url", "https://my.openai.example.net"); + assertThat(properties).containsEntry("spring.ai.openai.chat.options.model", "llava1.6"); + assertThat(properties).containsEntry("spring.ai.openai.embedding.api-key", "demo2"); + assertThat(properties).containsEntry("spring.ai.openai.embedding.base-url", "https://my.openai2.example.net"); + assertThat(properties).containsEntry("spring.ai.openai.embedding.options.model", "text-embed-large"); + } + + @Test + void propertiesAreMissingModelCapabilities() { + new TanzuBindingsPropertiesProcessor().process(environment, bindingsMissingModelCapabilities, properties); + assertThat(properties).isEmpty(); + } + + @Test + void whenDisabledThenPropertiesAreNotContributed() { + environment.setProperty("%s.genai.enabled".formatted(CONFIG_PATH), "false"); + + new TanzuBindingsPropertiesProcessor().process(environment, bindings, properties); + assertThat(properties).isEmpty(); + } + +}