Skip to content

feat: add deterministic Python call linking during graph merge#580

Open
ParitKansal wants to merge 1 commit into
Egonex-AI:mainfrom
ParitKansal:feature/python-call-linker
Open

feat: add deterministic Python call linking during graph merge#580
ParitKansal wants to merge 1 commit into
Egonex-AI:mainfrom
ParitKansal:feature/python-call-linker

Conversation

@ParitKansal

@ParitKansal ParitKansal commented Jul 15, 2026

Copy link
Copy Markdown

Why this is needed

Understand Anything already extracts Python call sites (via tree-sitter) and builds a knowledge graph, but many obvious, structurally certain function-to-function relationships never become calls edges.

Today those edges mostly depend on the LLM file-analyzer noticing and emitting them. In practice that means:

  • Important call chains are often missing from the graph (especially across files).
  • Answers that rely on the graph (“what calls this?”, “how does this pipeline connect?”) are incomplete even when the source is unambiguous.
  • The same project can get different call coverage across runs, because LLM emission is not deterministic.

Meanwhile the hard part of the data is already available: call-graph records and import bindings from structural extraction. We just were not turning unambiguous cases into graph edges during assembly.

This PR fills that gap for Python — only where the callee can be resolved uniquely — so the graph becomes a more reliable map of real call flow without inventing edges the linker cannot prove.

Problem

Two concrete gaps made deterministic linking impossible today:

  1. Import aliases were dropped after extraction.
    Patterns like from pkg import run as execute or import pkg.helpers as helpers lost the local→original binding, so later stages could not resolve call sites that use the alias.

  2. No assembly-time call linker.
    Extraction produced callGraph data, but merge-batch-graphs.py never converted unambiguous caller/callee pairs into calls edges. Coverage depended on the LLM.

Solution

  1. Preserve import alias metadata in Python structural extraction / extract-structure results (aliases?: Record<string, string> — local binding → original name). Non-aliased imports stay unchanged.

  2. Add a deterministic Python call linker in merge-batch-graphs.py that runs after node deduplication and before edge dedup / tested_by linking.

It creates a calls edge only when:

  • language is Python;
  • exactly one caller function node exists in the caller file;
  • exactly one callee function node resolves via:
    • same-file function,
    • from module import name,
    • aliased import (as),
    • module-qualified call (import mod as m; m.fn() / import pkg.mod; pkg.mod.fn()),
    • from-import submodule call (from package import helper; helper.fn());
  • the edge is not already present.

Safety rule: prefer a missing edge over a wrong one. Skipped (no edge) when:

  • caller/callee missing or ambiguous (multiple candidates);
  • import rebinding order cannot be proven (missing line numbers, same-line import/call);
  • active binding is a later import that shadows an earlier one (do not fall back to the shadowed target);
  • imported name is a function in the parent module but the call looks like submodule attribute access;
  • dynamic / method-dispatch style calls (obj.method()), builtins, wildcards, etc.

This mirrors the existing philosophy of the deterministic tested_by linker: structural truth first, LLM for semantic judgment.

What this does not try to do

  • Method dispatch / instance method resolution
  • Dynamic imports / reflection
  • Whole-program type inference
  • Changing the knowledge-graph JSON schema
  • Adding the same linker for other languages (Python-only on purpose; other languages keep current behavior)

Changes

Area Files
Types packages/core/src/types.ts — optional aliases on import records
Extractor python-extractor.ts — emit alias maps (null-prototype safe for keys like __proto__)
Extract pipeline extract-structure-result.mjs — forward imports + aliases into extraction artifacts
Linker merge-batch-graphs.pylink_python_calls(), module path resolution, import rebinding, artifact loader, merge report counters
Tests extractor + extract-structure Vitest; expanded test_merge_batch_graphs.py

Merge report gains a section like:

Python call linker:
   N × calls edges produced
   M × unresolved or ambiguous calls skipped

How to review

  1. Skim link_python_calls / _active_python_import / _resolve_imported_python_targets in merge-batch-graphs.py — especially the “skip when ambiguous” paths.
  2. Confirm we never invent edges when multiple function nodes match.
  3. Confirm import rebinding uses the latest binding before the call line only.
  4. Confirm non-Python batches and missing extraction artifacts are non-fatal.

Test plan

Locally verified against the same commands as CI:

  • pnpm lint
  • pnpm --filter @understand-anything/core build
  • pnpm --filter @understand-anything/skill build
  • pnpm --filter understand-anything-viewer build
  • pnpm --filter @understand-anything/core test
  • pnpm test
  • python3 -m unittest tests.skill.understand.test_merge_batch_graphs tests.skill.understand.test_merge_subdomain_graphs tests.skill.knowledge.test_parse_knowledge_base -v
  • Focused coverage for: same-file calls, direct imports, aliases, module-qualified calls, from-import submodule calls, rebinding / shadowing, ambiguous targets, duplicate suppression, empty names, __proto__ alias keys, artifact load failures

Suggested reviewer check:

  • Run /understand --full on any small/medium Python repo and confirm the merge report includes Python call linker: counts, and that a few known same-file / imported calls appear as calls edges without dangling endpoints.

Compatibility

  • No new dependencies
  • No schema version bump
  • Existing LLM-emitted calls edges are kept; linker only adds missing unique ones and dedupes
  • Other languages unaffected

Happy to adjust naming, weights, or report wording if maintainers prefer a different convention.

Preserve import alias metadata in Python extraction and link unambiguous
same-file and imported function calls when assembling the knowledge graph.
Also handles from-import submodule calls and skips ambiguous bindings.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant