agentprop 0.1.0a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. agentprop/__init__.py +5 -0
  2. agentprop/algorithms/__init__.py +68 -0
  3. agentprop/algorithms/bottlenecks.py +103 -0
  4. agentprop/algorithms/observability.py +56 -0
  5. agentprop/algorithms/pruning.py +26 -0
  6. agentprop/algorithms/seed_selection.py +453 -0
  7. agentprop/algorithms/verifier_placement.py +148 -0
  8. agentprop/cli.py +610 -0
  9. agentprop/core/__init__.py +15 -0
  10. agentprop/core/graph.py +219 -0
  11. agentprop/core/models.py +94 -0
  12. agentprop/core/types.py +18 -0
  13. agentprop/core/validation.py +140 -0
  14. agentprop/dl/__init__.py +16 -0
  15. agentprop/dl/encoders.py +39 -0
  16. agentprop/dl/torch_gnn.py +508 -0
  17. agentprop/evaluation/__init__.py +122 -0
  18. agentprop/evaluation/artifacts.py +175 -0
  19. agentprop/evaluation/llm_execution.py +198 -0
  20. agentprop/evaluation/metrics.py +260 -0
  21. agentprop/evaluation/pruning.py +86 -0
  22. agentprop/evaluation/quality.py +157 -0
  23. agentprop/evaluation/readiness.py +331 -0
  24. agentprop/evaluation/reporting.py +565 -0
  25. agentprop/evaluation/routing.py +235 -0
  26. agentprop/evaluation/runner.py +152 -0
  27. agentprop/evaluation/verification.py +212 -0
  28. agentprop/integrations/__init__.py +55 -0
  29. agentprop/integrations/agent_instructions.py +151 -0
  30. agentprop/integrations/framework_adapters.py +630 -0
  31. agentprop/integrations/mcp_server.py +197 -0
  32. agentprop/integrations/trace_loader.py +131 -0
  33. agentprop/ml/__init__.py +57 -0
  34. agentprop/ml/checkpointing.py +184 -0
  35. agentprop/ml/datasets.py +197 -0
  36. agentprop/ml/features.py +96 -0
  37. agentprop/ml/models.py +287 -0
  38. agentprop/propagation/__init__.py +28 -0
  39. agentprop/propagation/base.py +64 -0
  40. agentprop/propagation/bootstrap_percolation.py +59 -0
  41. agentprop/propagation/independent_cascade.py +112 -0
  42. agentprop/propagation/learned.py +287 -0
  43. agentprop/propagation/linear_threshold.py +64 -0
  44. agentprop/propagation/randomized_zero_forcing.py +134 -0
  45. agentprop/propagation/zero_forcing.py +64 -0
  46. agentprop/rl/__init__.py +78 -0
  47. agentprop/rl/bandit.py +76 -0
  48. agentprop/rl/checkpointing.py +165 -0
  49. agentprop/rl/env.py +406 -0
  50. agentprop/rl/policies.py +31 -0
  51. agentprop/rl/ppo.py +247 -0
  52. agentprop/rl/q_learning.py +134 -0
  53. agentprop/rl/reinforce.py +194 -0
  54. agentprop/rl/rewards.py +85 -0
  55. agentprop/rl/trajectory.py +125 -0
  56. agentprop/visualization/__init__.py +5 -0
  57. agentprop/visualization/dot.py +39 -0
  58. agentprop/workflows/__init__.py +39 -0
  59. agentprop/workflows/export.py +20 -0
  60. agentprop/workflows/templates.py +355 -0
  61. agentprop-0.1.0a1.dist-info/METADATA +243 -0
  62. agentprop-0.1.0a1.dist-info/RECORD +65 -0
  63. agentprop-0.1.0a1.dist-info/WHEEL +4 -0
  64. agentprop-0.1.0a1.dist-info/entry_points.txt +3 -0
  65. agentprop-0.1.0a1.dist-info/licenses/LICENSE +155 -0
agentprop/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """AgentProp: graph optimization for multi-agent LLM workflows."""
2
+
3
+ from agentprop.core import AgentEdge, AgentGraph, AgentNode, NodeType
4
+
5
+ __all__ = ["AgentEdge", "AgentGraph", "AgentNode", "NodeType"]
@@ -0,0 +1,68 @@
1
+ """Classical graph optimization algorithms."""
2
+
3
+ from agentprop.algorithms.bottlenecks import (
4
+ articulation_bottlenecks,
5
+ bottleneck_nodes,
6
+ bridge_bottlenecks,
7
+ edge_bottlenecks,
8
+ failure_sensitive_nodes,
9
+ low_reliability_cut_points,
10
+ )
11
+ from agentprop.algorithms.observability import (
12
+ observability_coverage,
13
+ observability_scores,
14
+ verifier_observability_placement,
15
+ )
16
+ from agentprop.algorithms.pruning import high_cost_low_relevance_edges, low_weight_edges
17
+ from agentprop.algorithms.seed_selection import (
18
+ betweenness_seed_selection,
19
+ celf_seed_selection,
20
+ closeness_seed_selection,
21
+ cost_aware_greedy_seed_selection,
22
+ degree_seed_selection,
23
+ greedy_seed_selection,
24
+ k_core_seed_selection,
25
+ pagerank_seed_selection,
26
+ quality_aware_greedy_seed_selection,
27
+ random_seed_selection,
28
+ )
29
+ from agentprop.algorithms.verifier_placement import (
30
+ betweenness_verifier_placement,
31
+ context_sensitive_verifier_placement,
32
+ error_propagation_centrality,
33
+ error_propagation_verifier_placement,
34
+ greedy_correction_coverage_placement,
35
+ pagerank_verifier_placement,
36
+ risk_aware_verifier_placement,
37
+ )
38
+
39
+ __all__ = [
40
+ "articulation_bottlenecks",
41
+ "betweenness_seed_selection",
42
+ "betweenness_verifier_placement",
43
+ "bottleneck_nodes",
44
+ "bridge_bottlenecks",
45
+ "celf_seed_selection",
46
+ "closeness_seed_selection",
47
+ "cost_aware_greedy_seed_selection",
48
+ "context_sensitive_verifier_placement",
49
+ "degree_seed_selection",
50
+ "edge_bottlenecks",
51
+ "error_propagation_centrality",
52
+ "error_propagation_verifier_placement",
53
+ "failure_sensitive_nodes",
54
+ "greedy_seed_selection",
55
+ "greedy_correction_coverage_placement",
56
+ "k_core_seed_selection",
57
+ "high_cost_low_relevance_edges",
58
+ "low_reliability_cut_points",
59
+ "low_weight_edges",
60
+ "observability_coverage",
61
+ "observability_scores",
62
+ "pagerank_seed_selection",
63
+ "pagerank_verifier_placement",
64
+ "quality_aware_greedy_seed_selection",
65
+ "random_seed_selection",
66
+ "risk_aware_verifier_placement",
67
+ "verifier_observability_placement",
68
+ ]
@@ -0,0 +1,103 @@
1
+ """Workflow bottleneck detection."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import networkx as nx
6
+
7
+ from agentprop.core import AgentGraph
8
+
9
+
10
+ def bottleneck_nodes(graph: AgentGraph, *, limit: int = 5) -> list[tuple[str, float]]:
11
+ """Return nodes that look structurally important or operationally risky."""
12
+
13
+ nx_graph = graph.to_networkx()
14
+ if not nx_graph:
15
+ return []
16
+
17
+ betweenness = nx.betweenness_centrality(nx_graph, weight="weight")
18
+ scores: dict[str, float] = {}
19
+ for node in graph.nodes():
20
+ scores[node.id] = (
21
+ float(betweenness.get(node.id, 0.0))
22
+ + 0.05 * float(nx_graph.out_degree(node.id))
23
+ + 0.05 * float(nx_graph.in_degree(node.id))
24
+ + 0.25 * (1.0 - node.reliability)
25
+ + 0.25 * node.error_rate
26
+ )
27
+
28
+ return sorted(scores.items(), key=lambda item: (-item[1], item[0]))[:limit]
29
+
30
+
31
+ def articulation_bottlenecks(graph: AgentGraph, *, limit: int = 5) -> list[str]:
32
+ """Return nodes whose removal disconnects the weak workflow structure."""
33
+
34
+ undirected = graph.to_networkx().to_undirected()
35
+ if graph.node_count < 3:
36
+ return []
37
+ points = [str(node_id) for node_id in nx.articulation_points(undirected)]
38
+ points.sort()
39
+ return points[:limit]
40
+
41
+
42
+ def bridge_bottlenecks(graph: AgentGraph, *, limit: int = 5) -> list[tuple[str, str]]:
43
+ """Return communication edges that are weak bridges in the workflow graph."""
44
+
45
+ undirected = graph.to_networkx().to_undirected()
46
+ bridges = [(str(source), str(target)) for source, target in nx.bridges(undirected)]
47
+ bridges.sort()
48
+ return bridges[:limit]
49
+
50
+
51
+ def edge_bottlenecks(graph: AgentGraph, *, limit: int = 5) -> list[tuple[str, str, float]]:
52
+ """Rank edges by structural centrality and operational cost."""
53
+
54
+ nx_graph = graph.to_networkx()
55
+ if not nx_graph:
56
+ return []
57
+ edge_centrality = nx.edge_betweenness_centrality(nx_graph, weight="weight")
58
+ scored: list[tuple[str, str, float]] = []
59
+ for edge in graph.edges():
60
+ centrality = float(edge_centrality.get((edge.source, edge.target), 0.0))
61
+ cost_risk = 0.001 * edge.message_cost + 0.1 * (1.0 - edge.reliability)
62
+ scored.append((edge.source, edge.target, centrality + cost_risk))
63
+ return sorted(scored, key=lambda item: (-item[2], item[0], item[1]))[:limit]
64
+
65
+
66
+ def low_reliability_cut_points(graph: AgentGraph, *, limit: int = 5) -> list[tuple[str, float]]:
67
+ """Rank structurally central nodes with low reliability or high error rate."""
68
+
69
+ nx_graph = graph.to_networkx()
70
+ if not nx_graph:
71
+ return []
72
+ betweenness = nx.betweenness_centrality(nx_graph, weight="weight")
73
+ scores = {}
74
+ for node in graph.nodes():
75
+ reliability_risk = 1.0 - node.reliability + node.error_rate
76
+ scores[node.id] = reliability_risk * (1.0 + float(betweenness.get(node.id, 0.0)))
77
+ return sorted(scores.items(), key=lambda item: (-item[1], item[0]))[:limit]
78
+
79
+
80
+ def failure_sensitive_nodes(graph: AgentGraph, *, limit: int = 5) -> list[tuple[str, float]]:
81
+ """Rank nodes by reachable-pair loss after node removal."""
82
+
83
+ nx_graph = graph.to_networkx()
84
+ if not nx_graph:
85
+ return []
86
+ baseline_pairs = _reachable_pair_count(nx_graph)
87
+ if baseline_pairs == 0:
88
+ return []
89
+
90
+ scored = []
91
+ for node_id in nx_graph.nodes:
92
+ reduced = nx_graph.copy()
93
+ reduced.remove_node(node_id)
94
+ loss = baseline_pairs - _reachable_pair_count(reduced)
95
+ scored.append((str(node_id), loss / baseline_pairs))
96
+ return sorted(scored, key=lambda item: (-item[1], item[0]))[:limit]
97
+
98
+
99
+ def _reachable_pair_count(nx_graph: nx.DiGraph) -> float:
100
+ total = 0
101
+ for source in nx_graph.nodes:
102
+ total += len(nx.descendants(nx_graph, source))
103
+ return float(total)
@@ -0,0 +1,56 @@
1
+ """Observability and verifier-placement metrics."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import networkx as nx
6
+
7
+ from agentprop.core import AgentGraph
8
+
9
+
10
+ def observability_scores(graph: AgentGraph) -> dict[str, float]:
11
+ """Score nodes by usefulness for observing failures and corrections."""
12
+
13
+ nx_graph = graph.to_networkx()
14
+ if graph.node_count == 0:
15
+ return {}
16
+
17
+ betweenness = nx.betweenness_centrality(nx_graph, weight="weight")
18
+ scores: dict[str, float] = {}
19
+ max_reachable = max(graph.node_count - 1, 1)
20
+
21
+ for node in graph.nodes():
22
+ descendants = nx.descendants(nx_graph, node.id)
23
+ ancestors = nx.ancestors(nx_graph, node.id)
24
+ reach_score = len(descendants) / max_reachable
25
+ attribution_score = len(ancestors) / max_reachable
26
+ reliability_risk = 1.0 - node.reliability + node.error_rate
27
+ scores[node.id] = (
28
+ 0.35 * reach_score
29
+ + 0.25 * attribution_score
30
+ + 0.25 * float(betweenness.get(node.id, 0.0))
31
+ + 0.15 * reliability_risk
32
+ )
33
+
34
+ return scores
35
+
36
+
37
+ def observability_coverage(graph: AgentGraph, observers: list[str]) -> float:
38
+ """Return fraction of nodes observable by the selected observer nodes."""
39
+
40
+ nx_graph = graph.to_networkx()
41
+ observed = set(observers)
42
+ for observer in observers:
43
+ if observer in nx_graph:
44
+ observed.update(str(node_id) for node_id in nx.ancestors(nx_graph, observer))
45
+ observed.update(str(node_id) for node_id in nx.descendants(nx_graph, observer))
46
+ return len(observed) / max(graph.node_count, 1)
47
+
48
+
49
+ def verifier_observability_placement(graph: AgentGraph, k: int) -> list[str]:
50
+ """Choose verifier/logging nodes by observability score."""
51
+
52
+ if k < 1:
53
+ raise ValueError("k must be at least 1")
54
+ scores = observability_scores(graph)
55
+ ranked = sorted(scores.items(), key=lambda item: (-item[1], item[0]))
56
+ return [node_id for node_id, _ in ranked[:k]]
@@ -0,0 +1,26 @@
1
+ """Edge pruning heuristics."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from agentprop.core import AgentGraph
6
+
7
+
8
+ def low_weight_edges(graph: AgentGraph, *, fraction: float = 0.2) -> list[tuple[str, str]]:
9
+ """Return the lowest-weight edges as pruning candidates."""
10
+
11
+ if not 0 <= fraction <= 1:
12
+ raise ValueError("fraction must be between 0 and 1")
13
+ edges = sorted(graph.edges(), key=lambda edge: (edge.weight, edge.relevance, edge.reliability))
14
+ keep_count = int(round(len(edges) * fraction))
15
+ return [(edge.source, edge.target) for edge in edges[:keep_count]]
16
+
17
+
18
+ def high_cost_low_relevance_edges(graph: AgentGraph, *, limit: int = 5) -> list[tuple[str, str]]:
19
+ """Return expensive edges with low relevance/reliability."""
20
+
21
+ scored = []
22
+ for edge in graph.edges():
23
+ quality = max(edge.relevance * edge.reliability, 0.01)
24
+ score = edge.message_cost / quality
25
+ scored.append((score, edge.source, edge.target))
26
+ return [(source, target) for _, source, target in sorted(scored, reverse=True)[:limit]]