Files
aiagent/backend/app/services/feishu_card_actions.py
renjianbo beff3fac8d fix: delete agent 500 error + dynamic personality + deployment guide
- Fix delete agent 500: clean up FK records (agent_llm_logs, permissions,
  schedules, executions, team_members) and unbind goals/tasks before delete
- Remove hardcoded personality templates in Android, replace with dynamic
  system prompt generation from name + description
- Set promptSectionsEnabled=false to bypass PromptComposer for personality
- Add Tencent Cloud Linux deployment guide (Docker Compose)
- Accumulated backend service updates, frontend UI fixes, Android app changes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-29 01:17:21 +08:00

247 lines
8.5 KiB
Python

"""
飞书卡片动作处理器 — 处理用户点击反馈按钮(有用/无用/重试)
每个 WS Handler 在 _build_event_handler() 中注册此处理器。
"""
from __future__ import annotations
import json
import logging
from typing import Any, Dict
from app.services.feishu_card_builder import (
ACTION_FEEDBACK_THUMBS_UP,
ACTION_FEEDBACK_THUMBS_DOWN,
ACTION_FEEDBACK_REGENERATE,
build_feedback_response_card,
)
logger = logging.getLogger(__name__)
def handle_card_action(event: Any) -> Any:
"""处理飞书卡片按钮点击事件 (p2_card_action_trigger)。
Args:
event: P2CardActionTrigger 事件对象
Returns:
P2CardActionTriggerResponse 响应对象
"""
from lark_oapi.event.callback.model.p2_card_action_trigger import (
CallBackToast,
CallBackCard,
P2CardActionTriggerResponse,
)
try:
# 解析 action value
action_value_str = getattr(event.event.action, "value", "{}")
if isinstance(action_value_str, str):
action_value = json.loads(action_value_str)
else:
action_value = action_value_str or {}
action = action_value.get("action", "")
execution_log_id = action_value.get("execution_log_id", "")
agent_name = action_value.get("agent_name", "")
# 获取操作者信息
open_id = ""
try:
operator = event.event.operator
open_id = getattr(getattr(operator, "open_id", None), "union_id", None) or getattr(operator, "open_id", "")
except Exception:
pass
if not action or not execution_log_id:
logger.warning("卡片动作缺少 action 或 execution_log_id: %s", action_value)
return _toast_response("操作无效")
card_title = action_value.get("card_title", agent_name or "Agent 回复")
# 处理不同的反馈动作
if action == ACTION_FEEDBACK_THUMBS_UP:
_record_feedback(execution_log_id, agent_name, "thumbs_up", open_id)
toast = "收到,感谢反馈!"
replacement_card = _build_replacement_card(
execution_log_id, card_title, ACTION_FEEDBACK_THUMBS_UP
)
return _card_toast_response(toast, replacement_card)
elif action == ACTION_FEEDBACK_THUMBS_DOWN:
_record_feedback(execution_log_id, agent_name, "thumbs_down", open_id)
toast = "收到,我们会持续改进。"
replacement_card = _build_replacement_card(
execution_log_id, card_title, ACTION_FEEDBACK_THUMBS_DOWN
)
return _card_toast_response(toast, replacement_card)
elif action == ACTION_FEEDBACK_REGENERATE:
_trigger_regeneration(execution_log_id, agent_name, open_id, event)
toast = "正在重新生成..."
# 改卡片为"重新生成中"状态,移除反馈按钮
regenerating_card = build_feedback_response_card(
card_title, "", ACTION_FEEDBACK_REGENERATE
)
return _card_toast_response(toast, regenerating_card)
else:
logger.warning("未知卡片动作: %s", action)
return _toast_response("操作无效")
except Exception as e:
logger.error("处理卡片动作异常: %s", e, exc_info=True)
return _toast_response("处理失败,请稍后重试")
def _record_feedback(
execution_log_id: str, agent_name: str, signal_type: str, open_id: str
) -> None:
"""记录用户反馈到 FeedbackLearner 或直接更新日志评分。"""
try:
from app.services.feedback_learner import feedback_learner
if signal_type == "thumbs_up":
feedback_learner.record_feedback(
user_id=open_id,
signal_type="thumbs_up",
execution_log_id=execution_log_id,
agent_name=agent_name,
feedback_context={"source": "feishu_card", "open_id": open_id},
)
_update_execution_rating(execution_log_id, rating=5)
elif signal_type == "thumbs_down":
feedback_learner.record_feedback(
user_id=open_id,
signal_type="thumbs_down",
execution_log_id=execution_log_id,
agent_name=agent_name,
feedback_context={"source": "feishu_card", "open_id": open_id},
)
_update_execution_rating(execution_log_id, rating=1)
logger.info(
"用户反馈已记录: signal=%s execution_log=%s agent=%s",
signal_type, execution_log_id, agent_name,
)
except Exception as e:
logger.warning("记录反馈失败: %s", e)
def _update_execution_rating(execution_log_id: str, rating: int) -> None:
"""更新 AgentExecutionLog 的用户评分。"""
try:
from app.core.database import SessionLocal
from app.models.agent_execution_log import AgentExecutionLog
db = SessionLocal()
try:
log_entry = db.query(AgentExecutionLog).filter(
AgentExecutionLog.id == execution_log_id
).first()
if log_entry:
log_entry.user_rating = rating
db.commit()
finally:
db.close()
except Exception as e:
logger.warning("更新执行日志评分失败: %s", e)
def _trigger_regeneration(
execution_log_id: str, agent_name: str, open_id: str, event: Any
) -> None:
"""触发 Agent 重新生成回复。"""
try:
from app.tasks.agent_tasks import execute_agent_task
from app.models.agent_execution_log import AgentExecutionLog
db = _get_db_session()
if not db:
return
try:
log_entry = db.query(AgentExecutionLog).filter(
AgentExecutionLog.id == execution_log_id
).first()
if log_entry and log_entry.input_text:
execute_agent_task.delay(
agent_name,
{"USER_INPUT": log_entry.input_text, "regenerate": True},
)
logger.info("已触发重新生成: agent=%s input=%s", agent_name, log_entry.input_text[:50])
finally:
db.close()
except Exception as e:
logger.warning("触发重新生成失败: %s", e)
def _get_db_session():
"""获取数据库会话。"""
try:
from app.core.database import SessionLocal
return SessionLocal()
except Exception as e:
logger.warning("获取数据库会话失败: %s", e)
return None
def _toast_response(toast_text: str) -> Any:
"""创建卡片动作 Toast 响应。"""
from lark_oapi.event.callback.model.p2_card_action_trigger import (
CallBackToast,
P2CardActionTriggerResponse,
)
return P2CardActionTriggerResponse(
toast=CallBackToast(type_="info", content=toast_text)
)
def _card_toast_response(toast_text: str, card: Dict[str, Any]) -> Any:
"""创建同时包含 Toast 和替换卡片的响应。"""
from lark_oapi.event.callback.model.p2_card_action_trigger import (
CallBackToast,
CallBackCard,
P2CardActionTriggerResponse,
)
return P2CardActionTriggerResponse(
toast=CallBackToast(type_="info", content=toast_text),
card=CallBackCard(data=card),
)
def _build_replacement_card(
execution_log_id: str, card_title: str, action: str
) -> Dict[str, Any]:
"""为反馈动作构建替换卡片(移除反馈按钮,显示确认信息)。"""
original_content = ""
try:
from app.core.database import SessionLocal
from app.models.agent_execution_log import AgentExecutionLog
db = SessionLocal()
try:
log_entry = db.query(AgentExecutionLog).filter(
AgentExecutionLog.id == execution_log_id
).first()
if log_entry and log_entry.output_text:
# 截断过长内容,保留前 3000 字符
original_content = log_entry.output_text
if len(original_content) > 3000:
original_content = original_content[:3000] + "\n\n...(内容过长已截断)"
finally:
db.close()
except Exception as e:
logger.warning("获取原始回复内容失败: %s", e)
return build_feedback_response_card(
original_title=card_title,
original_content=original_content,
action=action,
)
# 导出 handler 函数引用,供 WS Handler 注册
card_action_handler = handle_card_action