fix: hybrid 검색 단순화 — FTS + ILIKE (vector/trgm 복잡 쿼리 제거)
asyncpg 파라미터 바인딩 충돌 문제 근본 해결. 한국어 검색: ILIKE fallback으로 안정 동작. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -143,55 +143,27 @@ async def _search_vector(session: AsyncSession, query: str, limit: int) -> list[
|
|||||||
|
|
||||||
|
|
||||||
async def _search_hybrid(session: AsyncSession, query: str, limit: int) -> list[SearchResult]:
|
async def _search_hybrid(session: AsyncSession, query: str, limit: int) -> list[SearchResult]:
|
||||||
"""하이브리드 검색 — FTS + 트리그램 + 벡터 가중 합산"""
|
"""하이브리드 검색 — FTS + ILIKE (안정적 한국어 지원)"""
|
||||||
# 벡터 임베딩 생성 (실패 시 FTS+트리그램만)
|
|
||||||
query_embedding = None
|
|
||||||
try:
|
|
||||||
client = AIClient()
|
|
||||||
query_embedding = await client.embed(query)
|
|
||||||
await client.close()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
vector_clause = ""
|
|
||||||
vector_score = "0"
|
|
||||||
params = {"query": query, "limit": limit, "w_fts": W_FTS, "w_trgm": W_TRGM, "w_vector": W_VECTOR}
|
|
||||||
|
|
||||||
if query_embedding:
|
|
||||||
vector_clause = "LEFT JOIN LATERAL (SELECT 1 - (d.embedding <=> :embedding::vector) AS vscore) v ON true"
|
|
||||||
vector_score = "coalesce(v.vscore, 0)"
|
|
||||||
params["embedding"] = str(query_embedding)
|
|
||||||
else:
|
|
||||||
# 벡터 없으면 FTS+트리그램만 사용
|
|
||||||
params["w_fts"] = 0.6
|
|
||||||
params["w_trgm"] = 0.4
|
|
||||||
params["w_vector"] = 0.0
|
|
||||||
|
|
||||||
result = await session.execute(
|
result = await session.execute(
|
||||||
text(f"""
|
text("""
|
||||||
SELECT * FROM (
|
SELECT id, title, ai_domain, ai_summary, file_format,
|
||||||
SELECT d.id, d.title, d.ai_domain, d.ai_summary, d.file_format,
|
GREATEST(
|
||||||
(
|
coalesce(ts_rank(
|
||||||
:w_fts * coalesce(ts_rank(
|
to_tsvector('simple', coalesce(title, '') || ' ' || coalesce(extracted_text, '')),
|
||||||
to_tsvector('simple', coalesce(d.title, '') || ' ' || coalesce(d.extracted_text, '')),
|
plainto_tsquery('simple', :query)
|
||||||
plainto_tsquery('simple', :query)
|
), 0),
|
||||||
), 0)
|
CASE WHEN (coalesce(title, '') || ' ' || coalesce(extracted_text, ''))
|
||||||
+ :w_trgm * coalesce(similarity(
|
ILIKE '%%' || :query || '%%' THEN 0.5 ELSE 0 END
|
||||||
coalesce(d.title, '') || ' ' || coalesce(d.extracted_text, ''),
|
) AS score,
|
||||||
:query
|
left(extracted_text, 200) AS snippet
|
||||||
), 0)
|
FROM documents
|
||||||
+ :w_vector * {vector_score}
|
WHERE to_tsvector('simple', coalesce(title, '') || ' ' || coalesce(extracted_text, ''))
|
||||||
) AS score,
|
@@ plainto_tsquery('simple', :query)
|
||||||
left(d.extracted_text, 200) AS snippet
|
OR (coalesce(title, '') || ' ' || coalesce(extracted_text, ''))
|
||||||
FROM documents d
|
ILIKE '%%' || :query || '%%'
|
||||||
{vector_clause}
|
ORDER BY score DESC
|
||||||
WHERE coalesce(d.extracted_text, '') != ''
|
|
||||||
OR (coalesce(d.title, '') || ' ' || coalesce(d.extracted_text, '')) ILIKE '%%' || :query || '%%'
|
|
||||||
) sub
|
|
||||||
WHERE sub.score > 0.001
|
|
||||||
ORDER BY sub.score DESC
|
|
||||||
LIMIT :limit
|
LIMIT :limit
|
||||||
"""),
|
"""),
|
||||||
params,
|
{"query": query, "limit": limit},
|
||||||
)
|
)
|
||||||
return [SearchResult(**row._mapping) for row in result]
|
return [SearchResult(**row._mapping) for row in result]
|
||||||
|
|||||||
Reference in New Issue
Block a user