diff --git a/java-cfenv-boot/src/main/java/io/pivotal/cfenv/spring/boot/GenAICfEnvProcessor.java b/java-cfenv-boot/src/main/java/io/pivotal/cfenv/spring/boot/GenAIChatCfEnvProcessor.java similarity index 56% rename from java-cfenv-boot/src/main/java/io/pivotal/cfenv/spring/boot/GenAICfEnvProcessor.java rename to java-cfenv-boot/src/main/java/io/pivotal/cfenv/spring/boot/GenAIChatCfEnvProcessor.java index 7f967194..5e722a31 100644 --- a/java-cfenv-boot/src/main/java/io/pivotal/cfenv/spring/boot/GenAICfEnvProcessor.java +++ b/java-cfenv-boot/src/main/java/io/pivotal/cfenv/spring/boot/GenAIChatCfEnvProcessor.java @@ -15,6 +15,7 @@ */ package io.pivotal.cfenv.spring.boot; +import java.util.ArrayList; import java.util.Map; import io.pivotal.cfenv.core.CfCredentials; @@ -22,30 +23,39 @@ /** * Retrieve GenAI on Tanzu Platform properties from {@link CfCredentials} and - * set {@literal spring.ai} + * set {@literal spring.ai.openai.chat} * Boot properties. * * @author Stuart Charlton + * @author Ed King **/ -public class GenAICfEnvProcessor implements CfEnvProcessor { +public class GenAIChatCfEnvProcessor implements CfEnvProcessor { @Override public boolean accept(CfService service) { - return service.existsByTagIgnoreCase("genai") || - service.existsByLabelStartsWith("genai"); + boolean isGenAIService = service.existsByTagIgnoreCase("genai") || service.existsByLabelStartsWith("genai"); + if (isGenAIService) { + ArrayList modelCapabilities = (ArrayList) service.getCredentials().getMap().get("model_capabilities"); + return modelCapabilities.contains("chat"); + } + + return false; } @Override public void process(CfCredentials cfCredentials, Map properties) { - properties.put("spring.ai.openai.base-url", cfCredentials.getString("api_base")); - properties.put("spring.ai.openai.api-key", cfCredentials.getString("api_key")); + properties.put("spring.ai.openai.api-key", "redundant"); + + properties.put("spring.ai.openai.chat.base-url", cfCredentials.getString("api_base")); + properties.put("spring.ai.openai.chat.api-key", cfCredentials.getString("api_key")); + properties.put("spring.ai.openai.chat.options.model", cfCredentials.getString("model_name")); } @Override public CfEnvProcessorProperties getProperties() { return CfEnvProcessorProperties.builder() - .propertyPrefixes("spring.ai.openai") - .serviceName("GenAI on Tanzu Platform") + .propertyPrefixes("spring.ai.openai.chat") + .serviceName("GenAI on Tanzu Platform (chat)") .build(); } } diff --git a/java-cfenv-boot/src/main/java/io/pivotal/cfenv/spring/boot/GenAIEmbeddingCfEnvProcessor.java b/java-cfenv-boot/src/main/java/io/pivotal/cfenv/spring/boot/GenAIEmbeddingCfEnvProcessor.java new file mode 100644 index 00000000..a6c0d319 --- /dev/null +++ b/java-cfenv-boot/src/main/java/io/pivotal/cfenv/spring/boot/GenAIEmbeddingCfEnvProcessor.java @@ -0,0 +1,61 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.pivotal.cfenv.spring.boot; + +import java.util.ArrayList; +import java.util.Map; + +import io.pivotal.cfenv.core.CfCredentials; +import io.pivotal.cfenv.core.CfService; + +/** + * Retrieve GenAI on Tanzu Platform properties from {@link CfCredentials} and + * set {@literal spring.ai.openai.embedding} + * Boot properties. + * + * @author Stuart Charlton + * @author Ed King + **/ +public class GenAIEmbeddingCfEnvProcessor implements CfEnvProcessor { + + @Override + public boolean accept(CfService service) { + boolean isGenAIService = service.existsByTagIgnoreCase("genai") || service.existsByLabelStartsWith("genai"); + if (isGenAIService) { + ArrayList modelCapabilities = (ArrayList) service.getCredentials().getMap().get("model_capabilities"); + return modelCapabilities.contains("embedding"); + } + + return false; + } + + @Override + public void process(CfCredentials cfCredentials, Map properties) { + properties.put("spring.ai.openai.api-key", "redundant"); + + properties.put("spring.ai.openai.embedding.base-url", cfCredentials.getString("api_base")); + properties.put("spring.ai.openai.embedding.api-key", cfCredentials.getString("api_key")); + properties.put("spring.ai.openai.embedding.options.model", cfCredentials.getString("model_name")); + } + + @Override + public CfEnvProcessorProperties getProperties() { + return CfEnvProcessorProperties.builder() + .propertyPrefixes("spring.ai.openai.embedding") + .serviceName("GenAI on Tanzu Platform (embedding)") + .build(); + } +} diff --git a/java-cfenv-boot/src/main/resources/META-INF/spring.factories b/java-cfenv-boot/src/main/resources/META-INF/spring.factories index c99ce64e..c25db022 100644 --- a/java-cfenv-boot/src/main/resources/META-INF/spring.factories +++ b/java-cfenv-boot/src/main/resources/META-INF/spring.factories @@ -13,7 +13,6 @@ io.pivotal.cfenv.spring.boot.CfEnvProcessor=\ io.pivotal.cfenv.spring.boot.AmqpCfEnvProcessor,\ io.pivotal.cfenv.spring.boot.CredHubCfEnvProcessor,\ io.pivotal.cfenv.spring.boot.CassandraCfEnvProcessor,\ - io.pivotal.cfenv.spring.boot.GenAICfEnvProcessor,\ + io.pivotal.cfenv.spring.boot.GenAIChatCfEnvProcessor,\ + io.pivotal.cfenv.spring.boot.GenAIEmbeddingCfEnvProcessor,\ io.pivotal.cfenv.spring.boot.VaultCfEnvProcessor - - diff --git a/java-cfenv-boot/src/test/java/io/pivotal/cfenv/spring/boot/GenAICfEnvProcessorTests.java b/java-cfenv-boot/src/test/java/io/pivotal/cfenv/spring/boot/GenAICfEnvProcessorTests.java deleted file mode 100644 index 1ce740cc..00000000 --- a/java-cfenv-boot/src/test/java/io/pivotal/cfenv/spring/boot/GenAICfEnvProcessorTests.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.pivotal.cfenv.spring.boot; - -import mockit.Mock; -import mockit.MockUp; -import org.junit.Test; - -import io.pivotal.cfenv.test.AbstractCfEnvTests; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Stuart Charlton - */ -public class GenAICfEnvProcessorTests extends AbstractCfEnvTests { - - private static final String EXPECTED_TOKEN_FROM_JSON_FILE = "sk-KW5kiNOKDd_1dFxsAjpVa"; - private static final String EXPECTED_URI_FROM_JSON_FILE = "https://genai-proxy.tpcf.io"; - private static final String TEST_GENAI_INFO_JSON = "test-genai-info.json"; - - @Test - public void genaiServiceCreation() { - mockConnectorLibrary(false); - mockVcapServices(getServicesPayload(readTestDataFile(TEST_GENAI_INFO_JSON))); - assertThat(getEnvironment().getProperty("spring.ai.openai.base-url")).isEqualTo(EXPECTED_URI_FROM_JSON_FILE); - assertThat(getEnvironment().getProperty("spring.ai.openai.api-key")).isEqualTo(EXPECTED_TOKEN_FROM_JSON_FILE); - } - - private MockUp mockConnectorLibrary(boolean isUsingConnectorLibrary) { - return new MockUp() { - @Mock - public boolean isUsingConnectorLibrary() { - return isUsingConnectorLibrary; - } - }; - } -} diff --git a/java-cfenv-boot/src/test/java/io/pivotal/cfenv/spring/boot/GenAIChatCfEnvProcessorTests.java b/java-cfenv-boot/src/test/java/io/pivotal/cfenv/spring/boot/GenAIChatCfEnvProcessorTests.java new file mode 100644 index 00000000..9172983d --- /dev/null +++ b/java-cfenv-boot/src/test/java/io/pivotal/cfenv/spring/boot/GenAIChatCfEnvProcessorTests.java @@ -0,0 +1,51 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.pivotal.cfenv.spring.boot; + +import org.junit.Test; + +import io.pivotal.cfenv.test.AbstractCfEnvTests; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Stuart Charlton + * @author Ed King + */ +public class GenAIChatCfEnvProcessorTests extends AbstractCfEnvTests { + + @Test + public void testGenAIBootPropertiesWithChatModelCapability() { + String TEST_GENAI_JSON_FILE = "test-genai-chat-model.json"; + + String EXPECTED_URI_FROM_JSON_FILE = "https://genai-proxy.tpcf.io"; + String EXPECTED_TOKEN_FROM_JSON_FILE = "sk-KW5kiNOKDd_1dFxsAjpVa"; + String EXPECTED_MODEL_FROM_JSON_FILE = "meta-llama/Meta-Llama-3-8B"; + + mockVcapServices(getServicesPayload(readTestDataFile(TEST_GENAI_JSON_FILE))); + + assertThat(getEnvironment().getProperty("spring.ai.openai.api-key")).isEqualTo("redundant"); + + assertThat(getEnvironment().getProperty("spring.ai.openai.chat.base-url")).isEqualTo(EXPECTED_URI_FROM_JSON_FILE); + assertThat(getEnvironment().getProperty("spring.ai.openai.chat.api-key")).isEqualTo(EXPECTED_TOKEN_FROM_JSON_FILE); + assertThat(getEnvironment().getProperty("spring.ai.openai.chat.options.model")).isEqualTo(EXPECTED_MODEL_FROM_JSON_FILE); + + assertThat(getEnvironment().getProperty("spring.ai.openai.embedding.options.model")).isNull(); + assertThat(getEnvironment().getProperty("spring.ai.openai.image.options.model")).isNull(); + assertThat(getEnvironment().getProperty("spring.ai.openai.audio.transcription.options.model")).isNull(); + assertThat(getEnvironment().getProperty("spring.ai.openai.audio.speech.options.model")).isNull(); + } +} diff --git a/java-cfenv-boot/src/test/java/io/pivotal/cfenv/spring/boot/GenAIEmbeddingCfEnvProcessorTests.java b/java-cfenv-boot/src/test/java/io/pivotal/cfenv/spring/boot/GenAIEmbeddingCfEnvProcessorTests.java new file mode 100644 index 00000000..f3d8268c --- /dev/null +++ b/java-cfenv-boot/src/test/java/io/pivotal/cfenv/spring/boot/GenAIEmbeddingCfEnvProcessorTests.java @@ -0,0 +1,51 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.pivotal.cfenv.spring.boot; + +import org.junit.Test; + +import io.pivotal.cfenv.test.AbstractCfEnvTests; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Stuart Charlton + * @author Ed King + */ +public class GenAIEmbeddingCfEnvProcessorTests extends AbstractCfEnvTests { + + @Test + public void testGenAIBootPropertiesWithEmbeddingModelCapability() { + String TEST_GENAI_JSON_FILE = "test-genai-embedding-model.json"; + + String EXPECTED_URI_FROM_JSON_FILE = "https://genai-proxy.tpcf.io"; + String EXPECTED_TOKEN_FROM_JSON_FILE = "sk-KW5kiNOKDd_1dFxsAjpVa"; + String EXPECTED_MODEL_FROM_JSON_FILE = "mixedbread-ai/mxbai-embed-large-v1"; + + mockVcapServices(getServicesPayload(readTestDataFile(TEST_GENAI_JSON_FILE))); + + assertThat(getEnvironment().getProperty("spring.ai.openai.api-key")).isEqualTo("redundant"); + + assertThat(getEnvironment().getProperty("spring.ai.openai.embedding.base-url")).isEqualTo(EXPECTED_URI_FROM_JSON_FILE); + assertThat(getEnvironment().getProperty("spring.ai.openai.embedding.api-key")).isEqualTo(EXPECTED_TOKEN_FROM_JSON_FILE); + assertThat(getEnvironment().getProperty("spring.ai.openai.embedding.options.model")).isEqualTo(EXPECTED_MODEL_FROM_JSON_FILE); + + assertThat(getEnvironment().getProperty("spring.ai.openai.chat.options.model")).isNull(); + assertThat(getEnvironment().getProperty("spring.ai.openai.image.options.model")).isNull(); + assertThat(getEnvironment().getProperty("spring.ai.openai.audio.transcription.options.model")).isNull(); + assertThat(getEnvironment().getProperty("spring.ai.openai.audio.speech.options.model")).isNull(); + } +} diff --git a/java-cfenv-boot/src/test/resources/io/pivotal/cfenv/spring/boot/test-genai-info.json b/java-cfenv-boot/src/test/resources/io/pivotal/cfenv/spring/boot/test-genai-chat-model.json similarity index 51% rename from java-cfenv-boot/src/test/resources/io/pivotal/cfenv/spring/boot/test-genai-info.json rename to java-cfenv-boot/src/test/resources/io/pivotal/cfenv/spring/boot/test-genai-chat-model.json index e8785d21..2750c133 100644 --- a/java-cfenv-boot/src/test/resources/io/pivotal/cfenv/spring/boot/test-genai-info.json +++ b/java-cfenv-boot/src/test/resources/io/pivotal/cfenv/spring/boot/test-genai-chat-model.json @@ -1,12 +1,20 @@ { "credentials": { "api_base": "https://genai-proxy.tpcf.io", - "api_key": "sk-KW5kiNOKDd_1dFxsAjpVa" + "api_key": "sk-KW5kiNOKDd_1dFxsAjpVa", + "model_name": "meta-llama/Meta-Llama-3-8B", + "model_aliases": [ + "llama3" + ], + "model_capabilities": [ + "chat", + "tools" + ] }, "instance_name": "genai", "label": "genai", "name": "genai", - "plan": "shared", + "plan": "meta-llama/Meta-Llama-3-8B", "provider": null, "syslog_drain_url": null, "tags": [ @@ -14,4 +22,4 @@ "llm" ], "volume_mounts": [] - } \ No newline at end of file +} \ No newline at end of file diff --git a/java-cfenv-boot/src/test/resources/io/pivotal/cfenv/spring/boot/test-genai-embedding-model.json b/java-cfenv-boot/src/test/resources/io/pivotal/cfenv/spring/boot/test-genai-embedding-model.json new file mode 100644 index 00000000..6207d084 --- /dev/null +++ b/java-cfenv-boot/src/test/resources/io/pivotal/cfenv/spring/boot/test-genai-embedding-model.json @@ -0,0 +1,24 @@ +{ + "credentials": { + "api_base": "https://genai-proxy.tpcf.io", + "api_key": "sk-KW5kiNOKDd_1dFxsAjpVa", + "model_name": "mixedbread-ai/mxbai-embed-large-v1", + "model_aliases": [ + "mxbai" + ], + "model_capabilities": [ + "embedding" + ] + }, + "instance_name": "genai", + "label": "genai", + "name": "genai", + "plan": "mixedbread-ai/mxbai-embed-large-v1", + "provider": null, + "syslog_drain_url": null, + "tags": [ + "genai", + "llm" + ], + "volume_mounts": [] +} \ No newline at end of file