Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
**/THIRD_PARTY_LICENSES.txt
sbin/**
**/*.bak
**/*.delete
**/tmp/**
**/temp/**
**/chatbot_graph.png
Expand Down
2 changes: 1 addition & 1 deletion opentofu/locals.tf
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// Housekeeping
locals {
compartment_ocid = var.compartment_ocid != "" ? var.compartment_ocid : var.tenancy_ocid
label_prefix = var.label_prefix != "" ? lower(var.label_prefix) : lower(substr(random_pet.label.id, 0, 12))
label_prefix = var.label_prefix != "" ? substr(lower(var.label_prefix), 0, 12) : substr(lower(random_pet.label.id), 0, 12)
}


Expand Down
14 changes: 8 additions & 6 deletions src/client/content/config/tabs/databases.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,17 @@
#####################################################
# Functions
#####################################################
def get_databases(validate: bool = False, force: bool = False) -> None:
def get_databases(force: bool = False) -> None:
"""Get Databases from API Server"""
if force or "database_configs" not in state or not state.database_configs:
try:
logger.info("Refreshing state.database_configs")
# Validation will be done on currently configured client database
# validation includes new vector_stores, etc.
if validate:
client_database = state.client_settings.get("database", {}).get("alias", {})
_ = api_call.get(endpoint=f"v1/databases/{client_database}")
client_database = state.client_settings.get("database", {}).get("alias", {})
_ = api_call.get(endpoint=f"v1/databases/{client_database}")

# Update state
state.database_configs = api_call.get(endpoint="v1/databases")
except api_call.ApiError as ex:
logger.error("Unable to populate state.database_configs: %s", ex)
Expand All @@ -53,10 +54,11 @@ def patch_database(name: str, supplied: dict, connected: bool) -> bool:
payload={"json": supplied},
)
logger.info("Database updated: %s", name)
st_common.clear_state_key("database_configs")
except api_call.ApiError as ex:
logger.error("Database not updated: %s (%s)", name, ex)
_ = [d.update(connected=False) for d in state.database_configs if d.get("name") == name]
state.database_error = str(ex)
st_common.clear_state_key("database_configs")
else:
st.toast("No changes detected.", icon="ℹ️")

Expand All @@ -66,7 +68,7 @@ def patch_database(name: str, supplied: dict, connected: bool) -> bool:
def drop_vs(vs: dict) -> None:
"""Drop a Vector Storage Table"""
api_call.delete(endpoint=f"v1/embed/{vs['vector_store']}")
get_databases(validate=True, force=True)
get_databases(force=True)


def select_ai_profile() -> None:
Expand Down
11 changes: 5 additions & 6 deletions src/client/content/config/tabs/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

This script allows importing/exporting configurations using Streamlit (`st`).
"""
# spell-checker:ignore streamlit, mvnw, obaas, ollama
# spell-checker:ignore streamlit mvnw obaas ollama vllm

import time
import os
Expand Down Expand Up @@ -159,7 +159,7 @@ def spring_ai_conf_check(ll_model: dict, embed_model: dict) -> str:

ll_provider = ll_model.get("provider", "")
embed_provider = embed_model.get("provider", "")
logger.info(f"llm chat:{ll_provider} - embeddings:{embed_provider}")
logger.info("llm chat: %s - embeddings: %s", ll_provider, embed_provider)
if all("hosted_vllm" in p for p in (ll_provider, embed_provider)):
return "hosted_vllm"
if all("openai" in p for p in (ll_provider, embed_provider)):
Expand Down Expand Up @@ -345,8 +345,8 @@ def display_settings():
embed_config = {}
spring_ai_conf = spring_ai_conf_check(ll_config, embed_config)

logger.info(f"config found:{spring_ai_conf}")
logger.info("config found: %s", spring_ai_conf)

if spring_ai_conf == "hybrid":
st.markdown(f"""
The current configuration combination of embedding and language models
Expand All @@ -365,15 +365,14 @@ def display_settings():
disabled=spring_ai_conf == "hybrid",
)
with col_centre:
if (spring_ai_conf != "hosted_vllm"):
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
file_name="spring_ai.zip", # Zip file name
mime="application/zip", # Mime type for zip file
disabled=spring_ai_conf == "hybrid",
)



if __name__ == "__main__":
Expand Down
2 changes: 1 addition & 1 deletion src/client/content/tools/tabs/split_embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ def display_split_embed() -> None:
)
st.success(f"Vector Store Populated: {response['message']}", icon="✅")
# Refresh database_configs state to reflect new vector stores
get_databases(validate=True, force=True)
get_databases(force=True)
except api_call.ApiError as ex:
st.error(ex, icon="🚨")

Expand Down
39 changes: 35 additions & 4 deletions src/common/logging_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

Default Logging Configuration
"""
# pylint: disable=too-few-public-methods
# spell-checker:ignore levelname inotify openai httpcore fsevents litellm

import os
import asyncio
import logging
from logging.config import dictConfig
from common._version import __version__
Expand All @@ -20,6 +22,30 @@ def filter(self, record):
return True


class PrettifyCancelledError(logging.Filter):
"""Filter that keeps the log but removes the traceback and replaces the message."""

def _contains_cancelled(self, exc: BaseException) -> bool:
if isinstance(exc, asyncio.CancelledError):
return True
if hasattr(exc, "exceptions") and isinstance(exc, BaseExceptionGroup): # type: ignore[name-defined]
return any(self._contains_cancelled(e) for e in exc.exceptions) # type: ignore[attr-defined]
return False

def filter(self, record: logging.LogRecord) -> bool:
exc_info = record.__dict__.get("exc_info")
if not exc_info:
return True
_, exc, _ = exc_info
if exc and self._contains_cancelled(exc):
# Strip the traceback and make it pretty
record.exc_info = None
record.msg = "Shutdown cancelled — graceful timeout exceeded."
record.levelno = logging.WARNING
record.levelname = logging.getLevelName(logging.WARNING)
return True


# Standard formatter
FORMATTER = {
"format": "%(asctime)s (v%(__version__)s) - %(levelname)-8s - (%(name)s): %(message)s",
Expand All @@ -33,9 +59,8 @@ def filter(self, record):
"standard": FORMATTER,
},
"filters": {
"version_filter": {
"()": VersionFilter,
},
"version_filter": {"()": VersionFilter},
"prettify_cancelled": {"()": PrettifyCancelledError},
},
"handlers": {
"default": {
Expand All @@ -56,13 +81,19 @@ def filter(self, record):
"level": LOG_LEVEL,
"handlers": ["default"],
"propagate": False,
"filters": ["prettify_cancelled"],
},
"uvicorn.access": {
"level": LOG_LEVEL,
"handlers": ["default"],
"propagate": False,
},
"asyncio": {"level": LOG_LEVEL, "handlers": ["default"], "propagate": False},
"asyncio": {
"level": LOG_LEVEL,
"handlers": ["default"],
"propagate": False,
"filters": ["prettify_cancelled"],
},
"watchdog.observers.inotify_buffer": {"level": "INFO", "handlers": ["default"], "propagate": False},
"PIL": {"level": "INFO", "handlers": ["default"], "propagate": False},
"fsevents": {"level": "INFO", "handlers": ["default"], "propagate": False},
Expand Down
6 changes: 4 additions & 2 deletions src/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,17 @@ dependencies = [
"langchain-core==0.3.75",
"httpx==0.28.1",
"oracledb~=3.1",
"plotly==6.2.0",
"plotly==6.3.0",
]

[project.optional-dependencies]
# Server component dependencies
server = [
"bokeh==3.8.0",
"evaluate==0.4.5",
"fastapi==0.116.1",
"faiss-cpu==1.12.0",
"fastapi==0.116.1",
"fastmcp==2.12.0",
"giskard==2.18.0",
"langchain-anthropic==0.3.19",
"langchain-azure-ai==0.1.5",
Expand All @@ -39,6 +40,7 @@ server = [
"langchain-groq==0.3.7",
"langchain-huggingface==0.3.1",
"langchain-mistralai==0.2.11",
"langchain-mcp-adapters==0.1.9",
"langchain-ollama==0.3.7",
"langchain-openai==0.3.32",
"langchain-perplexity==0.1.2",
Expand Down
62 changes: 0 additions & 62 deletions src/server/api/core/databases.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,65 +51,3 @@ def delete_database(name: DatabaseNameType) -> None:
"""Remove database from database objects"""
database_objects = bootstrap.DATABASE_OBJECTS
bootstrap.DATABASE_OBJECTS = [d for d in database_objects if d.name != name]


# for db in database_objects:
# if name and db.name != name:
# continue
# if validate:
# try:
# db_conn = connect(db)
# db.vector_stores = get_vs(db_conn)
# db.selectai = selectai_enabled(db_conn)
# if db.selectai:
# db.selectai_profiles = get_selectai_profiles(db_conn)
# except Exception as ex:
# logger.debug("Skipping Database %s - exception: %s", db.name, str(ex))
# db.connected = False
# if name:
# return db # Return the matched, connected DB immediately

# if name:
# # If we got here with a `name` then we didn't find it
# raise ValueError(f"{name} not found")

# return database_objects


# create_database


# delete_database


# def get_databases(
# name: Optional[DatabaseNameType] = None, validate: bool = True
# ) -> Union[list[Database], Database, None]:
# """
# Return all Database objects if `name` is not provided,
# or the single Database if `name` is provided and successfully connected.
# If a `name` is provided and not found, raise exception
# """
# database_objects = bootstrap.DATABASE_OBJECTS

# for db in database_objects:
# if name and db.name != name:
# continue
# if validate:
# try:
# db_conn = connect(db)
# db.vector_stores = get_vs(db_conn)
# db.selectai = selectai_enabled(db_conn)
# if db.selectai:
# db.selectai_profiles = get_selectai_profiles(db_conn)
# except Exception as ex:
# logger.debug("Skipping Database %s - exception: %s", db.name, str(ex))
# db.connected = False
# if name:
# return db # Return the matched, connected DB immediately

# if name:
# # If we got here with a `name` then we didn't find it
# raise ValueError(f"{name} not found")

# return database_objects
8 changes: 4 additions & 4 deletions src/server/api/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,16 @@ def get_client_settings(client: ClientIdType) -> Settings:
def get_server_config() -> Configuration:
"""Return server configuration"""
database_objects = bootstrap.DATABASE_OBJECTS
database_configs = [db for db in database_objects]
database_configs = list(database_objects)

model_objects = bootstrap.MODEL_OBJECTS
model_configs = [model for model in model_objects]
model_configs = list(model_objects)

oci_objects = bootstrap.OCI_OBJECTS
oci_configs = [oci for oci in oci_objects]
oci_configs = list(oci_objects)

prompt_objects = bootstrap.PROMPT_OBJECTS
prompt_configs = [prompt for prompt in prompt_objects]
prompt_configs = list(prompt_objects)

full_config = {
"database_configs": database_configs,
Expand Down
2 changes: 1 addition & 1 deletion src/server/api/utils/databases.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ def get_databases(
for db in databases:
try:
db_conn = connect(config=db)
except (ValueError, PermissionError, ConnectionError):
except (ValueError, PermissionError, ConnectionError, LookupError):
continue
db.vector_stores = _get_vs(db_conn)
db.selectai = _selectai_enabled(db_conn)
Expand Down
16 changes: 13 additions & 3 deletions tests/integration/client/content/config/tabs/test_databases.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
# spell-checker: disable
# pylint: disable=import-error

import re
import pytest

from conftest import TEST_CONFIG
Expand Down Expand Up @@ -49,7 +48,7 @@ def test_missing_details(self, app_server, app_test):
assert at.session_state.database_configs[0]["connected"] is False
assert at.session_state.database_configs[0]["vector_stores"] == []

def test_wrong_details(self, app_server, app_test):
def test_no_database(self, app_server, app_test):
"""Submits with wrong details"""
assert app_server is not None
at = app_test(self.ST_FILE).run()
Expand Down Expand Up @@ -129,7 +128,7 @@ def test_connected(self, app_server, app_test, db_container):
"username": TEST_CONFIG["db_username"],
"password": TEST_CONFIG["db_password"],
"dsn": "WRONG_TP",
"expected": "DPY-4026",
"expected": "DPY-4",
},
id="bad_dsn",
),
Expand All @@ -142,12 +141,23 @@ def test_disconnected(self, app_server, app_test, db_container, test_case):
assert db_container is not None
at = app_test(self.ST_FILE).run()
assert at.session_state.database_configs is not None

# Input and save good database
at.text_input(key="database_user").set_value(TEST_CONFIG["db_username"]).run()
at.text_input(key="database_password").set_value(TEST_CONFIG["db_password"]).run()
at.text_input(key="database_dsn").set_value(TEST_CONFIG["db_dsn"]).run()
at.button(key="save_database").click().run()

# Update Database Details and Save
at.text_input(key="database_user").set_value(test_case["username"]).run()
at.text_input(key="database_password").set_value(test_case["password"]).run()
at.text_input(key="database_dsn").set_value(test_case["dsn"]).run()
at.button(key="save_database").click().run()

# Check Errors
assert at.error[0].value == "Current Status: Disconnected"
assert test_case["expected"] in at.error[1].value and at.error[1].icon == "🚨"

# Due to the connection error, the settings should NOT be updated and be set
# to previous successful test connection; connected will be False for error handling
assert at.session_state.database_configs[0]["name"] == "DEFAULT"
Expand Down