Skip to content

Commit d4b862d

Browse files
authored
Merge pull request #350 from oracle/sqlcl_tooling
Implement NL2SQL tooling
2 parents 158eab8 + c5c61dd commit d4b862d

34 files changed

+1380
-1294
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ sbin/**
2121
!opentofu/examples/manual-test.sh
2222
!src/entrypoint.sh
2323
!src/client/spring_ai/templates/env.sh
24-
tests/db_startup_temp/**
24+
test*/db_startup_temp
2525

2626
##############################################################################
2727
# Environment (PyVen, IDE, etc.)

.pylintrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ ignore=CVS,.venv
5252
# ignore-list. The regex matches against paths and can be in Posix or Windows
5353
# format. Because '\\' represents the directory delimiter on Windows systems,
5454
# it can't be used as an escape character.
55-
ignore-paths=.*[/\\]wip[/\\].*,src/client/mcp,docs/themes/relearn,docs/public,docs/static/demoware
55+
ignore-paths=.*[/\\]wip[/\\].*,src/client/mcp,docs/themes/relearn,docs/public,docs/static/demoware,src/server/agents/chatbot.py
5656

5757
# Files or directories matching the regular expression patterns are skipped.
5858
# The regex matches against base names, not paths. The default value ignores

pyproject.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ server = [
3232
"langchain-aimlapi==0.1.0",
3333
"langchain-cohere==0.4.6",
3434
"langchain-community==0.3.31",
35-
"langchain-fireworks==0.3.0",
35+
#"langchain-fireworks==0.3.0",
3636
"langchain-google-genai==2.1.12",
3737
"langchain-ibm==0.3.20",
3838
"langchain-mcp-adapters==0.1.13",
@@ -43,7 +43,7 @@ server = [
4343
"langchain-openai==0.3.35",
4444
"langchain-together==0.3.1",
4545
"langgraph==1.0.1",
46-
"litellm==1.80.0",
46+
"litellm==1.80.7",
4747
"llama-index==0.14.8",
4848
"lxml==6.0.2",
4949
"matplotlib==3.10.7",
@@ -70,6 +70,7 @@ test = [
7070
"pytest",
7171
"pytest-asyncio",
7272
"pytest-cov",
73+
"types-jsonschema",
7374
"yamllint"
7475
]
7576

src/client/content/chatbot.py

Lines changed: 71 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from streamlit import session_state as state
1717

1818
from client.content.config.tabs.models import get_models
19-
from client.utils import st_common, api_call, client
19+
from client.utils import st_common, api_call, client, vs_options
2020
from client.utils.st_footer import render_chat_footer
2121
from common import logging_config
2222

@@ -26,15 +26,15 @@
2626
#############################################################################
2727
# Functions
2828
#############################################################################
29-
def show_vector_search_refs(context):
29+
def show_vector_search_refs(context, vs_metadata=None):
3030
"""When Vector Search Content Found, show the references"""
3131
st.markdown("**References:**")
3232
ref_src = set()
3333
ref_cols = st.columns([3, 3, 3])
3434
# Create a button in each column
35-
for i, (ref_col, chunk) in enumerate(zip(ref_cols, context[0])):
35+
for i, (ref_col, chunk) in enumerate(zip(ref_cols, context["documents"])):
3636
with ref_col.popover(f"Reference: {i + 1}"):
37-
chunk = context[0][i]
37+
chunk = context["documents"][i]
3838
logger.debug("Chunk Content: %s", chunk)
3939
st.subheader("Reference Text", divider="red")
4040
st.markdown(chunk["page_content"])
@@ -46,9 +46,32 @@ def show_vector_search_refs(context):
4646
except KeyError:
4747
logger.error("Chunk Metadata NOT FOUND!!")
4848

49-
for link in ref_src:
50-
st.markdown("- " + link)
51-
st.markdown(f"**Notes:** Vector Search Query - {context[1]}")
49+
# Display Vector Search details in expander
50+
if vs_metadata or ref_src:
51+
with st.expander("Vector Search Details", expanded=False):
52+
if ref_src:
53+
st.markdown("**Source Documents:**")
54+
for link in ref_src:
55+
st.markdown(f"- {link}")
56+
57+
if vs_metadata and vs_metadata.get("searched_tables"):
58+
st.markdown("**Tables Searched:**")
59+
for table in vs_metadata["searched_tables"]:
60+
st.markdown(f"- {table}")
61+
62+
if vs_metadata and vs_metadata.get("context_input"):
63+
st.markdown(f"**Search Query:** {vs_metadata.get('context_input')}")
64+
elif context.get("context_input"):
65+
st.markdown(f"**Search Query:** {context.get('context_input')}")
66+
67+
68+
def show_token_usage(token_usage):
69+
"""Display token usage for AI responses using caption"""
70+
if token_usage:
71+
prompt_tokens = token_usage.get("prompt_tokens", 0)
72+
completion_tokens = token_usage.get("completion_tokens", 0)
73+
total_tokens = token_usage.get("total_tokens", 0)
74+
st.caption(f"Token usage: {prompt_tokens} prompt + {completion_tokens} completion = {total_tokens} total")
5275

5376

5477
def setup_sidebar():
@@ -62,7 +85,7 @@ def setup_sidebar():
6285
st_common.tools_sidebar()
6386
st_common.history_sidebar()
6487
st_common.ll_sidebar()
65-
st_common.vector_search_sidebar()
88+
vs_options.vector_search_sidebar()
6689

6790
if not state.enable_client:
6891
st.stop()
@@ -80,22 +103,33 @@ def create_client():
80103

81104

82105
def display_chat_history(history):
83-
"""Display chat history messages"""
106+
"""Display chat history messages with metadata"""
84107
st.chat_message("ai").write("Hello, how can I help you?")
85108
vector_search_refs = []
86109

87110
for message in history or []:
88111
if not message["content"]:
89112
continue
90113

91-
if message["role"] == "tool" and message["name"] == "oraclevs_tool":
114+
if message["role"] == "tool" and message["name"] == "optimizer_vs-retriever":
92115
vector_search_refs = json.loads(message["content"])
93116

94117
elif message["role"] in ("ai", "assistant"):
95118
with st.chat_message("ai"):
96119
st.markdown(message["content"])
120+
121+
# Extract metadata from response_metadata
122+
response_metadata = message.get("response_metadata", {})
123+
vs_metadata = response_metadata.get("vs_metadata", {})
124+
token_usage = response_metadata.get("token_usage", {})
125+
126+
# Show token usage immediately after message
127+
if token_usage:
128+
show_token_usage(token_usage)
129+
130+
# Show vector search references if available
97131
if vector_search_refs:
98-
show_vector_search_refs(vector_search_refs)
132+
show_vector_search_refs(vector_search_refs, vs_metadata)
99133
vector_search_refs = []
100134

101135
elif message["role"] in ("human", "user"):
@@ -131,9 +165,32 @@ async def handle_chat_input(user_client):
131165
try:
132166
message_placeholder = st.chat_message("ai").empty()
133167
full_answer = ""
134-
async for chunk in user_client.stream(message=human_request.text, image_b64=file_b64):
135-
full_answer += chunk
136-
message_placeholder.markdown(full_answer)
168+
169+
# Animated thinking indicator
170+
async def animate_thinking():
171+
"""Animate the thinking indicator with increasing dots"""
172+
dots = 0
173+
while True:
174+
message_placeholder.markdown(f"🤔 Thinking{'.' * (dots % 4)}")
175+
dots += 1
176+
await asyncio.sleep(0.5) # Update every 500ms
177+
178+
# Start the thinking animation
179+
thinking_task = asyncio.create_task(animate_thinking())
180+
181+
try:
182+
async for chunk in user_client.stream(message=human_request.text, image_b64=file_b64):
183+
# Cancel thinking animation on first chunk
184+
if thinking_task and not thinking_task.done():
185+
thinking_task.cancel()
186+
thinking_task = None
187+
full_answer += chunk
188+
message_placeholder.markdown(full_answer)
189+
finally:
190+
# Ensure thinking task is cancelled
191+
if thinking_task and not thinking_task.done():
192+
thinking_task.cancel()
193+
137194
st.rerun()
138195
except (ConnectionError, TimeoutError, api_call.ApiError) as ex:
139196
logger.exception("Error during chat streaming: %s", ex)

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

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -363,17 +363,23 @@ def spring_ai_conf_check(ll_model: dict, embed_model: dict) -> str:
363363
def spring_ai_obaas(src_dir, file_name, provider, ll_config, embed_config):
364364
"""Get the system prompt for SpringAI export"""
365365

366+
## FUTURE FEATURE:
366367
# Determine which system prompt would be active based on tools_enabled
367-
tools_enabled = state.client_settings.get("tools_enabled", [])
368+
# tools_enabled = state.client_settings.get("tools_enabled", [])
368369

369370
# Select prompt name based on tools configuration
370-
if not tools_enabled:
371-
prompt_name = "optimizer_basic-default"
372-
if state.client_settings["vector_search"]["enabled"]:
373-
prompt_name = "optimizer_vs-no-tools-default"
371+
# if not tools_enabled:
372+
# prompt_name = "optimizer_basic-default"
373+
# if state.client_settings["vector_search"]["enabled"]:
374+
# prompt_name = "optimizer_vs-no-tools-default"
375+
# else:
376+
# # Tools are enabled, use tools-default prompt
377+
# prompt_name = "optimizer_tools-default"
378+
## Legacy Feature:
379+
if "Vector Search" in state.client_settings.get("tools_enabled", []):
380+
prompt_name = "optimizer_vs-no-tools-default"
374381
else:
375-
# Tools are enabled, use tools-default prompt
376-
prompt_name = "optimizer_tools-default"
382+
prompt_name = "optimizer_basic-default"
377383

378384
# Find the prompt in configs
379385
sys_prompt_obj = next((item for item in state.prompt_configs if item["name"] == prompt_name), None)

src/client/content/testbed.py

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
from client.content.config.tabs.models import get_models
1919

20-
from client.utils import st_common, api_call
20+
from client.utils import st_common, api_call, vs_options
2121

2222
from common import logging_config
2323

@@ -85,9 +85,12 @@ def create_gauge(value):
8585
st.dataframe(ll_settings_reversed, hide_index=True)
8686
if report["settings"]["testbed"]["judge_model"]:
8787
st.markdown(f"**Judge Model**: {report['settings']['testbed']['judge_model']}")
88-
# if discovery; then list out the tables that were discovered (MCP implementation)
89-
# if report["settings"]["vector_search"].get("discovery"):
90-
if report["settings"]["vector_search"]["enabled"]:
88+
# Backward compatibility
89+
try:
90+
vs_enabled = report["settings"]["vector_search"]["enabled"]
91+
except KeyError:
92+
vs_enabled = "Vector Search" in report["settings"]["tools_enabled"]
93+
if vs_enabled:
9194
st.subheader("Vector Search Settings")
9295
st.markdown(f"""**Database**: {report["settings"]["database"]["alias"]};
9396
**Vector Store**: {report["settings"]["vector_search"]["vector_store"]}
@@ -100,6 +103,8 @@ def create_gauge(value):
100103
if report["settings"]["vector_search"]["search_type"] == "Similarity":
101104
embed_settings.drop(["score_threshold", "fetch_k", "lambda_mult"], axis=1, inplace=True)
102105
st.dataframe(embed_settings, hide_index=True)
106+
# if discovery; then list out the tables that were discovered (MCP implementation)
107+
# if report["settings"]["vector_search"].get("discovery"):
103108
else:
104109
st.markdown("**Evaluated without Vector Search**")
105110

@@ -493,7 +498,7 @@ def render_evaluation_ui(available_ll_models: list) -> None:
493498
st.info("Use the sidebar settings for chatbot evaluation parameters", icon="⬅️")
494499
st_common.tools_sidebar()
495500
st_common.ll_sidebar()
496-
st_common.vector_search_sidebar()
501+
vs_options.vector_search_sidebar()
497502
st.write("Choose a model to judge the correctness of the chatbot answer, then start evaluation.")
498503
col_left, col_center, _ = st.columns([4, 3, 3])
499504

@@ -510,20 +515,13 @@ def render_evaluation_ui(available_ll_models: list) -> None:
510515
on_change=st_common.update_client_settings("testbed"),
511516
)
512517

513-
# Check if vector search is enabled but no vector store is selected
514-
evaluation_disabled = False
515-
if state.client_settings.get("vector_search", {}).get("enabled", False):
516-
# If vector search is enabled, check if a vector store is selected
517-
if not state.client_settings.get("vector_search", {}).get("vector_store"):
518-
evaluation_disabled = True
519-
520518
if col_center.button(
521519
"Start Evaluation",
522520
type="primary",
523521
key="evaluate_button",
524522
help="Evaluation will automatically save the TestSet to the Database",
525523
on_click=qa_update_db,
526-
disabled=evaluation_disabled,
524+
disabled=not state.get("enable_client", True),
527525
):
528526
with st.spinner("Starting Q&A evaluation... please be patient.", show_time=True):
529527
st_common.clear_state_key("testbed_evaluations")

0 commit comments

Comments
 (0)