"""Choose proactive evidence for the answering agent. The agent that answers the turn already reads the whole conversation and is the strongest judge of whether a piece of knowledge applies. XKB does the one thing it cannot: search the library. Jev's per-turn relevance verdict and local rules decide what to show; nothing here calls a generation model. 2026-10-06 取代了「先由 Sonnet 解出這輪需求、再由 Sonnet 挑段落」的做法。 那兩次呼叫每輪多等約 6 秒、每輪都付費,判斷的卻是回答者下一秒自己就會判斷 的事。對昨天 26 輪真實紀錄的重算:有內容的輪次,Sonnet 挑的段落幾乎都在 jev 前三名;它真正多做的是在「好 直接送」這類輪次保持安靜,而多送一兩條給回答者 的代價只是幾百字。 """ from __future__ import annotations import hashlib import math import re import unicodedata import xkb_score from xkb_evidence import IDENTITY_PREFIX, bounded_excerpt, fields, identity_key, render_context DELIVERY_FLOOR = 0.5 MAX_SUGGESTIONS = 3 EXCERPT_CHARS = 900 # 歷史紀錄(每日筆記、其他對話)說的是「當時發生了什麼」,不是可重複使用的 # 知識,而且常常已經過時。2026-10-06 開發 XKB 時送出的 33 條有 27 條是這類: # 半年前 Telegram 時期討論 XKB 的摘要,跟話題相關,卻在推薦已刪掉的腳本。 # 知識(卡片、wiki)先挑;歷史每輪最多一條,而且要明顯相關才送。用 10/5~10/6 # 的真實紀錄模擬:XKB 開發 43 條降到 17 條(知識 13),影片工作的 wiki 反而 # 多了 6 條,因為原本被對話紀錄占掉的名額空出來了。 HISTORY_TYPES = {"memory_note", "conversation_trace"} HISTORY_FLOOR = 0.75 MAX_HISTORY = 1 def diagnostic(packet: dict) -> str: """Do not confuse unavailable judgement/retrieval with a quiet decision.""" if ((packet.get("quality") or {}).get("status") == "degraded" or (packet.get("delivery") or {}).get("status") == "degraded"): return ("degraded: retrieval or relevance judgement is incomplete. " "Missing suggestions do not establish that no useful knowledge exists. " "Candidates may remain available in the service recall packet; this is an internal diagnostic, " "not a recommendation to the user.") return "" def verdict(record: dict) -> float | None: value = record.get("judge") return float(value) if type(value) in (int, float) and math.isfinite(value) and 0 <= value <= 1 else None def rank(records: list[dict], judge: dict) -> list[dict]: """Fuse every retrieval leg, then order judged slots by current usefulness. Unknown verdicts retain their retrieval positions during partial outages. The strongest judged representative carries the text and verdict for a fused identity. Anonymous evidence remains independent, as in xkb_score.rank. """ active = judge.get("status") in {"judged", "partial"} if active: # Swap within each identity only. Global sorting here would change RRF # ties and move unknown evidence during a partial judge outage. records = list(records) first, best = {}, {} for index, record in enumerate(records): key = identity_key(record) or ("anonymous", index) first.setdefault(key, index) old = best.setdefault(key, index) score, previous = verdict(record), verdict(records[old]) if score is not None and (previous is None or score > previous): best[key] = index for key, index in first.items(): other = best[key] records[index], records[other] = records[other], records[index] fused = xkb_score.rank(records) if not active: return fused judged = iter(sorted((r for r in fused if verdict(r) is not None), key=lambda r: (bool(r.get("demoted")), -verdict(r), -r.get("unified_score", 0)))) return [next(judged) if verdict(r) is not None else r for r in fused] def claim_text(value: str) -> str: # Exact text normalization only: keep numbers, negations and qualifiers. return re.sub(r"\s+", "", unicodedata.normalize("NFKC", value).casefold()) # 書籤卡片有一批標題是推文 ID(例如 2044072293383712878),回答者光看標題 # 不知道那是什麼。標題沒有任何文字時改用內文第一句。 _SENTENCE_END = re.compile(r"[。!?!?]|\.(?:\s|$)|\n") # 「Xkb Case 6769746875625f...」「Local Readme 064efe10」這類由前綴加雜湊組成的 # 標題,跟純數字一樣說不出內容。 _ID_TITLE = re.compile(r"[A-Za-z ]{0,16}(?=[0-9A-Fa-f_-]*\d)[0-9A-Fa-f_-]{8,}") _HEADING = re.compile(r"^\s*#{1,6}\s") _LINE_MARKERS = re.compile(r"^[\s>\-\*\d.)、]+") def display_title(record: dict) -> str: title, body, _ = fields(record) if any(c.isalpha() for c in title) and not _ID_TITLE.fullmatch(title.strip()): return title[:120] # Card chunks open with a numbered section heading ("## 2. ..."), so the # first "sentence" used to be just "2". Use the first real content line. for line in body.splitlines(): if not line.strip() or _HEADING.match(line): continue text = _LINE_MARKERS.sub("", line).replace("**", "").strip() match = _SENTENCE_END.search(text) first = (text[:match.start()] if match else text).strip() if len(first) >= 4: return first[:60] return title[:120] # 卡片固定的十個段落裡,有三段是欄位不是知識:2. Claim 等級(可信度標記)、 # 6. 與現有知識的關係(一串其他卡片 ID,多半寫「無法判定關聯」)、9. 原始來源 # (網址)。卡片是逐段被檢索的,2026-10-07 jev 給這兩種段落 0.57 與 0.82,於是 # 「關係清單」被當成建議送出。明確查詢仍拿得到它們;主動建議不送。 _CARD_META_HEADING = re.compile(r"^\s*#{1,6}\s*(?:2\.\s*Claim|6\.\s*與現有知識的關係|9\.\s*原始來源)") _CARD_META_LINE = re.compile( r"^\s*-\s*(?:\*\*\d{12,}\*\*|\*\*等級\*\*|(?:Attested|Scholarship|Inference|Speculation)\s*[::])") def card_metadata(record: dict, body: str) -> bool: if record.get("record_type") not in {"knowledge_card", "knowledge_chunk"}: return False if _CARD_META_HEADING.match(body): return True lines = [line for line in body.splitlines() if line.strip() and not _HEADING.match(line)] return bool(lines) and sum(bool(_CARD_META_LINE.match(line)) for line in lines) * 2 >= len(lines) def excerpt(body: str, limit: int = EXCERPT_CHARS) -> str: """Whole paragraphs when they fit; otherwise a labelled cut at a sentence.""" if len(body) <= limit: return body kept = bounded_excerpt(body, limit) if not kept: cut = body[:limit] ends = [m.end() for m in _SENTENCE_END.finditer(cut)] kept = cut[:ends[-1]].rstrip() if ends else cut.rstrip() return kept + "\n(節錄)" def content_fingerprint(body: str) -> str: """Version of the delivered knowledge, independent of its identity. Taken from the normalized source body, before excerpting, so titles, whitespace and the excerpt label cannot lift session deduplication; an edited source can. Evidence identity keeps meaning "which document". """ return hashlib.sha256(claim_text(body).encode("utf-8")).hexdigest()[:16] def _versioned(key: str) -> bool: """Cards are retrieved as whichever chunk matched this turn, so their text is not a version: two chunks of one unedited card hash differently (11 of 75 card identities on 2026-10-05). Wiki sections, notes and traces return one stable text per identity, and those are what gets edited mid-conversation.""" return bool(key) and not key.startswith(IDENTITY_PREFIX + '["card"') def select(records: list[dict], judge: dict, conversation: list[dict], *, query: str = "", delivered: frozenset = frozenset()) -> dict: """Pick at most three judged-relevant records the session has not seen. ``delivered`` holds what this session was already given: ``(key, fingerprint)`` pairs for versioned evidence, or a bare key (cards, and turns recorded before fingerprints existed) that blocks every version. A partial judgement still delivers its positive verdicts: the answering agent filters, and withholding them would make one failed batch erase everything. The packet is marked degraded so the gap stays visible. No adopted/rejected preference is inferred from presentation or silence. """ # key -> versions given. None means "some version, unknown which": a card, # a turn recorded before fingerprints, or a pair whose evidence is now # unversioned. An unknown version on either side blocks, so a format # change at deploy cannot resend what this session already saw. given: dict[str, set] = {} for entry in delivered: key_, version = entry if isinstance(entry, tuple) else (entry, None) given.setdefault(key_, set()).add(version) status = judge.get("status") active = status in {"judged", "partial"} previous = [claim_text(m["content"]) for m in conversation if m["role"] == "assistant"] withheld, presented, eligible = [], [], [] for index, record in enumerate(records): key = identity_key(record) body = fields(record)[1] claim = claim_text(body) fingerprint = content_fingerprint(body) if _versioned(key) else None in_reply = len(claim) >= 40 and any(claim in text for text in previous) if in_reply: presented.append(key) score = verdict(record) if active else None if score is None: reason = "unjudged" elif score < DELIVERY_FLOOR: reason = "weak_relevance" elif record.get("excerpt_truncated"): reason = "incomplete_source_excerpt" elif card_metadata(record, body): reason = "card_metadata_section" elif key in given and (fingerprint is None or None in given[key] or fingerprint in given[key]): reason = "delivered_earlier_in_session" elif in_reply: reason = "already_in_conversation" elif record.get("record_type") in HISTORY_TYPES and score < HISTORY_FLOOR: reason = "history_below_floor" else: reason = "" if reason: withheld.append((index, {"evidence_key": key, "reason": reason})) continue eligible.append((index, record, key, body, claim, fingerprint)) # Knowledge first, then at most MAX_HISTORY history items for leftover slots. selected, seen, history = [], set(), 0 for want_history in (False, True): for index, record, key, body, claim, fingerprint in eligible: is_history = record.get("record_type") in HISTORY_TYPES if is_history != want_history: continue if claim and claim in seen: reason = "duplicate_claim" elif len(selected) >= MAX_SUGGESTIONS: reason = "limit" elif is_history and history >= MAX_HISTORY: reason = "history_limit" else: reason = "" if reason: withheld.append((index, {"evidence_key": key, "reason": reason})) continue seen.add(claim) history += int(is_history) shown = {**record, "title": display_title(record), "summary": excerpt(body)} if fingerprint: shown["content_fingerprint"] = fingerprint selected.append(shown) withheld = [item for _, item in sorted(withheld, key=lambda pair: pair[0])] return {"mode": "suggest" if selected else "background" if records else "none", "policy": "jev-direct", "records": selected, "context": render_context(selected), "floor": DELIVERY_FLOOR, "limit": MAX_SUGGESTIONS, "withheld": withheld, "previously_presented": presented, "status": "degraded" if records and status != "judged" else "ready"}