-
Notifications
You must be signed in to change notification settings - Fork 0
Feat/rag chat and error handling #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1251198
c75e849
5260bef
20a2e9b
1f94581
99ef72a
951a23f
b0a0e29
782e78c
362158c
595e057
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,30 @@ | ||
| import threading | ||
| from supabase import create_client, Client | ||
| from app.core.config import settings | ||
|
|
||
| def get_supabase_client() -> Client: | ||
| SUPABASE_CONFIG_ERROR = "SUPABASE_URL and SUPABASE_SERVICE_KEY must be configured" | ||
|
|
||
| def _get_supabase_client() -> Client: | ||
| """ | ||
| Returns a configured Supabase client using the URL and Service Key. | ||
| The Service Key is used to bypass RLS for administrative backend tasks | ||
| like upserting documents or fetching metadata securely. | ||
| """ | ||
| supabase_url = settings.SUPABASE_URL or "http://localhost:8000" | ||
| supabase_key = settings.SUPABASE_SERVICE_KEY or "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSJ9.1234567890" | ||
| supabase_url = settings.SUPABASE_URL | ||
| supabase_key = settings.SUPABASE_SERVICE_KEY | ||
| if not supabase_url or not supabase_key: | ||
| raise RuntimeError(SUPABASE_CONFIG_ERROR) | ||
| return create_client(supabase_url, supabase_key) | ||
|
|
||
| # Initialize a global client to be reused | ||
| supabase_client = get_supabase_client() | ||
|
|
||
| _client_lock = threading.Lock() | ||
| # Lazy initialization for Supabase client | ||
| _supabase_client: Client | None = None | ||
|
|
||
| def get_client() -> Client: | ||
| global _supabase_client | ||
| if _supabase_client is None: | ||
| with _client_lock: | ||
| if _supabase_client is None: | ||
| _supabase_client = _get_supabase_client() | ||
| return _supabase_client |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| ============================= test session starts ============================= | ||
| platform win32 -- Python 3.12.12, pytest-9.0.2, pluggy-1.6.0 | ||
| rootdir: C:\Users\ysn65\Desktop\antigravity\philo-rag\backend | ||
| plugins: anyio-4.12.1, asyncio-1.3.0, cov-7.0.0 | ||
| asyncio: mode=Mode.STRICT, debug=False, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function | ||
| collected 0 items / 1 error | ||
|
|
||
| =================================== ERRORS ==================================== | ||
| ___________________ ERROR collecting tests/unit/test_llm.py ___________________ | ||
| tests\unit\test_llm.py:12: in <module> | ||
| from app.services.llm import get_english_translation, get_response_stream, get_response_stream_async | ||
| app\services\llm.py:8: in <module> | ||
| raise RuntimeError("GEMINI_API_KEY must be configured") | ||
| E RuntimeError: GEMINI_API_KEY must be configured | ||
| ============================== warnings summary =============================== | ||
| <frozen importlib._bootstrap>:488 | ||
| <frozen importlib._bootstrap>:488: DeprecationWarning: Type google._upb._message.MessageMapContainer uses PyType_Spec with a metaclass that has custom tp_new. This is deprecated and will no longer be allowed in Python 3.14. | ||
|
|
||
| <frozen importlib._bootstrap>:488 | ||
| <frozen importlib._bootstrap>:488: DeprecationWarning: Type google._upb._message.ScalarMapContainer uses PyType_Spec with a metaclass that has custom tp_new. This is deprecated and will no longer be allowed in Python 3.14. | ||
|
|
||
| -- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html | ||
| =========================== short test summary info =========================== | ||
| ERROR tests/unit/test_llm.py - RuntimeError: GEMINI_API_KEY must be configured | ||
| !!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!! | ||
| ======================== 2 warnings, 1 error in 4.52s ========================= |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,45 +1,87 @@ | ||
| import asyncio | ||
| import os | ||
|
|
||
| import sys | ||
| import pytest | ||
| from pathlib import Path | ||
|
|
||
| # dynamically add backend dir to path | ||
| backend_dir = Path(__file__).resolve().parent | ||
| # dynamically add backend root dir to path | ||
| backend_dir = Path(__file__).resolve().parents[2] | ||
| if str(backend_dir) not in sys.path: | ||
| sys.path.insert(0, str(backend_dir)) | ||
|
|
||
| from app.services.llm import get_english_translation, get_response_stream | ||
| from app.core.config import settings | ||
| import os | ||
|
|
||
| @pytest.mark.skipif(not settings.GEMINI_API_KEY, reason="GEMINI_API_KEY is not configured") | ||
| def test_translation(): | ||
| print("Testing translation...") | ||
| @pytest.fixture(autouse=True) | ||
| def setup_test_env(monkeypatch): | ||
| monkeypatch.setenv("GEMINI_API_KEY", "dummy_test_key") | ||
| monkeypatch.setenv("SUPABASE_URL", "http://localhost:8000") | ||
| monkeypatch.setenv("SUPABASE_SERVICE_KEY", "dummy_test_key") | ||
|
|
||
| # Ensure settings reflect the mocked env vars globally in case they were initialized | ||
| try: | ||
| from app.core.config import settings | ||
| monkeypatch.setattr(settings, "GEMINI_API_KEY", "dummy_test_key") | ||
| monkeypatch.setattr(settings, "SUPABASE_URL", "http://localhost:8000") | ||
| monkeypatch.setattr(settings, "SUPABASE_SERVICE_KEY", "dummy_test_key") | ||
| except ImportError: | ||
| pass | ||
|
|
||
| from unittest.mock import patch, MagicMock | ||
|
|
||
| def test_translation(setup_test_env): | ||
| print("Testing translation...") | ||
| from app.services.llm import get_english_translation | ||
| with patch("app.services.llm.translation_prompt") as mock_prompt, \ | ||
| patch("app.services.llm.llm") as _mock_llm, \ | ||
| patch("app.services.llm.StrOutputParser") as _mock_parser: | ||
|
|
||
| mock_chain = MagicMock() | ||
| mock_chain.invoke.return_value = "Translated Text" | ||
| mock_chain.__or__.return_value = mock_chain | ||
| mock_prompt.__or__.return_value = mock_chain | ||
|
|
||
| translated = get_english_translation("미덕이란 무엇인가?") | ||
| print("Translation:", translated) | ||
| assert translated.strip() != "", "Translation must not be empty" | ||
| except Exception as e: | ||
| raise AssertionError(f"Translation error: {str(e)}") | ||
| assert translated == "Translated Text", "Translation output mocked mismatch" | ||
|
|
||
| @pytest.mark.skipif(not settings.GEMINI_API_KEY, reason="GEMINI_API_KEY is not configured") | ||
| def test_streaming(): | ||
| def test_streaming(setup_test_env): | ||
| print("Testing streaming...") | ||
| try: | ||
| from app.services.llm import get_response_stream | ||
| with patch("app.services.llm.get_rag_prompt") as mock_prompt, \ | ||
| patch("app.services.llm.llm") as _mock_llm, \ | ||
| patch("app.services.llm.StrOutputParser") as _mock_parser: | ||
|
|
||
| mock_chain = MagicMock() | ||
| mock_chain.stream.return_value = ["안녕하세요", " ", "철학자", "입니다."] | ||
| mock_chain.__or__.return_value = mock_chain | ||
| mock_prompt.return_value.__or__.return_value = mock_chain | ||
|
|
||
| stream = get_response_stream(context="Virtue is excellence.", query="What is virtue?") | ||
| chunks_received = 0 | ||
| for chunk in stream: | ||
| print(chunk, end="", flush=True) | ||
| chunks_received += 1 | ||
| print("\nStream finished") | ||
| assert chunks_received > 0, "No chunks received from streaming API" | ||
| except Exception as e: | ||
| raise AssertionError(f"Stream error: {str(e)}") | ||
| results = list(stream) | ||
| assert results == ["안녕하세요", " ", "철학자", "입니다."], "Stream chunks mocked mismatch" | ||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_streaming_async(setup_test_env): | ||
| print("Testing streaming async...") | ||
| from app.services.llm import get_response_stream_async | ||
| with patch("app.services.llm.get_rag_prompt") as mock_prompt, \ | ||
| patch("app.services.llm.llm") as _mock_llm, \ | ||
| patch("app.services.llm.StrOutputParser") as _mock_parser: | ||
|
|
||
| mock_chain = MagicMock() | ||
| async def mock_astream(*_args, **_kwargs): | ||
| for chunk in ["안녕하세요", " ", "철학자", "입니다."]: | ||
| yield chunk | ||
| mock_chain.astream = mock_astream | ||
| mock_chain.__or__.return_value = mock_chain | ||
| mock_prompt.return_value.__or__.return_value = mock_chain | ||
|
|
||
| stream = get_response_stream_async(context="Virtue is excellence.", query="What is virtue?") | ||
| results = [chunk async for chunk in stream] | ||
| assert results == ["안녕하세요", " ", "철학자", "입니다."], "Async stream chunks mocked mismatch" | ||
|
Comment on lines
+62
to
+80
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check for pytest-asyncio in test dependencies
echo "=== Checking for pytest-asyncio in dependencies ==="
# Search in common dependency files
for file in pyproject.toml setup.py setup.cfg requirements*.txt; do
if [ -f "$file" ]; then
echo "--- Checking $file ---"
cat -n "$file" | grep -i "pytest-asyncio\|pytest_asyncio" || echo "Not found in $file"
fi
done
echo ""
echo "=== Listing all test/dev dependency files ==="
find . -maxdepth 2 -type f \( -name "pyproject.toml" -o -name "setup.py" -o -name "setup.cfg" -o -name "*requirements*.txt" \) 2>/dev/nullRepository: SanghunYun95/philo-rag Length of output: 191 🏁 Script executed: #!/bin/bash
# Read the requirements.txt file to see all dependencies
echo "=== Contents of backend/requirements.txt ==="
cat backend/requirements.txt
echo ""
echo "=== Searching for pytest or asyncio related packages ==="
cat backend/requirements.txt | grep -i "pytest\|asyncio"Repository: SanghunYun95/philo-rag Length of output: 436 🏁 Script executed: #!/bin/bash
# Check if there are other requirements files or test configuration
echo "=== Finding all requirements files ==="
find . -name "*requirements*.txt" -o -name "*requirements*.in" 2>/dev/null | head -20
echo ""
echo "=== Checking for pytest configuration ==="
find . -name "pytest.ini" -o -name "pyproject.toml" -o -name "setup.cfg" -o -name "conftest.py" 2>/dev/null | head -20
echo ""
echo "=== Checking backend directory structure ==="
ls -la backend/ | head -20Repository: SanghunYun95/philo-rag Length of output: 1025 🏁 Script executed: #!/bin/bash
# Check the pytest log for information about pytest-asyncio
echo "=== Checking pytest log ==="
cat backend/pytest_log.txt | head -100
echo ""
echo "=== Searching for asyncio or pytest-asyncio mentions in logs ==="
grep -i "asyncio\|pytest.mark.asyncio" backend/pytest_log.txt || echo "No mentions found"Repository: SanghunYun95/philo-rag Length of output: 3921 🏁 Script executed: #!/bin/bash
# Check the UTF-8 version of the log for clearer reading
echo "=== Checking pytest_log_utf8.txt ==="
cat backend/pytest_log_utf8.txt | grep -A 5 "plugins:" || cat backend/pytest_log_utf8.txt | head -50Repository: SanghunYun95/philo-rag Length of output: 461 🏁 Script executed: #!/bin/bash
# The log shows asyncio-1.3.0 is installed. Let's verify where it comes from
echo "=== Checking if pytest-asyncio comes from dependencies ==="
grep -r "pytest-asyncio\|pytest_asyncio" . --include="*.txt" --include="*.toml" --include="*.py" 2>/dev/null | grep -v ".coverage" | grep -v "pytest_log" || echo "Not found in source files"
echo ""
echo "=== Checking for dev/test dependencies in the repo root ==="
ls -la | grep -i "require\|setup\|pyproject"Repository: SanghunYun95/philo-rag Length of output: 211
🤖 Prompt for AI Agents |
||
|
|
||
| # For manual execution | ||
| async def run_manual_test(): | ||
| if __name__ == "__main__": | ||
| import asyncio | ||
| test_translation() | ||
| test_streaming() | ||
|
|
||
| if __name__ == "__main__": | ||
| asyncio.run(run_manual_test()) | ||
| asyncio.run(test_streaming_async()) | ||
Uh oh!
There was an error while loading. Please reload this page.