Include utility modules, shared channels, prompts, read-only skills, wardrobe memory and regression tests. Externalize legacy credentials and exclude private runtime state.
86 lines
4.1 KiB
Python
86 lines
4.1 KiB
Python
"""Hybrid reasoning policy: casual chat off, Agent planning on."""
|
|
|
|
from pathlib import Path
|
|
import sys
|
|
import unittest
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
BASE = Path(__file__).resolve().parents[1]
|
|
sys.path[:0] = [str(BASE / "bot-utility"), str(BASE)]
|
|
|
|
from chat import engine
|
|
|
|
|
|
class ReasoningPolicyTests(unittest.IsolatedAsyncioTestCase):
|
|
def test_reasoning_parameters_are_explicit(self):
|
|
self.assertEqual(
|
|
engine._reasoning_params(False),
|
|
{"thinking": {"type": "disabled"}},
|
|
)
|
|
self.assertEqual(
|
|
engine._reasoning_params(True),
|
|
{"thinking": {"type": "enabled"}, "reasoning_effort": "high"},
|
|
)
|
|
|
|
def test_any_tool_call_requires_agent_reasoning(self):
|
|
context = object()
|
|
for tool in ("setu", "web_search", "fetch", "mail"):
|
|
parsed = {"understood": True, "tools": [{"name": tool}]}
|
|
self.assertTrue(engine._tool_plan_requires_reasoning(parsed, context))
|
|
self.assertFalse(engine._tool_plan_requires_reasoning({"tools": []}, context))
|
|
self.assertFalse(engine._tool_plan_requires_reasoning({"understood": False, "tools": [{}]}, context))
|
|
self.assertFalse(engine._tool_plan_requires_reasoning({"tools": [{}]}, None))
|
|
|
|
def test_model_self_assessment_can_request_reasoning(self):
|
|
context = object()
|
|
parsed = {"need_reasoning": True, "tools": []}
|
|
self.assertTrue(engine._chat_requires_reasoning(parsed, context, False))
|
|
self.assertFalse(engine._chat_requires_reasoning(parsed, context, True))
|
|
self.assertFalse(engine._chat_requires_reasoning({"need_reasoning": False, "tools": []}, context, False))
|
|
prompt = engine.load_instruction("agent_instructions.txt")
|
|
self.assertIn("need_reasoning", prompt)
|
|
self.assertIn("没有把握", prompt)
|
|
|
|
def test_last_json_retry_uses_reasoning(self):
|
|
self.assertFalse(engine._json_retry_uses_reasoning(0))
|
|
self.assertTrue(engine._json_retry_uses_reasoning(engine.MAX_RETRIES - 2))
|
|
self.assertFalse(engine._json_retry_uses_reasoning(engine.MAX_RETRIES - 1))
|
|
|
|
def test_reminder_tone_forbids_threatening_or_startling_users(self):
|
|
prompt = engine.BASE_PROMPT
|
|
self.assertIn("威胁、恐吓、惊吓", prompt)
|
|
self.assertIn("底色必须温和、让人安心", prompt)
|
|
self.assertIn("别被白花吓一跳", prompt)
|
|
self.assertIn("只响这一次", prompt)
|
|
self.assertIn("只响一次", prompt)
|
|
self.assertIn("今晚八点提醒你吃饭", prompt)
|
|
|
|
async def test_plain_chat_defaults_to_non_thinking(self):
|
|
official = AsyncMock(return_value="ok")
|
|
with patch.object(engine, "_modelscope_available", return_value=False), \
|
|
patch.object(engine, "_build_user_content", new=AsyncMock(return_value="聊天")), \
|
|
patch.object(engine, "_call_deepseek_official", new=official):
|
|
await engine.call_deepseek("system", "聊天")
|
|
self.assertIs(official.await_args.kwargs["reasoning"], False)
|
|
|
|
async def test_agent_request_explicitly_uses_high_reasoning(self):
|
|
official = AsyncMock(return_value="ok")
|
|
with patch.object(engine, "_modelscope_available", return_value=False), \
|
|
patch.object(engine, "_build_user_content", new=AsyncMock(return_value="调用工具")), \
|
|
patch.object(engine, "_call_deepseek_official", new=official):
|
|
await engine.call_deepseek("system", "调用工具", reasoning=True)
|
|
self.assertIs(official.await_args.kwargs["reasoning"], True)
|
|
|
|
async def test_modelscope_receives_same_reasoning_flag(self):
|
|
modelscope = AsyncMock(return_value="ok")
|
|
with patch.object(engine, "_modelscope_available", return_value=True), \
|
|
patch.object(engine, "_build_user_content", new=AsyncMock(return_value="聊天")), \
|
|
patch.object(engine, "_call_modelscope", new=modelscope), \
|
|
patch.object(engine, "_modelscope_mark_success"):
|
|
await engine.call_deepseek("system", "聊天", reasoning=True)
|
|
self.assertIs(modelscope.await_args.kwargs["reasoning"], True)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|