Spaces:
Running on Zero
Running on Zero
Update graph_engine.py
Browse files- graph_engine.py +55 -24
graph_engine.py
CHANGED
|
@@ -1,18 +1,19 @@
|
|
| 1 |
#!/usr/bin/env python
|
| 2 |
# coding: utf-8
|
| 3 |
|
| 4 |
-
import numpy as np
|
| 5 |
-
import hashlib
|
| 6 |
import json
|
|
|
|
|
|
|
| 7 |
from typing import List, Dict, Any, Optional
|
|
|
|
| 8 |
|
| 9 |
class Node:
|
| 10 |
def __init__(self, node_id: str, node_type: str, text: str,
|
| 11 |
expected_keys: List[str] = None, runs: List[str] = None,
|
| 12 |
execution_time_ms: float = None):
|
| 13 |
self.id = node_id
|
| 14 |
-
self.type = node_type
|
| 15 |
-
self.text = text
|
| 16 |
self.embedding = None
|
| 17 |
self.is_valid_format = True
|
| 18 |
self.validation_error = None
|
|
@@ -21,6 +22,7 @@ class Node:
|
|
| 21 |
self.consistency_score = 100.0
|
| 22 |
self.instability_index = 0.0
|
| 23 |
self.execution_time_ms = execution_time_ms
|
|
|
|
| 24 |
|
| 25 |
def get_signature(self) -> str:
|
| 26 |
content = f"{self.type}:{self.text.strip().lower()}"
|
|
@@ -50,9 +52,10 @@ class TrajectoryGraph:
|
|
| 50 |
edge = Edge(from_node_id, to_node_id)
|
| 51 |
self.edges.append(edge)
|
| 52 |
else:
|
| 53 |
-
raise ValueError(
|
| 54 |
|
| 55 |
def validate_tool_calls(self):
|
|
|
|
| 56 |
for node in self.nodes.values():
|
| 57 |
if node.type == 'tool':
|
| 58 |
stripped_text = node.text.strip()
|
|
@@ -61,6 +64,13 @@ class TrajectoryGraph:
|
|
| 61 |
node.validation_error = "Empty tool execution response"
|
| 62 |
continue
|
| 63 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
if stripped_text.startswith('{') or stripped_text.startswith('['):
|
| 65 |
try:
|
| 66 |
data = json.loads(stripped_text)
|
|
@@ -69,35 +79,53 @@ class TrajectoryGraph:
|
|
| 69 |
missing_keys = [key for key in node.expected_keys if key not in data]
|
| 70 |
if missing_keys:
|
| 71 |
node.is_valid_format = False
|
| 72 |
-
node.validation_error = f"Missing required fields: {missing_keys}"
|
| 73 |
except json.JSONDecodeError as e:
|
| 74 |
node.is_valid_format = False
|
| 75 |
-
node.validation_error = f
|
| 76 |
else:
|
| 77 |
node.is_valid_format = True
|
| 78 |
|
| 79 |
def detect_trajectory_cycles(self) -> List[Dict[str, Any]]:
|
|
|
|
| 80 |
detected_loops = []
|
| 81 |
-
|
|
|
|
| 82 |
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
|
|
|
| 87 |
detected_loops.append({
|
| 88 |
'failure_type': 'INFINITE_EXECUTION_LOOP',
|
| 89 |
-
'from_node':
|
| 90 |
-
'to_node':
|
| 91 |
-
'reason': f"Infinite
|
| 92 |
-
'details': f"
|
| 93 |
})
|
| 94 |
-
|
| 95 |
-
seen_signatures[sig] = node
|
| 96 |
|
| 97 |
return detected_loops
|
| 98 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
def calculate_node_consistency(self, get_embedding_func):
|
| 100 |
-
|
| 101 |
for node in self.nodes.values():
|
| 102 |
if node.runs and len(node.runs) >= 2:
|
| 103 |
embeddings = [get_embedding_func(run) for run in node.runs]
|
|
@@ -107,11 +135,12 @@ class TrajectoryGraph:
|
|
| 107 |
sim = cosine_similarity([embeddings[i]], [embeddings[j]])[0][0]
|
| 108 |
similarities.append(float(sim))
|
| 109 |
|
| 110 |
-
avg_sim = float(np.mean(similarities))
|
| 111 |
-
node.consistency_score = avg_sim * 100.0
|
| 112 |
-
node.instability_index = 100.0 - node.consistency_score
|
| 113 |
|
| 114 |
def calculate_drift_scores(self):
|
|
|
|
| 115 |
for edge in self.edges:
|
| 116 |
node_from = self.nodes[edge.from_node_id]
|
| 117 |
node_to = self.nodes[edge.to_node_id]
|
|
@@ -121,5 +150,7 @@ class TrajectoryGraph:
|
|
| 121 |
norm_a = np.linalg.norm(node_from.embedding)
|
| 122 |
norm_b = np.linalg.norm(node_to.embedding)
|
| 123 |
sim = dot / (norm_a * norm_b) if (norm_a * norm_b) > 0 else 0.0
|
| 124 |
-
|
| 125 |
-
|
|
|
|
|
|
|
|
|
| 1 |
#!/usr/bin/env python
|
| 2 |
# coding: utf-8
|
| 3 |
|
|
|
|
|
|
|
| 4 |
import json
|
| 5 |
+
import hashlib
|
| 6 |
+
import numpy as np
|
| 7 |
from typing import List, Dict, Any, Optional
|
| 8 |
+
from sklearn.metrics.pairwise import cosine_similarity
|
| 9 |
|
| 10 |
class Node:
|
| 11 |
def __init__(self, node_id: str, node_type: str, text: str,
|
| 12 |
expected_keys: List[str] = None, runs: List[str] = None,
|
| 13 |
execution_time_ms: float = None):
|
| 14 |
self.id = node_id
|
| 15 |
+
self.type = node_type.lower()
|
| 16 |
+
self.text = str(text or "")
|
| 17 |
self.embedding = None
|
| 18 |
self.is_valid_format = True
|
| 19 |
self.validation_error = None
|
|
|
|
| 22 |
self.consistency_score = 100.0
|
| 23 |
self.instability_index = 0.0
|
| 24 |
self.execution_time_ms = execution_time_ms
|
| 25 |
+
self.token_count = max(1, len(self.text.split()))
|
| 26 |
|
| 27 |
def get_signature(self) -> str:
|
| 28 |
content = f"{self.type}:{self.text.strip().lower()}"
|
|
|
|
| 52 |
edge = Edge(from_node_id, to_node_id)
|
| 53 |
self.edges.append(edge)
|
| 54 |
else:
|
| 55 |
+
raise ValueError(f"Both nodes [{from_node_id}, {to_node_id}] must exist before creating an edge.")
|
| 56 |
|
| 57 |
def validate_tool_calls(self):
|
| 58 |
+
"""Valideaza payload-urile JSON si detecteaza erori de executie ale uneltelor."""
|
| 59 |
for node in self.nodes.values():
|
| 60 |
if node.type == 'tool':
|
| 61 |
stripped_text = node.text.strip()
|
|
|
|
| 64 |
node.validation_error = "Empty tool execution response"
|
| 65 |
continue
|
| 66 |
|
| 67 |
+
# Verificare erori standard de sistem
|
| 68 |
+
error_signatures = ["traceback (most recent call last)", "error:", "exception:", "unauthorized", "timed out"]
|
| 69 |
+
if any(sig in stripped_text.lower() for sig in error_signatures):
|
| 70 |
+
node.is_valid_format = False
|
| 71 |
+
node.validation_error = f"Tool Execution Failure: {stripped_text[:100]}"
|
| 72 |
+
continue
|
| 73 |
+
|
| 74 |
if stripped_text.startswith('{') or stripped_text.startswith('['):
|
| 75 |
try:
|
| 76 |
data = json.loads(stripped_text)
|
|
|
|
| 79 |
missing_keys = [key for key in node.expected_keys if key not in data]
|
| 80 |
if missing_keys:
|
| 81 |
node.is_valid_format = False
|
| 82 |
+
node.validation_error = f"Missing required fields in tool output: {missing_keys}"
|
| 83 |
except json.JSONDecodeError as e:
|
| 84 |
node.is_valid_format = False
|
| 85 |
+
node.validation_error = f"Malformed JSON structure: {str(e)}"
|
| 86 |
else:
|
| 87 |
node.is_valid_format = True
|
| 88 |
|
| 89 |
def detect_trajectory_cycles(self) -> List[Dict[str, Any]]:
|
| 90 |
+
"""Detecteaza cicluri repetitive de tip Ping-Pong sau bucle infinite consecutive."""
|
| 91 |
detected_loops = []
|
| 92 |
+
if len(self.edges) < 2:
|
| 93 |
+
return detected_loops
|
| 94 |
|
| 95 |
+
# Cautam secvente consecutive repetitive: A -> B urmat din nou de A -> B
|
| 96 |
+
transition_history = []
|
| 97 |
+
for edge in self.edges:
|
| 98 |
+
transition = f"{self.nodes[edge.from_node_id].get_signature()}->{self.nodes[edge.to_node_id].get_signature()}"
|
| 99 |
+
if transition in transition_history:
|
| 100 |
detected_loops.append({
|
| 101 |
'failure_type': 'INFINITE_EXECUTION_LOOP',
|
| 102 |
+
'from_node': edge.from_node_id,
|
| 103 |
+
'to_node': edge.to_node_id,
|
| 104 |
+
'reason': f"Infinite State Cycle: Repeated transition pattern detected between [{edge.from_node_id}] and [{edge.to_node_id}].",
|
| 105 |
+
'details': f"Cycle Signature: {transition[:16]}..."
|
| 106 |
})
|
| 107 |
+
transition_history.append(transition)
|
|
|
|
| 108 |
|
| 109 |
return detected_loops
|
| 110 |
|
| 111 |
+
def detect_stagnation(self, min_drift_threshold: float = 1.5) -> List[Dict[str, Any]]:
|
| 112 |
+
"""Detecteaza daca agentul bate pasul pe loc fara progres semantic intre unelte."""
|
| 113 |
+
stagnations = []
|
| 114 |
+
for edge in self.edges:
|
| 115 |
+
n_from = self.nodes[edge.from_node_id]
|
| 116 |
+
n_to = self.nodes[edge.to_node_id]
|
| 117 |
+
if n_from.type in ['tool', 'thought'] and n_to.type in ['tool', 'thought']:
|
| 118 |
+
if edge.drift_score < min_drift_threshold:
|
| 119 |
+
stagnations.append({
|
| 120 |
+
'failure_type': 'AGENT_STAGNATION',
|
| 121 |
+
'from_node': edge.from_node_id,
|
| 122 |
+
'to_node': edge.to_node_id,
|
| 123 |
+
'reason': f"Semantic Stagnation: Minimal cognitive drift ({edge.drift_score:.2f}%) between consecutive tool steps. Redundant execution suspected."
|
| 124 |
+
})
|
| 125 |
+
return stagnations
|
| 126 |
+
|
| 127 |
def calculate_node_consistency(self, get_embedding_func):
|
| 128 |
+
"""Calculeaza stabilitatea nodului intre multiple rulari."""
|
| 129 |
for node in self.nodes.values():
|
| 130 |
if node.runs and len(node.runs) >= 2:
|
| 131 |
embeddings = [get_embedding_func(run) for run in node.runs]
|
|
|
|
| 135 |
sim = cosine_similarity([embeddings[i]], [embeddings[j]])[0][0]
|
| 136 |
similarities.append(float(sim))
|
| 137 |
|
| 138 |
+
avg_sim = float(np.mean(similarities)) if similarities else 1.0
|
| 139 |
+
node.consistency_score = float(avg_sim * 100.0)
|
| 140 |
+
node.instability_index = float(100.0 - node.consistency_score)
|
| 141 |
|
| 142 |
def calculate_drift_scores(self):
|
| 143 |
+
"""Calculeaza si normalizeaza driftul semantic (0 - 100%) pe fiecare tranzitie."""
|
| 144 |
for edge in self.edges:
|
| 145 |
node_from = self.nodes[edge.from_node_id]
|
| 146 |
node_to = self.nodes[edge.to_node_id]
|
|
|
|
| 150 |
norm_a = np.linalg.norm(node_from.embedding)
|
| 151 |
norm_b = np.linalg.norm(node_to.embedding)
|
| 152 |
sim = dot / (norm_a * norm_b) if (norm_a * norm_b) > 0 else 0.0
|
| 153 |
+
|
| 154 |
+
# Normalizare sigura intre 0% si 100%
|
| 155 |
+
similarity_percentage = float(np.clip(sim, -1.0, 1.0)) * 100.0
|
| 156 |
+
edge.drift_score = float(np.clip(100.0 - similarity_percentage, 0.0, 100.0))
|