diff --git a/src/jarvis/reply/engine.py b/src/jarvis/reply/engine.py index d04c41c..39f4620 100644 --- a/src/jarvis/reply/engine.py +++ b/src/jarvis/reply/engine.py @@ -889,6 +889,37 @@ def _maybe_deterministic_site_search(text: str, db, cfg, language) -> Optional[s return None +_WEATHER_INTENT_WORDS = ( + "날씨", "기온", "더워", "더운", "추워", "추운", "비 와", "비와", "비 올", + "눈 와", "눈와", "weather", "temperature", "forecast", +) + + +def _maybe_deterministic_weather(text: str, db, cfg, language) -> Optional[str]: + """Run getWeather directly and return its concise Korean sentence, bypassing + the chat model. The 7B otherwise re-synthesises the weather into multiple + sentences and leaks units ("25도 Celsius"); the tool already formats one + clean Korean sentence, so for a plain weather ask we just return it. + Fail-open: any problem returns None and the normal flow continues. + """ + try: + low = (text or "").lower() + if not any(w in low for w in _WEATHER_INTENT_WORDS): + return None + from ..tools.registry import run_tool_with_retries + res = run_tool_with_retries( + db=db, cfg=cfg, tool_name="getWeather", tool_args={}, + system_prompt="", original_prompt="", redacted_text=redact(text), + max_retries=1, language=language, + ) + if res and getattr(res, "success", False) and res.reply_text: + debug_log("deterministic weather executed", "tools") + return res.reply_text + except Exception as e: # noqa: BLE001 + debug_log(f"deterministic weather failed (fail-open): {e}", "tools") + return None + + def run_reply_engine(db: "Database", cfg, tts: Optional[Any], text: str, dialogue_memory: "DialogueMemory", language: Optional[str] = None) -> Optional[str]: @@ -920,6 +951,13 @@ def run_reply_engine(db: "Database", cfg, tts: Optional[Any], if _site_search_reply is not None: return _site_search_reply + # Step 0.6: Deterministic weather — return getWeather's concise Korean + # sentence directly so the chat model can't rephrase it into multiple + # sentences or leak units. + _weather_reply = _maybe_deterministic_weather(text, db, cfg, language) + if _weather_reply is not None: + return _weather_reply + # Step 2: Check for recent dialogue context recent_messages = [] is_new_conversation = True diff --git a/src/jarvis/tools/builtin/weather.py b/src/jarvis/tools/builtin/weather.py index dab8694..36b495f 100644 --- a/src/jarvis/tools/builtin/weather.py +++ b/src/jarvis/tools/builtin/weather.py @@ -440,17 +440,11 @@ class WeatherTool(Tool): _ko_parts.append(_t) ko_sentence = ", ".join(_ko_parts) + "입니다." - # Build response text — concise current conditions (Korean sentence - # first so the model echoes it; English detail kept for any follow-up - # reasoning but the forecast firehose is dropped to curb rambling). - lines = [ - f"한국어로 정확히 이 한 문장만 답하세요: {ko_sentence}", - f"(참고 데이터 — 답변에 추가하지 말 것: {location_display}, {weather_desc}, " - f"{temp_c}°C feels {feels_like_c}°C, humidity {humidity}%, wind {wind_speed}km/h)", - ] - # Forecast (hourly / 7-day) is intentionally omitted from the default - # voice reply to keep it to one spoken sentence; current conditions - # are what "날씨 알려줘" asks for. + # The reply is the clean Korean sentence ONLY — no English/°C source + # for the model to echo ("25도 Celsius"), no forecast firehose to + # ramble over. The deterministic weather path in the engine returns + # this verbatim; on the LLM path the model just echoes one sentence. + lines = [ko_sentence] reply_text = "\n".join(lines)