| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127 |
- """Resolved search plan + FallbackPolicy — single source of truth between intent and pipeline."""
- from __future__ import annotations
- from dataclasses import dataclass, field
- from .search_recipe import SearchRecipe, finalize_plan_recipe
- @dataclass
- class FallbackPolicy:
- allow_arxiv_only: bool = True
- reason: str = "auto"
- @dataclass
- class ResolvedSearchPlan:
- """All search parameters resolved once, used by pipeline. No LLM calls in retrieval layer."""
- query: str = ""
- keywords: list[str] = field(default_factory=list)
- authors: list[str] = field(default_factory=list)
- venues: list[str] = field(default_factory=list)
- year_from: int | None = None
- year_to: int | None = None
- sources: list[str] = field(default_factory=list)
- sort: str = "relevance"
- ranking_profile: str = "accuracy"
- use_llm_rank: bool = True
- recall_max_candidates: int = 24
- target_titles: list[str] = field(default_factory=list)
- arxiv_id_list: list[str] = field(default_factory=list)
- main_conference_proceedings_only: bool = False
- raw_user_message: str = ""
- wants_recent: bool = False
- wants_classic: bool = False
- fallback: FallbackPolicy = field(default_factory=FallbackPolicy)
- use_tavily: bool = False
- max_results: int = 10
- recipe: SearchRecipe = SearchRecipe.GENERAL
- method_acronym: str | None = None
- @classmethod
- def from_search_intent(cls, intent) -> "ResolvedSearchPlan":
- plan = cls(
- query=(intent.query or "").strip()[:500],
- keywords=list(intent.keywords or [])[:16],
- authors=list(getattr(intent, "authors", []) or [])[:8],
- venues=list(intent.venues or []),
- year_from=_norm_year(intent.year_from),
- year_to=_norm_year(intent.year_to),
- sources=_resolve_sources(intent),
- sort=_resolve_sort(intent),
- ranking_profile=_resolve_profile(intent),
- use_llm_rank=bool(getattr(intent, "use_llm_rank", True)),
- recall_max_candidates=_resolve_recall_max(intent),
- target_titles=list(getattr(intent, "target_titles", []) or [])[:6],
- arxiv_id_list=list(getattr(intent, "arxiv_id_list", []) or [])[:16],
- main_conference_proceedings_only=bool(getattr(intent, "main_conference_proceedings_only", False)),
- raw_user_message=(getattr(intent, "raw_user_message", "") or "")[:3200],
- wants_recent=bool(getattr(intent, "wants_recent", False)),
- wants_classic=bool(getattr(intent, "wants_classic", False)),
- use_tavily=_resolve_use_tavily(intent),
- max_results=max(5, min(30, int(getattr(intent, "max_results", 10) or 10))),
- )
- return _finalize_plan_for_retrieval(plan)
- def _finalize_plan_for_retrieval(plan: ResolvedSearchPlan) -> ResolvedSearchPlan:
- """RECIPE_RULES 判定并应用策略;派生状态见 plan_helpers。"""
- return finalize_plan_recipe(plan)
- def _resolve_sort(intent) -> str:
- rk = getattr(intent, "ranking_strategy", None)
- if rk == "date":
- return "date"
- if rk == "relevance":
- return "relevance"
- if getattr(intent, "wants_recent", False):
- return "date"
- if getattr(intent, "wants_classic", False):
- return "relevance"
- return str(getattr(intent, "sort", "relevance") or "relevance")
- def _resolve_profile(intent) -> str:
- if getattr(intent, "wants_classic", False):
- return "classic"
- if getattr(intent, "wants_recent", False):
- return "novelty"
- return "accuracy"
- def _resolve_sources(intent) -> list[str]:
- llm_src = getattr(intent, "sources", []) or []
- allowed = {"arxiv", "dblp", "openalex"}
- if llm_src:
- resolved = [s for s in llm_src if s in allowed]
- if resolved:
- return resolved
- return ["arxiv", "dblp", "openalex"]
- def _resolve_use_tavily(intent) -> bool:
- llm_src = [str(s).strip().lower() for s in (getattr(intent, "sources", []) or [])]
- return "tavily" in llm_src or bool(getattr(intent, "use_tavily_presearch", False))
- def _resolve_recall_max(intent) -> int:
- try:
- from ...settings import get_settings
- cap = int(get_settings().papergraph_recall_max_candidates)
- except Exception:
- cap = 24
- raw = int(getattr(intent, "rerank_recall_max", 24) or 24)
- return max(8, min(cap, raw))
- def _norm_year(y) -> int | None:
- if y is None:
- return None
- try:
- yi = int(y)
- return yi if 1900 <= yi <= 2100 else None
- except (TypeError, ValueError):
- return None
|