langchain-ai/langchain · error · TypeError

SyncTextProjection requires a string final value

Error message

SyncTextProjection requires a string final value

What it means

`TypeError` from `SyncTextProjection.complete`: the final accumulated value supplied by the producer is not a `str` (e.g. a message object, `None`, or bytes). `complete(final_value)` closes the projection with the definitive text; passing anything else breaks every consumer (`get`, `__str__`, iteration), so it is rejected immediately.

Source

Thrown at libs/core/langchain_core/language_models/chat_model_stream.py:303

    Adds typed string producers and consumers, plus `__str__`, `__bool__`,
    `__repr__` for ergonomic use with `.text` and `.reasoning` projections.
    """

    __slots__ = ()

    def push(self, delta: str) -> None:
        """Append a text delta."""
        if not isinstance(cast("Any", delta), str):
            msg = "SyncTextProjection requires a string delta"
            raise TypeError(msg)
        super().push(delta)

    def complete(self, final_value: str) -> None:
        """Set the final accumulated text and mark the projection as done."""
        if not isinstance(cast("Any", final_value), str):
            msg = "SyncTextProjection requires a string final value"
            raise TypeError(msg)
        super().complete(final_value)

    def __iter__(self) -> Iterator[str]:
        """Yield text deltas, raising if a producer supplied a non-string value."""
        for delta in super().__iter__():
            if not isinstance(delta, str):
                msg = "SyncTextProjection received a non-string delta"
                raise TypeError(msg)
            yield delta

    def get(self) -> str:
        """Drain and return the full accumulated string, or empty if unfinished."""
        value = super().get()
        if value is None:
            return ""
        if not isinstance(value, str):
            msg = "SyncTextProjection received a non-string final value"
            raise TypeError(msg)

View on GitHub (pinned to e32fa9a52e)

Solutions

  1. Always complete with a string; for empty streams use `complete("")`.
  2. Convert explicitly at the completion site: `projection.complete(msg.content if isinstance(msg.content, str) else "")`.
  3. Audit producers that branch on stream termination to ensure every path passes a string.

Example fix

# before
projection.complete(final_message)  # AIMessage, not str

# after
projection.complete(final_message.text or "")
Defensive patterns

Strategy: type-guard

Validate before calling

final = final_value if isinstance(final_value, str) else ""
projection.complete(final)

Type guard

def is_final_text(v: object) -> bool:
    """True when v is acceptable as a SyncTextProjection final value."""
    return isinstance(v, str)

Try / catch

null

Prevention

When it happens

Trigger: Producer code calling `projection.complete(final_text)` with a non-string — e.g. `complete(message)` where `message` is an `AIMessage`, or `complete(None)` when a stream produced no final text.

Common situations: Custom model adapters completing `.text` with the full message object instead of `message.content`; defensive `complete(result or None)` patterns where `None` flows in when the stream ended empty; refactors from dict-based buffers to the typed projection.

Related errors


AI-assisted analysis of langchain-ai/langchain@e32fa9a52e (2026-08-14). Data as JSON: /api/errors/30c0c6b10270416f. Report an issue: GitHub.