assistant/backend/prospecting/execution.py

60 lines
3.3 KiB
Python

"""Incremental evidence analysis and input-bound reuse; no page operations."""
from __future__ import annotations
import hashlib
import json
from typing import Any, Callable
from backend.browser.llm_factory import get_llm_config
from backend.prospecting.evidence_context import related_context
def fingerprint(value: Any) -> str:
return hashlib.sha256(json.dumps(value, ensure_ascii=False, sort_keys=True, default=str).encode()).hexdigest()
def analysis_inputs(task: dict, brief) -> tuple[list[dict], dict[str, str]]:
artifacts = task.get("artifacts") or {}
corrections = {row.get("source_id"): row.get("text") for row in artifacts.get("corrections") or []}
sources = [{**row, **({"user_correction": corrections[row["source_id"]]} if corrections.get(row.get("source_id")) else {})}
for row in artifacts.get("sources") or []]
criteria = brief.model_dump(exclude={"target_candidates", "discovery_scope", "max_notes", "max_comments_per_note", "use_web_search"})
cfg = get_llm_config()
signatures = {}
for source in sources:
context = related_context([source], sources)
signatures[source["source_id"]] = fingerprint([1, cfg["provider"], cfg["model"], criteria, source, context])
return sources, signatures
def analysis_is_current(task: dict, brief) -> bool:
sources, signatures = analysis_inputs(task, brief)
artifacts = task.get("artifacts") or {}
saved = (artifacts.get("analysis_cache") or {}).get("source_signatures") or {}
decisions = {row.get("source_id") for row in artifacts.get("decisions") or []}
return bool(sources) and signatures == saved and decisions == set(signatures)
async def analyze_evidence(task: dict, brief, analyzer: Callable, *, force: bool = False) -> dict:
sources, signatures = analysis_inputs(task, brief)
if not sources:
raise ValueError("当前没有已保存资料可供分析")
artifacts = task.get("artifacts") or {}
saved = (artifacts.get("analysis_cache") or {}).get("source_signatures") or {}
old = {row["source_id"]: row for row in artifacts.get("decisions") or [] if row.get("source_id")}
pending = [source for source in sources if force or saved.get(source["source_id"]) != signatures[source["source_id"]]]
pending_ids = {source["source_id"] for source in pending}
pending.extend(source for source in sources if source["source_id"] not in old and source["source_id"] not in pending_ids)
result = await analyzer(brief, pending, context_sources=sources) if pending else {}
merged = {**old, **{row["source_id"]: row for row in result.get("decisions") or []}}
if set(signatures) - set(merged):
raise ValueError("已保存资料尚未全部形成有效判断,原结果保留")
decisions = [merged[source["source_id"]] for source in sources]
leads = [{**decision, "id": decision["source_id"], "status": "pending"}
for decision in decisions if decision.get("decision") == "keep"]
return {**result, "decisions": decisions, "leads": leads,
"analysis_goal_version": task.get("inputs", {}).get("_goal_version", 1),
"analysis_cache": {"version": 1, "source_signatures": signatures},
"analysis_reused_count": len(sources) - len(pending),
"analysis_new_count": len(pending), "analysis_cached": not pending}