| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859 |
- """作用域定位(scope-link):候选值 → 本地树 embedding 余弦最近邻 → top-K。
- 对得上(高分)→ 复用现有节点原名,ingest 时按名挂靠;对不上 → 保留为新建值(丰富树)。
- 本地内存 + numpy 暴力最近邻,无需向量数据库。
- 编程接口:
- from scripts.scope_link import ScopeLinker
- ScopeLinker().link("撕裂共识", source_type="作用", top_k=5)
- 自测:python scripts/scope_link.py
- """
- from __future__ import annotations
- import json
- from pathlib import Path
- import numpy as np
- from core.embedding import ArkEmbedConfig, embed_text
- OUT = Path("scope_trees")
- class ScopeLinker:
- def __init__(self, env_file: str = ".env") -> None:
- self.index = json.loads((OUT / "trees_index.json").read_text(encoding="utf-8"))
- emb = np.load(OUT / "trees_embeddings.npy")
- self.norm = emb / (np.linalg.norm(emb, axis=1, keepdims=True) + 1e-9)
- self.src = np.array([it["source_type"] for it in self.index])
- self.cfg = ArkEmbedConfig.from_env(env_file)
- def link(self, candidate: str, source_type: str | None = None, top_k: int = 5) -> list[dict]:
- q = np.asarray(embed_text(candidate, self.cfg), dtype=np.float32)
- q = q / (np.linalg.norm(q) + 1e-9)
- sims = self.norm @ q
- idxs = (np.where(self.src == source_type)[0] if source_type
- else np.arange(len(sims)))
- order = idxs[np.argsort(-sims[idxs])][:top_k]
- return [{
- "name": self.index[i]["name"], "path": self.index[i]["path"],
- "source_type": self.index[i]["source_type"],
- "score": round(float(sims[i]), 4),
- } for i in order]
- def _selftest() -> None:
- sl = ScopeLinker()
- cases = [
- ("撕裂共识", "作用"), ("撕裂共识", "意图"), ("三幕结构", "形式"),
- ("角色代入共鸣", "感受"), ("反转", "形式"), ("引发讨论", "作用"),
- ]
- for cand, st in cases:
- print(f"\n[{cand}] @ {st}:")
- for hit in sl.link(cand, st, 3):
- print(f" {hit['score']:.3f} {hit['name']} ({hit['path']})")
- if __name__ == "__main__":
- _selftest()
|