Spjimr / workbench_grounded_theory /node_pattern_detection.py
Viske's picture
Fix deployment: defer agent load, add missing workbench packages and credentials
9ebfd41
Raw
History Blame Contribute Delete
3.01 kB
# ============================================================================
# node_pattern_detection.py — Step 1 (REAL, compliant thin wrapper)
# ============================================================================
#
# COMPLIANCE
# ----------
# This node is a thin orchestrator. It:
# 1. reads params from state
# 2. calls one tool to cluster sentences
# 3. calls one tool to label each cluster
# 4. shapes output rows and writes the structured result to state
#
# All domain logic lives in the tools (workbench_grounded_theory/tools/).
# This file only routes data between state, tools, and state again.
# ============================================================================
from training_data import TRAINING_EXAMPLES
from .tools import cluster_sentences, label_clusters
def pattern_detection_node(state):
sentences = [e["sentence"] for e in TRAINING_EXAMPLES]
true_labels = [e["label"] for e in TRAINING_EXAMPLES]
cluster_result = cluster_sentences(
sentences=sentences,
similarity_threshold=state["similarity_threshold"],
min_cluster_size=state["min_cluster_size"],
n_nearest=state["n_nearest"],
)
# Build the representatives payload for the labeling tool:
# {cluster_id_str: [sentence, sentence, ...], ...}
reps_for_llm = {
str(cid): [sentences[i] for i, _d in reps]
for cid, reps in cluster_result["representatives"].items()
}
cluster_labels = label_clusters(
cluster_representatives=reps_for_llm,
llm_provider=state["llm_provider"],
llm_key=state["llm_key"],
)
# Shape output rows. dict.get with default handles noise sentences
# without a branching if/else.
sentence_rows = [
{
"idx": idx,
"sentence": sentences[idx],
"true_label": true_labels[idx],
"cluster_id": str(cluster_result["cluster_ids"][idx]),
"cluster_label": cluster_labels.get(
str(cluster_result["cluster_ids"][idx]), ""
),
"dist_to_centroid": cluster_result["distances_to_centroid"][idx],
}
for idx in range(len(sentences))
]
detection_result = {
"n_clusters_found": cluster_result["n_clusters_found"],
"n_noise_points": cluster_result["n_noise_points"],
"cluster_labels": cluster_labels,
"similarity_threshold": state["similarity_threshold"],
"min_cluster_size": state["min_cluster_size"],
"n_nearest": state["n_nearest"],
"sentence_rows": sentence_rows,
}
return {
"detection_result": detection_result,
"steps": [{
"step": state.get("iteration", 0),
"node": "pattern_detection",
"action": "cluster + label (one LLM call per cluster)",
"detail": (
f"{cluster_result['n_clusters_found']} clusters, "
f"{cluster_result['n_noise_points']} noise"
),
}],
}