提示词这一侧不再自己存天气

原来提示词这条路自己存了一份,插件给的失败提示被当成天气报告存了 8 小时,网络好了也出不来天气。现在这边不存了,成功和失败都交给插件那边的缓存管:成功的存 8 小时,失败的存 5 分钟。存量空条目照旧清掉,管理员配好凭据后下一次问就能出天气。
This commit is contained in:
hrz committed 2026-10-24 08:18:40 +00:00
1 parent b77ef426e3
commit 8a0d933074
2 files changed
+91 -44

No files matched your search

@@ -64,8 +64,8 @@ class PromptManager:
self.logger = logger or setup_logging()
self.base_prompt_template = None
self.last_update_time = 0
# 天气取不到时给用户的一句话,提示词要用它占位
self._weather_notice = ""
# 这次取到的天气内容,取不到时是一句给用户的说明
self._weather_content = ""
# 导入全局缓存管理器
from core.utils.cache.manager import cache_manager, CacheType
@@ -172,17 +172,19 @@ class PromptManager:
return "未知位置"
def _get_weather_info(self, conn: "ConnectionHandler", location: str) -> str:
"""获取天气信息"""
"""获取天气信息
<p>
这一路不再自己存天气。插件那边存了:成功的报告 8 小时,失败的提示
5 分钟。这边再存一份,存的是插件给什么就存什么,失败提示会被存成
8 小时,网络恢复了、凭据配好了也出不来天气。
"""
try:
# 先从缓存获取
cached_weather = self.cache_manager.get(self.CacheType.WEATHER, location)
if cached_weather:
return cached_weather
# 缓存里留着空条目说明上次没取到,留着会挡住下一次取,
# 删掉重来,开发者把凭据配好之后下一次就该出天气
# 以前这条路过缓存,存进去的条目是空的时候,管理员把凭据配好之后
# 还得等它过期才出天气。没人读它了,清掉
self.cache_manager.delete(self.CacheType.WEATHER, location)
# 缓存未命中,调用 async get_weather 函数
# 调用 async get_weather 函数
# Windows ProactorEventLoop 不支持 run_coroutine_threadsafe().result()
# 因此用 call_soon_threadsafe 提交任务 + threading.Event 等待结果
# 注意:Event.wait() 只阻塞当前线程池线程,不阻塞主事件循环
@@ -212,25 +214,23 @@ class PromptManager:
if isinstance(result, ActionResponse):
weather_report = result.result
if weather_report:
self.cache_manager.set(
self.CacheType.WEATHER, location, weather_report
)
self._weather_notice = ""
# 成功还是失败,插件那边分得很清楚:成功的存 8 小时,
# 失败的只存 5 分钟。这边原样用,不另存
self._weather_content = weather_report
return weather_report
# 凭据没配好时插件给不出报告。这种结果不写进缓存:
# 空条目留着的话,开发者配好凭据后还得等缓存过期才出天气
# 凭据没配好时插件给不出报告
if result.action == Action.RESPONSE:
self._weather_notice = WEATHER_NOT_CONFIGURED_NOTICE
self._weather_content = WEATHER_NOT_CONFIGURED_NOTICE
else:
self._weather_notice = WEATHER_UNAVAILABLE_NOTICE
return self._weather_notice
self._weather_notice = WEATHER_UNAVAILABLE_NOTICE
return self._weather_notice
self._weather_content = WEATHER_UNAVAILABLE_NOTICE
return self._weather_content
self._weather_content = WEATHER_UNAVAILABLE_NOTICE
return self._weather_content
except Exception as e:
self.logger.bind(tag=TAG).error(f"获取天气信息失败: {e}")
self._weather_notice = WEATHER_UNAVAILABLE_NOTICE
return self._weather_notice
self._weather_content = WEATHER_UNAVAILABLE_NOTICE
return self._weather_content
def update_context_info(self, conn, client_ip: str):
"""同步更新上下文信息"""
@@ -270,17 +270,14 @@ class PromptManager:
except Exception as e:
self.logger.bind(tag=TAG).error(f"更新上下文信息失败: {e}")
def _get_weather_context(self, location: str) -> str:
def _get_weather_context(self) -> str:
"""给提示词用的天气内容
<p>
缓存里没有有效报告时给一句说明,不留空。占位符空着的话,模型会自己
编一个天气出来。
天气没取到时给一句说明,不留空。占位符空着的话,模型会自己编一个
天气出来。
"""
cached_weather = self.cache_manager.get(self.CacheType.WEATHER, location)
if cached_weather:
return cached_weather
return self._weather_notice or WEATHER_UNAVAILABLE_NOTICE
return self._weather_content or WEATHER_UNAVAILABLE_NOTICE
def build_enhanced_prompt(
self, user_prompt: str, device_id: str, client_ip: str = None, *args, **kwargs
@@ -295,7 +292,6 @@ class PromptManager:
# 获取缓存的上下文信息
local_address = ""
weather_info = WEATHER_UNAVAILABLE_NOTICE
if client_ip:
# 获取位置信息(从全局缓存)
@@ -303,9 +299,8 @@ class PromptManager:
self.cache_manager.get(self.CacheType.LOCATION, client_ip) or ""
)
# 获取天气信息(从全局缓存)
if local_address:
weather_info = self._get_weather_context(local_address)
# 获取天气信息(取天气的那一步已经把内容放在管理器上了)
weather_info = self._get_weather_context()
# 获取TTS选择的语言,默认值为中文
language = (
@@ -2,7 +2,7 @@
天气有两条路进模型:工具调用那条走插件,提示词那条走 {weather_info} 占位符。
这条用例盯的是占位符那条:凭据没配好时它得给用户一句说明,不能留空让模型
自己编天气;取到的报告也不能被空条目挡住。
自己编天气;取到的报告和失败提示也不能被塞进长缓存。
"""
from __future__ import annotations
@@ -18,6 +18,7 @@ _PROJECT_ROOT = Path(__file__).resolve().parents[3]
# 提示词里不能出现这个路径,模型会照着念给终端用户
CREDENTIAL_HINT = "尚未配置和风天气 api_key,请参考 docs/weather-integration.md 申请后填入"
WEATHER_REPORT = "您查询的位置是:北京\n\n当前天气: 晴\n"
FAILURE_MESSAGE = "暂时查不到 北京 的天气(城市未找到或天气服务不可用),请稍后再试"
@pytest.fixture(autouse=True)
@@ -67,18 +68,32 @@ class _FakeConn:
self.device_id = "test-device"
def _install_fake_plugin(monkeypatch):
"""把真插件换成假的:按有没有 api_key 走两种返回"""
def _install_fake_plugin(monkeypatch, responses=None):
"""把真插件换成假的:按有没有 api_key 走两种返回
<p>
responses 给了就按顺序回,回完最后一条就一直回最后一条。返回调用次数,
用例要看插件被调了几次。
"""
import plugins_func.functions.get_weather as weather_module
from plugins_func.register import Action, ActionResponse
calls = []
async def _fake_get_weather(conn, location=None, lang="zh_CN"):
api_key = str(conn.config.get("plugins", {}).get("get_weather", {}).get("api_key") or "")
api_key = str(
conn.config.get("plugins", {}).get("get_weather", {}).get("api_key") or ""
)
calls.append(location)
if not api_key.strip():
return ActionResponse(Action.RESPONSE, None, CREDENTIAL_HINT)
if responses:
index = min(len(calls) - 1, len(responses) - 1)
return responses[index]
return ActionResponse(Action.REQLLM, WEATHER_REPORT, None)
monkeypatch.setattr(weather_module, "get_weather", _fake_get_weather)
return calls
@pytest.fixture
@@ -135,13 +150,13 @@ def test_unconfigured_weather_gives_notice_and_caches_nothing(
@pytest.mark.parametrize("stale", [None, ""])
def test_empty_cached_entry_does_not_block_weather(
def test_empty_cached_entry_is_cleared_and_weather_is_fetched_again(
monkeypatch, _manager, _cache, stale
):
"""缓存里留着空条目时,配好凭据后下一次就该出天气"""
"""缓存里已经有空条目时,配好凭据后下一次就该出天气"""
from core.utils.cache.manager import CacheType
_install_fake_plugin(monkeypatch)
calls = _install_fake_plugin(monkeypatch)
loop = _FakeLoop()
conn = _FakeConn(loop, api_key="not-a-real-key")
_cache.set(CacheType.LOCATION, conn.client_ip, "北京")
@@ -153,13 +168,49 @@ def test_empty_cached_entry_does_not_block_weather(
loop.close()
assert "当前天气: 晴" in prompt
assert _cache.get(CacheType.WEATHER, "北京") == WEATHER_REPORT
# 存量空条目被清掉了,没有留在缓存里
assert _cache.get(CacheType.WEATHER, "北京") is None
assert len(calls) == 1
def test_failed_weather_is_not_cached_and_is_fetched_again(
monkeypatch, _manager, _cache
):
"""插件说查不到天气时,那句话不能进长缓存,下一次要重新取"""
from core.utils.cache.manager import CacheType
from plugins_func.register import Action, ActionResponse
calls = _install_fake_plugin(
monkeypatch,
responses=[
ActionResponse(Action.REQLLM, FAILURE_MESSAGE, None),
ActionResponse(Action.REQLLM, WEATHER_REPORT, None),
],
)
loop = _FakeLoop()
conn = _FakeConn(loop, api_key="not-a-real-key")
_cache.set(CacheType.LOCATION, conn.client_ip, "北京")
try:
first_prompt = _build_prompt(_manager, conn)
# 失败提示进提示词,用户能听到「暂时查不到」
assert "暂时查不到" in first_prompt
# 但它不能被存进 8 小时那档缓存,不然网络恢复了就出不来天气
assert _cache.get(CacheType.WEATHER, "北京") is None
second_prompt = _build_prompt(_manager, conn)
finally:
loop.close()
# 下一次重新取,插件被调了两次
assert len(calls) == 2
assert "当前天气: 晴" in second_prompt
def test_configured_weather_report_goes_to_placeholder(
monkeypatch, _manager, _cache
):
"""凭据齐全:报告照旧进占位符,也照旧进缓存"""
"""凭据齐全:报告照旧进占位符,缓存由插件那边管"""
from core.utils.cache.manager import CacheType
_install_fake_plugin(monkeypatch)
@@ -174,4 +225,5 @@ def test_configured_weather_report_goes_to_placeholder(
assert "当前天气: 晴" in prompt
assert "未配置" not in prompt
assert _cache.get(CacheType.WEATHER, "北京") == WEATHER_REPORT
# 提示词这一侧不再自己存一份,成功那份由插件按 8 小时存
assert _cache.get(CacheType.WEATHER, "北京") is None