Skip to content

Call detail shows duplicate and empty provider chips #279

Description

@mahimairaja

Summary

The Call detail modal's PROVIDERS row shows the same provider more than once, plus an empty chip. Reported on Discord with a screenshot showing six chips for one call:

Cartesia Deepgram Gemini cartesia deepgram google

That is three real providers rendered as six chips, plus one blank.

Not a rendering bug

Sessions.tsx:358 maps the array one-to-one with no transformation:

session.providers.map((p) => (
  <span key={p} className="neo-badge neo-badge--black">{p}</span>
))

So the duplicates are in the data. The read is a raw passthrough too, repository/session_repository.py:143:

SELECT DISTINCT provider FROM requests
WHERE session_id = :session_id ORDER BY provider

DISTINCT is case-sensitive on TEXT, so two spellings of one provider survive as two rows.

Three distinct causes

1. Empty chip: EOU rows write an empty provider.

inference/session/capture.py:681 writes end-of-utterance timing rows with provider="" and model_id="". replay_capture_middleware.py:170 does the same for state snapshots. The DISTINCT query has no WHERE provider != '', so the empty string comes back and renders as a blank badge.

This matches the screenshot exactly: the same call shows EOU | 8 requests | $0.000000 in the per-modality breakdown.

2. Case duplicates: the pipecat fallback never lowercases, despite saying it does.

inference/pipecat/observer.py:100-121. The docstring states it will "lowercase the leading alpha run as a weak provider hint". The code does not:

name = getattr(processor, "name", "") or ""
# Processor names look like "OpenAILLMService#0"; strip the "#N" suffix and
# lowercase the leading alpha run as a weak provider hint.
head = name.split("#", 1)[0]
return head or ""

head is returned verbatim, so a processor named CartesiaTTSService#0 yields CartesiaTTSService, and any branded name yields a capitalized provider. Meanwhile the primary path returns the module segment (pipecat.services.cartesia.tts to cartesia), lowercase. Both write the same column.

session/capture.py:229-231 has the same shape of gap: it falls back to getattr(component, "provider"/"_provider") and returns whatever the plugin exposes, with no normalization.

3. Alias duplicates: Gemini and google are the same provider.

The LiveKit module segment gives google (livekit.plugins.google). Something else reports Gemini. There is no alias map anywhere, so both persist as separate providers. This one is not fixed by lowercasing.

Impact

Cosmetic in the modal, but the provider column is not only used for display. Anything that groups or filters by provider (rate rules, per-provider cost rollups, reconciliation) sees Cartesia and cartesia as two providers, so per-provider totals split across spellings.

Suggested fix

Normalize at the write site, not the read site, so already-stored rows and future rows agree:

  1. Lowercase provider in both derivations (capture.py:_provider_name, observer.py:_provider_from_module), which is what the pipecat docstring already promises.
  2. Add a small alias map for branded names to canonical ids (gemini to google, and whatever else turns up).
  3. Filter empties out of the read: WHERE provider != '', or stop writing provider="" on EOU/state rows and use a sentinel the read excludes.
  4. A migration or one-off normalization pass for existing rows, otherwise old calls keep showing both.

Worth a test asserting that a session whose requests contain mixed spellings returns a single chip per provider.

Reproduce

Run an agent with the pipecat path where a service falls back to the name derivation, or any session mixing a module-derived provider with an attribute-derived one, then open Call detail.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions