How Edge Weights Are Determined in the Knowledge Graph: A Deep Dive into Understand-Anything
Edge weights in the Understand-Anything knowledge graph are determined by relationship type during graph construction (containment=1, imports=0.7, calls=0.8) and normalized during schema validation to ensure they fall within the 0-1 range.
The Understand-Anything repository builds a semantic knowledge graph to model codebases, where relationships between entities are stored as GraphEdge objects with numeric weights expressing confidence levels. Every edge carries a weight property normalized between 0 and 1, established through a two-stage pipeline involving the GraphBuilder and schema validation logic.
Relationship-Based Weight Assignment in GraphBuilder
During graph construction, the GraphBuilder class assigns type-specific weights that reflect the semantic strength of each relationship. These defaults are hard-coded in understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts based on the certainty of the underlying dependency.
Containment Edges (Weight = 1)
Containment edges represent structural ownership where a file definitively contains a function or class. Because this relationship is absolute, the builder assigns a weight of 1:
this.edges.push({
source: fileId,
target: funcId,
type: "contains",
direction: "forward",
weight: 1
});
Import Edges (Weight = 0.7)
Import edges (type: "imports") indicate module dependencies with moderate confidence. The builder assigns 0.7 to reflect that while the dependency exists, it may be conditional or tree-shaken:
this.edges.push({
source: `file:${fromFile}`,
target: `file:${toFile}`,
type: "imports",
direction: "forward",
weight: 0.7,
});
Call Edges (Weight = 0.8)
Call edges (type: "calls") represent function invocations with higher confidence than imports but less than physical containment. The builder assigns 0.8:
this.edges.push({
source: `function:${callerFile}:${callerFunc}`,
target: `function:${calleeFile}:${calleeFunc}`,
type: "calls",
direction: "forward",
weight: 0.8,
});
Schema Validation and Normalization
After construction, the GraphSchema in understand-anything-plugin/packages/core/src/schema.ts sanitizes every edge weight to guarantee validity. This validation layer ensures that downstream algorithms in edgeAggregation.ts receive properly bounded numeric values.
Handling Missing Weights
If the weight field is undefined or null, the schema injects a default value of 0.5 and records an auto-correction issue:
if (e.weight === undefined || e.weight === null) {
e.weight = 0.5;
}
Type Coercion and Clamping
The schema handles malformed inputs through progressive normalization:
- String parsing – If the weight arrives as a string, the validator attempts
parseFloat()coercion, falling back to0.5for non-numeric strings. - Range clamping – Any numeric value outside the
[0, 1]interval is clamped to the nearest bound usingMath.max(0, Math.min(1, e.weight)).
// Schema – string → number
else if (typeof e.weight === "string") {
const parsed = parseFloat(e.weight);
e.weight = isNaN(parsed) ? 0.5 : parsed;
}
// Schema – clamp to [0,1]
if (typeof e.weight === "number" && (e.weight < 0 || e.weight > 1)) {
e.weight = Math.max(0, Math.min(1, e.weight));
}
Practical Code Examples
Building a Graph with Automatic Weights
The GraphBuilder automatically applies the type-specific weights when constructing the knowledge graph:
import { GraphBuilder } from "@understand-anything/core";
const builder = new GraphBuilder();
// Add a file node and its functions (contains → weight = 1)
builder.addFileNode("src/service.ts");
// Record that service.ts imports utils.ts (imports → weight = 0.7)
builder.addImportEdge("src/service.ts", "src/utils.ts");
// Record that service.ts calls utils.calculate (calls → weight = 0.8)
builder.addCallEdge("src/service.ts", "main", "src/utils.ts", "calculate");
// Final graph with normalized edges
const graph = builder.build();
Handling Out-of-Range Weights
The schema validator automatically corrects invalid weights during parsing:
import { parseGraph } from "@understand-anything/core";
const raw = {
nodes: [{ id: "file:a.ts", type: "file", name: "a.ts", summary: "", tags: [], complexity: "simple" }],
edges: [{
source: "file:a.ts",
target: "file:b.ts",
type: "imports",
direction: "forward",
weight: 1.5
}]
};
const { data, issues } = parseGraph(raw);
console.log(data.edges[0].weight); // → 1 (clamped)
console.log(issues[0].category); // → "out-of-range"
Key Files in the Architecture
understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts– Constructs graph edges and assigns initial type-specific weights.understand-anything-plugin/packages/core/src/types.ts– Declares theGraphEdgeinterface with the mandatoryweight: numberfield.understand-anything-plugin/packages/core/src/schema.ts– Validates, coerces, and clamps edge weights to the [0, 1] range.understand-anything-plugin/packages/dashboard/src/utils/edgeAggregation.ts– Consumes normalized weights for visualization aggregation.
Summary
- GraphBuilder assigns hard-coded weights based on relationship semantics: containment (1), calls (0.8), and imports (0.7).
- GraphSchema ensures data integrity by defaulting missing weights to 0.5, parsing string values, and clamping out-of-range numbers to [0, 1].
- The weight system spans
graph-builder.ts(creation) andschema.ts(validation) before being utilized by visualization utilities. - All
GraphEdgeobjects are defined intypes.tswith a mandatory numeric weight property.
Frequently Asked Questions
What is the default edge weight if not specified?
If the weight property is missing, null, or undefined, the schema validator in schema.ts automatically assigns a default value of 0.5 and flags the record with an auto-correction issue. This ensures that downstream algorithms always receive a valid numeric weight even when the input data is incomplete.
What happens if an edge weight exceeds the 0-1 range?
The schema validation logic clamps any numeric weight outside the [0, 1] interval to the nearest valid bound. Values greater than 1 are reduced to 1, and negative values are raised to 0 using Math.max(0, Math.min(1, e.weight)), ensuring all weights remain valid confidence scores.
Why do different relationship types have different default weights?
The weight values reflect semantic certainty: containment relationships (1.0) represent physical file structure and are absolute; call relationships (0.8) indicate runtime behavior with high but not total certainty; and import relationships (0.7) acknowledge module dependencies that might be conditional or unused, warranting lower confidence.
Where is the weight property defined in the type system?
The weight field is declared in understand-anything-plugin/packages/core/src/types.ts as part of the GraphEdge interface, specified as a required number type. This type definition enforces that all edge objects must include a weight property, though the schema validator ensures backward compatibility with legacy data by filling in defaults when the field is absent.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →