hankcs/HanLP · error · InvalidTagSequence

" ".join(tag_sequence)

Error message

" ".join(tag_sequence)

What it means

bio_tags_to_spans requires every tag in the sequence to start with B, I or O. A tag like 'E-X', 'S-X', '' (empty), or a raw class name without a prefix raises InvalidTagSequence with the whole sequence.

Source

Thrown at hanlp/utils/span_util.py:348

        A list of string class labels `excluding` the bio tag
        which should be ignored when extracting spans.

    # Returns

    spans : `List[TypedStringSpan]`
        The typed, extracted spans from the sequence, in the format (label, (span_start, span_end)).
        Note that the label `does not` contain any BIO tag prefixes.
    """
    classes_to_ignore = classes_to_ignore or []
    spans: Set[Tuple[str, Tuple[int, int]]] = set()
    span_start = 0
    span_end = 0
    active_conll_tag = None
    for index, string_tag in enumerate(tag_sequence):
        # Actual BIO tag.
        bio_tag = string_tag[0]
        if bio_tag not in ["B", "I", "O"]:
            raise InvalidTagSequence(tag_sequence)
        conll_tag = string_tag[2:]
        if bio_tag == "O" or conll_tag in classes_to_ignore:
            # The span has ended.
            if active_conll_tag is not None:
                spans.add((active_conll_tag, (span_start, span_end)))
            active_conll_tag = None
            # We don't care about tags we are
            # told to ignore, so we do nothing.
            continue
        elif bio_tag == "B":
            # We are entering a new span; reset indices
            # and active tag to new span.
            if active_conll_tag is not None:
                spans.add((active_conll_tag, (span_start, span_end)))
            active_conll_tag = conll_tag
            span_start = index
            span_end = index
        elif bio_tag == "I" and conll_tag == active_conll_tag:

View on GitHub (pinned to ddb1299bdd)

Solutions

  1. Convert IOBES to BIO first: map S-X→B-X, E-X→I-X (and keep B/I), or use an iobes-normalizing utility, before calling bio_tags_to_spans.
  2. If your model outputs BMES/IOBES, use the matching evaluator (e.g. evaluate_iobes) instead of the BIO-based one.
  3. Sanity-check the first character of each predicted tag in your decoding step.

Example fix

# before
spans = bio_tags_to_spans(pred_tags)  # tags like ['S-PER','E-PER']
# after
fixed = [('B' + t[1:] if t[0] == 'S' else 'I' + t[1:] if t[0] == 'E' else t) for t in pred_tags]
spans = bio_tags_to_spans(fixed)
Defensive patterns

Strategy: validation

Validate before calling

assert all(t and t[0] in 'BIO' for t in tags), 'tags must be BIO-encoded'

Type guard

def is_bio_sequence(tags):
    return all(isinstance(t, str) and len(t) >= 2 and t[0] in 'BIO' and t[1] == '-' for t in tags)

Prevention

When it happens

Trigger: Calling bio_tags_to_spans (via predict_data or evaluate_iob2) on tags in IOBES/BMES encoding (which contain S-/E-), or on malformed tags missing the BIO prefix.

Common situations: Mixing pipelines: feeding IOBES predictions from a neural model into an evaluator that expects IOB2; tags read from file without prefixes; downstream code stripping the B-/I- markers during post-processing.

Related errors


AI-assisted analysis of hankcs/HanLP@ddb1299bdd (2026-08-27). Data as JSON: /api/errors/fd973ca6fe5bc5d1. Report an issue: GitHub.