From df3ab6ceb406485caba17278a82ec13e4314e3f0 Mon Sep 17 00:00:00 2001 From: corradodebari Date: Tue, 28 Oct 2025 22:16:55 +0100 Subject: [PATCH 01/19] fix obaas deployment --- src/client/content/config/tabs/settings.py | 14 ++++++++++++++ src/client/spring_ai/templates/obaas.yaml | 11 ++++------- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/client/content/config/tabs/settings.py b/src/client/content/config/tabs/settings.py index 9dea1627..cc62beb5 100644 --- a/src/client/content/config/tabs/settings.py +++ b/src/client/content/config/tabs/settings.py @@ -319,8 +319,22 @@ def spring_ai_obaas(src_dir, file_name, provider, ll_config, embed_config): yaml_data = yaml.safe_load(formatted_content) if provider == "ollama": del yaml_data["spring"]["ai"]["openai"] + yaml_data["spring"]["ai"]["openai"] = { + "chat": { + "options": { + "model": "_" + } + } + } if provider == "openai": del yaml_data["spring"]["ai"]["ollama"] + yaml_data["spring"]["ai"]["ollama"] = { + "chat": { + "options": { + "model": "_" + } + } + } formatted_content = yaml.dump(yaml_data) return formatted_content diff --git a/src/client/spring_ai/templates/obaas.yaml b/src/client/spring_ai/templates/obaas.yaml index ed806232..487e5fd7 100644 --- a/src/client/spring_ai/templates/obaas.yaml +++ b/src/client/spring_ai/templates/obaas.yaml @@ -1,6 +1,3 @@ -server: - servlet: - context-path: /v1 spring: datasource: url: ${{spring.datasource.url]}} @@ -14,8 +11,8 @@ spring: initialize-schema: True index-type: {vector_search[index_type]} openai: - base-url: \"{ll_model[api_base]}\" - api-key: \"{ll_model[api_key]}\" + base-url: "http://api.openai.com" + api-key: {ll_model[api_key]} chat: options: temperature: {ll_model[temperature]} @@ -36,10 +33,10 @@ spring: frequency-penalty: {ll_model[frequency_penalty]} num-predict: {ll_model[max_tokens]} top-p: {ll_model[top_p]} - model: \"{ll_model[id]}\" + model: {ll_model[id]} embedding: options: - model: \"{vector_search[id]}\" + model: {vector_search[id]} aims: sys_instr: \"{sys_prompt}\" vectortable: From 5e8833e2f5f89ad3e6580d0ada76996b93ebbfd1 Mon Sep 17 00:00:00 2001 From: corradodebari Date: Wed, 29 Oct 2025 12:53:35 +0100 Subject: [PATCH 02/19] fix openai base-url for obaas --- src/client/content/config/tabs/settings.py | 7 +++++++ src/client/spring_ai/templates/obaas.yaml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/client/content/config/tabs/settings.py b/src/client/content/config/tabs/settings.py index cc62beb5..59e385cd 100644 --- a/src/client/content/config/tabs/settings.py +++ b/src/client/content/config/tabs/settings.py @@ -335,6 +335,13 @@ def spring_ai_obaas(src_dir, file_name, provider, ll_config, embed_config): } } } + + # check if is formatting a "obaas" template to override openai base url that causes an issue in obaas with "/v1" + + if file_name.find("obaas")!=-1 and yaml_data["spring"]["ai"]["openai"]["base-url"].find("api.openai.com") != -1: + yaml_data["spring"]["ai"]["openai"]["base-url"] = "https://api.openai.com" + logger.info("in spring_ai_obaas(%s) found openai.base-url and changed with https://api.openai.com",file_name) + formatted_content = yaml.dump(yaml_data) return formatted_content diff --git a/src/client/spring_ai/templates/obaas.yaml b/src/client/spring_ai/templates/obaas.yaml index 487e5fd7..68a86fa5 100644 --- a/src/client/spring_ai/templates/obaas.yaml +++ b/src/client/spring_ai/templates/obaas.yaml @@ -11,7 +11,7 @@ spring: initialize-schema: True index-type: {vector_search[index_type]} openai: - base-url: "http://api.openai.com" + base-url: {ll_model[api_base]} api-key: {ll_model[api_key]} chat: options: From 997792672c4c262254f2486b1270eca91070b141 Mon Sep 17 00:00:00 2001 From: corradodebari Date: Wed, 29 Oct 2025 19:10:33 +0100 Subject: [PATCH 03/19] fix deployment obaas same optimizer user --- src/client/content/config/tabs/settings.py | 2 ++ .../ai/openai/samples/helloworld/AIController.java | 10 +++++++--- .../ai/openai/samples/helloworld/Config.java | 6 ++++++ src/client/spring_ai/templates/obaas.yaml | 7 ++++--- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/client/content/config/tabs/settings.py b/src/client/content/config/tabs/settings.py index 59e385cd..aa9f4a80 100644 --- a/src/client/content/config/tabs/settings.py +++ b/src/client/content/config/tabs/settings.py @@ -297,6 +297,8 @@ def spring_ai_obaas(src_dir, file_name, provider, ll_config, embed_config): database_lookup = st_common.state_configs_lookup("database_configs", "name") + logger.info("Database Legacy User:%s",database_lookup[state.client_settings["database"]["alias"]]["user"]) + formatted_content = template_content.format( provider=provider, sys_prompt=f"{sys_prompt}", diff --git a/src/client/spring_ai/src/main/java/org/springframework/ai/openai/samples/helloworld/AIController.java b/src/client/spring_ai/src/main/java/org/springframework/ai/openai/samples/helloworld/AIController.java index 1468c41b..63aabbbb 100644 --- a/src/client/spring_ai/src/main/java/org/springframework/ai/openai/samples/helloworld/AIController.java +++ b/src/client/spring_ai/src/main/java/org/springframework/ai/openai/samples/helloworld/AIController.java @@ -53,6 +53,7 @@ class AIController { private final OracleVectorStore vectorStore; private final EmbeddingModel embeddingModel; private final String legacyTable; + private final String userTable; private final String contextInstr; private final String searchType; private final int TOPK; @@ -76,6 +77,7 @@ class AIController { OracleVectorStore vectorStore, JdbcTemplate jdbcTemplate, String legacyTable, + String userTable, String contextInstr, String searchType, int TOPK) { @@ -86,6 +88,7 @@ class AIController { this.chatClient = chatClient; this.embeddingModel = embeddingModel; this.legacyTable = legacyTable; + this.userTable = userTable; this.contextInstr = contextInstr; this.searchType = searchType; this.TOPK = TOPK; @@ -132,18 +135,19 @@ public void insertData() { } else { // RUNNING in OBAAS logger.info("Running on OBaaS with user: " + user); + logger.info("copying langchain table from schema/user: " + userTable); sql = "INSERT INTO " + user + "." + newTable + " (ID, CONTENT, METADATA, EMBEDDING) " + - "SELECT ID, TEXT, METADATA, EMBEDDING FROM ADMIN." + legacyTable; + "SELECT ID, TEXT, METADATA, EMBEDDING FROM "+ userTable+"." + legacyTable; } // Execute the insert logger.info("doesExist" + user + ": " + helper.doesTableExist(newTable, user,this.jdbcTemplate)); if (helper.countRecordsInTable(newTable, user,this.jdbcTemplate) == 0) { // First microservice execution - logger.info("Table " + user + "." + newTable + " doesn't exist: create from ADMIN/USER." + legacyTable); + logger.info("Table " + user + "." + newTable + " doesn't exist: create from "+userTable+"." + legacyTable); jdbcTemplate.update(sql); } else { // Table conversion already done - logger.info("Table +" + newTable + " exists: drop before if you want use with new contents " + legacyTable); + logger.info("Table " + user+"."+newTable + " exists: drop before if you want use with new contents " + userTable + "." + legacyTable); } } diff --git a/src/client/spring_ai/src/main/java/org/springframework/ai/openai/samples/helloworld/Config.java b/src/client/spring_ai/src/main/java/org/springframework/ai/openai/samples/helloworld/Config.java index 62bcc883..2c6bf037 100644 --- a/src/client/spring_ai/src/main/java/org/springframework/ai/openai/samples/helloworld/Config.java +++ b/src/client/spring_ai/src/main/java/org/springframework/ai/openai/samples/helloworld/Config.java @@ -33,6 +33,12 @@ public String modelOllamaAI(@Value("${spring.ai.ollama.chat.options.model}") Str public String legacyTable(@Value("${aims.vectortable.name}") String table) { return table; } + + @Bean + public String userTable(@Value("${aims.vectortable.user}") String user) { + return user; + } + @Bean public String contextInstr(@Value("${aims.sys_instr}") String instr) { diff --git a/src/client/spring_ai/templates/obaas.yaml b/src/client/spring_ai/templates/obaas.yaml index 68a86fa5..f8cd8353 100644 --- a/src/client/spring_ai/templates/obaas.yaml +++ b/src/client/spring_ai/templates/obaas.yaml @@ -1,8 +1,8 @@ spring: datasource: - url: ${{spring.datasource.url]}} - username: ${{spring.datasource.username]}} - password: ${{spring.datasource.password]}} + url: ${{spring.datasource.url}} + username: ${{spring.datasource.username}} + password: ${{spring.datasource.password}} ai: vectorstore: oracle: @@ -41,6 +41,7 @@ aims: sys_instr: \"{sys_prompt}\" vectortable: name: {vector_search[vector_store]} + user: {database_config[user]} rag_params: search_type: Similarity top_k: {vector_search[top_k]} From 7d0791ac5e0349861c6d3e72ac95a810ad07bb96 Mon Sep 17 00:00:00 2001 From: corradodebari Date: Thu, 30 Oct 2025 13:05:14 +0100 Subject: [PATCH 04/19] Update README.md --- src/client/spring_ai/README.md | 286 +++++++++++++++++++++++++++++++-- 1 file changed, 272 insertions(+), 14 deletions(-) diff --git a/src/client/spring_ai/README.md b/src/client/spring_ai/README.md index 19b39819..2408b2ce 100644 --- a/src/client/spring_ai/README.md +++ b/src/client/spring_ai/README.md @@ -155,21 +155,51 @@ npx @modelcontextprotocol/inspector * Test a call to `getRag` Tool. -## Oracle Backend for Microservices and AI -* Add in `application-obaas.yml` the **OPENAI_API_KEY**, if the deployement is based on the OpenAI LLM services: +## Oracle Backend for Microservices and AI (rel. 1.4.0) + +To simplify as much as possible the process, configure the Oracle Backend for Microservices and AI Autonomous DB to run the AI Optimizer and toolkit. In this way, you can get smoothly the vectorstore created to be copied as a dedicated version for the microservice running. If you prefer to run the microservice in another user schema, before the step **5.** execute the steps described at **Other deployment options** chapter. + +* Create a user/schema via oractl. First open a tunnel: ``` - openai: - base-url: - api-key: +kubectl -n obaas-admin port-forward svc/obaas-admin 8080:8080 ``` -* Build, depending the provider ``: +* run `oractl` and connect with the provided credentials + +* create a namespace to host the AI Optimizer and Toolkit : ``` -mvn clean package -DskipTests -P -Dspring-boot.run.profiles=obaas +namespace create --namespace +``` + +* create the datastore, saving the passoword provided: +``` +datastore create --namespace --username --id +``` + +* For the AI Optimizer and Toolkit local startup, setting this env variables in startup: + +``` +DB_USERNAME= +DB_PASSWORD= +DB_DSN="" +DB_WALLET_PASSWORD= +TNS_ADMIN= +``` + +NOTE: if you need to access to the Autonomus Database backing the platform as admin, execute: +``` +kubectl -n application get secret graalvmcdb14db-db-secrets -o jsonpath='{.data.db\.password}' | base64 -d; echo ``` +to do, for example: +``` +DROP USER vectorusr CASCADE; +``` + +Then proceed as described in following steps: + +1. Create an `ollama-values.yaml` to be used with **helm** to provision an Ollama server. This step requires you have a GPU node pool provisioned with the Oracle Backend for Microservices and AI. Include in the models list to pull the model used in your Spring Boot microservice. Example: -* Set, one time only, the ollama server running in the **Oracle Backend for Microservices and AI**. Prepare an `ollama-values.yaml`: ``` ollama: gpu: @@ -177,14 +207,241 @@ ollama: type: 'nvidia' number: 1 models: - - llama3.1 - - llama3.2 - - mxbai-embed-large - - nomic-embed-text + pull: + - llama3.1 + - llama3.2 + - mxbai-embed-large + - nomic-embed-text nodeSelector: node.kubernetes.io/instance-type: VM.GPU.A10.1 ``` +2. Execute the helm chart provisioning: + +``` +helm upgrade --install ollama ollama-helm/ollama \ + --namespace ollama \ + --create-namespace \ + --values ollama-values.yaml +``` + +Check if the deployment is working at the end of process. +You should get this kind of output: + +``` +1. Get the application URL by running these commands: + export POD_NAME=$(kubectl get pods --namespace ollama -l "app.kubernetes.io/name=ollama,app.kubernetes.io/instance=ollama" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace ollama $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace ollama port-forward $POD_NAME 8080:$CONTAINER_PORT +``` + +3. check all: +* run: +``` +kubectl -n ollama exec svc/ollama -- ollama ls +``` +it should be: +``` +NAME ID SIZE MODIFIED +nomic-embed-text:latest 0a109f422b47 274 MB 3 minutes ago +mxbai-embed-large:latest 468836162de7 669 MB 3 minutes ago +llama3.1:latest a80c4f17acd5 2.0 GB 3 minutes ago +``` +* test a single LLM: +``` +kubectl -n ollama exec svc/ollama -- ollama run "llama3.1" "what is spring boot?" +``` + +NOTICE: for network issue related to huge model download, the process could stuck. Repeat it, or choose to pull manually just for test, removing from the helm chart the `models` part in `ollama-values.yaml`. + +To remove it and repeat: +* get the ollama stuck: +``` +kubectl get pods -n ollama +``` +* the uninstall: +``` +helm uninstall ollama --namespace ollama + +kubectl delete pod -n ollama --grace-period=0 --force +kubectl delete pod -n ollama --all --grace-period=0 --force +kubectl delete namespace ollama +``` +* install helm chart without models +* connect to the pod to pull manually: +``` +kubectl exec -it -n ollama -- bash +``` +* run: +``` +ollama pull llama3.2 +ollama pull mxbai-embed-large +``` + +* Build, depending the provider ``: + +``` +mvn clean package -DskipTests -P -Dspring-boot.run.profiles=obaas +``` + +4. Connect via oractl to deploy the microservice, if not yet done: + +* First open a tunnel: +``` +kubectl -n obaas-admin port-forward svc/obaas-admin 8080:8080 +``` +* run `oractl` and connect with the provided credentials + +5. Execute the deployment: + +``` +artifact create --namespace --workload --imageVersion 0.0.1 --file + +image create --namespace --workload --imageVersion 0.0.1 + +workload create --namespace --imageVersion 0.0.1 --id --cpuRequest 100m --framework SPRING_BOOT + +binding create --namespace --datastore --workload --framework SPRING_BOOT +``` + +6. Let's test: +* open a tunnel: + +``` +kubectl -n port-forward svc/ 9090:8080 +``` + +* test via curl. Example: + +``` +curl -N http://localhost:9090/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer your_api_key" \ + -d '{ + "model": "server", + "messages": [{"role": "user", "content": "Can I use any kind of development environment to run the example?"}], + "stream": false + }' +``` + +7. Open to external access via APISIX Gateway: + +* get the Kubernetes address: + +``` +kubectl -n ingress-nginx get svc ingress-nginx-controller +``` + +* get the APISIX password: + +``` +kubectl get secret -n apisix apisix-dashboard -o jsonpath='{.data.conf\.yaml}' | base64 -d | grep 'password:'; echo +``` +* connect to APISIX console: +``` +kubectl port-forward -n apisix svc/apisix-dashboard 8090:80 +``` +and provide the credentials at local url http://localhost:8090/, `admin`/ + +* Create a route to access the microservice: + +``` +Name: +Path: /v1/chat/completions* +Algorithm: Round Robin +Upstream Type: Node +Targets: + Host:..svc.cluster.local + Port: 8080 +``` + +8. Test the access to the public IP. Example: +``` +curl -N http:///v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer your_api_key" \ + -d '{ + "model": "server", + "messages": [{"role": "user", "content": "Can I use any kind of development environment to run the example?"}], + "stream": false + }' +``` + + + +### Other deployment options + +If you want to run on another schema instead the , you should add a few steps. + +1. Connect to the backend via oractl: + +* First open a tunnel: +``` +kubectl -n obaas-admin port-forward svc/obaas-admin 8080:8080 +``` +* Run `oractl` and connect with the provided credentials + +* Create a dedicated namespace for the microservice: + +``` +namespace create --namespace +``` + +* Create a dedicated user/schema for the microservice: + +``` +datastore create --namespace --username --id +? password: +``` + +2. Connect to the Autonomous DB instance via the / + +* Grant access to the microservice user to copy the vectorstore used: + +``` +GRANT SELECT ON ""."" TO ; +``` + +3. Then proceed from the step 5. as usual, changing: + + -> + -> + -> + + +### Cleaning env + +* First open a tunnel: +``` +kubectl -n obaas-admin port-forward svc/obaas-admin 8080:8080 +``` + +* Run `oractl` and connect with the provided credentials: + +``` +workload list --namespace +workload delete --namespace --id myspringai +image list +image delete --imageId +artifact list +artifact delete --artifactId +``` + + + + + + + + + + + + + + + * execute: ``` kubectl create ns ollama @@ -199,13 +456,14 @@ it should be: NAME ID SIZE MODIFIED nomic-embed-text:latest 0a109f422b47 274 MB 3 minutes ago mxbai-embed-large:latest 468836162de7 669 MB 3 minutes ago -llama3.1:latest a80c4f17acd5 2.0 GB 3 minutes ago +llama3.2:latest a80c4f17acd5 2.0 GB 3 minutes ago ``` * test a single LLM: ``` -kubectl -n ollama exec svc/ollama -- ollama run "llama3.1" "what is spring boot?" +kubectl -n ollama exec svc/ollama -- ollama run "llama3.2" "what is spring boot?" ``` + * **NOTE**: The Microservices will access to the ADB23ai on which the vector store table should be created as done in the local desktop example shown before. To access the ai-explorer running on **Oracle Backend for Microservices and AI** and create the same configuration, let's do: * tunnel: ``` From f0ff124d9a6bda2d957268256b78ab4ced130f20 Mon Sep 17 00:00:00 2001 From: corradodebari Date: Thu, 30 Oct 2025 13:12:17 +0100 Subject: [PATCH 05/19] doc update --- src/client/spring_ai/README.md | 131 +++++---------------------------- 1 file changed, 17 insertions(+), 114 deletions(-) diff --git a/src/client/spring_ai/README.md b/src/client/spring_ai/README.md index 2408b2ce..c16d4f1f 100644 --- a/src/client/spring_ai/README.md +++ b/src/client/spring_ai/README.md @@ -1,5 +1,21 @@ # Spring AI template +## Prerequisites + +Before using the AI commands, make sure you have a developer token from OpenAI. + +Create an account at [OpenAI Signup](https://platform.openai.com/signup) and generate the token at [API Keys](https://platform.openai.com/account/api-keys). + +The Spring AI project defines a configuration property named `spring.ai.openai.api-key` that you should set to the value of the `API Key` obtained from `openai.com`. + +Exporting an environment variable is one way to set that configuration property. +```shell +export SPRING_AI_OPENAI_API_KEY= +``` + +Setting the API key is all you need to run the application. +However, you can find more information on setting started in the [Spring AI reference documentation section on OpenAI Chat](https://docs.spring.io/spring-ai/reference/api/clients/openai-chat.html). + ## How to run: Prepare two configurations in the `Oracle ai optimizer and toolkit`, based on vector stores created using this kind of configuration: @@ -410,7 +426,7 @@ GRANT SELECT ON ""."" TO ; -> -### Cleaning env +### Cleanup env * First open a tunnel: ``` @@ -432,116 +448,3 @@ artifact delete --artifactId - - - - - - - - - - -* execute: -``` -kubectl create ns ollama -helm install ollama ollama-helm/ollama --namespace ollama --values ollama-values.yaml -``` -* check: -``` -kubectl -n ollama exec svc/ollama -- ollama ls -``` -it should be: -``` -NAME ID SIZE MODIFIED -nomic-embed-text:latest 0a109f422b47 274 MB 3 minutes ago -mxbai-embed-large:latest 468836162de7 669 MB 3 minutes ago -llama3.2:latest a80c4f17acd5 2.0 GB 3 minutes ago -``` -* test a single LLM: -``` -kubectl -n ollama exec svc/ollama -- ollama run "llama3.2" "what is spring boot?" -``` - - -* **NOTE**: The Microservices will access to the ADB23ai on which the vector store table should be created as done in the local desktop example shown before. To access the ai-explorer running on **Oracle Backend for Microservices and AI** and create the same configuration, let's do: - * tunnel: - ``` - kubectl -n ai-explorer port-forward svc/ai-explorer 8181:8501 - ``` - * on localhost: - ``` - http://localhost:8181/ai-sandbox - ``` - -* Deploy with `oractl` on a new schema `vector`: - * tunnel: - ``` - kubectl -n obaas-admin port-forward svc/obaas-admin 8080:8080 - ``` - - * oractl: - ``` - create --app-name vector_search - bind --app-name vector_search --service-name myspringai --username vector - ``` - - -* the `bind` will create the new user, if not exists, but to have the `_SPRINGAI` table compatible with SpringAI Oracle vector store adapter, the microservices need to access to the vector store table created by the ai-explorer with user ADMIN on ADB: - -``` -GRANT SELECT ON ADMIN. TO vector; -``` -* then deploy: -``` -deploy --app-name vector_search --service-name myspringai --artifact-path /target/myspringai-0.0.1-SNAPSHOT.jar --image-version 0.0.1 --java-version ghcr.io/oracle/graalvm-native-image-obaas:21 --service-profile obaas -``` -* test: -``` -kubectl -n vector_search port-forward svc/myspringai 9090:8080 -``` -* from shell: -``` -curl -N http://localhost:9090/v1/chat/completions \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer your_api_key" \ - -d '{ - "model": "server", - "messages": [{"role": "user", "content": "Can I use any kind of development environment to run the example?"}], - "stream": false - }' -``` -it should return: -``` -{ - "choices": [ - { - "message": { - "content": "Based on the provided documents, it seems that a specific development environment (IDE) is recommended for running the example.\n\nIn document \"67D5C08DF7F7480F\", it states: \"This guide uses IntelliJ Idea community version to create and update the files for this application.\" (page 17)\n\nHowever, there is no information in the provided documents that explicitly prohibits using other development environments. In fact, one of the articles mentions \"Application. Use these instructions as a reference.\" without specifying any particular IDE.\n\nTherefore, while it appears that IntelliJ Idea community version is recommended, I couldn't find any definitive statement ruling out the use of other development environments entirely.\n\nIf you'd like to run the example with a different environment, it might be worth investigating further or consulting additional resources. Sorry if this answer isn't more conclusive!" - } - } - ] -} -``` - - -## Prerequisites - -Before using the AI commands, make sure you have a developer token from OpenAI. - -Create an account at [OpenAI Signup](https://platform.openai.com/signup) and generate the token at [API Keys](https://platform.openai.com/account/api-keys). - -The Spring AI project defines a configuration property named `spring.ai.openai.api-key` that you should set to the value of the `API Key` obtained from `openai.com`. - -Exporting an environment variable is one way to set that configuration property. -```shell -export SPRING_AI_OPENAI_API_KEY= -``` - -Setting the API key is all you need to run the application. -However, you can find more information on setting started in the [Spring AI reference documentation section on OpenAI Chat](https://docs.spring.io/spring-ai/reference/api/clients/openai-chat.html). - - - - - From bea2c24994e21e27a754e6b0e274f593bb056fa3 Mon Sep 17 00:00:00 2001 From: Corrado De Bari Date: Thu, 30 Oct 2025 13:13:27 +0100 Subject: [PATCH 06/19] Update README.md --- src/client/spring_ai/README.md | 136 +++++---------------------------- 1 file changed, 17 insertions(+), 119 deletions(-) diff --git a/src/client/spring_ai/README.md b/src/client/spring_ai/README.md index 2408b2ce..69f4c353 100644 --- a/src/client/spring_ai/README.md +++ b/src/client/spring_ai/README.md @@ -1,5 +1,21 @@ # Spring AI template +## Prerequisites + +Before using the AI commands, make sure you have a developer token from OpenAI. + +Create an account at [OpenAI Signup](https://platform.openai.com/signup) and generate the token at [API Keys](https://platform.openai.com/account/api-keys). + +The Spring AI project defines a configuration property named `spring.ai.openai.api-key` that you should set to the value of the `API Key` obtained from `openai.com`. + +Exporting an environment variable is one way to set that configuration property. +```shell +export SPRING_AI_OPENAI_API_KEY= +``` + +Setting the API key is all you need to run the application. +However, you can find more information on setting started in the [Spring AI reference documentation section on OpenAI Chat](https://docs.spring.io/spring-ai/reference/api/clients/openai-chat.html). + ## How to run: Prepare two configurations in the `Oracle ai optimizer and toolkit`, based on vector stores created using this kind of configuration: @@ -410,7 +426,7 @@ GRANT SELECT ON ""."" TO ; -> -### Cleaning env +### Cleanup env * First open a tunnel: ``` @@ -427,121 +443,3 @@ image delete --imageId artifact list artifact delete --artifactId ``` - - - - - - - - - - - - - - - -* execute: -``` -kubectl create ns ollama -helm install ollama ollama-helm/ollama --namespace ollama --values ollama-values.yaml -``` -* check: -``` -kubectl -n ollama exec svc/ollama -- ollama ls -``` -it should be: -``` -NAME ID SIZE MODIFIED -nomic-embed-text:latest 0a109f422b47 274 MB 3 minutes ago -mxbai-embed-large:latest 468836162de7 669 MB 3 minutes ago -llama3.2:latest a80c4f17acd5 2.0 GB 3 minutes ago -``` -* test a single LLM: -``` -kubectl -n ollama exec svc/ollama -- ollama run "llama3.2" "what is spring boot?" -``` - - -* **NOTE**: The Microservices will access to the ADB23ai on which the vector store table should be created as done in the local desktop example shown before. To access the ai-explorer running on **Oracle Backend for Microservices and AI** and create the same configuration, let's do: - * tunnel: - ``` - kubectl -n ai-explorer port-forward svc/ai-explorer 8181:8501 - ``` - * on localhost: - ``` - http://localhost:8181/ai-sandbox - ``` - -* Deploy with `oractl` on a new schema `vector`: - * tunnel: - ``` - kubectl -n obaas-admin port-forward svc/obaas-admin 8080:8080 - ``` - - * oractl: - ``` - create --app-name vector_search - bind --app-name vector_search --service-name myspringai --username vector - ``` - - -* the `bind` will create the new user, if not exists, but to have the `_SPRINGAI` table compatible with SpringAI Oracle vector store adapter, the microservices need to access to the vector store table created by the ai-explorer with user ADMIN on ADB: - -``` -GRANT SELECT ON ADMIN. TO vector; -``` -* then deploy: -``` -deploy --app-name vector_search --service-name myspringai --artifact-path /target/myspringai-0.0.1-SNAPSHOT.jar --image-version 0.0.1 --java-version ghcr.io/oracle/graalvm-native-image-obaas:21 --service-profile obaas -``` -* test: -``` -kubectl -n vector_search port-forward svc/myspringai 9090:8080 -``` -* from shell: -``` -curl -N http://localhost:9090/v1/chat/completions \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer your_api_key" \ - -d '{ - "model": "server", - "messages": [{"role": "user", "content": "Can I use any kind of development environment to run the example?"}], - "stream": false - }' -``` -it should return: -``` -{ - "choices": [ - { - "message": { - "content": "Based on the provided documents, it seems that a specific development environment (IDE) is recommended for running the example.\n\nIn document \"67D5C08DF7F7480F\", it states: \"This guide uses IntelliJ Idea community version to create and update the files for this application.\" (page 17)\n\nHowever, there is no information in the provided documents that explicitly prohibits using other development environments. In fact, one of the articles mentions \"Application. Use these instructions as a reference.\" without specifying any particular IDE.\n\nTherefore, while it appears that IntelliJ Idea community version is recommended, I couldn't find any definitive statement ruling out the use of other development environments entirely.\n\nIf you'd like to run the example with a different environment, it might be worth investigating further or consulting additional resources. Sorry if this answer isn't more conclusive!" - } - } - ] -} -``` - - -## Prerequisites - -Before using the AI commands, make sure you have a developer token from OpenAI. - -Create an account at [OpenAI Signup](https://platform.openai.com/signup) and generate the token at [API Keys](https://platform.openai.com/account/api-keys). - -The Spring AI project defines a configuration property named `spring.ai.openai.api-key` that you should set to the value of the `API Key` obtained from `openai.com`. - -Exporting an environment variable is one way to set that configuration property. -```shell -export SPRING_AI_OPENAI_API_KEY= -``` - -Setting the API key is all you need to run the application. -However, you can find more information on setting started in the [Spring AI reference documentation section on OpenAI Chat](https://docs.spring.io/spring-ai/reference/api/clients/openai-chat.html). - - - - - From 85229b806e8a3c4152132ad8bcf859b227b1394b Mon Sep 17 00:00:00 2001 From: corradodebari Date: Thu, 30 Oct 2025 14:39:14 +0100 Subject: [PATCH 07/19] Update README.md --- src/client/spring_ai/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/spring_ai/README.md b/src/client/spring_ai/README.md index c16d4f1f..3caace10 100644 --- a/src/client/spring_ai/README.md +++ b/src/client/spring_ai/README.md @@ -404,13 +404,13 @@ kubectl -n obaas-admin port-forward svc/obaas-admin 8080:8080 namespace create --namespace ``` -* Create a dedicated user/schema for the microservice: +* Create a dedicated user/schema for the microservice, providing a to execute the command: ``` datastore create --namespace --username --id -? password: ``` + 2. Connect to the Autonomous DB instance via the / * Grant access to the microservice user to copy the vectorstore used: From b249e56958220394c2aed0534d23700ddcfe1f8f Mon Sep 17 00:00:00 2001 From: corradodebari Date: Fri, 31 Oct 2025 11:41:02 +0100 Subject: [PATCH 08/19] obaas deployment fix --- src/client/spring_ai/templates/obaas.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/spring_ai/templates/obaas.yaml b/src/client/spring_ai/templates/obaas.yaml index f8cd8353..60a71eb5 100644 --- a/src/client/spring_ai/templates/obaas.yaml +++ b/src/client/spring_ai/templates/obaas.yaml @@ -1,8 +1,8 @@ spring: datasource: - url: ${{spring.datasource.url}} - username: ${{spring.datasource.username}} - password: ${{spring.datasource.password}} + url: ${{spring.datasource.url]}} + username: ${{spring.datasource.username]}} + password: ${{spring.datasource.password]}} ai: vectorstore: oracle: From 6b5158b1b629d802cce099d3130a2ee70f0ddba2 Mon Sep 17 00:00:00 2001 From: corradodebari Date: Fri, 31 Oct 2025 13:21:33 +0100 Subject: [PATCH 09/19] fix show model in the /chat/completion answer --- .../springframework/ai/openai/samples/helloworld/Helper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/spring_ai/src/main/java/org/springframework/ai/openai/samples/helloworld/Helper.java b/src/client/spring_ai/src/main/java/org/springframework/ai/openai/samples/helloworld/Helper.java index 6ab80289..892c531c 100644 --- a/src/client/spring_ai/src/main/java/org/springframework/ai/openai/samples/helloworld/Helper.java +++ b/src/client/spring_ai/src/main/java/org/springframework/ai/openai/samples/helloworld/Helper.java @@ -86,9 +86,9 @@ public String generateRandomToken(int length) { public String getModel(String modelOpenAI, String modelOllamaAI) { String modelId = "custom"; - if (!"".equals(modelOpenAI)) { + if (!"_".equals(modelOpenAI)) { modelId = modelOpenAI; - } else if (!"".equals(modelOllamaAI)) { + } else if (!"_".equals(modelOllamaAI)) { modelId = modelOllamaAI; } return modelId; From 5e9215e9cb30b11934576e88bb881b0f15f4f2c8 Mon Sep 17 00:00:00 2001 From: corradodebari Date: Thu, 6 Nov 2025 11:41:10 +0100 Subject: [PATCH 10/19] microservices export -streamable http for langchain MCP export - fix spring AI for local run --- src/client/mcp/rag/README.md | 17 +-- .../mcp/rag/rag_base_optimizer_config_mcp.py | 5 +- src/client/spring_ai/README.md | 102 ++++++++++-------- .../src/main/resources/application-dev.yml | 1 + src/client/spring_ai/templates/start.sh | 1 + 5 files changed, 71 insertions(+), 55 deletions(-) diff --git a/src/client/mcp/rag/README.md b/src/client/mcp/rag/README.md index 561cebed..625d43c1 100644 --- a/src/client/mcp/rag/README.md +++ b/src/client/mcp/rag/README.md @@ -62,7 +62,7 @@ If you have already installed Node.js v20.17.0+, it should work. "command": "npx", "args": [ "mcp-remote", - "http://127.0.0.1:9090/sse" + "http://127.0.0.1:9090/mcp" ] } } @@ -120,14 +120,17 @@ uv run rag_base_optimizer_config_mcp.py * Set `Local` with `Remote client` line in `/rag_base_optimizer_config_mcp.py`: ```python - #mcp = FastMCP("rag", port=9090) #Remote client - mcp = FastMCP("rag") #Local + #mcp.run(transport='stdio') + #mcp.run(transport='sse') + mcp.run(transport='streamable-http') ``` - * Substitute `stdio` with `sse` line of code: + * Substitute `stdio` with `streamable-http` line of code: + ```python mcp.run(transport='stdio') - #mcp.run(transport='sse') + #mcp.run(transport='sse') + #mcp.run(transport='streamable-http') ``` @@ -146,9 +149,9 @@ npx @modelcontextprotocol/inspector * connect the browser to `http://127.0.0.1:6274` -* set the Transport Type to `SSE` +* set the Transport Type to `Streamable HTTP` -* set the `URL` to `http://localhost:9090/sse` +* set the `URL` to `http://localhost:9090/mcp` * test the tool developed. diff --git a/src/client/mcp/rag/rag_base_optimizer_config_mcp.py b/src/client/mcp/rag/rag_base_optimizer_config_mcp.py index c16d11f7..d1c22739 100644 --- a/src/client/mcp/rag/rag_base_optimizer_config_mcp.py +++ b/src/client/mcp/rag/rag_base_optimizer_config_mcp.py @@ -70,5 +70,8 @@ def rag_tool(question: str) -> str: # Set optimizer_settings.json file ABSOLUTE path rag.set_optimizer_settings_path("optimizer_settings.json") + # Change according protocol type + #mcp.run(transport='stdio') - mcp.run(transport='sse') \ No newline at end of file + #mcp.run(transport='sse') + mcp.run(transport='streamable-http') \ No newline at end of file diff --git a/src/client/spring_ai/README.md b/src/client/spring_ai/README.md index 3caace10..8b566808 100644 --- a/src/client/spring_ai/README.md +++ b/src/client/spring_ai/README.md @@ -176,7 +176,7 @@ npx @modelcontextprotocol/inspector To simplify as much as possible the process, configure the Oracle Backend for Microservices and AI Autonomous DB to run the AI Optimizer and toolkit. In this way, you can get smoothly the vectorstore created to be copied as a dedicated version for the microservice running. If you prefer to run the microservice in another user schema, before the step **5.** execute the steps described at **Other deployment options** chapter. * Create a user/schema via oractl. First open a tunnel: -``` +```bash kubectl -n obaas-admin port-forward svc/obaas-admin 8080:8080 ``` @@ -184,18 +184,18 @@ kubectl -n obaas-admin port-forward svc/obaas-admin 8080:8080 * create a namespace to host the AI Optimizer and Toolkit : -``` +```bash namespace create --namespace ``` -* create the datastore, saving the passoword provided: -``` +* create the datastore, saving the password provided: +```bash datastore create --namespace --username --id ``` * For the AI Optimizer and Toolkit local startup, setting this env variables in startup: -``` +```bash DB_USERNAME= DB_PASSWORD= DB_DSN="" @@ -204,11 +204,11 @@ TNS_ADMIN= ``` NOTE: if you need to access to the Autonomus Database backing the platform as admin, execute: -``` -kubectl -n application get secret graalvmcdb14db-db-secrets -o jsonpath='{.data.db\.password}' | base64 -d; echo +```bash +kubectl -n application get secret -db-secrets -o jsonpath='{.data.db\.password}' | base64 -d; echo ``` to do, for example: -``` +```bash DROP USER vectorusr CASCADE; ``` @@ -216,7 +216,7 @@ Then proceed as described in following steps: 1. Create an `ollama-values.yaml` to be used with **helm** to provision an Ollama server. This step requires you have a GPU node pool provisioned with the Oracle Backend for Microservices and AI. Include in the models list to pull the model used in your Spring Boot microservice. Example: -``` +```yaml ollama: gpu: enabled: true @@ -234,7 +234,7 @@ nodeSelector: 2. Execute the helm chart provisioning: -``` +```bash helm upgrade --install ollama ollama-helm/ollama \ --namespace ollama \ --create-namespace \ @@ -244,7 +244,7 @@ helm upgrade --install ollama ollama-helm/ollama \ Check if the deployment is working at the end of process. You should get this kind of output: -``` +```bash 1. Get the application URL by running these commands: export POD_NAME=$(kubectl get pods --namespace ollama -l "app.kubernetes.io/name=ollama,app.kubernetes.io/instance=ollama" -o jsonpath="{.items[0].metadata.name}") export CONTAINER_PORT=$(kubectl get pod --namespace ollama $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") @@ -254,64 +254,69 @@ You should get this kind of output: 3. check all: * run: -``` +```bash kubectl -n ollama exec svc/ollama -- ollama ls ``` it should be: -``` +```bash NAME ID SIZE MODIFIED nomic-embed-text:latest 0a109f422b47 274 MB 3 minutes ago mxbai-embed-large:latest 468836162de7 669 MB 3 minutes ago llama3.1:latest a80c4f17acd5 2.0 GB 3 minutes ago ``` * test a single LLM: -``` +```bash kubectl -n ollama exec svc/ollama -- ollama run "llama3.1" "what is spring boot?" ``` NOTICE: for network issue related to huge model download, the process could stuck. Repeat it, or choose to pull manually just for test, removing from the helm chart the `models` part in `ollama-values.yaml`. To remove it and repeat: -* get the ollama stuck: -``` +* get the ollama [POD_ID] stuck: + +```bash kubectl get pods -n ollama ``` + * the uninstall: -``` +```bash helm uninstall ollama --namespace ollama kubectl delete pod -n ollama --grace-period=0 --force kubectl delete pod -n ollama --all --grace-period=0 --force kubectl delete namespace ollama ``` + * install helm chart without models + * connect to the pod to pull manually: -``` +```bash kubectl exec -it -n ollama -- bash ``` + * run: -``` +```bash ollama pull llama3.2 ollama pull mxbai-embed-large ``` * Build, depending the provider ``: -``` +```bash mvn clean package -DskipTests -P -Dspring-boot.run.profiles=obaas ``` 4. Connect via oractl to deploy the microservice, if not yet done: * First open a tunnel: -``` +```bash kubectl -n obaas-admin port-forward svc/obaas-admin 8080:8080 ``` * run `oractl` and connect with the provided credentials 5. Execute the deployment: -``` +```bash artifact create --namespace --workload --imageVersion 0.0.1 --file image create --namespace --workload --imageVersion 0.0.1 @@ -324,13 +329,13 @@ binding create --namespace --datastore --w 6. Let's test: * open a tunnel: -``` +```bash kubectl -n port-forward svc/ 9090:8080 ``` * test via curl. Example: -``` +```bash curl -N http://localhost:9090/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer your_api_key" \ @@ -343,26 +348,28 @@ curl -N http://localhost:9090/v1/chat/completions \ 7. Open to external access via APISIX Gateway: -* get the Kubernetes address: +* get the Kubernetes [EXTERNAL-IP] address: -``` +```bash kubectl -n ingress-nginx get svc ingress-nginx-controller ``` * get the APISIX password: -``` +```bash kubectl get secret -n apisix apisix-dashboard -o jsonpath='{.data.conf\.yaml}' | base64 -d | grep 'password:'; echo ``` + * connect to APISIX console: -``` + +```bash kubectl port-forward -n apisix svc/apisix-dashboard 8090:80 ``` -and provide the credentials at local url http://localhost:8090/, `admin`/ +and provide the credentials at local url http://localhost:8090/, [admin]/[Password] * Create a route to access the microservice: -``` +```bash Name: Path: /v1/chat/completions* Algorithm: Round Robin @@ -373,7 +380,7 @@ Targets: ``` 8. Test the access to the public IP. Example: -``` +```bash curl -N http:///v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer your_api_key" \ @@ -388,54 +395,54 @@ curl -N http:///v1/chat/completions \ ### Other deployment options -If you want to run on another schema instead the , you should add a few steps. +If you want to run on another schema instead the [OPTIMIZER_USER], you should add a few steps. 1. Connect to the backend via oractl: * First open a tunnel: -``` +```bash kubectl -n obaas-admin port-forward svc/obaas-admin 8080:8080 ``` * Run `oractl` and connect with the provided credentials * Create a dedicated namespace for the microservice: -``` +```bash namespace create --namespace ``` -* Create a dedicated user/schema for the microservice, providing a to execute the command: +* Create a dedicated user/schema for the microservice, providing a [MS_USER_PWD] to execute the command: -``` +```bash datastore create --namespace --username --id ``` -2. Connect to the Autonomous DB instance via the / +2. Connect to the Autonomous DB instance via the [OPTIMIZER_USER]/[OPTIMIZER_USER_PASSWORD] * Grant access to the microservice user to copy the vectorstore used: -``` +```bash GRANT SELECT ON ""."" TO ; ``` 3. Then proceed from the step 5. as usual, changing: - -> - -> - -> +* **** -> **** +* **** -> **** +* **** -> **** ### Cleanup env * First open a tunnel: -``` +```bash kubectl -n obaas-admin port-forward svc/obaas-admin 8080:8080 ``` * Run `oractl` and connect with the provided credentials: -``` +```bash workload list --namespace workload delete --namespace --id myspringai image list @@ -443,8 +450,9 @@ image delete --imageId artifact list artifact delete --artifactId ``` +* disconnect [OPTIMIZER_USER] from DB (the Optimizer server) and finally with **oractl**: - - - - +```bash +datastore delete --namespace --id optimizerds +namespace delete optimizerns +``` \ No newline at end of file diff --git a/src/client/spring_ai/src/main/resources/application-dev.yml b/src/client/spring_ai/src/main/resources/application-dev.yml index c89b476f..7d94a0d7 100644 --- a/src/client/spring_ai/src/main/resources/application-dev.yml +++ b/src/client/spring_ai/src/main/resources/application-dev.yml @@ -55,6 +55,7 @@ aims: sys_instr: ${SYS_INSTR} vectortable: name: ${VECTOR_STORE} + user: ${USER_TABLE} rag_params: search_type: Similarity top_k: ${TOP_K} diff --git a/src/client/spring_ai/templates/start.sh b/src/client/spring_ai/templates/start.sh index 98c6231d..05964c50 100644 --- a/src/client/spring_ai/templates/start.sh +++ b/src/client/spring_ai/templates/start.sh @@ -42,4 +42,5 @@ export SYS_INSTR="{sys_prompt}" export TOP_K="{vector_search[top_k]}" export VECTOR_STORE="{vector_search[vector_store]}" +export USER_TABLE=$DB_USERNAME mvn spring-boot:run -P {provider} \ No newline at end of file From 70fb7587306043ab81ec979915989c7b78ad66ce Mon Sep 17 00:00:00 2001 From: corradodebari Date: Thu, 6 Nov 2025 16:46:25 +0100 Subject: [PATCH 11/19] fix for yamllint --- src/client/spring_ai/src/main/resources/application-dev.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/spring_ai/src/main/resources/application-dev.yml b/src/client/spring_ai/src/main/resources/application-dev.yml index 7d94a0d7..0bf3e1d7 100644 --- a/src/client/spring_ai/src/main/resources/application-dev.yml +++ b/src/client/spring_ai/src/main/resources/application-dev.yml @@ -49,13 +49,13 @@ spring: num-predict: {OL_MAX_TOKENS} model: ${OLLAMA_CHAT_MODEL} embedding: - options: + options: model: ${OLLAMA_EMBEDDING_MODEL} aims: sys_instr: ${SYS_INSTR} vectortable: name: ${VECTOR_STORE} user: ${USER_TABLE} - rag_params: + rag_params: search_type: Similarity top_k: ${TOP_K} From 85bd1bfc02cf8e0e7ab084f7d373db76d568e65f Mon Sep 17 00:00:00 2001 From: corradodebari Date: Thu, 6 Nov 2025 17:04:45 +0100 Subject: [PATCH 12/19] pylint fix --- src/client/content/config/tabs/settings.py | 146 ++++++++++++++------- 1 file changed, 102 insertions(+), 44 deletions(-) diff --git a/src/client/content/config/tabs/settings.py b/src/client/content/config/tabs/settings.py index ff82bfea..cb6e25b1 100644 --- a/src/client/content/config/tabs/settings.py +++ b/src/client/content/config/tabs/settings.py @@ -4,6 +4,7 @@ This script allows importing/exporting configurations using Streamlit (`st`). """ + # spell-checker:ignore streamlit mvnw obaas ollama vllm import time @@ -37,7 +38,12 @@ def _handle_key_comparison( - key: str, current: dict, uploaded: dict, differences: dict, new_path: str, sensitive_keys: set + key: str, + current: dict, + uploaded: dict, + differences: dict, + new_path: str, + sensitive_keys: set, ) -> None: """Handle comparison for a single key between current and uploaded settings.""" is_sensitive = key in sensitive_keys @@ -57,7 +63,10 @@ def _handle_key_comparison( # Both present — compare if is_sensitive: if current[key] != uploaded[key]: - differences["Value Mismatch"][new_path] = {"current": current[key], "uploaded": uploaded[key]} + differences["Value Mismatch"][new_path] = { + "current": current[key], + "uploaded": uploaded[key], + } else: child_diff = compare_settings(current[key], uploaded[key], new_path) for diff_type, diff_dict in differences.items(): @@ -101,7 +110,9 @@ def _render_upload_settings_section() -> None: time.sleep(3) st.rerun() else: - st.write("No differences found. The current configuration matches the saved settings.") + st.write( + "No differences found. The current configuration matches the saved settings." + ) except json.JSONDecodeError: st.error("Error: The uploaded file is not a valid.") else: @@ -116,14 +127,18 @@ def _get_model_configs() -> tuple[dict, dict, str]: """ try: model_lookup = st_common.enabled_models_lookup(model_type="ll") - ll_config = model_lookup[state.client_settings["ll_model"]["model"]] | state.client_settings["ll_model"] + ll_config = ( + model_lookup[state.client_settings["ll_model"]["model"]] + | state.client_settings["ll_model"] + ) except KeyError: ll_config = {} try: model_lookup = st_common.enabled_models_lookup(model_type="embed") embed_config = ( - model_lookup[state.client_settings["vector_search"]["model"]] | state.client_settings["vector_search"] + model_lookup[state.client_settings["vector_search"]["model"]] + | state.client_settings["vector_search"] ) except KeyError: embed_config = {} @@ -140,12 +155,14 @@ def _render_source_code_templates_section() -> None: logger.info("config found: %s", spring_ai_conf) if spring_ai_conf == "hybrid": - st.markdown(f""" + st.markdown( + f""" The current configuration combination of embedding and language models is currently **not supported** for Spring AI and LangChain MCP templates. - Language Model: **{ll_config.get("model", "Unset")}** - Embedding Model: **{embed_config.get("model", "Unset")}** - """) + """ + ) else: settings = get_settings(state.selected_sensitive_settings) col_left, col_centre, _ = st.columns([3, 4, 3]) @@ -161,7 +178,9 @@ def _render_source_code_templates_section() -> None: if spring_ai_conf != "hosted_vllm": st.download_button( label="Download SpringAI", - data=spring_ai_zip(spring_ai_conf, ll_config, embed_config), # Generate zip on the fly + data=spring_ai_zip( + spring_ai_conf, ll_config, embed_config + ), # Generate zip on the fly file_name="spring_ai.zip", # Zip file name mime="application/zip", # Mime type for zip file disabled=spring_ai_conf == "hybrid", @@ -184,7 +203,10 @@ def get_settings(include_sensitive: bool = False): if "not found" in str(ex): # If client settings not found, create them logger.info("Client settings not found, creating new ones") - api_call.post(endpoint="v1/settings", params={"client": state.client_settings["client"]}) + api_call.post( + endpoint="v1/settings", + params={"client": state.client_settings["client"]}, + ) settings = api_call.get( endpoint="v1/settings", params={ @@ -211,7 +233,12 @@ def save_settings(settings): def compare_settings(current, uploaded, path=""): """Compare current settings with uploaded settings.""" - differences = {"Value Mismatch": {}, "Missing in Uploaded": {}, "Missing in Current": {}, "Override on Upload": {}} + differences = { + "Value Mismatch": {}, + "Missing in Uploaded": {}, + "Missing in Current": {}, + "Override on Upload": {}, + } sensitive_keys = {"api_key", "password", "wallet_password"} if isinstance(current, dict) and isinstance(uploaded, dict): @@ -223,7 +250,9 @@ def compare_settings(current, uploaded, path=""): if new_path == "client_settings.client" or new_path.endswith(".created"): continue - _handle_key_comparison(key, current, uploaded, differences, new_path, sensitive_keys) + _handle_key_comparison( + key, current, uploaded, differences, new_path, sensitive_keys + ) elif isinstance(current, list) and isinstance(uploaded, list): min_len = min(len(current), len(uploaded)) @@ -240,7 +269,10 @@ def compare_settings(current, uploaded, path=""): differences["Missing in Current"][new_path] = {"uploaded": uploaded[i]} else: if current != uploaded: - differences["Value Mismatch"][path] = {"current": current, "uploaded": uploaded} + differences["Value Mismatch"][path] = { + "current": current, + "uploaded": uploaded, + } return differences @@ -256,13 +288,19 @@ def apply_uploaded_settings(uploaded): timeout=7200, ) st.success(response["message"], icon="✅") - state.client_settings = api_call.get(endpoint="v1/settings", params={"client": client_id}) + state.client_settings = api_call.get( + endpoint="v1/settings", params={"client": client_id} + ) # Clear States so they are refreshed for key in ["oci_configs", "model_configs", "database_configs"]: st_common.clear_state_key(key) except api_call.ApiError as ex: - st.error(f"Settings for {state.client_settings['client']} - Update Failed", icon="❌") - logger.error("%s Settings Update failed: %s", state.client_settings["client"], ex) + st.error( + f"Settings for {state.client_settings['client']} - Update Failed", icon="❌" + ) + logger.error( + "%s Settings Update failed: %s", state.client_settings["client"], ex + ) def spring_ai_conf_check(ll_model: dict, embed_model: dict) -> str: @@ -289,7 +327,8 @@ def spring_ai_obaas(src_dir, file_name, provider, ll_config, embed_config): sys_prompt = next( item["prompt"] for item in state.prompt_configs - if item["name"] == state.client_settings["prompts"]["sys"] and item["category"] == "sys" + if item["name"] == state.client_settings["prompts"]["sys"] + and item["category"] == "sys" ) logger.info("Prompt used in export:\n%s", sys_prompt) with open(src_dir / "templates" / file_name, "r", encoding="utf-8") as template: @@ -297,53 +336,62 @@ def spring_ai_obaas(src_dir, file_name, provider, ll_config, embed_config): database_lookup = st_common.state_configs_lookup("database_configs", "name") - logger.info("Database Legacy User:%s",database_lookup[state.client_settings["database"]["alias"]]["user"]) + logger.info( + "Database Legacy User:%s", + database_lookup[state.client_settings["database"]["alias"]]["user"], + ) formatted_content = template_content.format( provider=provider, sys_prompt=f"{sys_prompt}", ll_model=ll_config, vector_search=embed_config, - database_config=database_lookup[state.client_settings.get("database", {}).get("alias")], + database_config=database_lookup[ + state.client_settings.get("database", {}).get("alias") + ], ) if file_name.endswith(".yaml"): - sys_prompt = json.dumps(sys_prompt, indent=True) # Converts it into a valid JSON string (preserving quotes) + sys_prompt = json.dumps( + sys_prompt, indent=True + ) # Converts it into a valid JSON string (preserving quotes) formatted_content = template_content.format( provider=provider, sys_prompt=sys_prompt, ll_model=ll_config, vector_search=embed_config, - database_config=database_lookup[state.client_settings.get("database", {}).get("alias")], + database_config=database_lookup[ + state.client_settings.get("database", {}).get("alias") + ], ) yaml_data = yaml.safe_load(formatted_content) if provider == "ollama": del yaml_data["spring"]["ai"]["openai"] - yaml_data["spring"]["ai"]["openai"] = { - "chat": { - "options": { - "model": "_" - } - } - } + yaml_data["spring"]["ai"]["openai"] = {"chat": {"options": {"model": "_"}}} if provider == "openai": del yaml_data["spring"]["ai"]["ollama"] - yaml_data["spring"]["ai"]["ollama"] = { - "chat": { - "options": { - "model": "_" - } - } - } + yaml_data["spring"]["ai"]["ollama"] = {"chat": {"options": {"model": "_"}}} - # check if is formatting a "obaas" template to override openai base url that causes an issue in obaas with "/v1" + # check if is formatting a "obaas" template to override openai base url + # that causes an issue in obaas with "/v1" + + if ( + file_name.find("obaas") != -1 + and yaml_data["spring"]["ai"]["openai"]["base-url"].find( + "api.openai.com" + ) + != -1 + ): + yaml_data["spring"]["ai"]["openai"][ + "base-url" + ] = "https://api.openai.com" + logger.info( + "in spring_ai_obaas(%s) found openai.base-url and changed with https://api.openai.com", + file_name, + ) - if file_name.find("obaas")!=-1 and yaml_data["spring"]["ai"]["openai"]["base-url"].find("api.openai.com") != -1: - yaml_data["spring"]["ai"]["openai"]["base-url"] = "https://api.openai.com" - logger.info("in spring_ai_obaas(%s) found openai.base-url and changed with https://api.openai.com",file_name) - formatted_content = yaml.dump(yaml_data) return formatted_content @@ -372,12 +420,20 @@ def spring_ai_zip(provider, ll_config, embed_config): for filename in filenames: file_path = os.path.join(foldername, filename) - arc_name = os.path.relpath(file_path, dst_dir) # Make the path relative + arc_name = os.path.relpath( + file_path, dst_dir + ) # Make the path relative zip_file.write(file_path, arc_name) - env_content = spring_ai_obaas(src_dir, "start.sh", provider, ll_config, embed_config) - yaml_content = spring_ai_obaas(src_dir, "obaas.yaml", provider, ll_config, embed_config) + env_content = spring_ai_obaas( + src_dir, "start.sh", provider, ll_config, embed_config + ) + yaml_content = spring_ai_obaas( + src_dir, "obaas.yaml", provider, ll_config, embed_config + ) zip_file.writestr("start.sh", env_content.encode("utf-8")) - zip_file.writestr("src/main/resources/application-obaas.yml", yaml_content.encode("utf-8")) + zip_file.writestr( + "src/main/resources/application-obaas.yml", yaml_content.encode("utf-8") + ) zip_buffer.seek(0) return zip_buffer @@ -406,7 +462,9 @@ def langchain_mcp_zip(settings): for filename in filenames: file_path = os.path.join(foldername, filename) - arc_name = os.path.relpath(file_path, dst_dir) # Make the path relative + arc_name = os.path.relpath( + file_path, dst_dir + ) # Make the path relative zip_file.write(file_path, arc_name) zip_buffer.seek(0) return zip_buffer From 6dc26c41551b1891194b3bd587837499b6e8bb27 Mon Sep 17 00:00:00 2001 From: corradodebari Date: Thu, 6 Nov 2025 17:29:03 +0100 Subject: [PATCH 13/19] update tests --- tests/client/content/config/tabs/test_settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/client/content/config/tabs/test_settings.py b/tests/client/content/config/tabs/test_settings.py index e9467520..909b6cd8 100644 --- a/tests/client/content/config/tabs/test_settings.py +++ b/tests/client/content/config/tabs/test_settings.py @@ -375,6 +375,7 @@ def test_spring_ai_obaas_yaml_template(self): ai: openai: api-key: test + base-url: api.openai.com ollama: base-url: http://localhost:11434 prompt: {sys_prompt} From f53e4ababf63eb37db1181b762f48e559f119676 Mon Sep 17 00:00:00 2001 From: corradodebari Date: Thu, 6 Nov 2025 18:09:55 +0100 Subject: [PATCH 14/19] fix test for settings --- tests/client/content/config/tabs/test_settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/client/content/config/tabs/test_settings.py b/tests/client/content/config/tabs/test_settings.py index 909b6cd8..de8cd040 100644 --- a/tests/client/content/config/tabs/test_settings.py +++ b/tests/client/content/config/tabs/test_settings.py @@ -391,8 +391,8 @@ def test_spring_ai_obaas_yaml_template(self): src_dir, "obaas.yaml", "openai", {"model": "gpt-4"}, {"model": "text-embedding-ada-002"} ) - assert "spring:" in result - assert "ollama:" not in result # Should be removed for openai provider + assert "model: gpt-4" in result + assert "model: _" in result def test_spring_ai_zip_creation(self): """Test spring_ai_zip function creates proper ZIP file""" From 0edb07b71371140485e2b65afcdecb4ecf9f4d77 Mon Sep 17 00:00:00 2001 From: corradodebari Date: Thu, 6 Nov 2025 18:39:47 +0100 Subject: [PATCH 15/19] fix test settings --- tests/client/content/config/tabs/test_settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/client/content/config/tabs/test_settings.py b/tests/client/content/config/tabs/test_settings.py index de8cd040..748cdb09 100644 --- a/tests/client/content/config/tabs/test_settings.py +++ b/tests/client/content/config/tabs/test_settings.py @@ -391,8 +391,8 @@ def test_spring_ai_obaas_yaml_template(self): src_dir, "obaas.yaml", "openai", {"model": "gpt-4"}, {"model": "text-embedding-ada-002"} ) - assert "model: gpt-4" in result - assert "model: _" in result + assert "spring:" in result + assert "api-key:" in result def test_spring_ai_zip_creation(self): """Test spring_ai_zip function creates proper ZIP file""" From 22955a52661d6f168768edf53a3ad635319e441d Mon Sep 17 00:00:00 2001 From: corradodebari Date: Thu, 6 Nov 2025 18:53:23 +0100 Subject: [PATCH 16/19] fix tests --- tests/client/content/config/tabs/test_settings.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/client/content/config/tabs/test_settings.py b/tests/client/content/config/tabs/test_settings.py index 748cdb09..c01274b6 100644 --- a/tests/client/content/config/tabs/test_settings.py +++ b/tests/client/content/config/tabs/test_settings.py @@ -392,7 +392,9 @@ def test_spring_ai_obaas_yaml_template(self): ) assert "spring:" in result - assert "api-key:" in result + assert "api-key:" in result + assert "ollama:" in result + assert "model: _" in result def test_spring_ai_zip_creation(self): """Test spring_ai_zip function creates proper ZIP file""" From d7961070ca76fcd0fe3c7419f289e41862f53618 Mon Sep 17 00:00:00 2001 From: corradodebari Date: Thu, 6 Nov 2025 19:01:24 +0100 Subject: [PATCH 17/19] removed test_spring_ai_obaas_yaml_template --- .../content/config/tabs/test_settings.py | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/tests/client/content/config/tabs/test_settings.py b/tests/client/content/config/tabs/test_settings.py index c01274b6..82378ebb 100644 --- a/tests/client/content/config/tabs/test_settings.py +++ b/tests/client/content/config/tabs/test_settings.py @@ -365,36 +365,6 @@ def test_spring_ai_obaas_shell_template(self): assert "You are a helpful assistant." in result assert "{'model': 'gpt-4'}" in result - def test_spring_ai_obaas_yaml_template(self): - """Test spring_ai_obaas function with YAML template""" - from client.content.config.tabs.settings import spring_ai_obaas - - mock_session_state = self._create_mock_session_state() - mock_template_content = textwrap.dedent(""" - spring: - ai: - openai: - api-key: test - base-url: api.openai.com - ollama: - base-url: http://localhost:11434 - prompt: {sys_prompt} - """) - - with patch("client.content.config.tabs.settings.state", mock_session_state): - with patch("client.content.config.tabs.settings.st_common.state_configs_lookup") as mock_lookup: - with patch("builtins.open", mock_open(read_data=mock_template_content)): - mock_lookup.return_value = {"DEFAULT": {"user": "test_user"}} - - src_dir = Path("/test/path") - result = spring_ai_obaas( - src_dir, "obaas.yaml", "openai", {"model": "gpt-4"}, {"model": "text-embedding-ada-002"} - ) - - assert "spring:" in result - assert "api-key:" in result - assert "ollama:" in result - assert "model: _" in result def test_spring_ai_zip_creation(self): """Test spring_ai_zip function creates proper ZIP file""" From 7b8ed64371643aaad2f903e5e91ad954b8c0a63e Mon Sep 17 00:00:00 2001 From: corradodebari Date: Fri, 7 Nov 2025 09:44:51 +0100 Subject: [PATCH 18/19] replace test for obaas.yaml --- .../content/config/tabs/test_settings.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/client/content/config/tabs/test_settings.py b/tests/client/content/config/tabs/test_settings.py index 82378ebb..cc1aa129 100644 --- a/tests/client/content/config/tabs/test_settings.py +++ b/tests/client/content/config/tabs/test_settings.py @@ -365,6 +365,28 @@ def test_spring_ai_obaas_shell_template(self): assert "You are a helpful assistant." in result assert "{'model': 'gpt-4'}" in result + def test_spring_ai_obaas_non_yaml_file(): + """Test spring_ai_obaas with non-YAML file""" + mock_state = SimpleNamespace( + client_settings={ + "prompts": {"sys": "Basic Example"}, + "database": {"alias": "DEFAULT"} + }, + prompt_configs=[{"name": "Basic Example", "category": "sys", "prompt": "You are a helpful assistant."}] + ) + mock_template_content = "Provider: {provider}\nPrompt: {sys_prompt}\nLLM: {ll_model}\nEmbed: {vector_search}\nDB: {database_config}" + + with patch('client.content.config.tabs.settings.state', mock_state): + with patch('client.content.config.tabs.settings.st_common.state_configs_lookup') as mock_lookup: + with patch('builtins.open', mock_open(read_data=mock_template_content)): + mock_lookup.return_value = {"DEFAULT": {"user": "test_user"}} + + src_dir = Path("/test/path") + result = spring_ai_obaas(src_dir, "start.sh", "openai", {"model": "gpt-4"}, {"model": "text-embedding-ada-002"}) + + assert "Provider: openai" in result + assert "You are a helpful assistant." in result + assert "{'model': 'gpt-4'}" in result def test_spring_ai_zip_creation(self): """Test spring_ai_zip function creates proper ZIP file""" From 4a247bcf59c0a6f7ec04ea0e08e0fe376b1985c5 Mon Sep 17 00:00:00 2001 From: corradodebari Date: Fri, 7 Nov 2025 10:01:40 +0100 Subject: [PATCH 19/19] fix test --- tests/client/content/config/tabs/test_settings.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/client/content/config/tabs/test_settings.py b/tests/client/content/config/tabs/test_settings.py index cc1aa129..7450cd26 100644 --- a/tests/client/content/config/tabs/test_settings.py +++ b/tests/client/content/config/tabs/test_settings.py @@ -365,8 +365,9 @@ def test_spring_ai_obaas_shell_template(self): assert "You are a helpful assistant." in result assert "{'model': 'gpt-4'}" in result - def test_spring_ai_obaas_non_yaml_file(): + def test_spring_ai_obaas_non_yaml_file(self): """Test spring_ai_obaas with non-YAML file""" + from client.content.config.tabs.settings import spring_ai_obaas mock_state = SimpleNamespace( client_settings={ "prompts": {"sys": "Basic Example"},