Spaces:
Running
Running
Download app/agent/skill_necessity.py from appQQQ/FinDataPilot: direct link, hf CLI and curl.
- Browser
- Download file 5.15 kB
-
https://huggingface.co/spaces/appQQQ/FinDataPilot/resolve/main/app/agent/skill_necessity.py
- Command line
-
hf download hf://spaces/appQQQ/FinDataPilot/app/agent/skill_necessity.py
-
curl -L -o skill_necessity.py https://huggingface.co/spaces/appQQQ/FinDataPilot/resolve/main/app/agent/skill_necessity.py
5.15 kB
| """Deterministic guardrails for deciding whether another Skill is necessary.""" | |
| from __future__ import annotations | |
| _EXPLICIT_SKILL_MARKERS: dict[str, tuple[str, ...]] = { | |
| "announcement-search": ( | |
| "公告", "披露", "财报", "季报", "年报", "业绩预告", | |
| "announcement", "filing", | |
| ), | |
| "news-search": ( | |
| "新闻", "资讯", "快讯", "舆情", "动态", "近况", "消息面", "news", | |
| ), | |
| "report-search": ( | |
| "研报", "研究报告", "券商观点", "机构观点", "research report", | |
| ), | |
| } | |
| _CAUSAL_OR_EVENT_ANALYSIS_MARKERS = ( | |
| "为什么", "原因", "怎么回事", "影响", "利好", "利空", "风险", | |
| "消息面", "催化", "异动", | |
| ) | |
| _DECISION_ANALYSIS_MARKERS = ( | |
| "投资价值", "值得买", "能买吗", "能不能买", "是否买", "该不该买", | |
| "下周一买", "买入", "卖出", "推荐哪些", "推荐哪", | |
| ) | |
| _BROAD_ANALYSIS_MARKERS = ( | |
| "综合分析", "全面分析", "深度分析", "多维分析", "各方面", "等等", | |
| ) | |
| _VERTICAL_SKILL_ORDER = ( | |
| "news-search", | |
| "announcement-search", | |
| "report-search", | |
| ) | |
| _DIRECT_FINANCIAL_DATA_MARKERS = ( | |
| "股票", "股价", "行情", "市值", "涨停", "跌停", "涨幅", "跌幅", | |
| "成交", "换手", "资金流", "走势", "技术面", "基金", "指数", "可转债", "期货", | |
| "营收", "营业收入", "净利润", "财务指标", "交易日", "开盘", | |
| "市盈率", "市净率", "股息率", "分红率", "毛利率", "净利率", | |
| "每股收益", "净资产收益率", "资产负债率", "现金流", | |
| ) | |
| def has_cross_source_analysis_intent(user_query: str) -> bool: | |
| """Whether the user asked for analysis that can justify source expansion. | |
| Bare movement words such as ``涨`` and ``跌`` are intentionally excluded: | |
| they also occur in simple data requests like ``列举全部涨停股票``. | |
| """ | |
| return bool(required_vertical_skills_for_query(user_query)) | |
| def explicitly_requests_skill(user_query: str, skill: str) -> bool: | |
| """Return True only when the latest question names that evidence family.""" | |
| q = (user_query or "").lower() | |
| return any(marker.lower() in q for marker in _EXPLICIT_SKILL_MARKERS.get(skill, ())) | |
| def required_vertical_skills_for_query(user_query: str) -> tuple[str, ...]: | |
| """Map user-requested evidence dimensions to the owning search Skills. | |
| The mapping follows each registered Skill's responsibility: | |
| news supplies public information, announcements supply first-party company | |
| events, and reports supply analyst/ratings evidence. A broad company | |
| analysis needs all three; causal analysis needs news plus announcements. | |
| """ | |
| q = (user_query or "").lower() | |
| required = { | |
| skill for skill in _VERTICAL_SKILL_ORDER if explicitly_requests_skill(q, skill) | |
| } | |
| if ( | |
| "news-search" not in required | |
| and {"announcement-search", "report-search"}.issubset(required) | |
| and any(marker in q for marker in ("或", "或者", "二选一", "任一")) | |
| ): | |
| # One vertical source is sufficient when the user explicitly offers | |
| # announcements or reports as alternatives. Keep announcement-first | |
| # behavior for consistency with the existing product contract. | |
| required.discard("report-search") | |
| causal = any(marker.lower() in q for marker in _CAUSAL_OR_EVENT_ANALYSIS_MARKERS) | |
| decision = any(marker.lower() in q for marker in _DECISION_ANALYSIS_MARKERS) | |
| broad = any(marker.lower() in q for marker in _BROAD_ANALYSIS_MARKERS) or ( | |
| "对" in q and "的分析" in q | |
| ) | |
| if causal: | |
| required.update(("news-search", "announcement-search")) | |
| if decision or broad: | |
| required.update(_VERTICAL_SKILL_ORDER) | |
| return tuple(skill for skill in _VERTICAL_SKILL_ORDER if skill in required) | |
| def is_skill_necessary_for_query(user_query: str, skill: str) -> bool: | |
| """Validate that a planned/reflected Skill serves the latest question. | |
| Structured financial queries remain generally valid. Public web search is | |
| retained as an empty-result fallback. Vertical financial searches require | |
| either an explicit request for that source or a genuine analysis/decision | |
| intent; merely discovering a stock name never authorizes extra lookups. | |
| """ | |
| if skill in {"financial-query", "anysearch"}: | |
| return True | |
| if skill in _EXPLICIT_SKILL_MARKERS: | |
| return skill in required_vertical_skills_for_query(user_query) | |
| return True | |
| def requests_financial_data(user_query: str) -> bool: | |
| """Recognize financial metrics independently of news/analysis dimensions.""" | |
| q = (user_query or "").lower() | |
| return any(marker.lower() in q for marker in _DIRECT_FINANCIAL_DATA_MARKERS) | |
| def is_direct_financial_data_query(user_query: str) -> bool: | |
| """Whether one or more structured financial lookups can answer the query.""" | |
| if not requests_financial_data(user_query): | |
| return False | |
| if has_cross_source_analysis_intent(user_query): | |
| return False | |
| return not any(explicitly_requests_skill(user_query, skill) for skill in _EXPLICIT_SKILL_MARKERS) | |