Skip to content

Commit 4f46ab1

Browse files
authored
Merge branch 'oracle-devrel:main' into main
2 parents b099fd1 + 898d0f3 commit 4f46ab1

17 files changed

Lines changed: 1176 additions & 92 deletions
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
name: agentic_rag integration tests
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- 'apps/agentic_rag/src/**'
8+
- 'apps/agentic_rag/tests/conftest.py'
9+
- 'apps/agentic_rag/tests/integration/**'
10+
- 'apps/agentic_rag/requirements.txt'
11+
- 'apps/agentic_rag/docker-compose.test.yml'
12+
- '.github/workflows/agentic_rag_integration.yml'
13+
workflow_dispatch: {}
14+
schedule:
15+
# Nightly at 04:17 UTC. Odd minute to avoid the top-of-hour queue.
16+
- cron: '17 4 * * *'
17+
18+
concurrency:
19+
group: agentic-rag-integration-${{ github.ref }}
20+
cancel-in-progress: true
21+
22+
jobs:
23+
integration:
24+
name: Full-stack integration
25+
runs-on: ubuntu-latest
26+
timeout-minutes: 45
27+
defaults:
28+
run:
29+
working-directory: apps/agentic_rag
30+
env:
31+
ORACLE_DB_USERNAME: SYSTEM
32+
ORACLE_DB_PASSWORD: OraclePW1_
33+
ORACLE_DB_DSN: localhost:1521/FREEPDB1
34+
OLLAMA_HOST: http://127.0.0.1:11434
35+
OLLAMA_TEST_MODEL: gemma3:270m
36+
steps:
37+
- uses: actions/checkout@v4
38+
39+
- name: Free disk space
40+
# Oracle DB Free image is ~2GB, torch deps are ~2GB, Ollama model + binary
41+
# is ~500MB. Default GitHub runner has 14GB free on /. Strip preinstalled
42+
# software we don't need to avoid running out mid-test.
43+
run: |
44+
sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android \
45+
/usr/local/share/boost "${AGENT_TOOLSDIRECTORY}" || true
46+
df -h
47+
working-directory: ${{ github.workspace }}
48+
49+
- name: Set up Python 3.12
50+
uses: actions/setup-python@v5
51+
with:
52+
python-version: '3.12'
53+
cache: 'pip'
54+
cache-dependency-path: apps/agentic_rag/requirements.txt
55+
56+
- name: Start Oracle Database Free container
57+
run: |
58+
docker run -d \
59+
--name oracle-free \
60+
-p 1521:1521 \
61+
-e ORACLE_PWD=${ORACLE_DB_PASSWORD} \
62+
-e ORACLE_CHARACTERSET=AL32UTF8 \
63+
container-registry.oracle.com/database/free:latest
64+
echo "Oracle container started. Waiting for DB to be ready..."
65+
66+
- name: Install Ollama
67+
run: |
68+
curl -fsSL https://ollama.com/install.sh | sh
69+
# Start Ollama in the background.
70+
nohup ollama serve > /tmp/ollama.log 2>&1 &
71+
# Wait for it to accept connections.
72+
for i in {1..30}; do
73+
if curl -sf http://127.0.0.1:11434/api/tags > /dev/null; then
74+
echo "Ollama is up"
75+
break
76+
fi
77+
sleep 2
78+
done
79+
80+
- name: Pull Ollama test model
81+
run: ollama pull ${OLLAMA_TEST_MODEL}
82+
83+
- name: Install Python dependencies
84+
# Install only what the integration tests need, not the full
85+
# requirements.txt (which pulls in torch, docling, etc. and can OOM
86+
# the runner). oracledb + langchain-oracledb drive the DB tests;
87+
# ollama is the Python SDK; playwright drives the Gradio UI test;
88+
# requests + httpx cover HTTP; pytest-asyncio lets us drop back
89+
# to async if needed.
90+
run: |
91+
python -m pip install --upgrade pip
92+
pip install \
93+
pytest \
94+
pytest-asyncio \
95+
oracledb \
96+
"langchain-oracledb" \
97+
"langchain-core" \
98+
ollama \
99+
playwright \
100+
requests \
101+
httpx \
102+
"fastapi" \
103+
"uvicorn" \
104+
"python-multipart" \
105+
pyyaml
106+
107+
- name: Install Playwright Chromium
108+
run: |
109+
python -m playwright install --with-deps chromium
110+
111+
- name: Wait for Oracle DB to be ready
112+
# Oracle DB Free takes ~90-150 seconds to finish startup. Poll the
113+
# container's built-in status script.
114+
run: |
115+
for i in {1..60}; do
116+
if docker exec oracle-free /opt/oracle/checkDBStatus.sh 2>/dev/null | grep -q "READY"; then
117+
echo "Oracle DB is ready"
118+
docker exec oracle-free /opt/oracle/checkDBStatus.sh || true
119+
break
120+
fi
121+
echo "Waiting for Oracle DB... (${i}/60)"
122+
sleep 5
123+
done
124+
# Final readiness probe: can we actually connect?
125+
python -c "
126+
import oracledb, os
127+
c = oracledb.connect(
128+
user=os.environ['ORACLE_DB_USERNAME'],
129+
password=os.environ['ORACLE_DB_PASSWORD'],
130+
dsn=os.environ['ORACLE_DB_DSN'],
131+
)
132+
cur = c.cursor()
133+
cur.execute('SELECT 1 FROM DUAL')
134+
assert cur.fetchone() == (1,)
135+
print('Oracle DB connection verified')
136+
"
137+
138+
- name: Run integration tests
139+
# -m '' overrides the default 'not integration' from pyproject.toml,
140+
# then -m integration selects just the integration suite.
141+
run: |
142+
pytest tests/integration/ -v -m integration \
143+
--tb=short \
144+
-o addopts=""
145+
146+
- name: Dump Oracle container logs on failure
147+
if: failure()
148+
run: |
149+
docker logs --tail 200 oracle-free || true
150+
cat /tmp/ollama.log 2>/dev/null | tail -100 || true
151+
152+
- name: Stop Oracle container
153+
if: always()
154+
run: docker stop oracle-free || true
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: agentic_rag smoke tests
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- 'apps/agentic_rag/src/**'
7+
- 'apps/agentic_rag/tests/test_smoke_reasoning.py'
8+
- '.github/workflows/agentic_rag_smoke.yml'
9+
push:
10+
branches: [main]
11+
paths:
12+
- 'apps/agentic_rag/src/**'
13+
- 'apps/agentic_rag/tests/test_smoke_reasoning.py'
14+
- '.github/workflows/agentic_rag_smoke.yml'
15+
16+
jobs:
17+
smoke:
18+
runs-on: ubuntu-latest
19+
defaults:
20+
run:
21+
working-directory: apps/agentic_rag
22+
steps:
23+
- uses: actions/checkout@v4
24+
25+
- name: Set up Python
26+
uses: actions/setup-python@v5
27+
with:
28+
python-version: '3.12'
29+
30+
- name: Install minimal test deps
31+
# Intentionally NOT installing requirements.txt -- the smoke test
32+
# stubs out agent_reasoning and mocks OraDBVectorStore, so it needs
33+
# only pytest. This keeps CI fast and hermetic.
34+
run: pip install pytest
35+
36+
- name: Run reasoning smoke tests
37+
run: pytest tests/test_smoke_reasoning.py -v
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Oracle Database Free container for local integration testing.
2+
#
3+
# Usage:
4+
# docker compose -f docker-compose.test.yml up -d
5+
# # Wait ~2 minutes for "DATABASE IS READY TO USE" in logs
6+
# docker compose -f docker-compose.test.yml logs -f oracle-free
7+
# # Then run:
8+
# pytest -m integration
9+
# # Teardown:
10+
# docker compose -f docker-compose.test.yml down -v
11+
#
12+
# The matching conftest.py fixtures default to:
13+
# ORACLE_DB_DSN=localhost:1521/FREEPDB1
14+
# ORACLE_DB_USERNAME=SYSTEM
15+
# ORACLE_DB_PASSWORD=OraclePW1_
16+
#
17+
# Note: Oracle's publicly available free container is "23ai Free". The user
18+
# may refer to it as "26ai free" but the image tag is database/free:latest
19+
# which tracks the current free release.
20+
21+
services:
22+
oracle-free:
23+
image: container-registry.oracle.com/database/free:latest
24+
container_name: agentic-rag-oracle-free
25+
environment:
26+
ORACLE_PWD: OraclePW1_
27+
ORACLE_CHARACTERSET: AL32UTF8
28+
ports:
29+
- "1521:1521"
30+
volumes:
31+
- oracle-free-data:/opt/oracle/oradata
32+
healthcheck:
33+
# Reports healthy once the listener accepts connections. Full DB
34+
# startup still takes ~2 minutes; use `docker logs` to wait for
35+
# "DATABASE IS READY TO USE" before running tests.
36+
test: ["CMD", "/opt/oracle/checkDBStatus.sh"]
37+
interval: 10s
38+
timeout: 5s
39+
retries: 30
40+
start_period: 120s
41+
42+
volumes:
43+
oracle-free-data:

apps/agentic_rag/gradio_app.py

Lines changed: 8 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,8 @@ def convert_to_tuples_format(history):
288288
else:
289289
# Assistant message without preceding user message
290290
tuples.append([None, content])
291+
elif isinstance(item, str):
292+
tuples.append([None, item])
291293
i += 1
292294
return tuples
293295

@@ -1122,73 +1124,6 @@ async def collect_events():
11221124

11231125
def create_interface():
11241126
"""Create Gradio interface"""
1125-
# Workaround for Gradio schema parsing bug with additionalProperties
1126-
# This error occurs when Gradio tries to generate API info from function signatures
1127-
# We'll patch the get_api_info method to handle errors gracefully and prevent blocking
1128-
original_blocks_init = gr.Blocks.__init__
1129-
1130-
def patched_blocks_init(self, *args, **kwargs):
1131-
"""Patched Blocks.__init__ that adds defensive API info generation"""
1132-
try:
1133-
original_blocks_init(self, *args, **kwargs)
1134-
except Exception as e:
1135-
# If Blocks initialization fails, log but continue
1136-
print(f"Warning: Error during Blocks initialization: {type(e).__name__}: {str(e)}")
1137-
# Try to continue with minimal initialization
1138-
try:
1139-
original_blocks_init(self, *args, **kwargs)
1140-
except:
1141-
pass
1142-
1143-
# Safely get the original get_api_info method
1144-
try:
1145-
original_get_api_info = self.get_api_info
1146-
except AttributeError:
1147-
# If get_api_info doesn't exist, create a dummy method
1148-
original_get_api_info = lambda: {}
1149-
1150-
def safe_get_api_info():
1151-
"""Safely get API info, never raising exceptions to prevent blocking"""
1152-
try:
1153-
api_info = original_get_api_info()
1154-
# Ensure we always return a dict, even if None
1155-
return api_info if isinstance(api_info, dict) else {}
1156-
except TypeError as e:
1157-
if "argument of type 'bool' is not iterable" in str(e) or "additionalProperties" in str(e):
1158-
# Return empty API info to avoid crashing
1159-
return {}
1160-
# Catch all TypeError and return empty dict
1161-
print(f"Warning: TypeError generating API info: {str(e)}")
1162-
return {}
1163-
except ValueError as e:
1164-
# Handle value errors during API generation
1165-
print(f"Warning: ValueError generating API info: {str(e)}")
1166-
return {}
1167-
except AttributeError as e:
1168-
# Handle attribute errors during API generation
1169-
print(f"Warning: AttributeError generating API info: {str(e)}")
1170-
return {}
1171-
except KeyError as e:
1172-
# Handle key errors during API generation
1173-
print(f"Warning: KeyError generating API info: {str(e)}")
1174-
return {}
1175-
except (ImportError, ModuleNotFoundError) as e:
1176-
# Handle import errors during API generation
1177-
print(f"Warning: Import error generating API info: {str(e)}")
1178-
return {}
1179-
except Exception as e:
1180-
# Catch all other errors and log but don't crash
1181-
print(f"Warning: Unexpected error generating API info: {type(e).__name__}: {str(e)}")
1182-
return {}
1183-
1184-
# Safely assign the patched method
1185-
try:
1186-
self.get_api_info = safe_get_api_info
1187-
except Exception as e:
1188-
print(f"Warning: Could not patch get_api_info: {str(e)}")
1189-
1190-
# Temporarily patch Blocks class
1191-
gr.Blocks.__init__ = patched_blocks_init
11921127

11931128
# Helper function to render Agent Cards
11941129
def render_agent_cards():
@@ -1232,9 +1167,8 @@ def render_agent_cards():
12321167
with gr.Column():
12331168
gr.Markdown("#### Synthesizer B (Concise)")
12341169
gr.JSON(value=all_cards.get("synthesizer_agent_v2", {}))
1235-
1236-
try:
1237-
with gr.Blocks(title="Agentic RAG System") as interface:
1170+
1171+
with gr.Blocks(title="Agentic RAG System", css=CUSTOM_CSS, theme=gr.themes.Soft()) as interface:
12381172
gr.Markdown("""
12391173
# 🤖 Agentic RAG System
12401174
@@ -1377,7 +1311,7 @@ def render_agent_cards():
13771311
# CoT is enabled by default for A2A Chat
13781312
a2a_use_cot_state = gr.State(value=True)
13791313

1380-
a2a_chatbot = gr.Chatbot(height=400, label="A2A Chat")
1314+
a2a_chatbot = gr.Chatbot(height=400, label="A2A Chat", type="tuples")
13811315
with gr.Row():
13821316
a2a_msg = gr.Textbox(label="Your Message", scale=8, placeholder="Ask a question...")
13831317
a2a_clear_button = gr.Button("Clear", scale=1, variant="secondary")
@@ -1734,7 +1668,7 @@ def reasoning_chat_wrapper(message, model, use_rag, collection, strategies, tot_
17341668

17351669
gr.Markdown("### 3. Execution Trace")
17361670
# converted to Chatbot as requested to prevent overflow
1737-
demo_log_output = gr.Chatbot(label="Task Trace", height=600, elem_id="a2a_trace_log")
1671+
demo_log_output = gr.Chatbot(label="Task Trace", height=600, elem_id="a2a_trace_log", type="tuples")
17381672

17391673
# Hidden state to store current log (history)
17401674
demo_log_state = gr.State(value=[])
@@ -2075,16 +2009,13 @@ def run_all_a2a_tests():
20752009
outputs=[a2a_chatbot],
20762010
api_name=False
20772011
)
2078-
a2a_clear_button.click(lambda: None, None, a2a_chatbot, queue=False, api_name=False)
2012+
a2a_clear_button.click(lambda: [], None, a2a_chatbot, queue=False, api_name=False)
20792013
# a2a_status_button removed from Chat tab
20802014

20812015
# Checkbox event listener removed
20822016

20832017

20842018
return interface
2085-
finally:
2086-
# Restore original Blocks.__init__
2087-
gr.Blocks.__init__ = original_blocks_init
20882019

20892020
def main():
20902021
# Check configuration
@@ -2111,9 +2042,7 @@ def main():
21112042
server_name="0.0.0.0",
21122043
server_port=7860,
21132044
share=True,
2114-
inbrowser=True,
2115-
css=CUSTOM_CSS,
2116-
theme=gr.themes.Soft()
2045+
inbrowser=True
21172046
)
21182047

21192048
def download_model(model_type: str) -> str:

apps/agentic_rag/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ python-multipart
1111
torch
1212
pyyaml
1313
trafilatura
14-
gradio
14+
gradio>=5.0,<6.0
1515
open-webui
1616
lxml_html_clean
1717
langchain

0 commit comments

Comments
 (0)