私聊建单前拦住枪支弹药爆炸物和毒品,避免真家伙进入报价。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-14 20:24:13 +08:00
co-authored by Cursor
parent 9ffba70741
commit 617c5e2619
8 changed files with 656 additions and 7 deletions
+101 -1
View File
@@ -43,7 +43,7 @@ from agent.schema.land_options import (
logger = logging.getLogger(__name__)
# 只有补问/核对还能续同一张多段草稿。已出号的拉群停点不能拦住新询价。
_FRONT = frozenset({"clarify", "wait_confirm"})
_FRONT = frozenset({"clarify", "wait_confirm", "sensitive_block"})
class MultiTextInquiryFlow:
@@ -170,6 +170,52 @@ class MultiTextInquiryFlow:
reply("多段询价请先把各段需求发齐并回复确定。需要人来接管时,请在工单创建后再说。")
return "multi_handoff_later"
def _gate_sensitive(
self,
*,
sender_id: str,
text: str,
reply: ReplyFn,
segments: list[Segment],
sess: Optional[FlowSession],
immutable: str,
) -> Optional[str]:
"""
多段私聊建单前的违禁品拦截。
已有工单号的旧会话不把违禁原话带进新的一段。没命中则把品名改口写回各段。
命中只回一句,不补问、不建单。
"""
from agent.policy.sensitive_cargo import BLOCK_REPLY, judge
facts: dict[str, str] = {}
for seg in segments:
for key, value in (seg.facts or {}).items():
if str(value or "").strip():
facts[str(key)] = str(value)
use_old = sess is not None and not (sess.work_order_no or "").strip()
blocked, new_hold, edits = judge(
text=text,
facts=facts,
hold=str(sess.sensitive_hold or "") if use_old else "",
)
for seg in segments:
for key, value in edits.items():
seg.facts[key] = value
if not blocked:
if use_old and sess is not None:
sess.sensitive_hold = new_hold
if edits:
self._put_segments(sess, segments)
self._save(sess)
return None
draft = self._draft(sender_id, segments, phase="sensitive_block", immutable=immutable or text)
draft.sensitive_hold = new_hold
self._save(draft)
reply(BLOCK_REPLY)
logger.info("multi.sensitive_block sender=%s", sender_id)
return "sensitive_block"
def on_text(
self,
*,
@@ -196,6 +242,19 @@ class MultiTextInquiryFlow:
sess = self.session_of(sid)
if sess and self.match_button(raw, sess):
return self._pull_group(sess, reply)
if sess and (sess.phase or "") == "sensitive_block" and not (sess.work_order_no or "").strip():
refused = self._gate_sensitive(
sender_id=sid,
text=raw,
reply=reply,
segments=self._segments(sess),
sess=sess,
immutable=sess.immutable_text or raw,
)
if refused:
return refused
sess.phase = "clarify"
self._save(sess)
verdict = classify_multi_text(raw)
if verdict == "same_mode":
reply(copy.MULTI_SAME_MODE)
@@ -276,7 +335,28 @@ class MultiTextInquiryFlow:
附件口播经常没有「起运港:」这种标签,要按段走和单聊一样的抽取,
不能只认标签行,否则会整段待补充。
"""
current = self.session_of(sender_id)
refused = self._gate_sensitive(
sender_id=sender_id,
text=immutable,
reply=reply,
segments=list(segments),
sess=current if current and not (current.work_order_no or "").strip() else None,
immutable=immutable,
)
if refused:
return refused
filled = self._enrich_segments(segments, allow_b=allow_b, chat_fn=chat_fn)
refused = self._gate_sensitive(
sender_id=sender_id,
text="",
reply=reply,
segments=filled,
sess=self.session_of(sender_id),
immutable=immutable,
)
if refused:
return refused
ready, updated, gaps = refresh_segments(filled)
if not ready:
self._reply_gaps(updated, gaps, reply)
@@ -358,6 +438,16 @@ class MultiTextInquiryFlow:
口语补句走单聊同一套抽取,不在这里猜「广州-深圳」。
"""
refused = self._gate_sensitive(
sender_id=sess.sender_id,
text=text,
reply=reply,
segments=self._segments(sess),
sess=sess,
immutable=sess.immutable_text or text,
)
if refused:
return refused
from agent.llm.extract_text import extract_inquiry_snapshot
from agent.policy.system_exception import handle_extract_tech_fail
@@ -407,6 +497,16 @@ class MultiTextInquiryFlow:
def _create_ticket(self, sess: FlowSession, reply: ReplyFn) -> str:
"""回复确定后建一张 MULTI 工单。含空运只给手动拉群提示,不含空运出拉群卡。"""
segments = self._segments(sess)
refused = self._gate_sensitive(
sender_id=sess.sender_id,
text="",
reply=reply,
segments=segments,
sess=sess,
immutable=sess.immutable_text,
)
if refused:
return refused
ready, updated, gaps = refresh_segments(segments)
if not ready:
self._reply_gaps(updated, gaps, reply)