Skip to content

symfonic.core.observability.node_names

node_names

Canonical LLM call-site discriminators (v7.15.0).

The framework stamps one of these strings on every LLMEndEvent.node_name so adopter cost dashboards, OTel spans, and budget trackers can attribute spend back to the originating subsystem.

NodeName is a :class:enum.StrEnum -- members compare equal to the underlying string, so existing code paths that write node_name="react" keep working verbatim. Use the enum members in new code for type-safety and refactor support.

This set is the framework's authored set. Adopters MAY emit custom node_name strings from their own bypass sites -- the framework does NOT validate LLMEndEvent.node_name against this set. Custom names propagate through OTel and metrics unchanged.

The non-validation contract is deliberate: v7.4.3's bypass sweep was explicitly designed around adopter-extensible discriminators (tenant-specific extractors, domain-specific summarisers). Validating at CallbackManager.on_llm_end would either (a) reject valid adopter extensions, or (b) silently drop spend attribution -- both worse than today.

Public API (re-exported from symfonic.core.observability):

from symfonic.core.observability import (
    NodeName,
    NODE_NAMES,
    NODE_LABELS,
    human_label,
)

NODE_LABELS module-attribute

NODE_LABELS: dict[str, str] = {
    NodeName.REACT.value: "React Loop",
    NodeName.METACOGNITION_CRITIC.value: "Metacognition Critic",
    NodeName.METACOGNITION_SKILL_GATE.value: "Skill-Gate Critic",
    NodeName.CONTEXT_WINDOW_SUMMARISER.value: "Context Window Summariser",
    NodeName.INSIGHT_EXTRACTOR.value: "Insight Extractor",
    NodeName.ENTITY_EXTRACTOR_LLM.value: "Entity Extractor (LLM)",
    NodeName.CONSOLIDATION_EXTRACTOR.value: "Consolidation Extractor",
    NodeName.PROCEDURAL_ROUTER.value: "Procedural Router",
    NodeName.PROCEDURAL_EXTRACTOR_LLM.value: "Procedural Extractor",
}

UI-friendly labels for the canonical node_name set.

Adopters wiring an admin dashboard can surface these directly instead of synthesising labels at the UI layer::

from symfonic.core.observability import human_label

row_label = human_label(event.node_name)  # round-trips unknown names

NODE_NAMES module-attribute

NODE_NAMES: frozenset[str] = frozenset(
    (n.value) for n in NodeName
)

Set of canonical node_name strings emitted by the framework.

Adopters can pre-populate dashboards, validate pricing-table coverage, or allocate per-node budget ceilings using this set::

from symfonic.core.observability import NODE_NAMES

for node_name in NODE_NAMES:
    budget_ceilings[node_name] = float(env.get(f"BUDGET_{node_name.upper()}", 0.50))

NodeName

Bases: StrEnum

Canonical LLM emission-site discriminators (v7.15.0).

human_label

human_label(node_name: str) -> str

Return the UI-friendly label for node_name.

Unknown names (adopter-extended sites) round-trip unchanged so dashboards do not lose attribution -- this is the explicit non-validation contract documented at module level.

Parameters:

Name Type Description Default
node_name str

The discriminator string from LLMEndEvent.node_name.

required

Returns:

Type Description
str

The matching label from :data:NODE_LABELS if present, otherwise

str

node_name itself.

Source code in src/symfonic/core/observability/node_names.py
def human_label(node_name: str) -> str:
    """Return the UI-friendly label for ``node_name``.

    Unknown names (adopter-extended sites) round-trip unchanged so
    dashboards do not lose attribution -- this is the explicit
    non-validation contract documented at module level.

    Args:
        node_name: The discriminator string from ``LLMEndEvent.node_name``.

    Returns:
        The matching label from :data:`NODE_LABELS` if present, otherwise
        ``node_name`` itself.
    """
    return NODE_LABELS.get(node_name, node_name)