Skip to content

ConversationDegenerationMetric silently drops punctuation/whitespace-only assistant turns instead of scoring them as degenerate #8519

Description

@shaurya416

sdks/python/src/opik/evaluation/metrics/conversation/heuristics/degeneration/metric.py:100-104

prev_tokens: Optional[List[str]] = None
for content in assistant_turns:
    tokens = _tokenize(content)
    if not tokens:
        continue

_tokenize is re.findall(r"\b\w+\b", text.lower()), which returns [] for any assistant turn whose content is punctuation-only ("...", "???", "!!!") or whitespace-only. That turn is then skipped completely: it contributes nothing to degeneracy_scores, per_turn_metadata, peak_score, or average_score, and it doesn't even update prev_tokens for the next real turn's overlap calculation. It is as if the turn never happened.

The class docstring and the published docs both describe the opposite behaviour. The docstring (metric.py:29-37) says the metric "inspects each assistant turn" so callers "can quickly flag sections where the assistant got stuck or stopped being helpful," and conversation_threads_metrics.mdx:39 calls it "a lightweight guard against models that fall into loops or short-circuit the dialogue." A punctuation-only reply is the textbook case of an assistant that stopped being helpful / short-circuited the dialogue — the one case this guard exists for — yet it is invisible to the scoring loop.

What happens (reproduced on the real module)

ConversationDegenerationMetric.score() (metric.py:100-104) tokenizes each assistant turn with _tokenize (word-boundary regex) and does if not tokens: continue for any turn whose content is punctuation- or whitespace-only. Such a turn does not increment degeneracy_scores, per_turn_metadata, or prev_tokens.

A conversation that degenerates partway through, e.g. one real reply followed by several '...'/'???'/'!!!' turns, therefore reports a near-zero peak/average score computed only over the surviving real turns. This contradicts the class docstring ('inspects each assistant turn... can quickly flag sections where the assistant got stuck or stopped being helpful') and the public docs ('a lightweight guard against models that fall into loops or short-circuit the dialogue', conversation_threads_metrics.mdx:39).

Nothing else in the code compensates:

  • No caller in evaluate_threads/score_statistics/evaluation_result or the ConversationThreadMetric base class filters, pads, or otherwise guards against this.
  • No existing test exercises the mixed real+garbage-turn case.
  • The all-garbage path (MetricComputationError when every turn tokenizes empty) shows the codebase already treats token-less content as a distinct, meaningful case elsewhere, just not consistently.

Fix: either score a token-less turn as maximal degeneration risk, or at minimum record the count of excluded turns in ScoreResult.metadata so threshold consumers aren't silently blind to them.

Happy to open the PR.

Activity

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions