HumboldtJoker commited on
Commit
c5bb56d
·
verified ·
1 Parent(s): 4ad5cf6

Clean up nested duplicate: v2/v2/pharos/pack_loader.py

Browse files
Files changed (1) hide show
  1. v2/v2/pharos/pack_loader.py +0 -209
v2/v2/pharos/pack_loader.py DELETED
@@ -1,209 +0,0 @@
1
- """Pharos pack loading + confidence-gated routing.
2
-
3
- Ported from the Pharos router on MTH
4
- (~/lab/projects/pharos/router.py): embedding similarity when
5
- sentence-transformers is installed, with a deterministic keyword-overlap
6
- fallback so routing still works on a bare deployment.
7
-
8
- Encoding policy follows the Pharos encoding-comparison results:
9
- triples-only is the safe default; richer encodings only above confidence
10
- thresholds; walk-only never (it hurt reasoning in the benchmark).
11
- """
12
-
13
- import json
14
- import re
15
- from dataclasses import dataclass, field
16
- from pathlib import Path
17
-
18
- DEFAULT_PACKS_DIR = Path(__file__).parent.parent.parent / "packs"
19
-
20
-
21
- @dataclass
22
- class Pack:
23
- name: str
24
- description: str
25
- triples: list
26
- path: Path
27
-
28
- def triples_text(self, max_triples: int = 200) -> str:
29
- lines = [f"Knowledge domain: {self.description}\n", "Key relationships:"]
30
- for t in self.triples[:max_triples]:
31
- s = t.get("subject", t.get("s", ""))
32
- p = t.get("predicate", t.get("p", ""))
33
- o = t.get("object", t.get("o", ""))
34
- lines.append(f"- {s} [{p}] {o}")
35
- return "\n".join(lines)
36
-
37
- def descriptor(self) -> str:
38
- from collections import Counter
39
- counter: Counter = Counter()
40
- for t in self.triples[:120]:
41
- for k in ("subject", "s", "predicate", "p", "object", "o"):
42
- counter.update(re.findall(
43
- r"[a-z]{3,}",
44
- str(t.get(k, "")).lower().replace("_", " "),
45
- ))
46
- common = [w for w, _ in counter.most_common(100)]
47
- return (f"{self.name.replace('_', ' ')}. {self.description}. "
48
- + " ".join(common))
49
-
50
-
51
- @dataclass
52
- class RoutedKnowledge:
53
- knowledge_text: str = ""
54
- pack_names: list = field(default_factory=list)
55
- confidence: float = 0.0
56
- encoding_policy: str = "none"
57
- explanation: str = ""
58
-
59
-
60
- class PackLibrary:
61
- def __init__(self, packs_dir: Path | str = DEFAULT_PACKS_DIR):
62
- self.packs_dir = Path(packs_dir)
63
- self.packs: list[Pack] = []
64
- self._load()
65
-
66
- def _load(self) -> None:
67
- if not self.packs_dir.exists():
68
- return
69
- # Flat .json packs (Multiverse demo format) ...
70
- for f in sorted(self.packs_dir.glob("*.json")):
71
- try:
72
- data = json.loads(f.read_text())
73
- except json.JSONDecodeError:
74
- continue
75
- if isinstance(data, dict) and "triples" in data:
76
- self.packs.append(Pack(
77
- name=data.get("pack_name", f.stem),
78
- description=data.get("description", f.stem),
79
- triples=data["triples"], path=f,
80
- ))
81
- # ... and directory packs (triples.json inside, Pharos pack format)
82
- for d in sorted(p for p in self.packs_dir.iterdir() if p.is_dir()):
83
- tf = d / "triples.json"
84
- if not tf.exists():
85
- continue
86
- try:
87
- data = json.loads(tf.read_text())
88
- except json.JSONDecodeError:
89
- continue
90
- if isinstance(data, list):
91
- triples, desc = data, d.name
92
- else:
93
- triples = data.get("triples", [])
94
- desc = data.get("description", d.name)
95
- self.packs.append(Pack(name=d.name, description=desc,
96
- triples=triples, path=tf))
97
-
98
- def get(self, name: str) -> Pack | None:
99
- for p in self.packs:
100
- if p.name == name:
101
- return p
102
- return None
103
-
104
-
105
- class PharosRouter:
106
- """Confidence-gated pack selection (see module docstring)."""
107
-
108
- def __init__(self, library: PackLibrary, match_threshold: float = 0.30,
109
- source_threshold: float = 0.55, max_packs: int = 2,
110
- embedding_model: str = "all-MiniLM-L6-v2"):
111
- self.library = library
112
- self.match_threshold = match_threshold
113
- self.source_threshold = source_threshold
114
- self.max_packs = max_packs
115
- self._st_model = None
116
- self._embeddings = None
117
- self._try_embeddings(embedding_model)
118
-
119
- def _try_embeddings(self, model_name: str) -> None:
120
- try:
121
- from sentence_transformers import SentenceTransformer
122
- except ImportError:
123
- return
124
- if not self.library.packs:
125
- return
126
- self._st_model = SentenceTransformer(model_name)
127
- descriptors = [p.descriptor() for p in self.library.packs]
128
- self._embeddings = self._st_model.encode(
129
- descriptors, convert_to_numpy=True,
130
- )
131
-
132
- def route(self, query: str) -> RoutedKnowledge:
133
- if not self.library.packs:
134
- return RoutedKnowledge(explanation="no packs loaded")
135
- scored = (self._route_embedding(query) if self._st_model is not None
136
- else self._route_keyword(query))
137
-
138
- matches = [(p, s) for p, s in scored if s >= self.match_threshold]
139
- matches = matches[: self.max_packs]
140
- if not matches:
141
- return RoutedKnowledge(
142
- explanation=f"no pack above threshold {self.match_threshold}",
143
- )
144
-
145
- top_conf = matches[0][1]
146
- policy = "triples_source" if top_conf >= self.source_threshold else "triples"
147
- knowledge = "\n\n".join(p.triples_text() for p, _ in matches)
148
- return RoutedKnowledge(
149
- knowledge_text=knowledge,
150
- pack_names=[p.name for p, _ in matches],
151
- confidence=round(top_conf, 3),
152
- encoding_policy=policy,
153
- explanation=(f"top={matches[0][0].name} ({top_conf:.3f}), "
154
- f"method={'embedding' if self._st_model else 'keyword'}"),
155
- )
156
-
157
- def _route_embedding(self, query: str) -> list:
158
- import numpy as np
159
- q = self._st_model.encode(query, convert_to_numpy=True)
160
- sims = self._embeddings @ q / (
161
- np.linalg.norm(self._embeddings, axis=1) * (np.linalg.norm(q) or 1)
162
- )
163
- ranked = sorted(zip(self.library.packs, sims.tolist()),
164
- key=lambda x: -x[1])
165
- return ranked
166
-
167
- def _route_keyword(self, query: str) -> list:
168
- """Fraction of (content-bearing) query words found in the pack
169
- descriptor, with 4-char prefix matching so migration/migrations
170
- and share/shared/shares count as hits."""
171
- stop = {"the", "and", "for", "with", "how", "what", "why", "does",
172
- "should", "would", "can", "you", "this", "that", "when"}
173
- q_words = [w for w in re.findall(r"[a-z]{3,}", query.lower())
174
- if w not in stop]
175
- scored = []
176
- for pack in self.library.packs:
177
- d_words = set(re.findall(r"[a-z]{3,}", pack.descriptor().lower()))
178
- if not q_words or not d_words:
179
- scored.append((pack, 0.0))
180
- continue
181
-
182
- def matched(qw: str) -> bool:
183
- if qw in d_words:
184
- return True
185
- if len(qw) >= 4:
186
- prefix = qw[:4]
187
- return any(dw.startswith(prefix) or qw.startswith(dw[:4])
188
- for dw in d_words if len(dw) >= 4)
189
- return False
190
-
191
- hits = sum(1 for qw in q_words if matched(qw))
192
- scored.append((pack, hits / len(q_words)))
193
- return sorted(scored, key=lambda x: -x[1])
194
-
195
-
196
- def load_router(packs_dir: Path | str = DEFAULT_PACKS_DIR,
197
- **kwargs) -> PharosRouter:
198
- return PharosRouter(PackLibrary(packs_dir), **kwargs)
199
-
200
-
201
- if __name__ == "__main__":
202
- import sys
203
- router = load_router(sys.argv[1] if len(sys.argv) > 1 else DEFAULT_PACKS_DIR)
204
- print(f"packs: {[p.name for p in router.library.packs]}")
205
- for q in ("How do I add a migration for a new table?",
206
- "Why does the webhook skip signature verification?"):
207
- r = router.route(q)
208
- print(f"\nQ: {q}\n -> {r.pack_names} conf={r.confidence} "
209
- f"policy={r.encoding_policy} ({r.explanation})")