44
55This script allows importing/exporting configurations using Streamlit (`st`).
66"""
7+
78# spell-checker:ignore streamlit mvnw obaas ollama vllm
89
910import time
3738
3839
3940def _handle_key_comparison (
40- key : str , current : dict , uploaded : dict , differences : dict , new_path : str , sensitive_keys : set
41+ key : str ,
42+ current : dict ,
43+ uploaded : dict ,
44+ differences : dict ,
45+ new_path : str ,
46+ sensitive_keys : set ,
4147) -> None :
4248 """Handle comparison for a single key between current and uploaded settings."""
4349 is_sensitive = key in sensitive_keys
@@ -57,7 +63,10 @@ def _handle_key_comparison(
5763 # Both present — compare
5864 if is_sensitive :
5965 if current [key ] != uploaded [key ]:
60- differences ["Value Mismatch" ][new_path ] = {"current" : current [key ], "uploaded" : uploaded [key ]}
66+ differences ["Value Mismatch" ][new_path ] = {
67+ "current" : current [key ],
68+ "uploaded" : uploaded [key ],
69+ }
6170 else :
6271 child_diff = compare_settings (current [key ], uploaded [key ], new_path )
6372 for diff_type , diff_dict in differences .items ():
@@ -101,7 +110,9 @@ def _render_upload_settings_section() -> None:
101110 time .sleep (3 )
102111 st .rerun ()
103112 else :
104- st .write ("No differences found. The current configuration matches the saved settings." )
113+ st .write (
114+ "No differences found. The current configuration matches the saved settings."
115+ )
105116 except json .JSONDecodeError :
106117 st .error ("Error: The uploaded file is not a valid." )
107118 else :
@@ -116,14 +127,18 @@ def _get_model_configs() -> tuple[dict, dict, str]:
116127 """
117128 try :
118129 model_lookup = st_common .enabled_models_lookup (model_type = "ll" )
119- ll_config = model_lookup [state .client_settings ["ll_model" ]["model" ]] | state .client_settings ["ll_model" ]
130+ ll_config = (
131+ model_lookup [state .client_settings ["ll_model" ]["model" ]]
132+ | state .client_settings ["ll_model" ]
133+ )
120134 except KeyError :
121135 ll_config = {}
122136
123137 try :
124138 model_lookup = st_common .enabled_models_lookup (model_type = "embed" )
125139 embed_config = (
126- model_lookup [state .client_settings ["vector_search" ]["model" ]] | state .client_settings ["vector_search" ]
140+ model_lookup [state .client_settings ["vector_search" ]["model" ]]
141+ | state .client_settings ["vector_search" ]
127142 )
128143 except KeyError :
129144 embed_config = {}
@@ -140,12 +155,14 @@ def _render_source_code_templates_section() -> None:
140155 logger .info ("config found: %s" , spring_ai_conf )
141156
142157 if spring_ai_conf == "hybrid" :
143- st .markdown (f"""
158+ st .markdown (
159+ f"""
144160 The current configuration combination of embedding and language models
145161 is currently **not supported** for Spring AI and LangChain MCP templates.
146162 - Language Model: **{ ll_config .get ("model" , "Unset" )} **
147163 - Embedding Model: **{ embed_config .get ("model" , "Unset" )} **
148- """ )
164+ """
165+ )
149166 else :
150167 settings = get_settings (state .selected_sensitive_settings )
151168 col_left , col_centre , _ = st .columns ([3 , 4 , 3 ])
@@ -161,7 +178,9 @@ def _render_source_code_templates_section() -> None:
161178 if spring_ai_conf != "hosted_vllm" :
162179 st .download_button (
163180 label = "Download SpringAI" ,
164- data = spring_ai_zip (spring_ai_conf , ll_config , embed_config ), # Generate zip on the fly
181+ data = spring_ai_zip (
182+ spring_ai_conf , ll_config , embed_config
183+ ), # Generate zip on the fly
165184 file_name = "spring_ai.zip" , # Zip file name
166185 mime = "application/zip" , # Mime type for zip file
167186 disabled = spring_ai_conf == "hybrid" ,
@@ -184,7 +203,10 @@ def get_settings(include_sensitive: bool = False):
184203 if "not found" in str (ex ):
185204 # If client settings not found, create them
186205 logger .info ("Client settings not found, creating new ones" )
187- api_call .post (endpoint = "v1/settings" , params = {"client" : state .client_settings ["client" ]})
206+ api_call .post (
207+ endpoint = "v1/settings" ,
208+ params = {"client" : state .client_settings ["client" ]},
209+ )
188210 settings = api_call .get (
189211 endpoint = "v1/settings" ,
190212 params = {
@@ -211,7 +233,12 @@ def save_settings(settings):
211233
212234def compare_settings (current , uploaded , path = "" ):
213235 """Compare current settings with uploaded settings."""
214- differences = {"Value Mismatch" : {}, "Missing in Uploaded" : {}, "Missing in Current" : {}, "Override on Upload" : {}}
236+ differences = {
237+ "Value Mismatch" : {},
238+ "Missing in Uploaded" : {},
239+ "Missing in Current" : {},
240+ "Override on Upload" : {},
241+ }
215242 sensitive_keys = {"api_key" , "password" , "wallet_password" }
216243
217244 if isinstance (current , dict ) and isinstance (uploaded , dict ):
@@ -223,7 +250,9 @@ def compare_settings(current, uploaded, path=""):
223250 if new_path == "client_settings.client" or new_path .endswith (".created" ):
224251 continue
225252
226- _handle_key_comparison (key , current , uploaded , differences , new_path , sensitive_keys )
253+ _handle_key_comparison (
254+ key , current , uploaded , differences , new_path , sensitive_keys
255+ )
227256
228257 elif isinstance (current , list ) and isinstance (uploaded , list ):
229258 min_len = min (len (current ), len (uploaded ))
@@ -240,7 +269,10 @@ def compare_settings(current, uploaded, path=""):
240269 differences ["Missing in Current" ][new_path ] = {"uploaded" : uploaded [i ]}
241270 else :
242271 if current != uploaded :
243- differences ["Value Mismatch" ][path ] = {"current" : current , "uploaded" : uploaded }
272+ differences ["Value Mismatch" ][path ] = {
273+ "current" : current ,
274+ "uploaded" : uploaded ,
275+ }
244276
245277 return differences
246278
@@ -256,13 +288,19 @@ def apply_uploaded_settings(uploaded):
256288 timeout = 7200 ,
257289 )
258290 st .success (response ["message" ], icon = "✅" )
259- state .client_settings = api_call .get (endpoint = "v1/settings" , params = {"client" : client_id })
291+ state .client_settings = api_call .get (
292+ endpoint = "v1/settings" , params = {"client" : client_id }
293+ )
260294 # Clear States so they are refreshed
261295 for key in ["oci_configs" , "model_configs" , "database_configs" ]:
262296 st_common .clear_state_key (key )
263297 except api_call .ApiError as ex :
264- st .error (f"Settings for { state .client_settings ['client' ]} - Update Failed" , icon = "❌" )
265- logger .error ("%s Settings Update failed: %s" , state .client_settings ["client" ], ex )
298+ st .error (
299+ f"Settings for { state .client_settings ['client' ]} - Update Failed" , icon = "❌"
300+ )
301+ logger .error (
302+ "%s Settings Update failed: %s" , state .client_settings ["client" ], ex
303+ )
266304
267305
268306def spring_ai_conf_check (ll_model : dict , embed_model : dict ) -> str :
@@ -289,38 +327,71 @@ def spring_ai_obaas(src_dir, file_name, provider, ll_config, embed_config):
289327 sys_prompt = next (
290328 item ["prompt" ]
291329 for item in state .prompt_configs
292- if item ["name" ] == state .client_settings ["prompts" ]["sys" ] and item ["category" ] == "sys"
330+ if item ["name" ] == state .client_settings ["prompts" ]["sys" ]
331+ and item ["category" ] == "sys"
293332 )
294333 logger .info ("Prompt used in export:\n %s" , sys_prompt )
295334 with open (src_dir / "templates" / file_name , "r" , encoding = "utf-8" ) as template :
296335 template_content = template .read ()
297336
298337 database_lookup = st_common .state_configs_lookup ("database_configs" , "name" )
299338
339+ logger .info (
340+ "Database Legacy User:%s" ,
341+ database_lookup [state .client_settings ["database" ]["alias" ]]["user" ],
342+ )
343+
300344 formatted_content = template_content .format (
301345 provider = provider ,
302346 sys_prompt = f"{ sys_prompt } " ,
303347 ll_model = ll_config ,
304348 vector_search = embed_config ,
305- database_config = database_lookup [state .client_settings .get ("database" , {}).get ("alias" )],
349+ database_config = database_lookup [
350+ state .client_settings .get ("database" , {}).get ("alias" )
351+ ],
306352 )
307353
308354 if file_name .endswith (".yaml" ):
309- sys_prompt = json .dumps (sys_prompt , indent = True ) # Converts it into a valid JSON string (preserving quotes)
355+ sys_prompt = json .dumps (
356+ sys_prompt , indent = True
357+ ) # Converts it into a valid JSON string (preserving quotes)
310358
311359 formatted_content = template_content .format (
312360 provider = provider ,
313361 sys_prompt = sys_prompt ,
314362 ll_model = ll_config ,
315363 vector_search = embed_config ,
316- database_config = database_lookup [state .client_settings .get ("database" , {}).get ("alias" )],
364+ database_config = database_lookup [
365+ state .client_settings .get ("database" , {}).get ("alias" )
366+ ],
317367 )
318368
319369 yaml_data = yaml .safe_load (formatted_content )
320370 if provider == "ollama" :
321371 del yaml_data ["spring" ]["ai" ]["openai" ]
372+ yaml_data ["spring" ]["ai" ]["openai" ] = {"chat" : {"options" : {"model" : "_" }}}
322373 if provider == "openai" :
323374 del yaml_data ["spring" ]["ai" ]["ollama" ]
375+ yaml_data ["spring" ]["ai" ]["ollama" ] = {"chat" : {"options" : {"model" : "_" }}}
376+
377+ # check if is formatting a "obaas" template to override openai base url
378+ # that causes an issue in obaas with "/v1"
379+
380+ if (
381+ file_name .find ("obaas" ) != - 1
382+ and yaml_data ["spring" ]["ai" ]["openai" ]["base-url" ].find (
383+ "api.openai.com"
384+ )
385+ != - 1
386+ ):
387+ yaml_data ["spring" ]["ai" ]["openai" ][
388+ "base-url"
389+ ] = "https://api.openai.com"
390+ logger .info (
391+ "in spring_ai_obaas(%s) found openai.base-url and changed with https://api.openai.com" ,
392+ file_name ,
393+ )
394+
324395 formatted_content = yaml .dump (yaml_data )
325396
326397 return formatted_content
@@ -349,12 +420,20 @@ def spring_ai_zip(provider, ll_config, embed_config):
349420 for filename in filenames :
350421 file_path = os .path .join (foldername , filename )
351422
352- arc_name = os .path .relpath (file_path , dst_dir ) # Make the path relative
423+ arc_name = os .path .relpath (
424+ file_path , dst_dir
425+ ) # Make the path relative
353426 zip_file .write (file_path , arc_name )
354- env_content = spring_ai_obaas (src_dir , "start.sh" , provider , ll_config , embed_config )
355- yaml_content = spring_ai_obaas (src_dir , "obaas.yaml" , provider , ll_config , embed_config )
427+ env_content = spring_ai_obaas (
428+ src_dir , "start.sh" , provider , ll_config , embed_config
429+ )
430+ yaml_content = spring_ai_obaas (
431+ src_dir , "obaas.yaml" , provider , ll_config , embed_config
432+ )
356433 zip_file .writestr ("start.sh" , env_content .encode ("utf-8" ))
357- zip_file .writestr ("src/main/resources/application-obaas.yml" , yaml_content .encode ("utf-8" ))
434+ zip_file .writestr (
435+ "src/main/resources/application-obaas.yml" , yaml_content .encode ("utf-8" )
436+ )
358437 zip_buffer .seek (0 )
359438 return zip_buffer
360439
@@ -383,7 +462,9 @@ def langchain_mcp_zip(settings):
383462 for filename in filenames :
384463 file_path = os .path .join (foldername , filename )
385464
386- arc_name = os .path .relpath (file_path , dst_dir ) # Make the path relative
465+ arc_name = os .path .relpath (
466+ file_path , dst_dir
467+ ) # Make the path relative
387468 zip_file .write (file_path , arc_name )
388469 zip_buffer .seek (0 )
389470 return zip_buffer
0 commit comments