Files
sanguo_moziplus_v2/src/api/mention_utils.py
T
cfdaily e7f28cd36e
CI / lint (pull_request) Successful in 7s
CI / test (pull_request) Successful in 8s
CI / notify-on-failure (pull_request) Successful in 0s
fix(mention): address PR #45 review feedback (M1-M3, S1-S3)
M1: Remove '帮忙' from help_keywords to fix keyword priority bug
M3: Add unit tests for mention_utils (§25.7)
S1: Merge two 'if action == opened' blocks in _handle_issues
S2: Extract _send_review_mentions helper to deduplicate @mention code
S3: Remove redundant 'import re' inside conditional block
2026-06-12 18:56:54 +08:00

170 lines
5.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""@mention 解析工具模块。供所有 toolchain handler 复用。"""
import re
import logging
from typing import List, Set
from src.config.agents import AGENT_IDS
logger = logging.getLogger(__name__)
# Gitea API 基地址常量(避免硬编码)
GITEA_API_BASE = "http://192.168.2.154:3000/api/v1"
GITEA_WEB_BASE = "http://192.168.2.154:3000"
# Agent 别名映射
# 规则:
# 1. 中文名(如"张飞")→ 完整 Agent ID
# 2. 英文短名(如"zhangfei")→ 完整 Agent ID
# 3. 前缀模糊匹配需唯一匹配(见 extract_mentions 假设 A2
AGENT_ALIAS: dict[str, str] = {
# 中文名
"张飞": "zhangfei-dev",
"关羽": "guanyu-dev",
"赵云": "zhaoyun-data",
"姜维": "jiangwei-infra",
"司马懿": "simayi-challenger",
"庞统": "pangtong-fujunshi",
# 字+号(常见写法)
"翼德": "zhangfei-dev",
"云长": "guanyu-dev",
"子龙": "zhaoyun-data",
"伯约": "jiangwei-infra",
"仲达": "simayi-challenger",
"士元": "pangtong-fujunshi",
# 英文短名
"zhangfei": "zhangfei-dev",
"guanyu": "guanyu-dev",
"zhaoyun": "zhaoyun-data",
"jiangwei": "jiangwei-infra",
"simayi": "simayi-challenger",
"pangtong": "pangtong-fujunshi",
}
# 正则:匹配 @后面跟着的合法 Agent 名(英文字母/中文/数字/连字符)
_MENTION_PATTERN = re.compile(r"@([a-zA-Z\u4e00-\u9fa5][a-zA-Z0-9\u4e00-\u9fff-]*)")
def extract_mentions(body: str, sender: str) -> list[str]:
"""从文本中提取 @mention 的 Agent ID 列表。
Args:
body: 评论文本
sender: 评论者 Gitea 用户名(用于排除自己 @自己)
Returns:
去重后的 Agent ID 列表
匹配优先级:精确 > 别名 > 前缀模糊(需唯一匹配,多候选则跳过)
"""
candidates = _MENTION_PATTERN.findall(body)
result: Set[str] = set()
for c in candidates:
# 1. 精确匹配(@zhangfei-dev
if c in AGENT_IDS:
result.add(c)
# 2. 别名匹配(@张飞、@zhangfei
elif c in AGENT_ALIAS:
result.add(AGENT_ALIAS[c])
else:
# 3. 前缀模糊匹配(@zhangf → zhangfei-dev
# 假设 A2:多个候选时不匹配,只 log warning
matches = [aid for aid in AGENT_IDS if aid.startswith(c)]
if len(matches) == 1:
result.add(matches[0])
elif len(matches) > 1:
logger.warning(
"Prefix '%s' matched %d agents (%s), skipping ambiguous mention",
c, len(matches), matches)
# 排除自己 @自己(假设 A1Gitea login = Agent ID
result.discard(sender)
return list(result)
def should_suppress_mention(
mentioned_agent: str,
auto_notify_targets: List[str],
) -> bool:
"""判断 @mention 通知是否应被抑制(因为自动流转已通知同一人)。
Args:
mentioned_agent: 被 @的 Agent ID
auto_notify_targets: 本次事件自动流转已通知的目标列表
Returns:
True 表示应抑制(不发 @mention Mail
"""
return mentioned_agent in auto_notify_targets
def infer_intent(body: str) -> str:
"""从 @mention 内容推断意图。
Returns:
"help" | "notify" | "collaborate" | "assign"
"""
# 分配子任务关键词
assign_keywords = ["交给", "分配", "负责", "认领", "做一下", "帮忙做", "implement"]
if any(kw in body for kw in assign_keywords):
return "assign"
# 求助关键词(注意:"帮忙"已由 assign_keywords 的"帮忙做"覆盖,"请帮忙"由 collab_keywords 覆盖)
help_keywords = ["怎么", "如何", "", "?", "什么", "哪个", "能否"]
if any(kw in body for kw in help_keywords):
return "help"
# 协作请求关键词
collab_keywords = ["请帮忙", "请协助", "请澄清", "请review", "请审查", "评估"]
if any(kw in body for kw in collab_keywords):
return "collaborate"
# 默认为通知关注
return "notify"
def _build_response_guidance(
intent: str,
gitea_api: str,
repo: str,
issue_number: int,
commenter: str,
) -> str:
"""根据意图类型生成响应指引文本。"""
if intent == "help":
return (
f"这是一条求助,请到 Gitea 评论回复:\n"
f"1. 获取评论上下文(上方 API\n"
f"2. 组织回答\n"
f"3. 在 Gitea 评论回复: POST {gitea_api}/repos/{repo}/issues/{issue_number}/comments\n"
f' Body: {{"body": "你的回答内容"}}'
)
elif intent == "notify":
return (
f"这是一条通知,请查看并知晓。如有意见,可到 Gitea 评论:\n"
f"- 查看 Issue/PR 详情(上方 API\n"
f"- 如有意见,评论回复: POST {gitea_api}/repos/{repo}/issues/{issue_number}/comments"
)
elif intent == "collaborate":
return (
f"这是一条协作请求,请评估后回复(评论或 Mail):\n"
f"1. 获取详情(上方 API\n"
f"2. 评估可行性\n"
f"3a. 评论回复: POST {gitea_api}/repos/{repo}/issues/{issue_number}/comments\n"
f' Body: {{"body": "你的回复"}}\n'
f"3b. 或通过 Mail 回复评论者: {commenter}"
)
elif intent == "assign":
return (
f"这是一条任务分配,请认领并执行:\n"
f"1. 获取 Issue 详情(上方 API\n"
f"2. 评估可行性\n"
f"3. 认领 Issue: POST {gitea_api}/repos/{repo}/issues/{issue_number}/assignees\n"
f' Body: {{"assignees": ["{{your_agent_id}}"]}}\n'
f"4. 执行任务\n"
f"5. 完成后更新 Issue 状态: PATCH {gitea_api}/repos/{repo}/issues/{issue_number}\n"
f' Body: {{"state": "closed"}}'
)
return "请查看详情(上方 API)并按需回复。"