Skip to content

Commit 412a778

Browse files
SonAIengineclaude
andcommitted
feat: 검색에서 kind/tag/search_keywords 활용 — FTS + 랭킹 부스트
## FTS 개선 (memory.py) - tag 매칭 가중치 0.5→1.0 (핵심 주제 반영) - properties["_search_keywords"] 매칭 추가 (LLM 생성 검색 키워드, 가중치 1.5) - properties["_summary"] 매칭 추가 (LLM 생성 요약, 가중치 0.5) ## 랭킹 부스트 (search.py) - kind-intent 부스트: 쿼리에 "실패"→LESSON, "정책"→RULE 등 매칭 시 +0.05 - tag-query 부스트: 쿼리 키워드가 노드 태그에 정확 매칭 시 +0.03/태그 - 보수적 부스트로 범용 태그에 의한 노이즈 최소화 ## Ablation 결과 - S1 +Ontology: S0 대비 -0.1% (이전 -0.8%) → 거의 중립 달성 - 규칙 기반 태그의 구조적 한계 — LLM 태그에서 효과 기대 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 33dca5f commit 412a778

2 files changed

Lines changed: 55 additions & 1 deletion

File tree

src/synaptic/backends/memory.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,16 @@ async def search_fts(self, query: str, *, limit: int = 20) -> list[Node]:
125125
# Tag 매칭 보너스
126126
if node.tags:
127127
tag_text = " ".join(node.tags).lower()
128-
score += sum(0.5 for t in terms if t in tag_text)
128+
score += sum(1.0 for t in terms if t in tag_text)
129+
130+
# _search_keywords 매칭 (LLM이 생성한 검색 최적화 키워드)
131+
if node.properties:
132+
search_kw = node.properties.get("_search_keywords", "").lower()
133+
if search_kw:
134+
score += sum(1.5 for t in terms if t in search_kw)
135+
summary = node.properties.get("_summary", "").lower()
136+
if summary:
137+
score += sum(0.5 for t in terms if t in summary)
129138

130139
if score > 0:
131140
scored.append((node, score))

src/synaptic/search.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,31 @@
99
from synaptic.resonance import ResonanceScorer
1010
from synaptic.synonyms import expand_synonyms
1111

12+
# Kind-query 키워드 매핑 (쿼리에 이런 단어가 있으면 해당 kind 부스트)
13+
_KIND_QUERY_HINTS: dict[NodeKind, list[str]] = {
14+
NodeKind.LESSON: [
15+
"실패", "에러", "오류", "장애", "교훈", "배운", "주의",
16+
"failure", "error", "incident", "lesson", "postmortem",
17+
],
18+
NodeKind.RULE: [
19+
"규칙", "정책", "규정", "금지", "필수", "가이드",
20+
"rule", "policy", "constraint", "must", "forbidden",
21+
],
22+
NodeKind.DECISION: [
23+
"결정", "선택", "판단", "채택", "어떻게",
24+
"decision", "choice", "decided", "approach",
25+
],
26+
NodeKind.ARTIFACT: [
27+
"api", "엔드포인트", "스키마", "명세", "코드",
28+
"endpoint", "schema", "spec", "interface",
29+
],
30+
NodeKind.ENTITY: [
31+
"회사", "조직", "제품", "서비스", "시스템",
32+
"company", "organization", "product", "service",
33+
],
34+
}
35+
_KIND_BOOST = 0.05 # kind 매칭 시 search_score 부스트량 (보수적)
36+
1237

1338
class HybridSearch:
1439
"""3-stage fallback search: FTS+vector → synonym expansion → query rewrite."""
@@ -107,10 +132,30 @@ async def search(
107132
if node.kind in kind_set
108133
}
109134

135+
# Kind-intent boost: 쿼리 키워드와 매칭되는 kind에 부스트
136+
preferred_kinds: set[NodeKind] = set()
137+
q_lower = query.lower()
138+
for kind, hints in _KIND_QUERY_HINTS.items():
139+
if any(h in q_lower for h in hints):
140+
preferred_kinds.add(kind)
141+
142+
# Tag-query boost: 쿼리 키워드가 노드 태그에 있으면 부스트
143+
query_terms_set = set(query.lower().split())
144+
110145
# Score with resonance
111146
now = time()
112147
activated: list[ActivatedNode] = []
113148
for _nid, (node, search_score) in all_nodes.items():
149+
# kind 부스트
150+
if preferred_kinds and node.kind in preferred_kinds:
151+
search_score = min(1.0, search_score + _KIND_BOOST)
152+
# tag 부스트 (정확 매칭만 — 2글자 이상 태그만)
153+
if node.tags and query_terms_set:
154+
tag_set = {t.lower() for t in node.tags if len(t) >= 2}
155+
tag_overlap = len(query_terms_set & tag_set)
156+
if tag_overlap > 0:
157+
search_score = min(1.0, search_score + tag_overlap * 0.03)
158+
114159
resonance = self._scorer.score(node, search_score=search_score, now=now)
115160
activated.append(
116161
ActivatedNode(

0 commit comments

Comments
 (0)