Skip to content

Commit 77d48f3

Browse files
Merge pull request #327 from oracle/322-obaas-deploy
OBaaS deploy
2 parents 5ee5abd + 30db684 commit 77d48f3

File tree

11 files changed

+518
-139
lines changed

11 files changed

+518
-139
lines changed

src/client/content/config/tabs/settings.py

Lines changed: 105 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
55
This script allows importing/exporting configurations using Streamlit (`st`).
66
"""
7+
78
# spell-checker:ignore streamlit mvnw obaas ollama vllm
89

910
import time
@@ -37,7 +38,12 @@
3738

3839

3940
def _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

212234
def 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

268306
def 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

src/client/mcp/rag/README.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ If you have already installed Node.js v20.17.0+, it should work.
6262
"command": "npx",
6363
"args": [
6464
"mcp-remote",
65-
"http://127.0.0.1:9090/sse"
65+
"http://127.0.0.1:9090/mcp"
6666
]
6767
}
6868
}
@@ -120,14 +120,17 @@ uv run rag_base_optimizer_config_mcp.py
120120
* Set `Local` with `Remote client` line in `<PROJECT_DIR>/rag_base_optimizer_config_mcp.py`:
121121

122122
```python
123-
#mcp = FastMCP("rag", port=9090) #Remote client
124-
mcp = FastMCP("rag") #Local
123+
#mcp.run(transport='stdio')
124+
#mcp.run(transport='sse')
125+
mcp.run(transport='streamable-http')
125126
```
126127

127-
* Substitute `stdio` with `sse` line of code:
128+
* Substitute `stdio` with `streamable-http` line of code:
129+
128130
```python
129131
mcp.run(transport='stdio')
130-
#mcp.run(transport='sse')
132+
#mcp.run(transport='sse')
133+
#mcp.run(transport='streamable-http')
131134
```
132135

133136

@@ -146,9 +149,9 @@ npx @modelcontextprotocol/inspector
146149

147150
* connect the browser to `http://127.0.0.1:6274`
148151

149-
* set the Transport Type to `SSE`
152+
* set the Transport Type to `Streamable HTTP`
150153

151-
* set the `URL` to `http://localhost:9090/sse`
154+
* set the `URL` to `http://localhost:9090/mcp`
152155

153156
* test the tool developed.
154157

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
"""
2+
Copyright (c) 2024, 2025, Oracle and/or its affiliates.
3+
Licensed under the Universal Permissive License v1.0 as shown at http://oss.oracle.com/licenses/upl.
4+
"""
5+
from typing import List
6+
from mcp.server.fastmcp import FastMCP
7+
import os
8+
from dotenv import load_dotenv
9+
#from sentence_transformers import CrossEncoder
10+
#from langchain_community.embeddings import HuggingFaceEmbeddings
11+
from langchain_core.prompts import PromptTemplate
12+
from langchain_core.runnables import RunnablePassthrough
13+
from langchain_core.output_parsers import StrOutputParser
14+
import json
15+
import logging
16+
logger = logging.getLogger(__name__)
17+
18+
logging.basicConfig(
19+
level=logging.INFO,
20+
format="%(name)s - %(levelname)s - %(message)s"
21+
)
22+
23+
from optimizer_utils import rag
24+
25+
26+
logging.info("Successfully imported libraries and modules")
27+
28+
CHUNKS_DIR = "chunks_temp"
29+
data = {}
30+
31+
# Initialize FastMCP server
32+
mcp = FastMCP("rag", port=9090) #Remote client
33+
#mcp = FastMCP("rag") #Local
34+
35+
36+
@mcp.tool()
37+
def rag_tool(question: str) -> str:
38+
"""
39+
Use this tool to answer any question that may benefit from up-to-date or domain-specific information.
40+
41+
Args:
42+
question: the question for which are you looking for an answer
43+
44+
Returns:
45+
JSON string with answer
46+
"""
47+
48+
answer = rag.rag_tool_base(question)
49+
50+
return f"{answer}"
51+
52+
if __name__ == "__main__":
53+
54+
# To dinamically change Tool description: not used but in future maybe
55+
rag_tool_desc=[
56+
f"""
57+
Use this tool to answer any question that may benefit from up-to-date or domain-specific information.
58+
59+
Args:
60+
question: the question for which are you looking for an answer
61+
62+
Returns:
63+
JSON string with answer
64+
"""
65+
]
66+
67+
68+
# Initialize and run the server
69+
70+
# Set optimizer_settings.json file ABSOLUTE path
71+
rag.set_optimizer_settings_path("optimizer_settings.json")
72+
73+
# Change according protocol type
74+
75+
#mcp.run(transport='stdio')
76+
#mcp.run(transport='sse')
77+
mcp.run(transport='streamable-http')

0 commit comments

Comments
 (0)