PMFlow/backend/app/services/ai_steward_service/mixin_title_utils.py

111 lines
5.1 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.

from __future__ import annotations
from .shared import *
class AIStewardTitleUtilsMixin:
def __init__(self, db: AsyncSession):
self.db = db
self.llm = LLMService()
@staticmethod
def _normalize_project_title_candidate(candidate: str, *, max_len: int = 24) -> str:
cleaned = re.sub(r"\s+", " ", str(candidate or "")).strip(" []【】()()::\"'“”")
if not cleaned:
return ""
cleaned = re.sub(
r"^(?:(?:项目|需求)(?:名字|名称|名)?(?:叫|是|为|名为|名称为|名字为)?|(?:项目|需求)?(?:名为|名称为|名字为|名称是|名字是|叫|为))\s*",
"",
cleaned,
flags=re.IGNORECASE,
).strip(" []【】()()::\"'“”")
if not cleaned:
return ""
normalized = re.sub(r"\s+", "", cleaned).lower()
generic_tokens = {
"项目",
"一个项目",
"新项目",
"项目列表",
"项目库",
"pmflow",
"pmflow项目列表",
}
if normalized in generic_tokens:
return ""
if any(token in normalized for token in ("创建项目", "新建项目", "新增项目", "不是创建信号")):
return ""
return compact_title(cleaned, max_len=max_len)
@classmethod
def _extract_explicit_project_title_from_instruction(cls, text: str) -> str:
cleaned = re.sub(r"\s+", " ", str(text or "")).strip()
if not cleaned:
return ""
explicit_patterns = (
r"(?:项目|需求)(?:名字|名称|名)?(?:固定|统一|必须)(?:叫|名为|名称为|名字为|命名为|定为|是|为)[::,,\s]*(.+)",
r"(?:项目|需求)(?:名字|名称|名)?就叫[::,,\s]*(.+)",
r"(?:这个|该)?(?:项目|需求)(?:先|就)?叫[::,,\s]*(.+)",
)
for pattern in explicit_patterns:
match = re.search(pattern, cleaned, flags=re.IGNORECASE)
if not match:
continue
candidate = re.sub(r"[,。,.;;!??].*$", "", match.group(1)).strip(" []【】()()::\"'“”")
normalized = cls._normalize_project_title_candidate(candidate, max_len=50)
if normalized:
return normalized
return ""
@classmethod
def _extract_project_title_from_instruction(cls, text: str) -> str:
cleaned = re.sub(r"\s+", " ", str(text or "")).strip()
if not cleaned:
return ""
explicit_title = cls._extract_explicit_project_title_from_instruction(cleaned)
if explicit_title:
return explicit_title
# 优先去掉“不是X,是Y”中的前置纠正片段,避免把纠正话术落到标题里。
cleaned = re.sub(r"^.*?不是[^,。,;;]*信号[^,。,;;]*[,,;; ]*是", "", cleaned)
quoted_match = re.search(r"(?:项目|新项目)[^\"“”']{0,10}[\"“”']([^\"“”']{2,40})[\"“”']", cleaned)
if quoted_match:
return cls._normalize_project_title_candidate(quoted_match.group(1))
for pattern in (
r"(?:项目|需求)(?:名字|名称|名)?(?:叫|名为|名称是|名称为|是|为)[::,,\s]*(.+)",
r"(?:帮我|请|麻烦|给我)?(?:新增|增加|添加|创建|新建|建立|立项|开个|开一个|建个|建一个|做个|做一个|加个|加一个)(?:一个|个)?项目(?:叫|名为|名称是|名称为|是|为)?[::,,\s]*(.+)",
):
match = re.search(pattern, cleaned, flags=re.IGNORECASE)
if not match:
continue
candidate = re.sub(r"[,。,.;;!??].*$", "", match.group(1)).strip(" []【】()()::\"'“”")
candidate = re.sub(r"^(?:一个|个|新项目|项目|需求)\s*", "", candidate, flags=re.IGNORECASE)
candidate = re.sub(
r"^(?:(?:项目|需求)(?:名字|名称|名)?(?:叫|是|为|名为|名称为|名字为)?|(?:项目|需求)?(?:名为|名称为|名字为|名称是|名字是|叫|为))\s*",
"",
candidate,
flags=re.IGNORECASE,
)
candidate = re.sub(r"(?:因为|所以|用来|为了|方便).*$", "", candidate).strip()
normalized = cls._normalize_project_title_candidate(candidate)
if normalized:
return normalized
return ""
@staticmethod
def _looks_like_instruction_sentence(text: str) -> bool:
normalized = re.sub(r"\s+", "", str(text or "")).lower()
if not normalized:
return False
return "项目" in normalized and any(
token in normalized
for token in ("创建", "新建", "新增", "增加", "添加", "帮我", "给我", "不是", "信号")
)
@classmethod
def _compact_project_description(cls, *, input_text: str, title: str, draft_description: str) -> str:
raw = str(draft_description or "").strip()
if raw and not cls._looks_like_instruction_sentence(raw):
return raw[:4000]
if title:
return f"项目:{title}"[:4000]
return str(input_text or "").strip()[:4000]