sgl-project/sglang · error · ValueError

Invalid external ngram corpus record at line {line_no}: expe

Error message

Invalid external ngram corpus record at line {line_no}: expected a JSON string.

What it means

Each non-blank line of the external ngram corpus must parse to a JSON string (one document per line). If json.loads succeeds but yields a non-str (dict, list, number), this ValueError is raised with the line number.

Source

Thrown at python/sglang/srt/speculative/cpp_ngram/external_corpus.py:39

    if max_tokens <= 0:
        raise ValueError("External ngram corpus max tokens must be positive.")

    total_tokens = 0
    has_previous_doc = False
    with corpus_path.open("r", encoding="utf-8") as f:
        for line_no, line in enumerate(f, start=1):
            if not line.strip():
                continue

            try:
                record = json.loads(line)
            except json.JSONDecodeError as e:
                raise ValueError(
                    f"Invalid JSON in external ngram corpus at line {line_no}: {e.msg}"
                ) from e

            if not isinstance(record, str):
                raise ValueError(
                    "Invalid external ngram corpus record at line "
                    f"{line_no}: expected a JSON string."
                )

            token_ids = list(tokenizer.encode(record, add_special_tokens=False))
            if not token_ids:
                continue

            separator_cost = 1 if has_previous_doc else 0
            next_total_tokens = total_tokens + separator_cost + len(token_ids)
            if next_total_tokens > max_tokens:
                raise ValueError(
                    "External ngram corpus exceeds the configured token limit "
                    f"({max_tokens}) at line {line_no} after loading "
                    f"{total_tokens} tokens."
                )
            total_tokens = next_total_tokens

View on GitHub (pinned to 0132848349)

Solutions

  1. Rewrite the corpus to one JSON string per line (json.dumps(doc) for doc in docs)
  2. Or unwrap the field first: json.dumps(json.loads(line)["text"])

Example fix

# before
{"text": "hello world"}
# after
"hello world"
Defensive patterns

Strategy: validation

Validate before calling

import json
def is_string_jsonl(path):
    with open(path) as f:
        return all(not line.strip() or isinstance(json.loads(line), str) for line in f)
assert is_string_jsonl(path)

Type guard

def is_corpus_record(line: str) -> bool:
    try:
        return isinstance(json.loads(line), str)
    except json.JSONDecodeError:
        return False

Prevention

When it happens

Trigger: A corpus file where lines are JSON objects like {"text": "..."} or JSON arrays instead of plain JSON-encoded strings.

Common situations: Corpus exported in a records format (JSONL with objects) rather than one JSON string per line; mixing token-array corpora with text corpora.

Related errors


AI-assisted analysis of sgl-project/sglang@0132848349 (2026-08-28). Data as JSON: /api/errors/00ec3c696c45f1a1. Report an issue: GitHub.