refactor: migrate workflow to v2 core and archive legacy modules

This commit is contained in:
2026-03-04 21:52:24 +08:00
parent e1ce17f2aa
commit fa61b11b02
156 changed files with 1781 additions and 2066 deletions

201
legacy/agent_pre_rules.py Normal file
View File

@@ -0,0 +1,201 @@
from __future__ import annotations
import logging
import random
from datetime import datetime
from typing import TYPE_CHECKING, Optional
from core.rules import Rule, RuleContext, RuleEngine, RuleResult
from services.risk_service import RiskService
if TYPE_CHECKING:
from core.pydantic_ai_agent import (
AgentResponse,
ConversationState,
CustomerMessage,
CustomerServiceAgent,
)
class AgentPreRuleService:
"""Pre-processing rule chain for short replies, cooldown, and text risk."""
def __init__(self, agent: "CustomerServiceAgent", risk_service: RiskService):
self.agent = agent
self.risk_service = risk_service
self.engine = self._build_engine()
async def run(
self,
*,
message: "CustomerMessage",
state: "ConversationState",
trace_id: str,
) -> Optional["AgentResponse"]:
ctx = RuleContext(data={"message": message, "state": state, "trace_id": trace_id})
result = await self.engine.run(ctx)
if not result.stop:
return None
response = result.payload.get("response")
return response
def _build_engine(self) -> RuleEngine:
return RuleEngine(
rules=[
Rule(
name="meaningless_short_text",
priority=10,
predicate=self._rule_pred_meaningless_short_text,
action=self._rule_act_meaningless_short_text,
),
Rule(
name="cooldown_silent",
priority=20,
predicate=self._rule_pred_cooldown_silent,
action=self._rule_act_cooldown_silent,
),
Rule(
name="manual_risk_block",
priority=30,
predicate=self._rule_pred_manual_risk_block,
action=self._rule_act_manual_risk_block,
),
Rule(
name="text_risk_block",
priority=40,
predicate=self._rule_pred_text_risk_block,
action=self._rule_act_text_risk_block,
),
]
)
async def _rule_pred_meaningless_short_text(self, ctx: RuleContext) -> bool:
message = ctx.get("message")
state = ctx.get("state")
return self.agent._should_handle_as_meaningless_short_text(state, message.msg)
async def _rule_act_meaningless_short_text(self, ctx: RuleContext) -> RuleResult:
from core.pydantic_ai_agent import AgentResponse
message = ctx.get("message")
state = ctx.get("state")
trace_id = ctx.get("trace_id", "")
ping = random.choice(("嗯咯", "嗯啦", "", ""))
state.last_reply_at = datetime.now()
self.agent._activity_log(
"agent_ping_reply",
trace_id=trace_id,
customer_id=message.from_id,
msg=message.msg,
reply=ping,
)
return RuleResult(
matched=True,
stop=True,
action="agent_ping_reply",
payload={"response": AgentResponse(reply=ping, should_reply=True, need_transfer=False)},
)
async def _rule_pred_cooldown_silent(self, ctx: RuleContext) -> bool:
message = ctx.get("message")
state = ctx.get("state")
return self.agent._in_cooldown(state, message.msg)
async def _rule_act_cooldown_silent(self, ctx: RuleContext) -> RuleResult:
from core.pydantic_ai_agent import AgentResponse
message = ctx.get("message")
state = ctx.get("state")
trace_id = ctx.get("trace_id", "")
elapsed = int((datetime.now() - state.last_reply_at).total_seconds()) if state.last_reply_at else 0
logger.info("[Agent] 冷却期静默(距上次回复 %ss%r", elapsed, message.msg)
self.agent._activity_log(
"agent_cooldown_silent",
trace_id=trace_id,
customer_id=message.from_id,
elapsed_s=elapsed,
)
return RuleResult(
matched=True,
stop=True,
action="agent_cooldown_silent",
payload={"response": AgentResponse(reply="", should_reply=False, need_transfer=False)},
)
async def _rule_pred_manual_risk_block(self, ctx: RuleContext) -> bool:
message = ctx.get("message")
decision = self.risk_service.check_manual_block(message.from_id)
ctx.set("manual_risk_decision", decision)
return decision.blocked
async def _rule_act_manual_risk_block(self, ctx: RuleContext) -> RuleResult:
from core.pydantic_ai_agent import AgentResponse, TRANSFER_MESSAGE
message = ctx.get("message")
trace_id = ctx.get("trace_id", "")
decision = ctx.get("manual_risk_decision")
self.agent._activity_log(
"agent_manual_risk_reject",
trace_id=trace_id,
customer_id=message.from_id,
risk=(decision.profile if decision else {}),
)
return RuleResult(
matched=True,
stop=True,
action="agent_manual_risk_reject",
payload={
"response": AgentResponse(
reply="这边无法继续为你处理该类需求,给你转人工专员对接。",
should_reply=True,
need_transfer=True,
transfer_msg=TRANSFER_MESSAGE,
)
},
)
async def _rule_pred_text_risk_block(self, ctx: RuleContext) -> bool:
message = ctx.get("message")
decision = await self.risk_service.check_text_block(
message.msg,
political_detector=self.agent._is_political_inquiry,
map_detector=self.agent._is_map_inquiry,
)
ctx.set("text_risk_decision", decision)
return decision.blocked
async def _rule_act_text_risk_block(self, ctx: RuleContext) -> RuleResult:
from core.pydantic_ai_agent import AgentResponse
message = ctx.get("message")
state = ctx.get("state")
trace_id = ctx.get("trace_id", "")
decision = ctx.get("text_risk_decision")
state.pending_image_urls.clear()
state.pending_requirements.clear()
self.agent._sync_pending_quote_state(message.from_id, state)
reject_text = self.risk_service.build_reject_text(decision.category if decision else "other")
reply = await self.agent._rewrite_reply_with_ai(
message=message,
state=state,
reply=reject_text,
scene="risk_reject",
)
state.last_reply_at = datetime.now()
logger.info("[REPLY->CUSTOMER] %s", reply)
self.agent._activity_log(
"agent_risk_reject",
trace_id=trace_id,
customer_id=message.from_id,
risk_category=(decision.category if decision else "other"),
risk_source=(decision.source if decision else "unknown"),
reply=reply,
)
return RuleResult(
matched=True,
stop=True,
action="agent_risk_reject",
payload={"response": AgentResponse(reply=reply, should_reply=True, need_transfer=False)},
)
logger = logging.getLogger("cs_agent")