feat: role-based skills, AI-first replies, and deferred batch quote routing

This commit is contained in:
2026-03-01 11:03:56 +08:00
parent 3c77c618e7
commit e31bb80063
10 changed files with 777 additions and 36 deletions

View File

@@ -13,6 +13,9 @@ class RegressionPipelineTest(unittest.IsolatedAsyncioTestCase):
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()
@@ -31,7 +34,7 @@ class RegressionPipelineTest(unittest.IsolatedAsyncioTestCase):
)
resp = await agent.process_message(msg)
self.assertTrue(resp.should_reply)
self.assertIn("", resp.reply)
self.assertTrue(resp.reply.strip())
st = agent._get_conversation_state(self.customer_id)
self.assertEqual(len(st.pending_image_urls), 2)
@@ -113,6 +116,78 @@ class RegressionPipelineTest(unittest.IsolatedAsyncioTestCase):
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)
@@ -139,6 +214,95 @@ class RegressionPipelineTest(unittest.IsolatedAsyncioTestCase):
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)