refactor: unify core pipeline logging with cs_agent logger
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
import random
|
import random
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Optional
|
||||||
@@ -108,7 +109,7 @@ class AgentPreRuleService:
|
|||||||
state = ctx.get("state")
|
state = ctx.get("state")
|
||||||
trace_id = ctx.get("trace_id", "")
|
trace_id = ctx.get("trace_id", "")
|
||||||
elapsed = int((datetime.now() - state.last_reply_at).total_seconds()) if state.last_reply_at else 0
|
elapsed = int((datetime.now() - state.last_reply_at).total_seconds()) if state.last_reply_at else 0
|
||||||
print(f"[Agent] 冷却期静默(距上次回复 {elapsed}s):{message.msg!r}")
|
logger.info("[Agent] 冷却期静默(距上次回复 %ss):%r", elapsed, message.msg)
|
||||||
self.agent._activity_log(
|
self.agent._activity_log(
|
||||||
"agent_cooldown_silent",
|
"agent_cooldown_silent",
|
||||||
trace_id=trace_id,
|
trace_id=trace_id,
|
||||||
@@ -183,7 +184,7 @@ class AgentPreRuleService:
|
|||||||
scene="risk_reject",
|
scene="risk_reject",
|
||||||
)
|
)
|
||||||
state.last_reply_at = datetime.now()
|
state.last_reply_at = datetime.now()
|
||||||
print(f"{self.agent.C_REPLY}[REPLY->CUSTOMER]{self.agent.C_RESET} {reply}")
|
logger.info("[REPLY->CUSTOMER] %s", reply)
|
||||||
self.agent._activity_log(
|
self.agent._activity_log(
|
||||||
"agent_risk_reject",
|
"agent_risk_reject",
|
||||||
trace_id=trace_id,
|
trace_id=trace_id,
|
||||||
@@ -198,3 +199,4 @@ class AgentPreRuleService:
|
|||||||
action="agent_risk_reject",
|
action="agent_risk_reject",
|
||||||
payload={"response": AgentResponse(reply=reply, should_reply=True, need_transfer=False)},
|
payload={"response": AgentResponse(reply=reply, should_reply=True, need_transfer=False)},
|
||||||
)
|
)
|
||||||
|
logger = logging.getLogger("cs_agent")
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from pydantic_ai import RunContext
|
from pydantic_ai import RunContext
|
||||||
@@ -8,6 +9,8 @@ from db.customer_risk_db import risk_db
|
|||||||
from services.service_tuhui_upload import upload_to_tuhui
|
from services.service_tuhui_upload import upload_to_tuhui
|
||||||
from core.order_helpers import parse_order_info
|
from core.order_helpers import parse_order_info
|
||||||
|
|
||||||
|
logger = logging.getLogger("cs_agent")
|
||||||
|
|
||||||
|
|
||||||
def register_tools(agent) -> None:
|
def register_tools(agent) -> None:
|
||||||
"""注册所有 Tool,让 Agent 可以主动调用。"""
|
"""注册所有 Tool,让 Agent 可以主动调用。"""
|
||||||
@@ -65,9 +68,15 @@ def register_tools(agent) -> None:
|
|||||||
subject=result.get("subject", ""),
|
subject=result.get("subject", ""),
|
||||||
quality=result.get("quality", ""),
|
quality=result.get("quality", ""),
|
||||||
)
|
)
|
||||||
print(f"[Agent] Workflow 任务已创建 | 客户: {ctx.deps.from_id} | 比例: {result.get('aspect_ratio')} | 透视: {result.get('perspective')} | 图片: {image_url[:60]}...")
|
logger.info(
|
||||||
|
"[Agent] Workflow 任务已创建 | 客户: %s | 比例: %s | 透视: %s | 图片: %s...",
|
||||||
|
ctx.deps.from_id,
|
||||||
|
result.get("aspect_ratio"),
|
||||||
|
result.get("perspective"),
|
||||||
|
image_url[:60],
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[Agent] Workflow 任务创建失败: {e}")
|
logger.exception("[Agent] Workflow 任务创建失败: %s", e)
|
||||||
|
|
||||||
# 组装给 AI 的分析报告
|
# 组装给 AI 的分析报告
|
||||||
risk = result.get("risk", "none")
|
risk = result.get("risk", "none")
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any
|
||||||
from core.post_ops import negotiation_strategy_reply
|
from core.post_ops import negotiation_strategy_reply
|
||||||
|
|
||||||
|
logger = logging.getLogger("cs_agent")
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from core.pydantic_ai_agent import AgentDeps, ConversationState, CustomerMessage, CustomerServiceAgent
|
from core.pydantic_ai_agent import AgentDeps, ConversationState, CustomerMessage, CustomerServiceAgent
|
||||||
|
|
||||||
@@ -124,13 +127,14 @@ async def execute_ai_turn(
|
|||||||
for part in getattr(msg, "parts", []):
|
for part in getattr(msg, "parts", []):
|
||||||
part_type = type(part).__name__
|
part_type = type(part).__name__
|
||||||
if "ToolCall" in part_type:
|
if "ToolCall" in part_type:
|
||||||
print(
|
logger.info(
|
||||||
f"{agent.C_TOOL}[THINK/TOOL_CALL]{agent.C_RESET} "
|
"[THINK/TOOL_CALL] %s(%s)",
|
||||||
f"{getattr(part, 'tool_name', '')}({getattr(part, 'args', '')})"
|
getattr(part, "tool_name", ""),
|
||||||
|
getattr(part, "args", ""),
|
||||||
)
|
)
|
||||||
elif "ToolReturn" in part_type:
|
elif "ToolReturn" in part_type:
|
||||||
ret = str(getattr(part, "content", ""))[:120]
|
ret = str(getattr(part, "content", ""))[:120]
|
||||||
print(f"{agent.C_TOOL}[THINK/TOOL_RETURN]{agent.C_RESET} {ret}")
|
logger.info("[THINK/TOOL_RETURN] %s", ret)
|
||||||
|
|
||||||
print(f"{agent.C_THINK}[THINK/RAW_OUTPUT]{agent.C_RESET} {repr(reply_text)}")
|
logger.info("[THINK/RAW_OUTPUT] %r", reply_text)
|
||||||
return reply_text
|
return reply_text
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import logging
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
logger = logging.getLogger("cs_agent")
|
||||||
|
|
||||||
|
|
||||||
def calc_avg_complexity(complexity_history: list) -> str:
|
def calc_avg_complexity(complexity_history: list) -> str:
|
||||||
"""计算平均复杂度。"""
|
"""计算平均复杂度。"""
|
||||||
@@ -153,7 +156,7 @@ def get_customer_profile_context(agent, customer_id: str) -> str:
|
|||||||
|
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[Agent] 获取客户画像失败: {e}")
|
logger.exception("[Agent] 获取客户画像失败: %s", e)
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
|
logger = logging.getLogger("cs_agent")
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from core.pydantic_ai_agent import AgentResponse, ConversationState, CustomerMessage, CustomerServiceAgent
|
from core.pydantic_ai_agent import AgentResponse, ConversationState, CustomerMessage, CustomerServiceAgent
|
||||||
|
|
||||||
@@ -55,7 +58,7 @@ async def handle_find_image_batch_flow(
|
|||||||
fallback=defer_fallback,
|
fallback=defer_fallback,
|
||||||
)
|
)
|
||||||
state.last_reply_at = datetime.now()
|
state.last_reply_at = datetime.now()
|
||||||
print(f"{agent.C_REPLY}[REPLY->CUSTOMER]{agent.C_RESET} {defer_reply}")
|
logger.info("[REPLY->CUSTOMER] %s", defer_reply)
|
||||||
return AgentResponse(reply=defer_reply, should_reply=True, need_transfer=False)
|
return AgentResponse(reply=defer_reply, should_reply=True, need_transfer=False)
|
||||||
quote_res = await agent._quote_pending_images(state, message)
|
quote_res = await agent._quote_pending_images(state, message)
|
||||||
reply_text = agent._colloquialize_reply(quote_res.get("reply", ""))
|
reply_text = agent._colloquialize_reply(quote_res.get("reply", ""))
|
||||||
@@ -67,7 +70,7 @@ async def handle_find_image_batch_flow(
|
|||||||
)
|
)
|
||||||
need_transfer = bool(quote_res.get("need_transfer"))
|
need_transfer = bool(quote_res.get("need_transfer"))
|
||||||
state.last_reply_at = datetime.now()
|
state.last_reply_at = datetime.now()
|
||||||
print(f"{agent.C_REPLY}[REPLY->CUSTOMER]{agent.C_RESET} {reply_text}")
|
logger.info("[REPLY->CUSTOMER] %s", reply_text)
|
||||||
return AgentResponse(
|
return AgentResponse(
|
||||||
reply=reply_text,
|
reply=reply_text,
|
||||||
should_reply=not need_transfer,
|
should_reply=not need_transfer,
|
||||||
@@ -89,7 +92,7 @@ async def handle_find_image_batch_flow(
|
|||||||
fallback=ack_fallback,
|
fallback=ack_fallback,
|
||||||
)
|
)
|
||||||
state.last_reply_at = datetime.now()
|
state.last_reply_at = datetime.now()
|
||||||
print(f"{agent.C_REPLY}[REPLY->CUSTOMER]{agent.C_RESET} {ack}")
|
logger.info("[REPLY->CUSTOMER] %s", ack)
|
||||||
return AgentResponse(reply=ack, should_reply=True, need_transfer=False)
|
return AgentResponse(reply=ack, should_reply=True, need_transfer=False)
|
||||||
|
|
||||||
if not state.pending_image_urls:
|
if not state.pending_image_urls:
|
||||||
@@ -118,7 +121,7 @@ async def handle_find_image_batch_flow(
|
|||||||
fallback=clarify_fallback,
|
fallback=clarify_fallback,
|
||||||
)
|
)
|
||||||
state.last_reply_at = datetime.now()
|
state.last_reply_at = datetime.now()
|
||||||
print(f"{agent.C_REPLY}[REPLY->CUSTOMER]{agent.C_RESET} {clarify}")
|
logger.info("[REPLY->CUSTOMER] %s", clarify)
|
||||||
return AgentResponse(reply=clarify, should_reply=True, need_transfer=False)
|
return AgentResponse(reply=clarify, should_reply=True, need_transfer=False)
|
||||||
|
|
||||||
if state.quote_phase == "ready_to_quote" and state.quote_ready_turns <= 0 and short_intent in {"progress_query", "ack", "finish_signal"}:
|
if state.quote_phase == "ready_to_quote" and state.quote_ready_turns <= 0 and short_intent in {"progress_query", "ack", "finish_signal"}:
|
||||||
@@ -132,7 +135,7 @@ async def handle_find_image_batch_flow(
|
|||||||
)
|
)
|
||||||
need_transfer = bool(quote_res.get("need_transfer"))
|
need_transfer = bool(quote_res.get("need_transfer"))
|
||||||
state.last_reply_at = datetime.now()
|
state.last_reply_at = datetime.now()
|
||||||
print(f"{agent.C_REPLY}[REPLY->CUSTOMER]{agent.C_RESET} {reply_text}")
|
logger.info("[REPLY->CUSTOMER] %s", reply_text)
|
||||||
return AgentResponse(
|
return AgentResponse(
|
||||||
reply=reply_text,
|
reply=reply_text,
|
||||||
should_reply=not need_transfer,
|
should_reply=not need_transfer,
|
||||||
@@ -150,7 +153,7 @@ async def handle_find_image_batch_flow(
|
|||||||
fallback=progress_fallback,
|
fallback=progress_fallback,
|
||||||
)
|
)
|
||||||
state.last_reply_at = datetime.now()
|
state.last_reply_at = datetime.now()
|
||||||
print(f"{agent.C_REPLY}[REPLY->CUSTOMER]{agent.C_RESET} {progress}")
|
logger.info("[REPLY->CUSTOMER] %s", progress)
|
||||||
return AgentResponse(reply=progress, should_reply=True, need_transfer=False)
|
return AgentResponse(reply=progress, should_reply=True, need_transfer=False)
|
||||||
|
|
||||||
if agent._needs_clarification_in_collecting(text_without_urls):
|
if agent._needs_clarification_in_collecting(text_without_urls):
|
||||||
@@ -163,7 +166,7 @@ async def handle_find_image_batch_flow(
|
|||||||
fallback=ask_fallback,
|
fallback=ask_fallback,
|
||||||
)
|
)
|
||||||
state.last_reply_at = datetime.now()
|
state.last_reply_at = datetime.now()
|
||||||
print(f"{agent.C_REPLY}[REPLY->CUSTOMER]{agent.C_RESET} {ask}")
|
logger.info("[REPLY->CUSTOMER] %s", ask)
|
||||||
return AgentResponse(reply=ask, should_reply=True, need_transfer=False)
|
return AgentResponse(reply=ask, should_reply=True, need_transfer=False)
|
||||||
if agent._is_batch_finish_intent(
|
if agent._is_batch_finish_intent(
|
||||||
text=customer_text,
|
text=customer_text,
|
||||||
@@ -182,7 +185,7 @@ async def handle_find_image_batch_flow(
|
|||||||
fallback=defer_fallback,
|
fallback=defer_fallback,
|
||||||
)
|
)
|
||||||
state.last_reply_at = datetime.now()
|
state.last_reply_at = datetime.now()
|
||||||
print(f"{agent.C_REPLY}[REPLY->CUSTOMER]{agent.C_RESET} {defer_reply}")
|
logger.info("[REPLY->CUSTOMER] %s", defer_reply)
|
||||||
return AgentResponse(reply=defer_reply, should_reply=True, need_transfer=False)
|
return AgentResponse(reply=defer_reply, should_reply=True, need_transfer=False)
|
||||||
quote_res = await agent._quote_pending_images(state, message)
|
quote_res = await agent._quote_pending_images(state, message)
|
||||||
reply_text = agent._colloquialize_reply(quote_res.get("reply", ""))
|
reply_text = agent._colloquialize_reply(quote_res.get("reply", ""))
|
||||||
@@ -194,7 +197,7 @@ async def handle_find_image_batch_flow(
|
|||||||
)
|
)
|
||||||
need_transfer = bool(quote_res.get("need_transfer"))
|
need_transfer = bool(quote_res.get("need_transfer"))
|
||||||
state.last_reply_at = datetime.now()
|
state.last_reply_at = datetime.now()
|
||||||
print(f"{agent.C_REPLY}[REPLY->CUSTOMER]{agent.C_RESET} {reply_text}")
|
logger.info("[REPLY->CUSTOMER] %s", reply_text)
|
||||||
return AgentResponse(
|
return AgentResponse(
|
||||||
reply=reply_text,
|
reply=reply_text,
|
||||||
should_reply=not need_transfer,
|
should_reply=not need_transfer,
|
||||||
@@ -211,5 +214,5 @@ async def handle_find_image_batch_flow(
|
|||||||
fallback=remind_fallback,
|
fallback=remind_fallback,
|
||||||
)
|
)
|
||||||
state.last_reply_at = datetime.now()
|
state.last_reply_at = datetime.now()
|
||||||
print(f"{agent.C_REPLY}[REPLY->CUSTOMER]{agent.C_RESET} {remind}")
|
logger.info("[REPLY->CUSTOMER] %s", remind)
|
||||||
return AgentResponse(reply=remind, should_reply=True, need_transfer=False)
|
return AgentResponse(reply=remind, should_reply=True, need_transfer=False)
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
logger = logging.getLogger("cs_agent")
|
||||||
|
|
||||||
|
|
||||||
async def handle_image_workflow(*, workflow_router: Any, message: str, data: dict, image_urls: list) -> bool:
|
async def handle_image_workflow(*, workflow_router: Any, message: str, data: dict, image_urls: list) -> bool:
|
||||||
"""处理图片工作流(根据客户说的话判断执行哪种工作流)。"""
|
"""处理图片工作流(根据客户说的话判断执行哪种工作流)。"""
|
||||||
@@ -15,10 +18,10 @@ async def handle_image_workflow(*, workflow_router: Any, message: str, data: dic
|
|||||||
acc_type = data.get("acc_type", "AliWorkbench")
|
acc_type = data.get("acc_type", "AliWorkbench")
|
||||||
image_url = image_urls[0]
|
image_url = image_urls[0]
|
||||||
|
|
||||||
print(f"[Agent] 检测到工作流类型:{workflow_type} (置信度:{confidence})")
|
logger.info("[Agent] 检测到工作流类型:%s (置信度:%s)", workflow_type, confidence)
|
||||||
|
|
||||||
if workflow_type == "find_image":
|
if workflow_type == "find_image":
|
||||||
print(f"[Agent] 执行查找图片工作流 | 客户:{customer_id}")
|
logger.info("[Agent] 执行查找图片工作流 | 客户:%s", customer_id)
|
||||||
from core.workflow import workflow
|
from core.workflow import workflow
|
||||||
|
|
||||||
return await workflow.find_image_workflow(
|
return await workflow.find_image_workflow(
|
||||||
@@ -28,7 +31,7 @@ async def handle_image_workflow(*, workflow_router: Any, message: str, data: dic
|
|||||||
acc_type=acc_type,
|
acc_type=acc_type,
|
||||||
)
|
)
|
||||||
if workflow_type == "process_image":
|
if workflow_type == "process_image":
|
||||||
print(f"[Agent] 执行处理图片工作流 | 客户:{customer_id}")
|
logger.info("[Agent] 执行处理图片工作流 | 客户:%s", customer_id)
|
||||||
from core.workflow import workflow
|
from core.workflow import workflow
|
||||||
|
|
||||||
return await workflow.process_image_workflow(
|
return await workflow.process_image_workflow(
|
||||||
@@ -38,7 +41,7 @@ async def handle_image_workflow(*, workflow_router: Any, message: str, data: dic
|
|||||||
acc_type=acc_type,
|
acc_type=acc_type,
|
||||||
)
|
)
|
||||||
if workflow_type == "transfer_human":
|
if workflow_type == "transfer_human":
|
||||||
print(f"[Agent] 执行转人工派单工作流 | 客户:{customer_id}")
|
logger.info("[Agent] 执行转人工派单工作流 | 客户:%s", customer_id)
|
||||||
from core.workflow import workflow
|
from core.workflow import workflow
|
||||||
|
|
||||||
return await workflow.transfer_to_designer_workflow(
|
return await workflow.transfer_to_designer_workflow(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from core.ai_reply_flow import execute_ai_turn
|
from core.ai_reply_flow import execute_ai_turn
|
||||||
@@ -11,6 +12,8 @@ from core.reply_finalize_flow import finalize_ai_reply
|
|||||||
from utils.metrics_tracker import emit as metrics_emit
|
from utils.metrics_tracker import emit as metrics_emit
|
||||||
from utils.observability import build_trace_id
|
from utils.observability import build_trace_id
|
||||||
|
|
||||||
|
logger = logging.getLogger("cs_agent")
|
||||||
|
|
||||||
|
|
||||||
async def process_incoming_message(agent: Any, message: Any) -> Any:
|
async def process_incoming_message(agent: Any, message: Any) -> Any:
|
||||||
"""主消息处理编排:预处理 -> 业务流 -> AI -> 收尾。"""
|
"""主消息处理编排:预处理 -> 业务流 -> AI -> 收尾。"""
|
||||||
@@ -72,7 +75,7 @@ async def process_incoming_message(agent: Any, message: Any) -> Any:
|
|||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
err_str = str(e)
|
err_str = str(e)
|
||||||
print(f"[Agent] AI 调用失败: {e},使用兜底回复")
|
logger.exception("[Agent] AI 调用失败,使用兜底回复: %s", err_str)
|
||||||
agent._activity_log("agent_ai_error", customer_id=message.from_id, acc_id=message.acc_id, error=err_str)
|
agent._activity_log("agent_ai_error", customer_id=message.from_id, acc_id=message.acc_id, error=err_str)
|
||||||
metrics_emit("ai_call_failed", customer_id=message.from_id, acc_id=message.acc_id)
|
metrics_emit("ai_call_failed", customer_id=message.from_id, acc_id=message.acc_id)
|
||||||
if "AccountOverdueError" in err_str or "overdue" in err_str.lower():
|
if "AccountOverdueError" in err_str or "overdue" in err_str.lower():
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import logging
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Optional
|
||||||
from core.post_ops import record_deal_success
|
from core.post_ops import record_deal_success
|
||||||
from core.order_helpers import parse_order_info
|
from core.order_helpers import parse_order_info
|
||||||
|
|
||||||
|
logger = logging.getLogger("cs_agent")
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from core.pydantic_ai_agent import AgentResponse, ConversationState, CustomerMessage, CustomerServiceAgent
|
from core.pydantic_ai_agent import AgentResponse, ConversationState, CustomerMessage, CustomerServiceAgent
|
||||||
|
|
||||||
@@ -53,9 +56,9 @@ async def handle_order_notification(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[Agent] 触发作图失败: {e}")
|
logger.exception("[Agent] 触发作图失败: %s", e)
|
||||||
elif not customer_text:
|
elif not customer_text:
|
||||||
print(f"[Agent] 订单通知静默({pay_status or order_status}),跳过回复")
|
logger.info("[Agent] 订单通知静默(%s),跳过回复", pay_status or order_status)
|
||||||
return AgentResponse(reply="", should_reply=False, need_transfer=False)
|
return AgentResponse(reply="", should_reply=False, need_transfer=False)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
import re
|
import re
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from utils.metrics_tracker import emit as metrics_emit
|
from utils.metrics_tracker import emit as metrics_emit
|
||||||
|
|
||||||
CASE_LIBRARY_LINK = "https://www.yuque.com/zuowei-dfvpq/kge0in/mynala0g35b8cec5"
|
CASE_LIBRARY_LINK = "https://www.yuque.com/zuowei-dfvpq/kge0in/mynala0g35b8cec5"
|
||||||
|
logger = logging.getLogger("cs_agent")
|
||||||
|
|
||||||
|
|
||||||
def detect_price(reply: str, state: Any) -> None:
|
def detect_price(reply: str, state: Any) -> None:
|
||||||
@@ -102,9 +104,9 @@ async def record_deal_success(
|
|||||||
db.clear_quote_no_convert(customer_id)
|
db.clear_quote_no_convert(customer_id)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
print(f"[Agent] 成交记录: {customer_id} {reason} {amount}元")
|
logger.info("[Agent] 成交记录: %s %s %s元", customer_id, reason, amount)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[Agent] 成交记录失败: {e}")
|
logger.exception("[Agent] 成交记录失败: %s", e)
|
||||||
|
|
||||||
|
|
||||||
async def record_deal_fail(
|
async def record_deal_fail(
|
||||||
@@ -128,9 +130,9 @@ async def record_deal_fail(
|
|||||||
platform=platform or "",
|
platform=platform or "",
|
||||||
)
|
)
|
||||||
db.mark_quote_no_convert(customer_id)
|
db.mark_quote_no_convert(customer_id)
|
||||||
print(f"[Agent] 未成交记录: {customer_id} {reason}")
|
logger.info("[Agent] 未成交记录: %s %s", customer_id, reason)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[Agent] 未成交记录失败: {e}")
|
logger.exception("[Agent] 未成交记录失败: %s", e)
|
||||||
|
|
||||||
|
|
||||||
async def auto_tag(message: Any, state: Any) -> None:
|
async def auto_tag(message: Any, state: Any) -> None:
|
||||||
|
|||||||
@@ -22,15 +22,10 @@ from pydantic_ai.models.openai import OpenAIChatModel
|
|||||||
from pydantic_ai.providers.openai import OpenAIProvider
|
from pydantic_ai.providers.openai import OpenAIProvider
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from utils.metrics_tracker import emit as metrics_emit
|
from utils.metrics_tracker import emit as metrics_emit
|
||||||
from utils.observability import emit_activity, build_trace_id
|
from utils.observability import emit_activity
|
||||||
from core.quote_state_machine import QuoteStateMachine
|
from core.quote_state_machine import QuoteStateMachine
|
||||||
from services.risk_service import RiskService
|
from services.risk_service import RiskService
|
||||||
from core.agent_pre_rules import AgentPreRuleService
|
from core.agent_pre_rules import AgentPreRuleService
|
||||||
from core.find_image_flow import handle_find_image_batch_flow
|
|
||||||
from core.order_flow import handle_order_notification
|
|
||||||
from core.ai_reply_flow import execute_ai_turn
|
|
||||||
from core.reply_finalize_flow import finalize_ai_reply
|
|
||||||
from core.prompt_flow import build_prompt_bundle
|
|
||||||
from core.order_helpers import parse_order_info, order_instruction as build_order_instruction
|
from core.order_helpers import parse_order_info, order_instruction as build_order_instruction
|
||||||
from core.collection_intent_helpers import (
|
from core.collection_intent_helpers import (
|
||||||
append_requirement,
|
append_requirement,
|
||||||
@@ -81,7 +76,6 @@ load_dotenv()
|
|||||||
|
|
||||||
from services.service_tuhui_upload import upload_to_tuhui
|
from services.service_tuhui_upload import upload_to_tuhui
|
||||||
from core.workflow_router import get_workflow_router
|
from core.workflow_router import get_workflow_router
|
||||||
from core.workflow_router import get_workflow_router
|
|
||||||
|
|
||||||
# ========== 企业微信通知 ==========
|
# ========== 企业微信通知 ==========
|
||||||
_WECHAT_WEBHOOK = os.getenv("WECHAT_WEBHOOK", "")
|
_WECHAT_WEBHOOK = os.getenv("WECHAT_WEBHOOK", "")
|
||||||
@@ -91,7 +85,7 @@ logger = logging.getLogger("cs_agent")
|
|||||||
async def _notify_wechat(content: str, tag: str = "通知"):
|
async def _notify_wechat(content: str, tag: str = "通知"):
|
||||||
"""发送企业微信 markdown 通知,任何异常都发"""
|
"""发送企业微信 markdown 通知,任何异常都发"""
|
||||||
if not _WECHAT_WEBHOOK:
|
if not _WECHAT_WEBHOOK:
|
||||||
print(f"[{tag}] 未配置 WECHAT_WEBHOOK,跳过推送")
|
logger.info("[%s] 未配置 WECHAT_WEBHOOK,跳过推送", tag)
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
import httpx
|
import httpx
|
||||||
@@ -102,11 +96,11 @@ async def _notify_wechat(content: str, tag: str = "通知"):
|
|||||||
})
|
})
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
if data.get("errcode") == 0:
|
if data.get("errcode") == 0:
|
||||||
print(f"[{tag}] 企业微信推送成功 ✓")
|
logger.info("[%s] 企业微信推送成功", tag)
|
||||||
else:
|
else:
|
||||||
print(f"[{tag}] 企业微信推送失败: {data}")
|
logger.warning("[%s] 企业微信推送失败: %s", tag, data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[{tag}] 企业微信发送异常: {e}")
|
logger.exception("[%s] 企业微信发送异常: %s", tag, e)
|
||||||
|
|
||||||
|
|
||||||
async def _notify_wechat_overdue():
|
async def _notify_wechat_overdue():
|
||||||
@@ -246,7 +240,7 @@ def load_skill_map(skills_dir: str = "skills") -> Dict[str, str]:
|
|||||||
else:
|
else:
|
||||||
skill_map[skill_name] = content
|
skill_map[skill_name] = content
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"警告: 读取 {skill_file} 失败: {e}")
|
logger.warning("读取技能文件失败: %s | err=%s", skill_file, e)
|
||||||
return skill_map
|
return skill_map
|
||||||
|
|
||||||
|
|
||||||
@@ -441,9 +435,7 @@ class CustomerServiceAgent:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def _log_block(title: str, content: str):
|
def _log_block(title: str, content: str):
|
||||||
"""统一的控制台分层日志输出。"""
|
"""统一的控制台分层日志输出。"""
|
||||||
print(f"{CustomerServiceAgent.C_PROMPT}[{title}]{CustomerServiceAgent.C_RESET}")
|
logger.info("[%s]\n%s\n--------------------", title, content)
|
||||||
print(content)
|
|
||||||
print(f"{CustomerServiceAgent.C_MUTED}────────────────────{CustomerServiceAgent.C_RESET}")
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _normalize_reply_text(text: Optional[str]) -> str:
|
def _normalize_reply_text(text: Optional[str]) -> str:
|
||||||
@@ -783,7 +775,7 @@ class CustomerServiceAgent:
|
|||||||
return
|
return
|
||||||
paid = float(m.group())
|
paid = float(m.group())
|
||||||
|
|
||||||
print(f"[Agent] 订单金额核查:报价 {quoted}元 vs 实付 {paid}元(客户 {customer_id})")
|
logger.info("[Agent] 订单金额核查:报价 %s元 vs 实付 %s元(客户 %s)", quoted, paid, customer_id)
|
||||||
|
|
||||||
# 实付金额明显低于报价(低于报价的 60%)才预警
|
# 实付金额明显低于报价(低于报价的 60%)才预警
|
||||||
if paid < quoted * 0.6:
|
if paid < quoted * 0.6:
|
||||||
@@ -795,10 +787,10 @@ class CustomerServiceAgent:
|
|||||||
f"实付:{paid}元\n"
|
f"实付:{paid}元\n"
|
||||||
f"差额:{quoted - paid:.1f}元 — 请人工核查"
|
f"差额:{quoted - paid:.1f}元 — 请人工核查"
|
||||||
)
|
)
|
||||||
print(f"[Agent] {msg}")
|
logger.warning("[Agent] %s", msg)
|
||||||
await self._notify_wechat(msg)
|
await self._notify_wechat(msg)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[Agent] 订单金额核查失败: {e}")
|
logger.exception("[Agent] 订单金额核查失败: %s", e)
|
||||||
|
|
||||||
def _extract_image_url(self, msg: str) -> str:
|
def _extract_image_url(self, msg: str) -> str:
|
||||||
"""从消息中提取图片URL,兼容纯URL和 text#*#url 两种格式"""
|
"""从消息中提取图片URL,兼容纯URL和 text#*#url 两种格式"""
|
||||||
@@ -887,7 +879,7 @@ class CustomerServiceAgent:
|
|||||||
quality=r.get("quality", ""),
|
quality=r.get("quality", ""),
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[Agent] Workflow 批量任务创建失败: {e}")
|
logger.exception("[Agent] Workflow 批量任务创建失败: %s", e)
|
||||||
|
|
||||||
_calc_requirement_surcharge = staticmethod(calc_requirement_surcharge)
|
_calc_requirement_surcharge = staticmethod(calc_requirement_surcharge)
|
||||||
_build_batch_quote_reply = staticmethod(build_batch_quote_reply)
|
_build_batch_quote_reply = staticmethod(build_batch_quote_reply)
|
||||||
@@ -945,7 +937,7 @@ class CustomerServiceAgent:
|
|||||||
raise RuntimeError(str(link))
|
raise RuntimeError(str(link))
|
||||||
links.append(link)
|
links.append(link)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[Agent] 找图自动处理失败,回退需求澄清: {e}")
|
logger.exception("[Agent] 找图自动处理失败,回退需求澄清: %s", e)
|
||||||
return {
|
return {
|
||||||
"reply": "这种可以做类似款。你先说下具体需求:要几张、是否改字、尺寸比例、交付格式(单图/打包链接),我按需求给你直接做。",
|
"reply": "这种可以做类似款。你先说下具体需求:要几张、是否改字、尺寸比例、交付格式(单图/打包链接),我按需求给你直接做。",
|
||||||
"need_transfer": False,
|
"need_transfer": False,
|
||||||
@@ -1064,7 +1056,7 @@ async def test_agent():
|
|||||||
)
|
)
|
||||||
|
|
||||||
response = await agent.process_message(test_msg)
|
response = await agent.process_message(test_msg)
|
||||||
print(f"回复内容: {response.reply}")
|
logger.info("回复内容: %s", response.reply)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from utils.metrics_tracker import emit as metrics_emit
|
from utils.metrics_tracker import emit as metrics_emit
|
||||||
from core.post_ops import auto_tag, detect_discount, detect_price, record_deal_fail
|
from core.post_ops import auto_tag, detect_discount, detect_price, record_deal_fail
|
||||||
|
|
||||||
|
logger = logging.getLogger("cs_agent")
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from core.pydantic_ai_agent import AgentResponse, ConversationState, CustomerMessage, CustomerServiceAgent
|
from core.pydantic_ai_agent import AgentResponse, ConversationState, CustomerMessage, CustomerServiceAgent
|
||||||
|
|
||||||
@@ -25,7 +28,7 @@ async def finalize_ai_reply(
|
|||||||
|
|
||||||
blocked, fallback = should_block_reply(reply_text)
|
blocked, fallback = should_block_reply(reply_text)
|
||||||
if blocked:
|
if blocked:
|
||||||
print("[Agent] 敏感词拦截,使用兜底回复")
|
logger.warning("[Agent] 敏感词拦截,使用兜底回复")
|
||||||
reply_text = fallback or "好的,您稍等,我帮您确认一下"
|
reply_text = fallback or "好的,您稍等,我帮您确认一下"
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
@@ -88,9 +91,9 @@ async def finalize_ai_reply(
|
|||||||
|
|
||||||
if should_reply:
|
if should_reply:
|
||||||
state.last_reply_at = datetime.now()
|
state.last_reply_at = datetime.now()
|
||||||
print(f"{agent.C_REPLY}[REPLY->CUSTOMER]{agent.C_RESET} {reply_text}")
|
logger.info("[REPLY->CUSTOMER] %s", reply_text)
|
||||||
else:
|
else:
|
||||||
print(f"{agent.C_MUTED}[REPLY->CUSTOMER]{agent.C_RESET} <静默/不发送>")
|
logger.info("[REPLY->CUSTOMER] <静默/不发送>")
|
||||||
|
|
||||||
agent._activity_log(
|
agent._activity_log(
|
||||||
"agent_outbound_decision",
|
"agent_outbound_decision",
|
||||||
|
|||||||
Reference in New Issue
Block a user