落地销售调价:按自然语言改价并单独记销售价,拉群和后台不再把调后价当成 TMS 原价。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-20 14:40:20 +08:00
co-authored by Cursor
parent e4743541f7
commit 26dd5d72a1
36 changed files with 2795 additions and 176 deletions
+49 -14
View File
@@ -67,6 +67,9 @@ def _prepare_model_tool_args(
if intent == "continue_thread" or action_type == "CONTINUE_HISTORICAL_TICKET":
data["intent"] = "continue_thread"
action["action_type"] = "CONTINUE_HISTORICAL_TICKET"
if intent == "quote_adjust" or action_type == "QUOTE_ADJUST":
data["intent"] = "quote_adjust"
action["action_type"] = "QUOTE_ADJUST"
target = action.get("target") if isinstance(action.get("target"), dict) else {}
wo = str(target.get("work_order_no") or "").strip()
if data.get("intent") == "continue_thread" and not wo:
@@ -98,9 +101,13 @@ _SYSTEM = (
"原话在描述新的运输需求(港口/机场、货物、件数重量体积、空运海运陆运等),"
"即使偶尔拿旧单号当参考,也走新询价,work_order_no 留空。"
"陆运核对卡之后回复确定、确认、查价、可以查了,仍是新询价,禁止标闲聊。"
"【调价】intent=quote_adjust,action_type=QUOTE_ADJUST。"
"inbound_facts 里 session_quoted=true 时,销售用自然语言改当前已有报价:"
"涨、跌、加、减、上升、上浮、往上抬、改到某金额、在现有报价基础上调整,口令不写死。"
"只说报价/总价、没点名费用,仍是调价,不是闲聊也不是新询价。"
"【闲聊】intent=ordinary_text_other,action_type=ORDINARY_TEXT_OTHER。"
"问候、谢谢、无关闲聊。点卡片按钮字(生成Excel/生成PDF/不采用等)也标闲聊。"
"禁止因为销售没说指定口令就把续办判成新询价。"
"禁止因为销售没说指定口令就把续办或调价判成新询价或闲聊。"
)
@@ -134,22 +141,29 @@ class DeepSeekARouter:
allow_network=allow_network,
)
def build_messages(self, *, text: str, sender_id: str) -> list[dict[str, Any]]:
"""构造仅含 inbound_facts 的 messages(不做字段抽取)。"""
facts = {
"sender_id": sender_id,
"text": text,
"text_sha256": _digest_text(text or ""),
}
def build_messages(
self,
*,
text: str,
sender_id: str,
session_quoted: bool = False,
fee_names: Optional[list[str]] = None,
work_order_no: str = "",
) -> list[dict[str, Any]]:
"""构造仅含 inbound_facts 的 messages(不做字段抽取、不算价)。"""
names = "、".join(str(x).strip() for x in (fee_names or []) if str(x).strip())
return [
{"role": "system", "content": _SYSTEM},
{
"role": "user",
"content": (
"inbound_facts (JSON-like):\n"
f"sender_id={facts['sender_id']}\n"
f"text_sha256={facts['text_sha256']}\n"
f"text={facts['text']}\n"
f"sender_id={sender_id}\n"
f"text_sha256={_digest_text(text or '')}\n"
f"text={text}\n"
f"session_quoted={'true' if session_quoted else 'false'}\n"
f"work_order_no={work_order_no or '-'}\n"
f"fee_names={names or '-'}\n"
"Call route_decision once."
),
},
@@ -212,12 +226,21 @@ class DeepSeekARouter:
logger.info("DeepSeekA stub fixture sender=%s", sender_id)
return self.route_from_tool_args(args)
def route(self, *, text: str, sender_id: str) -> RouteDecision:
def route(
self,
*,
text: str,
sender_id: str,
session_quoted: bool = False,
fee_names: Optional[list[str]] = None,
work_order_no: str = "",
) -> RouteDecision:
"""
执行 A 路由。
无 sender_id → reject;未开网络或未确认数据用途 → stub fixture;
开网络 → tool_choice 强制 route_decision。
session_quoted 告诉模型当前私聊已有可调报价,口令不写死。
"""
if not sender_id:
return RouteDecision(intent="reject_no_sender", confidence=1.0)
@@ -228,8 +251,20 @@ class DeepSeekARouter:
)
return self.route_stub_fixture(text=text, sender_id=sender_id)
logger.info("DeepSeekA 真网路由 sender=%s chars=%s", sender_id, len(text or ""))
messages = self.build_messages(text=text, sender_id=sender_id)
logger.info(
"DeepSeekA 真网路由 sender=%s chars=%s quoted=%s wo=%s",
sender_id,
len(text or ""),
session_quoted,
work_order_no or "-",
)
messages = self.build_messages(
text=text,
sender_id=sender_id,
session_quoted=session_quoted,
fee_names=fee_names,
work_order_no=work_order_no,
)
tools = [ordered_actions_tool_definition()]
result: LlmHttpResult = self.http.chat(
mode=LlmMode.ORDERED_ACTIONS,