Skip to content

symfonic.core.learning.refiner

refiner

InsightExtractor -- uses an LLM to identify business facts from conversation.

InsightExtractor

InsightExtractor(
    llm: Any = None,
    custom_prompt: str | None = None,
    min_confidence: float = 0.5,
    *,
    observability_hook: ObservabilityHook | None = None,
)

Extracts LearnedInsight objects from conversation text using an LLM.

The extraction prompt can be customized with domain-specific instructions via custom_prompt. The default prompt targets general business insights.

Graceful degradation: when no LLM is configured, extract() returns an empty list without raising.

Source code in src/symfonic/core/learning/refiner.py
def __init__(
    self,
    llm: Any = None,
    custom_prompt: str | None = None,
    min_confidence: float = 0.5,
    *,
    observability_hook: ObservabilityHook | None = None,
) -> None:
    self._llm = llm
    self._prompt_template = custom_prompt or _DEFAULT_PROMPT
    self._min_confidence = min_confidence
    # v8.3.2 observability-bug fix (G2): the insight-extractor LLM call
    # emitted ONLY on the merge-dependent callback manager and made ZERO
    # ``ObservabilityHook`` calls -- the same bug class as the v8.3.1
    # critic.  The engine now threads in the always-on hook
    # (``deps.require(ObservabilityHook)``); ``None`` resolves to a
    # ``NoOpObservabilityHook`` so the default is byte-identical
    # (additive only).  Attribution is MODEL-BASED (no node_name on the
    # hook signature); node-level disambiguation is deferred to Build 2.
    if observability_hook is None:
        from symfonic.core.observability.hooks import NoOpObservabilityHook

        observability_hook = NoOpObservabilityHook()
    self._observability_hook = observability_hook

extract async

extract(
    conversation: str,
    tenant_id: str = "",
    *,
    callback_manager: CallbackManager | None = None,
    run_id: str = "",
    model_name: str = "",
) -> list[LearnedInsight]

Extract insights from conversation text.

Parameters:

Name Type Description Default
conversation str

The raw conversation text to analyze.

required
tenant_id str

Tenant identifier to stamp on each insight.

''
callback_manager CallbackManager | None

v7.4.3 -- when present, fires on_llm_end(node_name="insight_extractor") after the LLM call (mirrors react.py:303-311). None preserves pre-7.4.3 behaviour.

None
run_id str

Engine run identifier for callback correlation.

''
model_name str

Optional model identifier to stamp on the LLMEndEvent. When empty, falls back to "insight_extractor".

''

Returns:

Type Description
list[LearnedInsight]

List of LearnedInsight objects above the min_confidence threshold.

list[LearnedInsight]

Returns empty list when no LLM is configured or the conversation

list[LearnedInsight]

is blank (never raises).

Source code in src/symfonic/core/learning/refiner.py
async def extract(
    self,
    conversation: str,
    tenant_id: str = "",
    *,
    callback_manager: CallbackManager | None = None,
    run_id: str = "",
    model_name: str = "",
) -> list[LearnedInsight]:
    """Extract insights from conversation text.

    Args:
        conversation: The raw conversation text to analyze.
        tenant_id: Tenant identifier to stamp on each insight.
        callback_manager: v7.4.3 -- when present, fires
            ``on_llm_end(node_name="insight_extractor")`` after the
            LLM call (mirrors react.py:303-311).  ``None`` preserves
            pre-7.4.3 behaviour.
        run_id: Engine run identifier for callback correlation.
        model_name: Optional model identifier to stamp on the
            LLMEndEvent.  When empty, falls back to ``"insight_extractor"``.

    Returns:
        List of LearnedInsight objects above the min_confidence threshold.
        Returns empty list when no LLM is configured or the conversation
        is blank (never raises).
    """
    if not self._llm or not conversation.strip():
        return []

    prompt = self._prompt_template.format(conversation=conversation)

    try:
        raw_text = await self._invoke_llm(
            prompt,
            callback_manager=callback_manager,
            run_id=run_id,
            model_name=model_name or "insight_extractor",
        )
        return self._parse_insights(raw_text, tenant_id)
    except Exception:
        logger.warning("Insight extraction failed", exc_info=True)
        return []