Skip to content

Commit 0ecaec7

Browse files
authored
OCI GenAI - Auto Model Loading (#238)
* Bump Python Modules * Dynamic OCI GenAI Model * Boostrap GenAI Models * OCI_GENAI_SERVICE_ENDPOINT replaced by OCI_GENAI_REGION
1 parent 91f7c77 commit 0ecaec7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+915
-643
lines changed

.github/workflows/pytest.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ jobs:
5050
run: |
5151
cd src/
5252
python -m pip install --upgrade pip wheel setuptools
53-
pip install torch==2.7.1+cpu -f https://download.pytorch.org/whl/cpu/torch
53+
pip install torch==2.8.0+cpu -f https://download.pytorch.org/whl/cpu/torch
5454
pip install -e ".[all-test]"
5555
5656
- name: Run All Tests

docs/content/client/configuration/model_config.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,14 @@ To use OCI GenAI, the {{< short_app_ref >}} must be configured for [OCI access](
8989
>[!code]Skip the GUI!
9090
>You can set the following environment variables to automatically enable OCI GenAI models:
9191
>```shell
92-
>export OCI_GENAI_SERVICE_ENDPOINT=<OCI GenAI Service Endpoint>
92+
>export OCI_GENAI_REGION=<OCI GenAI Service Region>
9393
>export OCI_GENAI_COMPARTMENT_ID=<OCI Compartment OCID of the OCI GenAI Service>
9494
>```
9595
>
9696
>Alternatively, you can specify the following in the `~/.oci/config` configfile under the appropriate OCI profile:
9797
>```shell
98-
>service_endpoint=<OCI GenAI Service Endpoint>
99-
>compartment_id=<OCI Compartment OCID of the OCI GenAI Service>
98+
>genai_compartment_id=<OCI Compartment OCID of the OCI GenAI Service>
99+
>genai_region=<OCI GenAI Region>
100100
>```
101101
102102
{{% /tab %}}

docs/content/client/configuration/oci_config.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ Depending on the runtime environment, either [Bare Metal](#bare-metal) or [Conta
4242

4343
In addition to the standard configuration file, two additional entries are required to enable OCI GenAI Services:
4444

45-
- **service_endpoint**: the URL endpoint for the OCI GenAI Service
46-
- **compartment_id**: the compartment OCID of the OCI GenAI Service
45+
- **genai_region**: the Region for the OCI GenAI Service
46+
- **genai_compartment_id**: the Compartment OCID of the OCI GenAI Service
4747

4848
#### Bare Metal
4949

opentofu/modules/vm/templates/cloudinit-compute.tpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ write_files:
6565
python3.11 -m venv .venv
6666
source .venv/bin/activate
6767
pip3.11 install --upgrade pip wheel setuptools
68-
pip3.11 install torch==2.7.1+cpu -f https://download.pytorch.org/whl/cpu/torch
68+
pip3.11 install torch==2.8.0+cpu -f https://download.pytorch.org/whl/cpu/torch
6969
pip3.11 install -e ".[all]" --quiet --no-input &
7070
INSTALL_PID=$!
7171

src/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ RUN for dir in server client common; do \
2424
COPY pyproject.toml /opt/package/pyproject.toml
2525
# Use the virtual environment for pip installations
2626
RUN /opt/.venv/bin/pip install --upgrade pip wheel setuptools && \
27-
/opt/.venv/bin/pip install torch==2.7.1+cpu -f https://download.pytorch.org/whl/cpu/torch && \
27+
/opt/.venv/bin/pip install torch==2.8.0+cpu -f https://download.pytorch.org/whl/cpu/torch && \
2828
/opt/.venv/bin/pip install --no-cache-dir "/opt/package[all]"
2929

3030
##################################################

src/client/content/config/models.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ def edit_model(model_type: str, action: Literal["add", "edit"], model_id: str =
108108
)
109109
api_values = get_model_apis(model_type)
110110
api_index = next((i for i, item in enumerate(api_values) if item == model["api"]), None)
111+
disable_for_oci = model["api"] in ["ChatOCIGenAI", "OCIGenAIEmbeddings"]
111112
model["api"] = st.selectbox(
112113
"API (Required):",
113114
help=help_text.help_dict["model_api"],
@@ -122,13 +123,15 @@ def edit_model(model_type: str, action: Literal["add", "edit"], model_id: str =
122123
help=help_text.help_dict["model_api_url"],
123124
key="add_model_api_url",
124125
value=model.get("url", ""),
126+
disabled=disable_for_oci
125127
)
126128
model["api_key"] = st.text_input(
127129
"API Key:",
128130
help=help_text.help_dict["model_api_key"],
129131
key="add_model_api_key",
130132
type="password",
131133
value=model.get("api_key", ""),
134+
disabled=disable_for_oci
132135
)
133136
if model_type == "ll":
134137
model["context_length"] = st.number_input(
@@ -201,7 +204,7 @@ def edit_model(model_type: str, action: Literal["add", "edit"], model_id: str =
201204

202205
def render_model_rows(model_type):
203206
"""Render rows of the models"""
204-
data_col_widths = [0.05, 0.25, 0.2, 0.28, 0.12]
207+
data_col_widths = [0.07, 0.23, 0.2, 0.28, 0.12]
205208
table_col_format = st.columns(data_col_widths, vertical_alignment="center")
206209
col1, col2, col3, col4, col5 = table_col_format
207210
col1.markdown("&#x200B;", help="Active", unsafe_allow_html=True)
@@ -213,7 +216,7 @@ def render_model_rows(model_type):
213216
model_id = model["id"]
214217
col1.text_input(
215218
"Enabled",
216-
value="✅" if model["enabled"] else "⚪",
219+
value=st_common.bool_to_emoji(model["enabled"]),
217220
key=f"{model_type}_{model_id}_enabled",
218221
label_visibility="collapsed",
219222
disabled=True,

src/client/content/config/oci.py

Lines changed: 61 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
# spell-checker:ignore streamlit, ocid, selectbox, genai, oraclecloud
99

1010
import inspect
11-
import re
11+
import pandas as pd
1212

1313
import streamlit as st
1414
from streamlit import session_state as state
@@ -36,7 +36,21 @@ def get_oci(force: bool = False) -> None:
3636
state.oci_configs = {}
3737

3838

39-
def patch_oci(auth_profile: str, supplied: dict, namespace: str) -> bool:
39+
def get_genai_models() -> list[dict]:
40+
"""Get Subscribed OCI Regions"""
41+
endpoint = f"v1/oci/genai/{state.client_settings['oci']['auth_profile']}"
42+
genai_models = api_call.get(endpoint=endpoint)
43+
return genai_models
44+
45+
46+
def create_genai_models() -> list[dict]:
47+
"""Create OCI GenAI Models"""
48+
endpoint = f"v1/oci/genai/{state.client_settings['oci']['auth_profile']}"
49+
genai_models = api_call.post(endpoint=endpoint)
50+
return genai_models
51+
52+
53+
def patch_oci(auth_profile: str, supplied: dict, namespace: str, toast: bool = True) -> bool:
4054
"""Update OCI"""
4155
rerun = False
4256
# Check if the OIC configuration is changed, or no namespace
@@ -49,19 +63,15 @@ def patch_oci(auth_profile: str, supplied: dict, namespace: str) -> bool:
4963
supplied["authentication"] = "security_token"
5064

5165
with st.spinner(text="Updating OCI Profile...", show_time=True):
52-
_ = api_call.patch(
53-
endpoint=f"v1/oci/{auth_profile}",
54-
payload={"json": supplied},
55-
)
66+
_ = api_call.patch(endpoint=f"v1/oci/{auth_profile}", payload={"json": supplied}, toast=toast)
5667
logger.info("OCI Profile updated: %s", auth_profile)
5768
except api_call.ApiError as ex:
5869
logger.error("OCI Update failed: %s", ex)
5970
state.oci_error = ex
6071
st_common.clear_state_key("oci_configs")
61-
if supplied.get("service_endpoint"):
62-
st_common.clear_state_key("model_configs")
6372
else:
64-
st.toast("No Changes Detected.", icon="ℹ️")
73+
if toast:
74+
st.toast("No Changes Detected.", icon="ℹ️")
6575

6676
return rerun
6777

@@ -155,31 +165,54 @@ def main() -> None:
155165
OCI Authentication must be configured above.
156166
""")
157167
with st.container(border=True):
158-
supplied["compartment_id"] = st.text_input(
168+
if "genai_models" not in state:
169+
state.genai_models = []
170+
supplied["genai_compartment_id"] = st.text_input(
159171
"OCI GenAI Compartment OCID:",
160-
value=oci_lookup[selected_oci_auth_profile]["compartment_id"],
172+
value=oci_lookup[selected_oci_auth_profile]["genai_compartment_id"],
161173
placeholder="Compartment OCID for GenAI Services",
162174
key="oci_genai_compartment_id",
163175
disabled=not namespace,
164176
)
165-
match = re.search(
166-
r"\.([a-zA-Z\-0-9]+)\.oci\.oraclecloud\.com", oci_lookup[selected_oci_auth_profile]["service_endpoint"]
167-
)
168-
supplied["service_endpoint"] = match.group(1) if match else None
169-
supplied["service_endpoint"] = st.text_input(
170-
"OCI GenAI Region:",
171-
value=oci_lookup[selected_oci_auth_profile]["service_endpoint"],
172-
help="Region of GenAI Service",
173-
key="oci_genai_region",
174-
disabled=not namespace,
175-
)
176-
177-
if st.button("Save OCI GenAI", key="save_oci_genai", disabled=not namespace):
178-
if not (supplied["compartment_id"] and supplied["service_endpoint"]):
179-
st.error("All fields are required.", icon="🛑")
177+
if st.button("Check for OCI GenAI Models", key="check_oci_genai", disabled=not namespace):
178+
if not supplied["genai_compartment_id"]:
179+
st.error("OCI GenAI Compartment OCID is required.", icon="🛑")
180180
st.stop()
181-
if patch_oci(selected_oci_auth_profile, supplied, namespace):
182-
st.rerun()
181+
with st.spinner("Looking for OCI GenAI Models... please be patient.", show_time=True):
182+
patch_oci(selected_oci_auth_profile, supplied, namespace, toast=False)
183+
state.genai_models = get_genai_models()
184+
if state.genai_models:
185+
regions = list({item["region"] for item in state.genai_models if "region" in item})
186+
supplied["genai_region"] = st.selectbox(
187+
"Region:",
188+
regions,
189+
key="selected_genai_region",
190+
)
191+
filtered_models = [
192+
m
193+
for m in state.genai_models
194+
if m["region"] == supplied["genai_region"]
195+
and ("CHAT" in m["capabilities"] or "TEXT_EMBEDDINGS" in m["capabilities"])
196+
]
197+
table_data = []
198+
for m in filtered_models:
199+
table_data.append(
200+
{
201+
"Model Name": m["model_name"],
202+
"Large Language": st_common.bool_to_emoji("CHAT" in m["capabilities"]),
203+
"Embedding": st_common.bool_to_emoji("TEXT_EMBEDDINGS" in m["capabilities"]),
204+
}
205+
)
206+
207+
# Convert to DataFrame and display
208+
df = pd.DataFrame(table_data)
209+
st.dataframe(df, hide_index=True)
210+
if st.button("Enable Region Models", key="enable_oci_region_models", type="primary"):
211+
with st.spinner("Enabling OCI GenAI Models... please be patient.", show_time=True):
212+
patch_oci(selected_oci_auth_profile, supplied, namespace, toast=False)
213+
_ = create_genai_models()
214+
st_common.clear_state_key("model_configs")
215+
st.success("Oracle GenAI models - Enabled.", icon="✅")
183216

184217

185218
if __name__ == "__main__" or "page.py" in inspect.stack()[1].filename:

src/client/utils/st_common.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ def enabled_models_lookup(model_type: str) -> dict[str, dict[str, Any]]:
5252
#############################################################################
5353
# Common Helpers
5454
#############################################################################
55+
def bool_to_emoji(value):
56+
"Return an Emoji for Bools"
57+
return "✅" if value else "⚪"
5558
def local_file_payload(uploaded_files: Union[BytesIO, list[BytesIO]]) -> list:
5659
"""Upload Single file from Streamlit to the Server"""
5760
# If it's a single file, convert it to a list for consistent processing

src/common/schema.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Copyright (c) 2024, 2025, Oracle and/or its affiliates.
33
Licensed under the Universal Permissive License v1.0 as shown at http://oss.oracle.com/licenses/upl.
44
"""
5-
# spell-checker:ignore ollama, hnsw, mult, ocid, testset, selectai, explainsql, showsql, vector_search, aioptimizer
5+
# spell-checker:ignore ollama hnsw mult ocid testset selectai explainsql showsql vector_search aioptimizer genai
66

77
import time
88
from typing import Optional, Literal, Union, get_args, Any
@@ -180,11 +180,21 @@ class OracleCloudSettings(BaseModel):
180180

181181
auth_profile: str = Field(default="DEFAULT", description="Config File Profile")
182182
namespace: Optional[str] = Field(default=None, description="Object Store Namespace", readOnly=True)
183-
user: Optional[str] = Field(default=None, description="Optional if using Auth Token")
183+
user: Optional[str] = Field(
184+
default=None,
185+
description="Optional if using Auth Token",
186+
pattern=r"^([0-9a-zA-Z-_]+[.:])([0-9a-zA-Z-_]*[.:]){3,}([0-9a-zA-Z-_]+)$",
187+
)
184188
security_token_file: Optional[str] = Field(default=None, description="Security Key File for Auth Token")
185189
authentication: Literal["api_key", "instance_principal", "oke_workload_identity", "security_token"] = Field(
186190
default="api_key", description="Authentication Method."
187191
)
192+
genai_compartment_id: Optional[str] = Field(
193+
default=None,
194+
description="Optional Compartment OCID of OCI GenAI services",
195+
pattern=r"^([0-9a-zA-Z-_]+[.:])([0-9a-zA-Z-_]*[.:]){3,}([0-9a-zA-Z-_]+)$",
196+
)
197+
genai_region: Optional[str] = Field(default=None, description="Optional Region OCID of OCI GenAI services")
188198

189199
model_config = {
190200
"extra": "allow" # enable extra fields
@@ -471,6 +481,7 @@ class EvaluationReport(Evaluation):
471481
ModelTypeType = Model.__annotations__["type"]
472482
ModelEnabledType = ModelAccess.__annotations__["enabled"]
473483
OCIProfileType = OracleCloudSettings.__annotations__["auth_profile"]
484+
OCIResourceOCID = OracleResource.__annotations__["ocid"]
474485
PromptNameType = Prompt.__annotations__["name"]
475486
PromptCategoryType = Prompt.__annotations__["category"]
476487
PromptPromptType = PromptText.__annotations__["prompt"]

src/pyproject.toml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ authors = [
1414

1515
# Common dependencies that are always needed
1616
dependencies = [
17-
"langchain-core==0.3.72",
17+
"langchain-core==0.3.74",
1818
"httpx==0.28.1",
1919
"oracledb~=3.1",
2020
"plotly==6.2.0",
@@ -32,16 +32,16 @@ server = [
3232
"langchain-community==0.3.27",
3333
"langchain-huggingface==0.3.1",
3434
"langchain-ollama==0.3.6",
35-
"langchain-openai==0.3.28",
36-
"langgraph==0.6.3",
37-
"litellm==1.75.0",
38-
"llama-index==0.13.0",
35+
"langchain-openai==0.3.29",
36+
"langgraph==0.6.4",
37+
"litellm==1.75.3",
38+
"llama-index==0.13.1",
3939
"lxml==6.0.0",
4040
"matplotlib==3.10.5",
4141
"oci~=2.0",
4242
"psutil==7.0.0",
4343
"python-multipart==0.0.20",
44-
"torch==2.7.1",
44+
"torch==2.8.0",
4545
"umap-learn==0.5.9.post2",
4646
"uvicorn==0.35.0",
4747
]

0 commit comments

Comments
 (0)