Skip to content

Feat/fix unanswered queries#20

Merged
SanghunYun95 merged 51 commits intomainfrom
feat/fix-unanswered-queries
Mar 6, 2026
Merged

Feat/fix unanswered queries#20
SanghunYun95 merged 51 commits intomainfrom
feat/fix-unanswered-queries

Conversation

@SanghunYun95
Copy link
Copy Markdown
Owner

@SanghunYun95 SanghunYun95 commented Mar 6, 2026

Summary by CodeRabbit

성능 개선 및 안정성 강화

  • 성능 개선
    • 주요 작업 타임아웃을 30초로 확대하여 작업 안정성과 성공률을 향상시켰습니다.
    • 데이터베이스 호출 동시성을 제어해 과부하를 완화했습니다.
  • 버그 수정
    • 번역·임베딩·검색 단계의 타임아웃 이후 오류 처리를 개선해 더 명확한 진단 정보를 제공합니다.
    • 스트리밍 종료·클라이언트 연결 해제 상황을 안정적으로 처리합니다.
  • Chores
    • 단계별 성능 로깅과 모니터링을 강화했습니다.
  • 호환성
    • 데이터베이스 클라이언트의 연결 타임아웃 설정을 적용했습니다.

…ifespan, and move asyncio import to stdlib group
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
philo-rag Ready Ready Preview, Comment Mar 6, 2026 1:18am

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 6, 2026

📝 Walkthrough

Walkthrough

채팅 경로에 성능 타이밍 계측(t0–t6), 전역 CHAT_TIMEOUT(30s) 및 DB RPC 동시성 제어 세마포어를 추가하고, 번역·임베딩·DB검색·제목생성 호출에 타임아웃·타이밍 로그·타임아웃 기반 에러 처리를 도입했습니다. llm 서비스의 스트림 종료 안전성 로직도 개선되었습니다.

Changes

Cohort / File(s) Summary
Chat API (타이밍·타임아웃·세마포어)
backend/app/api/routes/chat.py
CHAT_TIMEOUT(30.0) 및 _db_rpc_semaphore 추가; perf_counter 기반 타이밍 계측(t0–t6); 번역·임베딩·DB 검색·채팅 제목 생성 호출에 asyncio.wait_for 타임아웃 적용(10s→30s); 단계별 경과 시간 로그와 타임아웃/예외 메시지 추가; DB 검색을 세마포어로 감싸고 결과 수·소요시간 로깅 및 no-docs 경로에서의 타이밍-aware 처리; 스트리밍(첫 청크 시간, 청크 수, 연결 끊김 로그, 폴백 동작) 수정.
LLM 서비스 (스트림 종료 안전성, 로그)
backend/app/services/llm.py
스트림 로그에서 쿼리 원문 대신 쿼리 길이로 로깅 변경; 제너레이터 닫기(aclose())를 try/except로 감싸 예외 무시/디버그 로깅 처리.
Database client config (Supabase 타임아웃)
backend/app/services/database.py
ClientOptions(postgrest_client_timeout=30)를 생성해 create_client(..., options=...)에 전달하여 Supabase 클라이언트의 PostgREST 요청 타임아웃 설정 추가.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant API as Chat API
    participant Translator as Translation Service
    participant Embed as Embedding Service
    participant DB as Database (Supabase)
    participant LLM as LLM Service / Stream

    rect rgba(200,220,255,0.5)
    Client->>API: 요청 (채팅 메시지)
    API->>Translator: 번역 (async, timeout=30s)
    Translator-->>API: 번역된 텍스트 + elapsed
    end

    rect rgba(200,255,200,0.5)
    API->>Embed: 임베딩 생성 (async, timeout=30s)
    Embed-->>API: 벡터 + elapsed
    end

    rect rgba(255,230,200,0.5)
    API->>DB: 문서 검색 (세마포어 획득, timeout=30s)
    DB-->>API: 문서 목록 + elapsed
    end

    rect rgba(255,200,220,0.5)
    API->>LLM: 스트리밍 생성 (stream)
    LLM-->>API: 첫 청크 (till first chunk time logged)
    LLM-->>Client: SSE 청크 스트리밍
    Client-->>API: 연결 끊김 (optional)
    API->>API: 스트리밍 완료 로그 (청크수/총시간) 또는 폴백 전송
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Feat/book metadata #17: 유사한 chat 엔드포인트(번역·임베딩·타임아웃·SSE 처리) 변경을 포함 — 타임아웃/스트리밍 관련 코드 레이어에서 강한 관련성 있음.
  • Feat/book metadata #16: embedding 호출에 대한 asyncio.wait_for 적용을 다룸 — embedding 타임아웃 처리 구현과 직접적 연관성 있음.

개요

두 개의 백엔드 파일에서 성능 타이밍 계측을 추가하고, 번역, 임베딩, 데이터베이스 검색 및 채팅 제목 생성의 타임아웃을 10초에서 30초로 증가시켰으며, 경과 시간 정보가 포함된 개선된 에러 처리 및 로깅을 구현했습니다.

변경사항

집단 / 파일 요약
성능 계측 및 타임아웃 조정
backend/app/api/routes/chat.py
perf_counter를 사용한 타이밍 계측 추가 (t0–t6); 번역, 임베딩, 데이터베이스 검색, 채팅 제목 생성의 타임아웃을 10초에서 30초로 증가; 각 단계 후 경과 시간을 포함한 로깅 문구 추가.
향상된 에러 처리 및 스트리밍 로직
backend/app/api/routes/chat.py
타임아웃 시 경과 시간 보고 추가; 데이터베이스 검색 타임아웃 및 no-document 경로에 대한 로깅 강화; LLM 청크 수와 총 스트리밍 시간을 포함한 클라이언트 연결 해제 추적; 0개 청크일 때의 폴백 동작 조정.
로깅 및 에러 처리 개선
backend/app/services/llm.py
타임아웃 로깅을 쿼리 길이 포함으로 변경; 제너레이터 종료 시 try/except로 aclose() 감싸기.
Supabase 클라이언트 옵션
backend/app/services/database.py
ClientOptions(postgrest_client_timeout=30) 사용하여 DB 클라이언트의 PostgREST 요청 타임아웃 설정 추가.

추정 코드 리뷰 노력

🎯 4 (Complex) | ⏱️ ~45 minutes

관련 가능성이 있는 PR

  • Feat/book metadata #17: 동일한 backend/app/api/routes/chat.py 파일의 번역/임베딩 타임아웃 처리 및 SSE 에러 처리 로직을 수정하며, 이번 PR은 타임아웃을 30초로 확장하고 타이밍 계측 및 로깅을 추가하므로 관련이 있습니다.
  • Feat/book metadata #16: embedding 타임아웃 래핑(예: asyncio.wait_for)을 다루며, embedding 호출 타임아웃 처리와 직접 연결됩니다.

🐰 초를 재며 팔짝씩, 내가 말하네
번역은 길게, 타임아웃은 길게 (30초!)
로그엔 발자국처럼 시간이 찍히고
청크는 춤추고 끊김은 쿨하게 기록되네 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive 제목이 변경사항의 주요 내용과 일치하지 않습니다. PR의 실제 변경사항은 채팅 API에 타이밍 계측, 동시성 제어, 타임아웃 처리를 추가한 것이지만, 제목은 '답변되지 않은 쿼리'라는 추상적이고 모호한 표현을 사용합니다. 제목을 더 구체적으로 변경하세요. 예를 들어 'Add timeout instrumentation and concurrency control to chat API' 또는 'Implement timing metrics and database semaphore for chat endpoint' 등으로 수정하면 변경사항을 명확히 설명할 수 있습니다.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/fix-unanswered-queries

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/app/api/routes/chat.py`:
- Around line 82-87: The DB RPC call using _search_documents must have an
explicit timeout and null-safe handling: wrap the
asyncio.to_thread(_search_documents, query_vector) call with asyncio.wait_for
using a sensible timeout, catch asyncio.TimeoutError and log via logger.error,
and on success ensure response.data is treated safely (e.g., set documents =
response.data or [] if response.data is None) before calling len(documents) in
logger.info; update the try/except to handle both timeout and generic exceptions
and ensure documents is always a list when referenced.

In `@backend/app/services/llm.py`:
- Around line 135-138: The warning log currently prints the raw user query
(logger.warning(... Query: {query})), which exposes PII; change the
logger.warning call in backend/app/services/llm.py to omit the user text and
instead log only non-sensitive metadata (for example the query length and a
request ID or correlation_id if available, or generate/attach one like
request_id), e.g., include variables such as len(query) and
request_id/correlation_id; leave the subsequent raise as-is. Ensure you
reference and use the existing logger variable and the query variable only to
compute length (not the content).
- Around line 139-140: finally 블록에서 await generator.aclose()가 실행될 때 발생한 예외가 이전에
발생한 TimeoutError 같은 원래 예외를 덮어쓸 수 있으므로, generator.aclose() 호출을 별도의 보호문으로 감싸 원래
예외가 유지되도록 처리하세요: 즉 현재 finally 블록 안의 await generator.aclose()를 try/except로
감싸고(aclose 관련 예외는 로그만 남기거나 무시) 원래 예외가 있으면 재발생시키거나 그대로 유지되도록 변경하여
generator.aclose() 예외가 원래 TimeoutError을 덮지 않게 하세요 (대상 식별자: generator.aclose(),
finally 블록, TimeoutError).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a014b00a-6eba-4574-9fbb-45ca038296d3

📥 Commits

Reviewing files that changed from the base of the PR and between 21c1bb5 and 9b00c6c.

📒 Files selected for processing (6)
  • backend/app/api/routes/chat.py
  • backend/app/core/config.py
  • backend/app/main.py
  • backend/app/services/embedding.py
  • backend/app/services/llm.py
  • backend/requirements.txt

Comment thread backend/app/api/routes/chat.py
Comment thread backend/app/services/llm.py
Comment thread backend/app/services/llm.py Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
backend/app/api/routes/chat.py (1)

48-49: 타임아웃 값(30.0)은 상수로 올려서 드리프트를 막는 것을 권장합니다.

현재 여러 지점에 하드코딩되어 있어 추후 조정 시 누락 위험이 있습니다.

Also applies to: 66-67, 85-86, 199-199

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/api/routes/chat.py` around lines 48 - 49, Multiple calls set
timeout=30.0 inline; extract this magic value to a module-level constant (e.g.,
CHAT_TIMEOUT = 30.0) and replace all occurrences of timeout=30.0 with
timeout=CHAT_TIMEOUT to prevent drift and make future adjustments safe; ensure
the constant is defined near the top of the module and update any imports/uses
in the same module so functions/methods that currently pass timeout=30.0 (look
for the literal timeout=30.0 instances) reference CHAT_TIMEOUT instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/app/api/routes/chat.py`:
- Around line 83-86: The Supabase client is created without RPC/HTTP timeouts so
asyncio.wait_for(asyncio.to_thread(_search_documents, ...)) only cancels the
wrapper thread but doesn't stop the underlying DB RPC; update get_client() in
backend/app/services/database.py to call create_client(supabase_url,
supabase_key, ClientOptions(postgrest_client_timeout=30)) (or equivalent
ClientOptions param) so the HTTP/RPC call has a 30s timeout, and in
backend/app/api/routes/chat.py wrap calls to
asyncio.wait_for(asyncio.to_thread(_search_documents, query_vector), ...) with a
semaphore to limit concurrent executions (create/await an asyncio.Semaphore and
acquire/release around the wait_for/to_thread block) to prevent thread-pool
exhaustion.

---

Nitpick comments:
In `@backend/app/api/routes/chat.py`:
- Around line 48-49: Multiple calls set timeout=30.0 inline; extract this magic
value to a module-level constant (e.g., CHAT_TIMEOUT = 30.0) and replace all
occurrences of timeout=30.0 with timeout=CHAT_TIMEOUT to prevent drift and make
future adjustments safe; ensure the constant is defined near the top of the
module and update any imports/uses in the same module so functions/methods that
currently pass timeout=30.0 (look for the literal timeout=30.0 instances)
reference CHAT_TIMEOUT instead.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 525d0865-5d2a-4d31-b8d6-f8a804656cac

📥 Commits

Reviewing files that changed from the base of the PR and between 9b00c6c and bba2528.

📒 Files selected for processing (2)
  • backend/app/api/routes/chat.py
  • backend/app/services/llm.py

Comment thread backend/app/api/routes/chat.py Outdated
Comment on lines +83 to +86
response = await asyncio.wait_for(
asyncio.to_thread(_search_documents, query_vector),
timeout=30.0,
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find database.py file
fd database.py

# Check its location and size
find . -name database.py -type f -exec wc -l {} \;

Repository: SanghunYun95/philo-rag

Length of output: 136


🏁 Script executed:

#!/bin/bash
# Search for timeout configurations in database.py
rg -n -C3 'create_client|ClientOptions|timeout|postgrest|httpx|supabase' backend/app/services/database.py 2>/dev/null || echo "File not found at expected location"

# Also try to find database.py in other locations
fd database.py -x cat {} \;

Repository: SanghunYun95/philo-rag

Length of output: 2228


🏁 Script executed:

#!/bin/bash
# Verify the wait_for(to_thread pattern in chat.py
rg -n -C5 'asyncio\.wait_for\(\s*asyncio\.to_thread\(' backend/app/api/routes/chat.py

Repository: SanghunYun95/philo-rag

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Find and examine _search_documents function
rg -n -B2 -A10 'def _search_documents' backend/

Repository: SanghunYun95/philo-rag

Length of output: 939


🏁 Script executed:

#!/bin/bash
# Read chat.py lines 80-95 to see the exact code at lines 83-86
sed -n '75,95p' backend/app/api/routes/chat.py

Repository: SanghunYun95/philo-rag

Length of output: 985


🏁 Script executed:

#!/bin/bash
# Check entire chat.py to understand the context
wc -l backend/app/api/routes/chat.py
cat -n backend/app/api/routes/chat.py

Repository: SanghunYun95/philo-rag

Length of output: 9740


🏁 Script executed:

#!/bin/bash
# Search for any timeout configuration in the entire codebase
rg -n 'timeout' backend/ --type py

Repository: SanghunYun95/philo-rag

Length of output: 1141


🏁 Script executed:

#!/bin/bash
# Verify Supabase Python client supports timeout configuration
rg -n 'from supabase|import supabase' backend/ --type py
# Check if there's any ClientOptions or timeout configuration attempts
rg -n 'ClientOptions|timeout' backend/app/services/ --type py

Repository: SanghunYun95/philo-rag

Length of output: 674


🌐 Web query:

Supabase Python SDK httpx timeout configuration create_client

💡 Result:

In supabase-py, create_client(url, key, options=...) expects a ClientOptions object, not a plain dict. Timeouts are configured via ClientOptions fields (and/or by supplying your own httpx.Client). [1]

Sync client (create_client)

import httpx
from supabase import create_client, ClientOptions

# Option A: set the PostgREST (DB REST API) timeout only
opts = ClientOptions(postgrest_client_timeout=30)  # seconds (or httpx.Timeout)
supabase = create_client(SUPABASE_URL, SUPABASE_KEY, options=opts)

postgrest_client_timeout is passed into the underlying PostgREST client as its request timeout. [1][2]

import httpx
from supabase import create_client, ClientOptions

# Option B: supply a custom httpx client (shared by PostgREST, Auth, Storage, Functions)
http = httpx.Client(timeout=httpx.Timeout(30.0, connect=5.0))
opts = ClientOptions(httpx_client=http)

supabase = create_client(SUPABASE_URL, SUPABASE_KEY, options=opts)

If httpx_client is provided, Supabase uses it for PostgREST/Auth/Storage/Functions. [1][2]

Async client (create_async_client / acreate_client)

Use AsyncClientOptions(httpx_client=...) with an httpx.AsyncClient. [1]

Other per-service timeouts available on ClientOptions:

  • storage_client_timeout
  • function_client_timeout
  • postgrest_client_timeout [1]

Sources:
[1] ClientOptions / AsyncClientOptions definition (timeouts + httpx_client) (raw.githubusercontent.com)
[2] Sync client wiring: how postgrest_client_timeout / httpx_client are applied (raw.githubusercontent.com)

Citations:


wait_for(to_thread(...)) 타임아웃은 DB RPC 작업을 실제로 중단하지 못합니다.

backend/app/services/database.pyget_client()에서 Supabase 클라이언트를 생성할 때 ClientOptions를 통한 HTTP/RPC 타임아웃이 설정되지 않았습니다. 현재 코드는 create_client(supabase_url, supabase_key)만 호출하므로, RPC 요청은 무한정 대기할 수 있습니다. 이 상태에서 asyncio.wait_for(asyncio.to_thread(...))는 asyncio 래퍼만 취소하고 스레드의 실제 RPC 호출을 중단하지 못하므로, 타임아웃 발생 후에도 _search_documents 스레드 작업이 계속 실행되어 스레드 풀 고갈로 이어질 수 있습니다.

필수 조치:

  1. backend/app/services/database.py에서 클라이언트 생성 시 ClientOptions(postgrest_client_timeout=30)을 전달
  2. 추가적으로 동시성 제한(semaphore)을 wait_for 블록에 적용
해결 방법
import threading
from supabase import create_client, Client, ClientOptions
from app.core.config import settings

SUPABASE_CONFIG_ERROR = "SUPABASE_URL and SUPABASE_SERVICE_KEY must be configured"

def _get_supabase_client() -> Client:
    supabase_url = settings.SUPABASE_URL
    supabase_key = settings.SUPABASE_SERVICE_KEY
    if not supabase_url or not supabase_key:
        raise RuntimeError(SUPABASE_CONFIG_ERROR)
+   options = ClientOptions(postgrest_client_timeout=30)
-   return create_client(supabase_url, supabase_key)
+   return create_client(supabase_url, supabase_key, options=options)

_client_lock = threading.Lock()
_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

그리고 backend/app/api/routes/chat.py 라인 82–86에 동시성 제한 추가:

+_db_rpc_semaphore = asyncio.Semaphore(16)

     try:
+        async with _db_rpc_semaphore:
             response = await asyncio.wait_for(
                 asyncio.to_thread(_search_documents, query_vector),
                 timeout=30.0,
             )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/api/routes/chat.py` around lines 83 - 86, The Supabase client is
created without RPC/HTTP timeouts so
asyncio.wait_for(asyncio.to_thread(_search_documents, ...)) only cancels the
wrapper thread but doesn't stop the underlying DB RPC; update get_client() in
backend/app/services/database.py to call create_client(supabase_url,
supabase_key, ClientOptions(postgrest_client_timeout=30)) (or equivalent
ClientOptions param) so the HTTP/RPC call has a 30s timeout, and in
backend/app/api/routes/chat.py wrap calls to
asyncio.wait_for(asyncio.to_thread(_search_documents, query_vector), ...) with a
semaphore to limit concurrent executions (create/await an asyncio.Semaphore and
acquire/release around the wait_for/to_thread block) to prevent thread-pool
exhaustion.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
backend/app/api/routes/chat.py (2)

17-20: 동시성 제한값도 설정화하면 운영 튜닝이 쉬워집니다.

Line 20의 16은 환경별로 적정치가 달라질 수 있어 설정값으로 빼두는 것을 권장합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/api/routes/chat.py` around lines 17 - 20, The semaphore
concurrency hardcode (_db_rpc_semaphore = asyncio.Semaphore(16)) should be
replaced with a configurable value; add or read a setting (e.g.,
DB_RPC_CONCURRENCY or a config entry) and use it to construct _db_rpc_semaphore
(keeping CHAT_TIMEOUT as-is), validate/parse the env/config as int and provide a
sensible default (16) if unset, and update any docstring or comment near
_db_rpc_semaphore to note it is configurable.

157-164: 첫 청크 로그 시점이 실제 전송 시점과 어긋날 수 있습니다.

Line 157의 로그가 disconnect 체크(Line 161)보다 먼저 실행되어, 실제 전송이 없는데도 첫 청크 수신 로그가 찍힐 수 있습니다.

🧹 제안 수정
         async for chunk in get_response_stream_async(context=combined_context, query=english_query, history=formatted_history):
-            if chunk_count == 0:
-                logger.info(f"First LLM chunk received in {time.perf_counter() - t6:.2f}s")
-                
             # If client disconnects, stop generating
             if await request.is_disconnected():
                 disconnected = True
                 logger.info(f"Client disconnected during streaming after {chunk_count} chunks.")
                 break
+            if chunk_count == 0:
+                logger.info(f"First LLM chunk received in {time.perf_counter() - t6:.2f}s")
                 
             chunk_count += 1
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/api/routes/chat.py` around lines 157 - 164, The "First LLM chunk
received" log is emitted before we confirm the client is still connected, so it
can fire even when the client disconnected; move the first-chunk logging and any
increment/usage of chunk_count to occur after the disconnected check (i.e., call
await request.is_disconnected() first and only if False then set chunk_count ==
0 handling and call logger.info). Update the loop around chunk handling in
chat.py to reference chunk_count, request.is_disconnected(), and logger.info so
the first-chunk message is logged only when a chunk is actually sent to a
connected client.
backend/app/services/database.py (1)

18-19: 타임아웃 하드코딩은 공통 설정으로 단일화하는 편이 안전합니다.

Line 18의 30은 다른 경로의 타임아웃 상수와 쉽게 드리프트가 생길 수 있습니다. 설정값(예: settings)으로 단일화하면 운영 중 튜닝/변경이 훨씬 안전해집니다.

♻️ 제안 예시
+POSTGREST_TIMEOUT = float(getattr(settings, "POSTGREST_TIMEOUT", 30.0))
 ...
-options = ClientOptions(postgrest_client_timeout=30)
+options = ClientOptions(postgrest_client_timeout=POSTGREST_TIMEOUT)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/services/database.py` around lines 18 - 19, The hardcoded timeout
30 in the ClientOptions construction should be replaced with a centralized
configuration value; change the call that constructs
ClientOptions(postgrest_client_timeout=30) to use a settings constant (e.g.,
settings.POSTGREST_CLIENT_TIMEOUT or settings.DB_CLIENT_TIMEOUT) and ensure the
settings value is used when calling create_client(supabase_url, supabase_key,
options=options); update any import or settings reference required so
ClientOptions and postgrest_client_timeout use the single source-of-truth
timeout value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/app/api/routes/chat.py`:
- Around line 95-97: In the except asyncio.TimeoutError handler (the block that
currently calls logger.error and then yields {"event": "error", "data": "..."}),
replace logger.error(...) with logger.exception(...) so the timeout is logged
consistently with other handlers and includes the stack trace; leave the yield
{"event": "error", "data": "..."} behavior unchanged.

---

Nitpick comments:
In `@backend/app/api/routes/chat.py`:
- Around line 17-20: The semaphore concurrency hardcode (_db_rpc_semaphore =
asyncio.Semaphore(16)) should be replaced with a configurable value; add or read
a setting (e.g., DB_RPC_CONCURRENCY or a config entry) and use it to construct
_db_rpc_semaphore (keeping CHAT_TIMEOUT as-is), validate/parse the env/config as
int and provide a sensible default (16) if unset, and update any docstring or
comment near _db_rpc_semaphore to note it is configurable.
- Around line 157-164: The "First LLM chunk received" log is emitted before we
confirm the client is still connected, so it can fire even when the client
disconnected; move the first-chunk logging and any increment/usage of
chunk_count to occur after the disconnected check (i.e., call await
request.is_disconnected() first and only if False then set chunk_count == 0
handling and call logger.info). Update the loop around chunk handling in chat.py
to reference chunk_count, request.is_disconnected(), and logger.info so the
first-chunk message is logged only when a chunk is actually sent to a connected
client.

In `@backend/app/services/database.py`:
- Around line 18-19: The hardcoded timeout 30 in the ClientOptions construction
should be replaced with a centralized configuration value; change the call that
constructs ClientOptions(postgrest_client_timeout=30) to use a settings constant
(e.g., settings.POSTGREST_CLIENT_TIMEOUT or settings.DB_CLIENT_TIMEOUT) and
ensure the settings value is used when calling create_client(supabase_url,
supabase_key, options=options); update any import or settings reference required
so ClientOptions and postgrest_client_timeout use the single source-of-truth
timeout value.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 86e2a1d5-d89a-4fec-a513-6e0fa2b73251

📥 Commits

Reviewing files that changed from the base of the PR and between bba2528 and ad7e026.

📒 Files selected for processing (2)
  • backend/app/api/routes/chat.py
  • backend/app/services/database.py

Comment on lines +95 to +97
except asyncio.TimeoutError:
logger.error(f"Database search timed out after {time.perf_counter() - t4:.2f}s")
yield {"event": "error", "data": "검색이 지연되고 있어요. 잠시 후 다시 시도해 주세요."}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read-only verification: timeout except 블록의 로깅 API 확인
rg -n -C2 'except asyncio\.TimeoutError:|logger\.(error|exception)\(' backend/app/api/routes/chat.py

Repository: SanghunYun95/philo-rag

Length of output: 2296


데이터베이스 검색 타임아웃 핸들러의 로깅 일관성 개선 필요

Line 96의 logger.errorlogger.exception으로 변경해 주세요. 현재 코드는 같은 파일의 다른 TimeoutError 핸들러들(라인 57, 75, 215)과 달리 로깅 방식이 불일치합니다. 또한 logger.exception을 사용하면 스택 트레이스 정보를 포함하여 디버깅이 용이합니다.

제안 수정
 except asyncio.TimeoutError:
-    logger.error(f"Database search timed out after {time.perf_counter() - t4:.2f}s")
+    logger.exception(f"Database search timed out after {time.perf_counter() - t4:.2f}s")
     yield {"event": "error", "data": "검색이 지연되고 있어요. 잠시 후 다시 시도해 주세요."}
🧰 Tools
🪛 Ruff (0.15.2)

[warning] 96-96: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/api/routes/chat.py` around lines 95 - 97, In the except
asyncio.TimeoutError handler (the block that currently calls logger.error and
then yields {"event": "error", "data": "..."}), replace logger.error(...) with
logger.exception(...) so the timeout is logged consistently with other handlers
and includes the stack trace; leave the yield {"event": "error", "data": "..."}
behavior unchanged.

@SanghunYun95 SanghunYun95 merged commit 98bf7bd into main Mar 6, 2026
3 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Mar 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant