This commit is contained in:
2026-03-06 12:44:57 +08:00
parent fa61b11b02
commit 006b035de4
132 changed files with 1361 additions and 17400 deletions

View File

@@ -1,7 +1,7 @@
import os
import unittest
from core.websocket_client import QingjianAPIClient
from core.websocket_client_v2 import QingjianAPIClient
class MultiWorkerRoutingTest(unittest.TestCase):

View File

@@ -3,7 +3,7 @@ import unittest
from websockets.protocol import State
from core.websocket_client import QingjianAPIClient
from core.websocket_client_v2 import QingjianAPIClient
class _DummyWS:

View File

@@ -1,7 +1,7 @@
import os
import unittest
from core.websocket_client import QingjianAPIClient
from core.websocket_client_v2 import QingjianAPIClient
class OversizeGuardTest(unittest.TestCase):

View File

@@ -1,465 +0,0 @@
import os
import unittest
from unittest.mock import AsyncMock
from core.pydantic_ai_agent import CustomerServiceAgent, CustomerMessage
from db.customer_db import db
class RegressionPipelineTest(unittest.IsolatedAsyncioTestCase):
def setUp(self):
self.customer_id = "__regression_test_customer__"
db.clear_pending_quote_state(self.customer_id)
os.environ["FEATURE_BATCH_QUOTE_ENABLED"] = "true"
os.environ["FEATURE_BATCH_QUOTE_PERCENT"] = "100"
os.environ["FEATURE_BATCH_QUOTE_SHOPS"] = ""
os.environ["AI_DYNAMIC_COLLECTION_REPLIES"] = "false"
os.environ["AI_REWRITE_ALL_REPLIES"] = "false"
os.environ["BATCH_QUOTE_DELAY_TURNS"] = "0"
async def test_collect_images_then_ack(self):
agent = CustomerServiceAgent()
msg = CustomerMessage(
msg_id="m1",
acc_id="test_shop",
msg="https://img.alicdn.com/a.jpg#*#https://img.alicdn.com/b.jpg",
from_id=self.customer_id,
from_name="t",
cy_id=self.customer_id,
acc_type="AliWorkbench",
msg_type=0,
cy_name="t",
goods_name="专业找图",
goods_order="",
)
resp = await agent.process_message(msg)
self.assertTrue(resp.should_reply)
self.assertTrue(resp.reply.strip())
st = agent._get_conversation_state(self.customer_id)
self.assertEqual(len(st.pending_image_urls), 2)
async def test_finish_signal_triggers_batch_quote(self):
agent = CustomerServiceAgent()
st = agent._get_conversation_state(self.customer_id)
st.pending_image_urls = ["https://img.alicdn.com/a.jpg"]
st.pending_requirements = ["去背景"]
agent._sync_pending_quote_state(self.customer_id, st)
agent._quote_pending_images = AsyncMock(return_value={"reply": "打包15元确认我就安排", "need_transfer": False})
msg = CustomerMessage(
msg_id="m2",
acc_id="test_shop",
msg="发完了,报价吧",
from_id=self.customer_id,
from_name="t",
cy_id=self.customer_id,
acc_type="AliWorkbench",
msg_type=0,
cy_name="t",
goods_name="专业找图",
goods_order="",
)
resp = await agent.process_message(msg)
self.assertTrue(resp.should_reply)
self.assertIn("15", resp.reply)
agent._quote_pending_images.assert_awaited()
async def test_single_image_requirement_intent_triggers_quote(self):
agent = CustomerServiceAgent()
st = agent._get_conversation_state(self.customer_id)
st.pending_image_urls = ["https://img.alicdn.com/a.jpg"]
st.pending_requirements = []
agent._sync_pending_quote_state(self.customer_id, st)
agent._quote_pending_images = AsyncMock(return_value={"reply": "这张20元定了我马上做", "need_transfer": False})
msg = CustomerMessage(
msg_id="m3",
acc_id="test_shop",
msg="这个门头上面的字做一下",
from_id=self.customer_id,
from_name="t",
cy_id=self.customer_id,
acc_type="AliWorkbench",
msg_type=0,
cy_name="t",
goods_name="专业找图",
goods_order="",
)
resp = await agent.process_message(msg)
self.assertTrue(resp.should_reply)
self.assertIn("20", resp.reply)
agent._quote_pending_images.assert_awaited()
async def test_multi_image_finish_intent_triggers_quote(self):
agent = CustomerServiceAgent()
st = agent._get_conversation_state(self.customer_id)
st.pending_image_urls = ["https://img.alicdn.com/a.jpg", "https://img.alicdn.com/b.jpg"]
st.pending_requirements = ["改字"]
agent._sync_pending_quote_state(self.customer_id, st)
agent._quote_pending_images = AsyncMock(return_value={"reply": "两张打包45定了我就开做", "need_transfer": False})
msg = CustomerMessage(
msg_id="m4",
acc_id="test_shop",
msg="就这几张,先按这些报个价",
from_id=self.customer_id,
from_name="t",
cy_id=self.customer_id,
acc_type="AliWorkbench",
msg_type=0,
cy_name="t",
goods_name="专业找图",
goods_order="",
)
resp = await agent.process_message(msg)
self.assertTrue(resp.should_reply)
self.assertIn("45", resp.reply)
agent._quote_pending_images.assert_awaited()
async def test_finish_signal_with_meile_triggers_quote(self):
agent = CustomerServiceAgent()
st = agent._get_conversation_state(self.customer_id)
st.pending_image_urls = ["https://img.alicdn.com/a.jpg"]
st.pending_requirements = []
agent._sync_pending_quote_state(self.customer_id, st)
agent._quote_pending_images = AsyncMock(return_value={"reply": "这张15元确认就开始", "need_transfer": False})
msg = CustomerMessage(
msg_id="m4b",
acc_id="test_shop",
msg="没有了,报下价",
from_id=self.customer_id,
from_name="t",
cy_id=self.customer_id,
acc_type="AliWorkbench",
msg_type=0,
cy_name="t",
goods_name="专业找图",
goods_order="",
)
resp = await agent.process_message(msg)
self.assertTrue(resp.should_reply)
self.assertIn("15", resp.reply)
agent._quote_pending_images.assert_awaited()
async def test_finish_signal_defers_quote_when_delay_enabled(self):
os.environ["BATCH_QUOTE_DELAY_TURNS"] = "1"
agent = CustomerServiceAgent()
st = agent._get_conversation_state(self.customer_id)
st.pending_image_urls = ["https://img.alicdn.com/a.jpg"]
st.pending_requirements = []
agent._sync_pending_quote_state(self.customer_id, st)
agent._quote_pending_images = AsyncMock(return_value={"reply": "这张20元确认就开做", "need_transfer": False})
msg1 = CustomerMessage(
msg_id="m4c-1",
acc_id="test_shop",
msg="没有了,报价吧",
from_id=self.customer_id,
from_name="t",
cy_id=self.customer_id,
acc_type="AliWorkbench",
msg_type=0,
cy_name="t",
goods_name="专业找图",
goods_order="",
)
resp1 = await agent.process_message(msg1)
self.assertTrue(resp1.should_reply)
self.assertNotIn("20", resp1.reply)
agent._quote_pending_images.assert_not_awaited()
msg2 = CustomerMessage(
msg_id="m4c-2",
acc_id="test_shop",
msg="有吗",
from_id=self.customer_id,
from_name="t",
cy_id=self.customer_id,
acc_type="AliWorkbench",
msg_type=0,
cy_name="t",
goods_name="专业找图",
goods_order="",
)
resp2 = await agent.process_message(msg2)
self.assertTrue(resp2.should_reply)
self.assertIn("20", resp2.reply)
agent._quote_pending_images.assert_awaited()
os.environ["BATCH_QUOTE_DELAY_TURNS"] = "0"
async def test_cross_image_composite_intent_triggers_quote(self):
agent = CustomerServiceAgent()
st = agent._get_conversation_state(self.customer_id)
st.pending_image_urls = ["https://img.alicdn.com/a.jpg", "https://img.alicdn.com/b.jpg"]
st.pending_requirements = []
agent._sync_pending_quote_state(self.customer_id, st)
agent._quote_pending_images = AsyncMock(return_value={"reply": "这个跨图合成可以做打包50元", "need_transfer": False})
msg = CustomerMessage(
msg_id="m5",
acc_id="test_shop",
msg="A图的图案转换到B图上去",
from_id=self.customer_id,
from_name="t",
cy_id=self.customer_id,
acc_type="AliWorkbench",
msg_type=0,
cy_name="t",
goods_name="专业找图",
goods_order="",
)
resp = await agent.process_message(msg)
self.assertTrue(resp.should_reply)
self.assertIn("50", resp.reply)
agent._quote_pending_images.assert_awaited()
async def test_result_followup_query_uses_progress_reply(self):
agent = CustomerServiceAgent()
st = agent._get_conversation_state(self.customer_id)
st.pending_image_urls = ["https://img.alicdn.com/a.jpg"]
st.pending_requirements = []
agent._sync_pending_quote_state(self.customer_id, st)
agent._quote_pending_images = AsyncMock(return_value={"reply": "不应触发报价", "need_transfer": False})
msg = CustomerMessage(
msg_id="m5b",
acc_id="test_shop",
msg="没找到吗",
from_id=self.customer_id,
from_name="t",
cy_id=self.customer_id,
acc_type="AliWorkbench",
msg_type=0,
cy_name="t",
goods_name="专业找图",
goods_order="",
)
resp = await agent.process_message(msg)
self.assertTrue(resp.should_reply)
self.assertNotIn("不太懂你的意思", resp.reply)
self.assertNotIn("没完全理解", resp.reply)
self.assertNotIn("没听明白", resp.reply)
agent._quote_pending_images.assert_not_awaited()
async def test_short_youma_followup_uses_progress_reply(self):
agent = CustomerServiceAgent()
st = agent._get_conversation_state(self.customer_id)
st.pending_image_urls = ["https://img.alicdn.com/a.jpg"]
st.pending_requirements = []
agent._sync_pending_quote_state(self.customer_id, st)
agent._quote_pending_images = AsyncMock(return_value={"reply": "不应触发报价", "need_transfer": False})
msg = CustomerMessage(
msg_id="m5bb",
acc_id="test_shop",
msg="有吗",
from_id=self.customer_id,
from_name="t",
cy_id=self.customer_id,
acc_type="AliWorkbench",
msg_type=0,
cy_name="t",
goods_name="专业找图",
goods_order="",
)
resp = await agent.process_message(msg)
self.assertTrue(resp.should_reply)
self.assertNotIn("不太懂你的意思", resp.reply)
self.assertNotIn("没完全理解", resp.reply)
self.assertNotIn("没听明白", resp.reply)
st2 = agent._get_conversation_state(self.customer_id)
self.assertEqual(st2.quote_phase, "waiting_result")
agent._quote_pending_images.assert_not_awaited()
def test_short_text_classifier(self):
agent = CustomerServiceAgent()
self.assertEqual(agent._classify_short_customer_text("有吗"), "progress_query")
self.assertEqual(agent._classify_short_customer_text("没有了"), "finish_signal")
self.assertEqual(agent._classify_short_customer_text("好的"), "ack")
async def test_related_screenshot_followup_is_marked(self):
agent = CustomerServiceAgent()
st = agent._get_conversation_state(self.customer_id)
st.pending_image_urls = ["https://img.alicdn.com/a.jpg"]
st.pending_requirements = []
agent._sync_pending_quote_state(self.customer_id, st)
msg = CustomerMessage(
msg_id="m5c",
acc_id="test_shop",
msg="这是上一张的截图#*#https://img.alicdn.com/b.jpg",
from_id=self.customer_id,
from_name="t",
cy_id=self.customer_id,
acc_type="AliWorkbench",
msg_type=0,
cy_name="t",
goods_name="专业找图",
goods_order="",
)
resp = await agent.process_message(msg)
self.assertTrue(resp.should_reply)
st2 = agent._get_conversation_state(self.customer_id)
self.assertIn("与上一张相关(截图/局部细节)", st2.pending_requirements)
async def test_find_image_not_edit_conflict_triggers_clarification(self):
agent = CustomerServiceAgent()
st = agent._get_conversation_state(self.customer_id)
st.pending_image_urls = ["https://img.alicdn.com/a.jpg"]
st.pending_requirements = []
agent._sync_pending_quote_state(self.customer_id, st)
agent._quote_pending_images = AsyncMock(return_value={"reply": "不该触发", "need_transfer": False})
msg = CustomerMessage(
msg_id="m6",
acc_id="test_shop",
msg="我要你帮我找图,不是让你做图",
from_id=self.customer_id,
from_name="t",
cy_id=self.customer_id,
acc_type="AliWorkbench",
msg_type=0,
cy_name="t",
goods_name="专业找图",
goods_order="",
)
resp = await agent.process_message(msg)
self.assertTrue(resp.should_reply)
self.assertIn("找图", resp.reply)
self.assertIn("不是做图", resp.reply)
agent._quote_pending_images.assert_not_awaited()
async def test_pending_state_restore(self):
db.update_pending_quote_state(self.customer_id, ["u1", "u2"], ["r1"])
agent = CustomerServiceAgent()
st = agent._get_conversation_state(self.customer_id)
self.assertEqual(st.pending_image_urls, ["u1", "u2"])
self.assertEqual(st.pending_requirements, ["r1"])
async def test_collection_reply_never_returns_none(self):
os.environ["AI_DYNAMIC_COLLECTION_REPLIES"] = "true"
agent = CustomerServiceAgent()
agent.agent_natural_reply.run = AsyncMock(side_effect=RuntimeError("mock ai fail"))
st = agent._get_conversation_state(self.customer_id)
msg = CustomerMessage(
msg_id="m-collection-fallback",
acc_id="test_shop",
msg="收到没",
from_id=self.customer_id,
from_name="t",
cy_id=self.customer_id,
acc_type="AliWorkbench",
msg_type=0,
cy_name="t",
goods_name="专业找图",
goods_order="",
)
reply = await agent._render_collection_reply_with_ai(
message=msg,
state=st,
scene="collect_ack",
intent_hint="确认已收到图片",
fallback="图片收到了,你继续发就行。",
)
self.assertEqual(reply, "图片收到了,你继续发就行。")
async def test_first_image_ack_avoids_unified_quote_wording(self):
os.environ["AI_DYNAMIC_COLLECTION_REPLIES"] = "true"
agent = CustomerServiceAgent()
st = agent._get_conversation_state(self.customer_id)
st.pending_image_urls = ["https://img.alicdn.com/a.jpg"]
msg = CustomerMessage(
msg_id="m-first-ack",
acc_id="test_shop",
msg="https://img.alicdn.com/a.jpg",
from_id=self.customer_id,
from_name="t",
cy_id=self.customer_id,
acc_type="AliWorkbench",
msg_type=0,
cy_name="t",
goods_name="专业找图",
goods_order="",
)
reply = await agent._render_collection_reply_with_ai(
message=msg,
state=st,
scene="collect_ack",
intent_hint="确认收到后先承接",
fallback="图片收到了,你继续发就行。",
)
self.assertIn("稍等", reply)
self.assertNotIn("统一报价", reply)
self.assertNotIn("发完", reply)
async def test_map_inquiry_is_rejected(self):
agent = CustomerServiceAgent()
msg = CustomerMessage(
msg_id="m-map-reject",
acc_id="test_shop",
msg="这个地图能做吗",
from_id=self.customer_id,
from_name="t",
cy_id=self.customer_id,
acc_type="AliWorkbench",
msg_type=0,
cy_name="t",
goods_name="专业找图",
goods_order="",
)
resp = await agent.process_message(msg)
self.assertTrue(resp.should_reply)
self.assertIn("地图", resp.reply)
self.assertIn("不做", resp.reply)
async def test_meaningless_short_text_gets_ping_reply(self):
agent = CustomerServiceAgent()
msg = CustomerMessage(
msg_id="m-meaningless",
acc_id="test_shop",
msg="好的",
from_id=self.customer_id,
from_name="t",
cy_id=self.customer_id,
acc_type="AliWorkbench",
msg_type=0,
cy_name="t",
goods_name="专业找图",
goods_order="",
)
resp = await agent.process_message(msg)
self.assertTrue(resp.should_reply)
self.assertIn(resp.reply, ("嗯咯", "嗯啦", "", ""))
async def test_meaningless_short_text_not_ping_when_collecting(self):
agent = CustomerServiceAgent()
st = agent._get_conversation_state(self.customer_id)
st.pending_image_urls = ["https://img.alicdn.com/a.jpg"]
st.quote_phase = "collecting"
agent._sync_pending_quote_state(self.customer_id, st)
msg = CustomerMessage(
msg_id="m-meaningless-collecting",
acc_id="test_shop",
msg="好的",
from_id=self.customer_id,
from_name="t",
cy_id=self.customer_id,
acc_type="AliWorkbench",
msg_type=0,
cy_name="t",
goods_name="专业找图",
goods_order="",
)
resp = await agent.process_message(msg)
self.assertTrue(resp.should_reply)
self.assertTrue((resp.reply or "").strip())
self.assertNotIn(resp.reply, ("嗯咯", "嗯啦", "", ""))
def tearDown(self):
db.clear_pending_quote_state(self.customer_id)
if __name__ == "__main__":
unittest.main(verbosity=2)

View File

@@ -1,57 +0,0 @@
import unittest
from core.quote_state_machine import QuoteStateMachine
from core.rules import Rule, RuleContext, RuleEngine, RuleResult
from services.risk_service import RiskService
class _StubState:
def __init__(self):
self.pending_image_urls = []
self.pending_requirements = []
self.quote_phase = "idle"
self.quote_ready_turns = 0
class RuleEngineTests(unittest.IsolatedAsyncioTestCase):
async def test_rule_engine_priority_and_stop(self):
async def pred_true(_ctx):
return True
async def act_first(_ctx):
return RuleResult(matched=True, stop=True, action="first", payload={"x": 1})
async def act_second(_ctx):
return RuleResult(matched=True, stop=True, action="second", payload={"x": 2})
engine = RuleEngine(
[
Rule(name="r2", priority=20, predicate=pred_true, action=act_second),
Rule(name="r1", priority=10, predicate=pred_true, action=act_first),
]
)
out = await engine.run(RuleContext())
self.assertTrue(out.matched)
self.assertEqual(out.action, "first")
self.assertEqual(out.payload["x"], 1)
def test_quote_state_machine_transitions(self):
sm = QuoteStateMachine(delay_turns=1)
st = _StubState()
st.pending_image_urls = ["u1"]
sm.refresh(st)
self.assertEqual(st.quote_phase, "collecting")
self.assertTrue(sm.should_defer_batch_quote(st, mark_ready=True))
self.assertEqual(st.quote_phase, "ready_to_quote")
self.assertEqual(st.quote_ready_turns, 0)
self.assertFalse(sm.should_defer_batch_quote(st, mark_ready=False))
def test_risk_service_reject_text(self):
svc = RiskService()
self.assertIn("地图", svc.build_reject_text("map"))
self.assertIn("政治", svc.build_reject_text("political"))
self.assertIn("涉黄", svc.build_reject_text("sexual"))
if __name__ == "__main__":
unittest.main(verbosity=2)

View File

@@ -2,7 +2,7 @@ import os
import unittest
from unittest.mock import AsyncMock
from core.websocket_client import QingjianAPIClient
from core.websocket_client_v2 import QingjianAPIClient
class SystemInquiryRulesTest(unittest.IsolatedAsyncioTestCase):

View File

@@ -1,6 +1,6 @@
import unittest
from core.websocket_client import QingjianAPIClient
from core.websocket_client_v2 import QingjianAPIClient
class TransferGreetingContextTest(unittest.TestCase):