feat: add per-shop persona routing for ai replies

This commit is contained in:
2026-03-02 13:02:07 +08:00
parent 684409686d
commit 4022ed8f7a
3 changed files with 66 additions and 3 deletions

View File

@@ -2,11 +2,28 @@
"shops": { "shops": {
"tb2801080146": { "tb2801080146": {
"type": "gemini_api", "type": "gemini_api",
"hint": "【店铺类型】Gemini API 账号。客户问账号/pro/续费/没pro时按API客服回复续费/充值/套餐说明。" "hint": "【店铺类型】Gemini API 账号。客户问账号/pro/续费/没pro时按API客服回复续费/充值/套餐说明。",
"persona": "技术型客服,表达清晰,回答直接,少废话,优先给可执行步骤和结论。"
}, },
"小威哥1216": { "小威哥1216": {
"type": "find_image", "type": "find_image",
"hint": "【店铺类型】找原图/修图。" "hint": "【店铺类型】找原图/修图。",
"persona": "修图老店主语气,接地气,像微信聊天,先承接再推进成交,不端着。"
},
"小威哥1216:媚媚": {
"type": "find_image",
"hint": "【店铺类型】找原图/修图。",
"persona": "女性店主口吻,亲切利落,话短不硬,先确认需求再自然推进下单。"
},
"tb7518056865:小林": {
"type": "find_image",
"hint": "【店铺类型】找原图/修图。",
"persona": "效率型客服,回复简短干脆,先给结论再补一句说明,避免重复。"
},
"tb637530900564:小威威": {
"type": "find_image",
"hint": "【店铺类型】找原图/修图。",
"persona": "熟客店主口吻,轻松自然,像真人在店里接单聊天,避免模板句。"
} }
}, },
"goods_keywords": { "goods_keywords": {
@@ -23,5 +40,10 @@
"gemini_api": "【店铺类型】Gemini API 账号。客户问账号/pro/续费/没pro时按API客服回复续费/充值/套餐说明,自然回复。", "gemini_api": "【店铺类型】Gemini API 账号。客户问账号/pro/续费/没pro时按API客服回复续费/充值/套餐说明,自然回复。",
"find_image": "【店铺类型】找原图/修图。" "find_image": "【店铺类型】找原图/修图。"
}, },
"_comment": "新增店铺:在 shops 加 acc_id。新增商品类型在 goods_keywords 加关键词→类型。" "type_personas": {
"gemini_api": "技术支持型客服,回答准确直给,先结论后解释,避免口水话。",
"find_image": "淘宝修图店主语气,口语自然,有温度但不啰嗦,先承接再推进成交。"
},
"default_persona": "淘宝老店主,说话自然,像真人微信聊天,不官腔、不背模板。",
"_comment": "新增店铺:在 shops 加 acc_id。可选 persona 设置店铺人设;未设置则走 type_personas/default_persona。"
} }

View File

@@ -25,6 +25,7 @@ def build_prompt(
state: Any, state: Any,
extract_image_url: Callable[[str], str], extract_image_url: Callable[[str], str],
shop_type_resolver: Callable[[str, str], str], shop_type_resolver: Callable[[str, str], str],
shop_persona_resolver: Callable[[str, str], str],
parse_order_info: Callable[[str], dict[str, str]], parse_order_info: Callable[[str], dict[str, str]],
build_order_instruction: Callable[[str, str], str], build_order_instruction: Callable[[str, str], str],
) -> str: ) -> str:
@@ -58,6 +59,7 @@ def build_prompt(
stage_info += f"\n【客户压价次数】{state.discount_count}" stage_info += f"\n【客户压价次数】{state.discount_count}"
shop_type = shop_type_resolver(message.acc_id or "", message.goods_name or "") shop_type = shop_type_resolver(message.acc_id or "", message.goods_name or "")
shop_persona = shop_persona_resolver(message.acc_id or "", message.goods_name or "")
shop_hint = "" shop_hint = ""
try: try:
from config.config import CONFIG_DIR from config.config import CONFIG_DIR
@@ -84,6 +86,8 @@ def build_prompt(
prompt += f"商品名称: {message.goods_name}\n" prompt += f"商品名称: {message.goods_name}\n"
if shop_hint: if shop_hint:
prompt += f"\n{shop_hint}\n" prompt += f"\n{shop_hint}\n"
if shop_persona:
prompt += f"\n【店铺人设】{shop_persona}\n"
order_paid = False order_paid = False
order_unpaid = False order_unpaid = False

View File

@@ -234,6 +234,37 @@ def _get_shop_type(acc_id: str = "", goods_name: str = "") -> str:
return "find_image" return "find_image"
def _get_shop_persona(acc_id: str = "", goods_name: str = "") -> str:
"""按店铺返回人设描述优先级shops.persona > type_personas > default_persona。"""
default_persona = "淘宝老店主,说话自然,像真人微信聊天,不官腔、不背模板。"
try:
from config.config import CONFIG_DIR
import json
cfg_path = CONFIG_DIR / "shop_prompts.json"
if not cfg_path.exists():
return default_persona
with open(cfg_path, "r", encoding="utf-8") as f:
cfg = json.load(f)
shops = cfg.get("shops", {})
if acc_id and acc_id in shops:
persona = str(shops[acc_id].get("persona", "")).strip()
if persona:
return persona
shop_type = _get_shop_type(acc_id, goods_name)
type_personas = cfg.get("type_personas", {})
persona = str(type_personas.get(shop_type, "")).strip()
if persona:
return persona
cfg_default = str(cfg.get("default_persona", "")).strip()
return cfg_default or default_persona
except Exception:
return default_persona
def load_skill_map(skills_dir: str = "skills") -> Dict[str, str]: def load_skill_map(skills_dir: str = "skills") -> Dict[str, str]:
"""按技能目录名加载 SKILL.md返回 {skill_name: content}。""" """按技能目录名加载 SKILL.md返回 {skill_name: content}。"""
skill_map: Dict[str, str] = {} skill_map: Dict[str, str] = {}
@@ -504,9 +535,11 @@ class CustomerServiceAgent:
) )
history = self.message_histories.get(message.from_id, []) history = self.message_histories.get(message.from_id, [])
pending_req = "".join((state.pending_requirements or [])[-4:]) or "" pending_req = "".join((state.pending_requirements or [])[-4:]) or ""
persona = _get_shop_persona(message.acc_id or "", message.goods_name or "")
user_prompt = ( user_prompt = (
"请按下面意图生成给客户的自然回复。\n" "请按下面意图生成给客户的自然回复。\n"
f"场景: {scene}\n" f"场景: {scene}\n"
f"店铺人设: {persona}\n"
f"回复意图: {intent_hint}\n" f"回复意图: {intent_hint}\n"
f"客户原话: {message.msg}\n" f"客户原话: {message.msg}\n"
f"当前已收图片数: {len(state.pending_image_urls)}\n" f"当前已收图片数: {len(state.pending_image_urls)}\n"
@@ -551,9 +584,11 @@ class CustomerServiceAgent:
) )
history = self.message_histories.get(message.from_id, []) history = self.message_histories.get(message.from_id, [])
pending_req = "".join((state.pending_requirements or [])[-4:]) or "" pending_req = "".join((state.pending_requirements or [])[-4:]) or ""
persona = _get_shop_persona(message.acc_id or "", message.goods_name or "")
prompt = ( prompt = (
"请把下面这句客服回复润色成更自然的微信聊天口吻,语义必须保持一致。\n" "请把下面这句客服回复润色成更自然的微信聊天口吻,语义必须保持一致。\n"
f"场景: {scene}\n" f"场景: {scene}\n"
f"店铺人设: {persona}\n"
f"客户原话: {message.msg}\n" f"客户原话: {message.msg}\n"
f"当前已收图: {len(state.pending_image_urls)}\n" f"当前已收图: {len(state.pending_image_urls)}\n"
f"当前需求摘要: {pending_req}\n" f"当前需求摘要: {pending_req}\n"
@@ -650,6 +685,7 @@ class CustomerServiceAgent:
_is_political_inquiry = staticmethod(is_political_inquiry) _is_political_inquiry = staticmethod(is_political_inquiry)
_is_map_inquiry = staticmethod(is_map_inquiry) _is_map_inquiry = staticmethod(is_map_inquiry)
_get_shop_type = staticmethod(_get_shop_type) _get_shop_type = staticmethod(_get_shop_type)
_get_shop_persona = staticmethod(_get_shop_persona)
_notify_wechat = staticmethod(_notify_wechat) _notify_wechat = staticmethod(_notify_wechat)
_notify_wechat_overdue = staticmethod(_notify_wechat_overdue) _notify_wechat_overdue = staticmethod(_notify_wechat_overdue)
@@ -989,6 +1025,7 @@ class CustomerServiceAgent:
state=state, state=state,
extract_image_url=self._extract_image_url, extract_image_url=self._extract_image_url,
shop_type_resolver=_get_shop_type, shop_type_resolver=_get_shop_type,
shop_persona_resolver=_get_shop_persona,
parse_order_info=parse_order_info, parse_order_info=parse_order_info,
build_order_instruction=build_order_instruction, build_order_instruction=build_order_instruction,
) )