feat(mcp): constraint + link_prediction graph metrics#607
Conversation
Extend distillery_relations action="metrics" with two algorithms the post-recommendation spikes (#605) ran in-process: - constraint: Burt's structural-hole constraint, ascending — lowest-constraint entries are the strongest brokers (topics/people bridging disconnected clusters). Returns [{id, score}]. - link_prediction: Adamic-Adar index over non-existent edges. Pass entry_id to score emerging adjacencies for one entry; omit for top predicted edges graph-wide (bound via scope="ego"). Returns [{source, target, score}]. Both compute on the undirected projection alongside the existing bridges/communities, gated behind the [graph] extra. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis PR adds two graph-analysis metrics ( ChangesGraph Metrics: Constraint and Link Prediction
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
tests/test_mcp_tools/test_relations_metrics.py (2)
135-141: 💤 Low valueConsider clarifying the docstring.
The docstring says "M brokers two otherwise-disconnected pairs {A,B} and {X,Y}", but the topology actually creates two triangles
{M,A,B}and{M,X,Y}since edgesA-BandX-Yare included. A more accurate description would be "M brokers two otherwise-disconnected triangles" to reflect that A-B and X-Y are directly connected within their components.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_mcp_tools/test_relations_metrics.py` around lines 135 - 141, Update the docstring for _seed_bowtie to accurately describe the topology created: instead of saying "M brokers two otherwise-disconnected pairs {A,B} and {X,Y}", state that it creates two triangles {M,A,B} and {M,X,Y} (or "M brokers two otherwise-disconnected triangles") since the pairs list in _seed_bowtie (pairs = [("M","A"),("M","B"),("A","B"),("M","X"),("M","Y"),("X","Y")]) includes the A-B and X-Y edges; keep the rest of the function (ids creation via _store_entry and return ids) unchanged.
185-195: 💤 Low valueConsider consolidating with the existing invalid-metric test.
This test duplicates
test_metrics_invalid_metric_returns_invalid_params(lines 198–207), which already validates that unknown metrics are rejected withINVALID_PARAMS. The only addition here is asserting that the error message enumerates the new metrics. You could update the existing test to include that assertion rather than maintaining two separate tests for the same behavior.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_mcp_tools/test_relations_metrics.py` around lines 185 - 195, Two tests duplicate coverage for rejecting unknown metrics: keep one and add the additional assertion about enumerated metrics to it. Remove the redundant test function test_metrics_invalid_metric_rejected and update the existing test_metrics_invalid_metric_returns_invalid_params to also assert that the error message contains the expanded metric names (e.g., check "constraint" and "link_prediction" in data["message"]) after calling _handle_relations(store, {"action":"metrics","metric":"pagerank","scope":"global"}) and parsing the result with _parse.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@tests/test_mcp_tools/test_relations_metrics.py`:
- Around line 135-141: Update the docstring for _seed_bowtie to accurately
describe the topology created: instead of saying "M brokers two
otherwise-disconnected pairs {A,B} and {X,Y}", state that it creates two
triangles {M,A,B} and {M,X,Y} (or "M brokers two otherwise-disconnected
triangles") since the pairs list in _seed_bowtie (pairs =
[("M","A"),("M","B"),("A","B"),("M","X"),("M","Y"),("X","Y")]) includes the A-B
and X-Y edges; keep the rest of the function (ids creation via _store_entry and
return ids) unchanged.
- Around line 185-195: Two tests duplicate coverage for rejecting unknown
metrics: keep one and add the additional assertion about enumerated metrics to
it. Remove the redundant test function test_metrics_invalid_metric_rejected and
update the existing test_metrics_invalid_metric_returns_invalid_params to also
assert that the error message contains the expanded metric names (e.g., check
"constraint" and "link_prediction" in data["message"]) after calling
_handle_relations(store,
{"action":"metrics","metric":"pagerank","scope":"global"}) and parsing the
result with _parse.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: fd516e07-deb1-4463-93ee-e19800b0d616
📒 Files selected for processing (5)
src/distillery/graph/metrics.pysrc/distillery/mcp/server.pysrc/distillery/mcp/tools/relations.pytests/graph/test_metrics.pytests/test_mcp_tools/test_relations_metrics.py
Extends
distillery_relations action="metrics"with the two algorithms the post-recommendation spikes (#605) ran in-process, so they're now first-class Distillery metrics next tobridges/communities.New metrics
constraint— Burt's structural-hole constraint on the undirected projection, sorted ascending. Lowest-constraint entries are the strongest brokers: topics/people bridging otherwise-disconnected clusters. Nodes with no neighbours (NaN) are excluded. Returns[{id, score}].link_prediction— Adamic-Adar index over non-existent edges. Passentry_idto score emerging adjacencies for one entry (candidate edges to its non-neighbours); omit it for the top predicted edges graph-wide. Returns[{source, target, score}]. Quadratic in node count when unsourced — bound withscope="ego".Both live in
distillery.graph.metrics, compute on the undirected projection, and stay gated behind the[graph]extra.Example
Tests
tests/graph/test_metrics.py): bowtie → broker has lowest constraint; Adamic-Adar from a source predicts the shared-neighbour node; global surfaces the shared pair; unknown source / empty graph →[].tests/test_mcp_tools/test_relations_metrics.py): constraint ranks the broker first; link_prediction withentry_idreturns{source, target, score}with the right top pair; unknown metric rejected with the expanded valid-set message.mypy --strictclean, ruff clean, all graph/relations/metrics suites green.Companion to #606 (temporal/weighted edge metadata); together they close the graph-algorithm and edge-attribute gaps the spikes surfaced.
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
constraint(identifies structural-hole brokers) andlink_prediction(predicts non-existent edges using Adamic–Adar index).Documentation
Tests