Skip to content

Commit 2feab2f

Browse files
rlafurudReokyoungKimclaude
authored
Add resilient scenario matching fallback (DBCall -> keyword -> default) (#15)
find_scenario no longer returns None on a failed/empty LLM match: it falls back to keyword overlap over scenario meta, then to the first scenario, and wraps DBCall errors. Verified the fallback path without a live API key. Closes #2 Co-authored-by: ReokyoungKim <zmffhem1207@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent af4b907 commit 2feab2f

1 file changed

Lines changed: 50 additions & 8 deletions

File tree

backend/pipeline.py

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,57 @@
3030
# --------------------------------------------------------------------------- #
3131
# (1) Find the most similar existing scenario in the DB
3232
# --------------------------------------------------------------------------- #
33+
def _scenario_details() -> dict:
34+
try:
35+
with open(DB_DIR / "scenario" / "meta.json", "r", encoding="utf-8") as f:
36+
return json.load(f).get("details", {})
37+
except Exception:
38+
return {}
39+
40+
41+
def _keyword_fallback(query: str, details: dict) -> str:
42+
"""Pick the scenario whose name+description shares the most words with the
43+
query. No LLM — used when the DBCall match is empty or errors out."""
44+
q = set(re.findall(r"[a-z0-9가-힣]+", query.lower()))
45+
best, best_score = None, -1
46+
for name, info in details.items():
47+
text = f"{name} {info.get('description', '')}".lower()
48+
score = len(q & set(re.findall(r"[a-z0-9가-힣]+", text)))
49+
if score > best_score:
50+
best, best_score = name, score
51+
return best if best_score > 0 else None
52+
53+
3354
def find_scenario(query: str, seed: int = None) -> str:
34-
"""Return the best-matching scenario name from db/scenario, or None."""
35-
db = DBCall(seed=seed)
36-
_, names = db.call_with_names(query, folder="scenario")
37-
if not names:
38-
print(" ⚠️ No matching scenario found in DB.")
39-
return None
40-
print(f" 🔎 Matched scenario(s): {names} -> using '{names[0]}'")
41-
return names[0]
55+
"""Return the best-matching scenario name from db/scenario.
56+
57+
Resilient: LLM match → keyword fallback → default to the first scenario.
58+
Returns None only when the scenario DB is empty/unreadable.
59+
"""
60+
details = _scenario_details()
61+
names = []
62+
try:
63+
db = DBCall(seed=seed)
64+
_, names = db.call_with_names(query, folder="scenario")
65+
except Exception as e:
66+
print(f" ⚠️ DBCall match failed ({e}); falling back.")
67+
68+
if names:
69+
print(f" 🔎 Matched scenario(s): {names} -> using '{names[0]}'")
70+
return names[0]
71+
72+
fb = _keyword_fallback(query, details)
73+
if fb:
74+
print(f" ↩️ Keyword fallback -> '{fb}'")
75+
return fb
76+
77+
if details:
78+
first = next(iter(details))
79+
print(f" ↩️ No overlap; defaulting to first scenario -> '{first}'")
80+
return first
81+
82+
print(" ⚠️ Scenario DB is empty.")
83+
return None
4284

4385

4486
# --------------------------------------------------------------------------- #

0 commit comments

Comments
 (0)