diff --git a/java-cfenv-boot/src/main/java/io/pivotal/cfenv/spring/boot/GenAIChatCfEnvProcessor.java b/java-cfenv-boot/src/main/java/io/pivotal/cfenv/spring/boot/GenAIChatCfEnvProcessor.java index 5e722a3..f478953 100644 --- a/java-cfenv-boot/src/main/java/io/pivotal/cfenv/spring/boot/GenAIChatCfEnvProcessor.java +++ b/java-cfenv-boot/src/main/java/io/pivotal/cfenv/spring/boot/GenAIChatCfEnvProcessor.java @@ -28,6 +28,7 @@ * * @author Stuart Charlton * @author Ed King + * @author Gareth Evans **/ public class GenAIChatCfEnvProcessor implements CfEnvProcessor { @@ -35,6 +36,10 @@ public class GenAIChatCfEnvProcessor implements CfEnvProcessor { public boolean accept(CfService service) { boolean isGenAIService = service.existsByTagIgnoreCase("genai") || service.existsByLabelStartsWith("genai"); if (isGenAIService) { + boolean endpointFormat = service.getCredentials().getMap().containsKey("endpoint"); + if (endpointFormat) { + return true; + } ArrayList modelCapabilities = (ArrayList) service.getCredentials().getMap().get("model_capabilities"); return modelCapabilities.contains("chat"); } @@ -46,9 +51,17 @@ public boolean accept(CfService service) { public void process(CfCredentials cfCredentials, Map properties) { 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")); + boolean endpointFormat = cfCredentials.getMap().containsKey("endpoint"); + if (endpointFormat) { + Map endpoint = (Map)cfCredentials.getMap().get("endpoint"); + properties.put("spring.ai.openai.chat.base-url", endpoint.get("api_base") + "/openai"); + properties.put("spring.ai.openai.chat.api-key", endpoint.get("api_key")); + properties.put("spring.ai.openai.chat.options.model", "tanzu://chat"); + } else { + 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 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 index a6c0d31..c2daa1f 100644 --- 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 @@ -28,6 +28,7 @@ * * @author Stuart Charlton * @author Ed King + * @author Gareth Evans **/ public class GenAIEmbeddingCfEnvProcessor implements CfEnvProcessor { @@ -35,6 +36,10 @@ public class GenAIEmbeddingCfEnvProcessor implements CfEnvProcessor { public boolean accept(CfService service) { boolean isGenAIService = service.existsByTagIgnoreCase("genai") || service.existsByLabelStartsWith("genai"); if (isGenAIService) { + boolean endpointFormat = service.getCredentials().getMap().containsKey("endpoint"); + if (endpointFormat) { + return true; + } ArrayList modelCapabilities = (ArrayList) service.getCredentials().getMap().get("model_capabilities"); return modelCapabilities.contains("embedding"); } @@ -46,9 +51,17 @@ public boolean accept(CfService service) { 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")); + boolean endpointFormat = cfCredentials.getMap().containsKey("endpoint"); + if (endpointFormat) { + Map endpoint = (Map)cfCredentials.getMap().get("endpoint"); + properties.put("spring.ai.openai.embedding.base-url", endpoint.get("api_base") + "/openai"); + properties.put("spring.ai.openai.embedding.api-key", endpoint.get("api_key")); + properties.put("spring.ai.openai.embedding.options.model", "tanzu://embedding"); + } else { + 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 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 index 9172983..dfa5fc5 100644 --- 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 @@ -24,6 +24,7 @@ /** * @author Stuart Charlton * @author Ed King + * @author Gareth Evans */ public class GenAIChatCfEnvProcessorTests extends AbstractCfEnvTests { @@ -48,4 +49,26 @@ public void testGenAIBootPropertiesWithChatModelCapability() { assertThat(getEnvironment().getProperty("spring.ai.openai.audio.transcription.options.model")).isNull(); assertThat(getEnvironment().getProperty("spring.ai.openai.audio.speech.options.model")).isNull(); } + + @Test + public void testGenAIBootPropertiesWithChatModelCapabilityEndpointFormat() { + String TEST_GENAI_JSON_FILE = "test-genai-endpoint-chat-model.json"; + + String EXPECTED_URI_FROM_JSON_FILE = "https://genai-proxy.tpcf.io/test/openai"; + String EXPECTED_TOKEN_FROM_JSON_FILE = "sk-KW5kiNOKDd_1dFxsAjpVa"; + String EXPECTED_MODEL_FROM_JSON_FILE = "tanzu://chat"; + + 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")).isNotNull(); + 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 index f3d8268..c0af4ff 100644 --- 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 @@ -24,6 +24,7 @@ /** * @author Stuart Charlton * @author Ed King + * @author Gareth Evans */ public class GenAIEmbeddingCfEnvProcessorTests extends AbstractCfEnvTests { @@ -48,4 +49,26 @@ public void testGenAIBootPropertiesWithEmbeddingModelCapability() { assertThat(getEnvironment().getProperty("spring.ai.openai.audio.transcription.options.model")).isNull(); assertThat(getEnvironment().getProperty("spring.ai.openai.audio.speech.options.model")).isNull(); } + + @Test + public void testGenAIBootPropertiesWithEmbeddingModelCapabilityEndpointFormat() { + String TEST_GENAI_JSON_FILE = "test-genai-endpoint-embedding-model.json"; + + String EXPECTED_URI_FROM_JSON_FILE = "https://genai-proxy.tpcf.io/test/openai"; + String EXPECTED_TOKEN_FROM_JSON_FILE = "sk-KW5kiNOKDd_1dFxsAjpVa"; + String EXPECTED_MODEL_FROM_JSON_FILE = "tanzu://embedding"; + + 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")).isNotNull(); + 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-endpoint-chat-model.json b/java-cfenv-boot/src/test/resources/io/pivotal/cfenv/spring/boot/test-genai-endpoint-chat-model.json new file mode 100644 index 0000000..3acd317 --- /dev/null +++ b/java-cfenv-boot/src/test/resources/io/pivotal/cfenv/spring/boot/test-genai-endpoint-chat-model.json @@ -0,0 +1,20 @@ +{ + "credentials": { + "endpoint": { + "api_base": "https://genai-proxy.tpcf.io/test", + "api_key": "sk-KW5kiNOKDd_1dFxsAjpVa", + "config_url": "https://genai-proxy.tpcf.io/test/config/v1/endpoint" + } + }, + "instance_name": "genai", + "label": "genai", + "name": "genai", + "plan": "meta-llama/Meta-Llama-3-8B", + "provider": null, + "syslog_drain_url": null, + "tags": [ + "genai", + "llm" + ], + "volume_mounts": [] +} \ No newline at end of file diff --git a/java-cfenv-boot/src/test/resources/io/pivotal/cfenv/spring/boot/test-genai-endpoint-embedding-model.json b/java-cfenv-boot/src/test/resources/io/pivotal/cfenv/spring/boot/test-genai-endpoint-embedding-model.json new file mode 100644 index 0000000..cb88e25 --- /dev/null +++ b/java-cfenv-boot/src/test/resources/io/pivotal/cfenv/spring/boot/test-genai-endpoint-embedding-model.json @@ -0,0 +1,20 @@ +{ + "credentials": { + "endpoint": { + "api_base": "https://genai-proxy.tpcf.io/test", + "api_key": "sk-KW5kiNOKDd_1dFxsAjpVa", + "config_url": "https://genai-proxy.tpcf.io/test/config/v1/endpoint" + } + }, + "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