Skip to content

Commit 8aa8dbb

Browse files
authored
Incorporate model capabilities into GenAICfEnvProcessor (#273)
* Incorporate model capabilities into GenAI CfEnvProcessor * Valid as of GenAI for Tanzu Platform v0.6.0 * Split the GenAICfEnvProcessor into two - one specific to chat and the other specific to embedding * This allows a developer to either make one binding to a model that supports both chat and embeddings, or to make two distinct bindings, one to a chat model and the other to an embeddings model * When binding to two different models, each model will have a different api key, which will be set accordingly on the corresponding spring.ai.openai.{chat,embedding}.* properties * Both processors additionally set the top-level property spring.ai.openai.api-key="redundant" in order to circumvent an error that is raised when configuring more specific api-keys on the chat and embedding properties * If desired, additional GenAI CfEnvProcessors may be written to cater to image and audio model options at a later time Signed-off-by: Ed King <[email protected]> * Tiny refactor to GenAI CfEnvProcessors Signed-off-by: Ed King <[email protected]> --------- Signed-off-by: Ed King <[email protected]>
1 parent 68dfa18 commit 8aa8dbb

File tree

8 files changed

+218
-65
lines changed

8 files changed

+218
-65
lines changed

java-cfenv-boot/src/main/java/io/pivotal/cfenv/spring/boot/GenAICfEnvProcessor.java renamed to java-cfenv-boot/src/main/java/io/pivotal/cfenv/spring/boot/GenAIChatCfEnvProcessor.java

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,37 +15,47 @@
1515
*/
1616
package io.pivotal.cfenv.spring.boot;
1717

18+
import java.util.ArrayList;
1819
import java.util.Map;
1920

2021
import io.pivotal.cfenv.core.CfCredentials;
2122
import io.pivotal.cfenv.core.CfService;
2223

2324
/**
2425
* Retrieve GenAI on Tanzu Platform properties from {@link CfCredentials} and
25-
* set {@literal spring.ai}
26+
* set {@literal spring.ai.openai.chat}
2627
* Boot properties.
2728
*
2829
* @author Stuart Charlton
30+
* @author Ed King
2931
**/
30-
public class GenAICfEnvProcessor implements CfEnvProcessor {
32+
public class GenAIChatCfEnvProcessor implements CfEnvProcessor {
3133

3234
@Override
3335
public boolean accept(CfService service) {
34-
return service.existsByTagIgnoreCase("genai") ||
35-
service.existsByLabelStartsWith("genai");
36+
boolean isGenAIService = service.existsByTagIgnoreCase("genai") || service.existsByLabelStartsWith("genai");
37+
if (isGenAIService) {
38+
ArrayList<String> modelCapabilities = (ArrayList<String>) service.getCredentials().getMap().get("model_capabilities");
39+
return modelCapabilities.contains("chat");
40+
}
41+
42+
return false;
3643
}
3744

3845
@Override
3946
public void process(CfCredentials cfCredentials, Map<String, Object> properties) {
40-
properties.put("spring.ai.openai.base-url", cfCredentials.getString("api_base"));
41-
properties.put("spring.ai.openai.api-key", cfCredentials.getString("api_key"));
47+
properties.put("spring.ai.openai.api-key", "redundant");
48+
49+
properties.put("spring.ai.openai.chat.base-url", cfCredentials.getString("api_base"));
50+
properties.put("spring.ai.openai.chat.api-key", cfCredentials.getString("api_key"));
51+
properties.put("spring.ai.openai.chat.options.model", cfCredentials.getString("model_name"));
4252
}
4353

4454
@Override
4555
public CfEnvProcessorProperties getProperties() {
4656
return CfEnvProcessorProperties.builder()
47-
.propertyPrefixes("spring.ai.openai")
48-
.serviceName("GenAI on Tanzu Platform")
57+
.propertyPrefixes("spring.ai.openai.chat")
58+
.serviceName("GenAI on Tanzu Platform (chat)")
4959
.build();
5060
}
5161
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.pivotal.cfenv.spring.boot;
17+
18+
import java.util.ArrayList;
19+
import java.util.Map;
20+
21+
import io.pivotal.cfenv.core.CfCredentials;
22+
import io.pivotal.cfenv.core.CfService;
23+
24+
/**
25+
* Retrieve GenAI on Tanzu Platform properties from {@link CfCredentials} and
26+
* set {@literal spring.ai.openai.embedding}
27+
* Boot properties.
28+
*
29+
* @author Stuart Charlton
30+
* @author Ed King
31+
**/
32+
public class GenAIEmbeddingCfEnvProcessor implements CfEnvProcessor {
33+
34+
@Override
35+
public boolean accept(CfService service) {
36+
boolean isGenAIService = service.existsByTagIgnoreCase("genai") || service.existsByLabelStartsWith("genai");
37+
if (isGenAIService) {
38+
ArrayList<String> modelCapabilities = (ArrayList<String>) service.getCredentials().getMap().get("model_capabilities");
39+
return modelCapabilities.contains("embedding");
40+
}
41+
42+
return false;
43+
}
44+
45+
@Override
46+
public void process(CfCredentials cfCredentials, Map<String, Object> properties) {
47+
properties.put("spring.ai.openai.api-key", "redundant");
48+
49+
properties.put("spring.ai.openai.embedding.base-url", cfCredentials.getString("api_base"));
50+
properties.put("spring.ai.openai.embedding.api-key", cfCredentials.getString("api_key"));
51+
properties.put("spring.ai.openai.embedding.options.model", cfCredentials.getString("model_name"));
52+
}
53+
54+
@Override
55+
public CfEnvProcessorProperties getProperties() {
56+
return CfEnvProcessorProperties.builder()
57+
.propertyPrefixes("spring.ai.openai.embedding")
58+
.serviceName("GenAI on Tanzu Platform (embedding)")
59+
.build();
60+
}
61+
}

java-cfenv-boot/src/main/resources/META-INF/spring.factories

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ io.pivotal.cfenv.spring.boot.CfEnvProcessor=\
1313
io.pivotal.cfenv.spring.boot.AmqpCfEnvProcessor,\
1414
io.pivotal.cfenv.spring.boot.CredHubCfEnvProcessor,\
1515
io.pivotal.cfenv.spring.boot.CassandraCfEnvProcessor,\
16-
io.pivotal.cfenv.spring.boot.GenAICfEnvProcessor,\
16+
io.pivotal.cfenv.spring.boot.GenAIChatCfEnvProcessor,\
17+
io.pivotal.cfenv.spring.boot.GenAIEmbeddingCfEnvProcessor,\
1718
io.pivotal.cfenv.spring.boot.VaultCfEnvProcessor
18-
19-

java-cfenv-boot/src/test/java/io/pivotal/cfenv/spring/boot/GenAICfEnvProcessorTests.java

Lines changed: 0 additions & 51 deletions
This file was deleted.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.pivotal.cfenv.spring.boot;
17+
18+
import org.junit.Test;
19+
20+
import io.pivotal.cfenv.test.AbstractCfEnvTests;
21+
22+
import static org.assertj.core.api.Assertions.assertThat;
23+
24+
/**
25+
* @author Stuart Charlton
26+
* @author Ed King
27+
*/
28+
public class GenAIChatCfEnvProcessorTests extends AbstractCfEnvTests {
29+
30+
@Test
31+
public void testGenAIBootPropertiesWithChatModelCapability() {
32+
String TEST_GENAI_JSON_FILE = "test-genai-chat-model.json";
33+
34+
String EXPECTED_URI_FROM_JSON_FILE = "https://genai-proxy.tpcf.io";
35+
String EXPECTED_TOKEN_FROM_JSON_FILE = "sk-KW5kiNOKDd_1dFxsAjpVa";
36+
String EXPECTED_MODEL_FROM_JSON_FILE = "meta-llama/Meta-Llama-3-8B";
37+
38+
mockVcapServices(getServicesPayload(readTestDataFile(TEST_GENAI_JSON_FILE)));
39+
40+
assertThat(getEnvironment().getProperty("spring.ai.openai.api-key")).isEqualTo("redundant");
41+
42+
assertThat(getEnvironment().getProperty("spring.ai.openai.chat.base-url")).isEqualTo(EXPECTED_URI_FROM_JSON_FILE);
43+
assertThat(getEnvironment().getProperty("spring.ai.openai.chat.api-key")).isEqualTo(EXPECTED_TOKEN_FROM_JSON_FILE);
44+
assertThat(getEnvironment().getProperty("spring.ai.openai.chat.options.model")).isEqualTo(EXPECTED_MODEL_FROM_JSON_FILE);
45+
46+
assertThat(getEnvironment().getProperty("spring.ai.openai.embedding.options.model")).isNull();
47+
assertThat(getEnvironment().getProperty("spring.ai.openai.image.options.model")).isNull();
48+
assertThat(getEnvironment().getProperty("spring.ai.openai.audio.transcription.options.model")).isNull();
49+
assertThat(getEnvironment().getProperty("spring.ai.openai.audio.speech.options.model")).isNull();
50+
}
51+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.pivotal.cfenv.spring.boot;
17+
18+
import org.junit.Test;
19+
20+
import io.pivotal.cfenv.test.AbstractCfEnvTests;
21+
22+
import static org.assertj.core.api.Assertions.assertThat;
23+
24+
/**
25+
* @author Stuart Charlton
26+
* @author Ed King
27+
*/
28+
public class GenAIEmbeddingCfEnvProcessorTests extends AbstractCfEnvTests {
29+
30+
@Test
31+
public void testGenAIBootPropertiesWithEmbeddingModelCapability() {
32+
String TEST_GENAI_JSON_FILE = "test-genai-embedding-model.json";
33+
34+
String EXPECTED_URI_FROM_JSON_FILE = "https://genai-proxy.tpcf.io";
35+
String EXPECTED_TOKEN_FROM_JSON_FILE = "sk-KW5kiNOKDd_1dFxsAjpVa";
36+
String EXPECTED_MODEL_FROM_JSON_FILE = "mixedbread-ai/mxbai-embed-large-v1";
37+
38+
mockVcapServices(getServicesPayload(readTestDataFile(TEST_GENAI_JSON_FILE)));
39+
40+
assertThat(getEnvironment().getProperty("spring.ai.openai.api-key")).isEqualTo("redundant");
41+
42+
assertThat(getEnvironment().getProperty("spring.ai.openai.embedding.base-url")).isEqualTo(EXPECTED_URI_FROM_JSON_FILE);
43+
assertThat(getEnvironment().getProperty("spring.ai.openai.embedding.api-key")).isEqualTo(EXPECTED_TOKEN_FROM_JSON_FILE);
44+
assertThat(getEnvironment().getProperty("spring.ai.openai.embedding.options.model")).isEqualTo(EXPECTED_MODEL_FROM_JSON_FILE);
45+
46+
assertThat(getEnvironment().getProperty("spring.ai.openai.chat.options.model")).isNull();
47+
assertThat(getEnvironment().getProperty("spring.ai.openai.image.options.model")).isNull();
48+
assertThat(getEnvironment().getProperty("spring.ai.openai.audio.transcription.options.model")).isNull();
49+
assertThat(getEnvironment().getProperty("spring.ai.openai.audio.speech.options.model")).isNull();
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,25 @@
11
{
22
"credentials": {
33
"api_base": "https://genai-proxy.tpcf.io",
4-
"api_key": "sk-KW5kiNOKDd_1dFxsAjpVa"
4+
"api_key": "sk-KW5kiNOKDd_1dFxsAjpVa",
5+
"model_name": "meta-llama/Meta-Llama-3-8B",
6+
"model_aliases": [
7+
"llama3"
8+
],
9+
"model_capabilities": [
10+
"chat",
11+
"tools"
12+
]
513
},
614
"instance_name": "genai",
715
"label": "genai",
816
"name": "genai",
9-
"plan": "shared",
17+
"plan": "meta-llama/Meta-Llama-3-8B",
1018
"provider": null,
1119
"syslog_drain_url": null,
1220
"tags": [
1321
"genai",
1422
"llm"
1523
],
1624
"volume_mounts": []
17-
}
25+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"credentials": {
3+
"api_base": "https://genai-proxy.tpcf.io",
4+
"api_key": "sk-KW5kiNOKDd_1dFxsAjpVa",
5+
"model_name": "mixedbread-ai/mxbai-embed-large-v1",
6+
"model_aliases": [
7+
"mxbai"
8+
],
9+
"model_capabilities": [
10+
"embedding"
11+
]
12+
},
13+
"instance_name": "genai",
14+
"label": "genai",
15+
"name": "genai",
16+
"plan": "mixedbread-ai/mxbai-embed-large-v1",
17+
"provider": null,
18+
"syslog_drain_url": null,
19+
"tags": [
20+
"genai",
21+
"llm"
22+
],
23+
"volume_mounts": []
24+
}

0 commit comments

Comments
 (0)