跳到正文
DEV Community· Jignesh Dabhi·· 3 小時前AI 評分59

Aevion 將 FalkorDB 九個 GitHub 儲存庫的歷史載入 FalkorDB,支援可追溯問答

We loaded FalkorDB's GitHub into FalkorDB: Now it explains itself

AI 導讀

Aevion 將 FalkorDB 九個 GitHub 儲存庫過去 12 個月的歷史載入 FalkorDB,並以圖譜資料回答程式碼專家排序、決策脈絡及新 issue 分流等問題。圖譜包含 20,961 個節點和 80,642 條邊,每個答案附有可追查的圖譜路徑,並可開啟相關 GitHub 項目。Aevion 已在 aevion-alpha.vercel.app 開放使用,無需登入。

正文

We chose the dataset where a wrong answer would get caught fastest: a year of FalkorDB's own GitHub history, loaded into FalkorDB. Ask Aevion "Who knows the falkordb-py client code best?" and it names five people, starting with gkorland, who authored 5 merged PRs in that code and reviewed 4. Under the answer is the graph walk behind every name, 20 nodes and 27 edges, from each person to the pull requests that earned their score and the files those PRs changed. Click a person, PR or issue and it opens on GitHub. The Cypher tab shows the read-only query that ran.

Asking Aevion

We built Aevion for Graph Hacks and had it in production about 23 hours after our first commit. No answer leaves our API without its path. It's live at aevion-alpha.vercel.app, no login needed.

The graph

Aevion's graph model: Person, PullRequest, Issue, Comment, File, Component, Repo, Decision, Release, Label, Team, CachedAnswer and TriageRun nodes and the relationships between them, with the expertise path Person to PullRequest to File to Component highlighted

Nine repositories, the last 12 months: 20,961 nodes and 80,642 edges, including 3,544 pull requests, 2,326 issues and 8,607 comments. Most of the 13 labels map straight onto GitHub (a Person REVIEWED a PullRequest, which FIXES an Issue). Two that we added carry most of this post. A Component is a directory prefix in a repo, such as src/udf in FalkorDB core, and every File sits IN_COMPONENT one. A Decision is a choice someone made in a PR or issue thread, linked to where it was made, who made it and the comments that show it. We extracted 534 of them.

Every record loads with UNWIND … MERGE on its key. A full ingest of the nine repos takes about 10 minutes, and running it a second time added 0 nodes and 0 edges.

Who knows the falkordb-py client code best?

The agent never writes Cypher. It picks from six fixed tools: experts_for, decision_trail, triage_candidates, area_history, related_work and catch_up. Here it used experts_for on falkordb, the package folder that is falkordb-py's only Component.

From the Component, the scoring query walks three hops: Component ← File ← PullRequest ← Person. Each merged PR that touched the code gives its author 3.0 points and each reviewer 2.0. A comment on such a PR is worth 0.5, scored by a second query on (:Person)-[:AUTHORED]->(:Comment)-[:ON]->(pr). Every contribution is multiplied by exp(-age/260 days), so a PR from about six months ago counts half. Bots and unmerged PRs never score. Here is the authored-and-reviewed half:

MATCH (c:Component)<-[:IN_COMPONENT]-(f:File)<-[:MODIFIES]-(pr:PullRequest)
WHERE c.key IN $components AND pr.merged_at IS NOT NULL
MATCH (p:Person)-[r:AUTHORED|REVIEWED]->(pr)
WHERE NOT p.is_bot
WITH p, pr, type(r) AS role, collect(f.key)[0] AS file, collect(c.key)[0] AS component,
     exp(-((timestamp() - pr.merged_at) / 86400000.0) / $decay) AS recency
RETURN p.login AS login, pr.key AS pr, pr.title AS title, pr.url AS url, pr.merged_at AS at,
       role, file, component,
       CASE role WHEN 'AUTHORED' THEN $authored ELSE $reviewed END * recency AS points

FalkorDB's timestamp() and exp() compute the decay inside the traversal, fresh on every ask. In design we considered precomputed EXPERT_IN edges and a PageRank job and dropped both: a stored score needs a job to keep it current, and it can't tell you which PRs earned it. Python only sums the points per person and keeps each person's three strongest PRs as evidence.

In the Path tab, two of the nine PRs behind gkorland's score read like this:

gkorland
├─ AUTHORED → falkordb-py#247  "chore(release): prepare v1.6.2"
│  └─ MODIFIES → falkordb/falkordb.py → IN_COMPONENT → falkordb
└─ REVIEWED → falkordb-py#236  "Fix async unix-socket connection crashing in Is_Cluster (#235)"
   └─ MODIFIES → falkordb/asyncio/cluster.py → IN_COMPONENT → falkordb

After gkorland come Naseem77, who fixed the execution plan parser and kwargs forwarding in Is_Cluster (#267), and swilly22, who wrote the UDF command propagation change. Then SantoshDhaladhuli, who fixed the async connection pool's kwargs mutation (#276), and AviAvni, a reviewer on the async connection fixes. The answer ends with a caveat we liked: the client code is spread across falkordb/falkordb.py, falkordb/cluster.py and falkordb/asyncio/cluster.py, "so the sync and async paths may have different experts."

That answer is cached as a node in the same graph, with a CITES edge to every node it cites, so the server returns it in 2 ms.

"Why did FalkorDB gate main on a merge queue?"

This one goes to decision_trail, which starts with FalkorDB's full-text index over Decision(summary, rationale). The index stems words, so "queues" matches "queue" without a synonym list.

We expected the merge-queue decision itself to rank first, but TF-IDF put another one above it. It ranked "Every CI job sets an explicit timeout-minutes, and merge queue entries are not cancelled" above "Gate main with a merge queue (merge_group) and run fork PR CI through it…". The shorter summary packed the query words into fewer terms, and TF-IDF rewards that. FalkorDB still does the finding and returns a pool of 50. Python re-ranks that pool by keyword coverage, the share of the question's keywords a Decision contains, and breaks ties with full-text score times extraction confidence. Over 534 Decisions that takes 8 to 20 ms.

From the winning Decision, one query walks the trail:

  • DECIDED_IN to FalkorDB/FalkorDB#2718, "ci: make PR CI fork-safe and gate main on a merge queue".
  • FIXES from that PR to FalkorDB/FalkorDB#2632, where CI couldn't run for external contributors because fork PRs get a read-only GITHUB_TOKEN.
  • MADE_BY to DvirDukhan, on 2026-09-17.
  • AFFECTS to the Components it changed, .github/workflows among them.

A second query follows EVIDENCED_BY to the two review comments on #2718 that the Decision rests on.

The rest of the answer comes from the Decision's rationale and alternatives properties. The alternative on the table was pull_request_target behind an approval label, which would hand a writable token to fork-authored code. A merge queue run gets a writable token natively. The path has 17 nodes and 19 edges.

The Decisions themselves come from an extraction pass over 1,177 candidate threads. A language model proposes them, and code checks each one against the graph: a person stays only if they took part in the thread, a Component only if the thread's PRs touched it, and evidence only if it's a real comment from that thread. Anything under 0.5 confidence is dropped.

"A new issue just came in. Where does it go?"

Take FalkorDB/FalkorDB#3357: "GRAPH.UDF LOAD REPLACE is not atomic: concurrent queries fail with Unknown function". A new issue has no edges yet, so triage reasons by analogy, along Issue ← FIXES – PullRequest – MODIFIES → File – IN_COMPONENT → Component.

Triaging FalkorDB/FalkorDB#3357 on the live app: hovering the issue lights the walk on the graph model, the result lists three similar issues and the src/udf Component, and clicking swilly22 on the map opens the pull requests they authored there

Full-text search on the title, re-ranked by coverage as above, finds three closed issues: #1529 "UDF getNeighbors incoming edges aren't returned", #2075 "Avoid QuickJS context creation when there are no UDF libraries" and #1265 "Implement UDF", fixed by #1530, #2076 and #1266. Those PRs changed files such as node_class.c and udf_ctx.c, and each file belongs to a Component. A PR that touched n Components gives each of them a vote scaled by 1/√n, so a focused fix counts for more per area than a sweeping refactor. Tests, CI, docs and packaging folders never vote; we added packaging to that list after docker and helm folders kept outvoting falkordb-py's real code.

src/udf wins, marked "moderate" because its votes cleared the 0.4 bar but not the 1.0 bar for "strong". experts_for on src/udf returns swilly22, with 8 merged PRs authored and 2 reviewed there. The whole triage uses 54 nodes and 49 edges.

The hop from issue to fixing PR is where our deploy went wrong. We had written it as one long MATCH, and FalkorDB planned it from a scan of every File node. On a laptop that took 0.8 s, under the 1 s default timeout, so we didn't notice. On Railway, a little after midnight on the night we deployed, it timed out. One WITH after the indexed Issue.key hop made the plan start from the index:

UNWIND $issues AS issue_key
MATCH (i:Issue {key: issue_key})<-[:FIXES]-(pr:PullRequest)
WITH issue_key, pr
MATCH (pr)-[:MODIFIES]->(f:File)-[:IN_COMPONENT]->(c:Component)
WHERE NOT f.is_doc
RETURN issue_key AS issue, pr.key AS pr, f.key AS file, c.key AS component, c.name AS name

Same rows, 5 ms. Our query notes now say: run GRAPH.EXPLAIN and look for Node By Label Scan on big labels.

The agent that acts

On FalkorDB's repos, triage is only a preview: it suggests the label area: src/udf, links swilly22's profile and drafts a reply, but posts nothing. On our sandbox repo it does all three.

When someone opens a sandbox issue, a signed webhook reaches the API. The issue is loaded into its own graph, org:aevion-sandbox, and triaged against org:falkordb. FalkorDB keeps many named graphs on one server, so the two stay apart while one process queries both. Sandbox issue #34 reports a server crash when a UDF library is replaced mid-query. The label and reply came 11 s after it was opened, and the reply led with FalkorDB/FalkorDB#3357:

Sandbox issue #34 on GitHub: a synthetic report that replacing a UDF library during queries crashes the server, the area: src/udf label added by Aevion, and its reply pointing to FalkorDB/FalkorDB#3357, the src/udf code and swilly22

The agent can write, so we locked it down in code, with tests:

  • Two locks on where it writes: a repo allow-list in the code and a GitHub token scoped to the sandbox repo only. A bug in one still can't touch FalkorDB's repos.
  • It never notifies anyone at FalkorDB. Experts appear as profile links, not @-mentions, and FalkorDB issues are written as code spans so they don't collect "mentioned this issue" events.
  • A final scan of the reply blocks the post if anything could still notify someone.
  • MERGE (t:TriageRun {issue_key: $key}) records each run, so the same issue is never answered twice, and only issues.opened starts a triage.

Every tool query also runs through GRAPH.RO_QUERY, so FalkorDB itself rejects a write from the read path.

What FalkorDB did for us

Architecture: visitors reach the Next.js app on Vercel, which calls a FastAPI service on Railway; FastAPI reads FalkorDB on Railway's private network and calls the LLM; GitHub sandbox webhooks come into FastAPI, and labels and comments go back to the sandbox only; an MCP server exposes the same tools; ingest and extraction write to FalkorDB

FalkorDB and our FastAPI service run on Railway, with FalkorDB reachable only on the private network. The web app is on Vercel. The six tools are shared by the HTTP API, the agent (at most four tool calls per answer) and an MCP server, so any MCP client can query the same graph.

The matched nodes and edges are the evidence we draw, and our tests check that every drawn edge exists in the graph. Full-text search lives in the same engine, so a question can start as a word search and carry on as a walk. On the full graph, locally, experts_for runs in 88 ms, decision_trail in 14 ms and triage_candidates in 75 ms.

We didn't re-ingest for production. We moved each graph with Redis DUMP/RESTORE over a temporary TCP proxy in about 9 s, indexes, Decisions and cached answers included, and both sides counted 20,961 nodes and 80,642 edges. The one gotcha we'd pass on: GRAPH.RO_QUERY on a graph that doesn't exist yet fails with "Invalid graph operation on empty key", so we create every graph at startup.

Next we want polling to keep the FalkorDB graph fresh (webhooks only cover our sandbox), and a second organisation in its own named graph.

Try it

The quickest test is the sandbox: open an issue there and count to ten.

來源:DEV Community · dev.to