DGX_AI / codeforge /kb /cluster.py
vasiuuu's picture
Initial commit for CodeForge GRPO training
acf77ab
Raw
History Blame
5.16 kB
from __future__ import annotations
import hashlib
from collections import Counter
from collections.abc import Iterable
from datetime import UTC, datetime
from typing import Any
from codeforge.kb.models import Cluster, ClusterManifest
from codeforge.kb.tokenizer import tokenize
_STOPWORDS: frozenset[str] = frozenset({
"the", "and", "for", "with", "this", "that", "are", "was",
"not", "but", "use", "can", "all", "one", "from", "when",
"which", "have", "any", "should", "would", "must", "will",
"your", "you", "our", "its", "their", "them", "they",
})
def _filter_tokens(tokens: list[str], min_length: int = 3) -> list[str]:
return [t for t in tokens if len(t) >= min_length and t not in _STOPWORDS]
def _jaccard(a: set[str], b: set[str]) -> float:
if not a and not b:
return 0.0
union = a | b
inter = a & b
return len(inter) / len(union)
def _connected_components(
node_ids: list[str], adj: dict[str, set[str]],
) -> list[set[str]]:
seen: set[str] = set()
comps: list[set[str]] = []
for start in node_ids:
if start in seen:
continue
comp: set[str] = set()
stack = [start]
while stack:
nid = stack.pop()
if nid in comp:
continue
comp.add(nid)
seen.add(nid)
for nbr in adj.get(nid, ()):
if nbr not in comp:
stack.append(nbr)
comps.append(comp)
return comps
def _dominant_domain(tags_list: Iterable[tuple[str, ...]]) -> str:
counts: Counter[str] = Counter()
for tags in tags_list:
for t in tags:
if t.startswith("domain:"):
counts[t.split(":", 1)[1]] += 1
if not counts:
return "general"
return counts.most_common(1)[0][0]
def _label_cluster(
nodes_data: list[dict[str, Any]],
) -> tuple[str, tuple[str, ...], str]:
all_tokens: Counter[str] = Counter()
tag_lists: list[tuple[str, ...]] = []
for nd in nodes_data:
all_tokens.update(nd["tokens"])
tag_lists.append(tuple(nd.get("tags", ())))
top3 = [t for t, _ in all_tokens.most_common(3)]
dominant = _dominant_domain(tag_lists)
label = f"{dominant}_" + "_".join(top3) if top3 else dominant
return label, tuple(top3), dominant
def build_clusters(
nodes: list[dict[str, Any]],
*,
jaccard_threshold: float = 0.15,
min_token_length: int = 3,
corpus_sha256: str = "",
generated_at: str | None = None,
) -> ClusterManifest:
if not nodes:
stamp = (
generated_at
if generated_at is not None
else datetime.now(UTC).isoformat(timespec="seconds")
)
return ClusterManifest(
generated_at=stamp,
corpus_sha256=corpus_sha256,
jaccard_threshold=jaccard_threshold,
total_clusters=0,
total_nodes_clustered=0,
singletons=0,
clusters=(),
)
node_ids = [str(n["id"]) for n in nodes]
token_sets: dict[str, set[str]] = {
str(n["id"]): set(
_filter_tokens(
tokenize(str(n.get("section_body", ""))), min_token_length,
),
)
for n in nodes
}
adj: dict[str, set[str]] = {nid: set() for nid in node_ids}
for i, a in enumerate(node_ids):
for b in node_ids[i + 1 :]:
sim = _jaccard(token_sets[a], token_sets[b])
if sim >= jaccard_threshold:
adj[a].add(b)
adj[b].add(a)
components = _connected_components(node_ids, adj)
node_by_id = {str(n["id"]): n for n in nodes}
clusters: list[Cluster] = []
singletons = 0
for comp in components:
member_ids = sorted(comp)
nodes_data = [
{
"tokens": _filter_tokens(
tokenize(str(node_by_id[nid].get("section_body", ""))),
min_token_length,
),
"tags": tuple(node_by_id[nid].get("tags", ())),
}
for nid in member_ids
]
label, top_tokens, dominant = _label_cluster(nodes_data)
cluster_id = hashlib.sha256(
"|".join(member_ids).encode(),
).hexdigest()[:12]
if len(member_ids) == 1:
singletons += 1
clusters.append(
Cluster(
cluster_id=cluster_id,
label=label,
dominant_domain=dominant,
top_tokens=top_tokens,
node_count=len(member_ids),
member_node_ids=tuple(member_ids),
),
)
clusters.sort(key=lambda c: (-c.node_count, c.cluster_id))
stamp = (
generated_at
if generated_at is not None
else datetime.now(UTC).isoformat(timespec="seconds")
)
return ClusterManifest(
generated_at=stamp,
corpus_sha256=corpus_sha256,
jaccard_threshold=jaccard_threshold,
total_clusters=len(clusters),
total_nodes_clustered=len(node_ids),
singletons=singletons,
clusters=tuple(clusters),
)