Skip to content

fix: strip json_object fence on streaming (aggregated) path#13

Closed
Abhinavexist wants to merge 1 commit into
mainfrom
fix/stream-json-object-fence
Closed

fix: strip json_object fence on streaming (aggregated) path#13
Abhinavexist wants to merge 1 commit into
mainfrom
fix/stream-json-object-fence

Conversation

@Abhinavexist

Copy link
Copy Markdown
Collaborator

Found while auditing the SDK against server PR InterfazeAI/interfaze#218 (OpenAI 1:1 spec-compat).

Problem

Interfaze wraps response_format: {"type": "json_object"} output in a ```json fence. Non-stream create() unwraps it (to_interfaze's strip flag), but the streaming path did not:

  • stream() discarded the strip flag (kw, _ = self._kwargs(...)), and
  • _State.build() never called strip_json_fence,

so get_final_completion() returned the raw fenced string for json_object streams — inconsistent with the non-stream path.

Verified: streamed ```json\n{"city":"Tokyo"}\n``` with response_format={"type":"json_object"}get_final_completion() content was '```json\n{"city": "Tokyo"}\n```' (leak); non-stream returned '{"city": "Tokyo"}'.

Fix

Thread the strip flag from stream() into InterfazeStream/AsyncInterfazeStream and apply strip_json_fence in _State.build(). Scoped to the aggregated result (get_final_completion()) — the live-delta path is intentionally not given a chunk-boundary fence stripper (incrementally-fenced JSON isn't a real usage; buffering a whole-message fence would defeat streaming). Moved strip_json_fence into _stream.py (shared with _chat) to avoid a circular import.

This is a pre-existing SDK inconsistency and is deploy-independent: pre-#218 it fixes the current leak; once #218 strips the fence server-side, our strip becomes a harmless no-op on already-bare JSON.

Tests

3 new: json_object stream → fence stripped + json.loads works; non-json_object stream with a fenced body → fence preserved (only strip in json_object mode); async json_object → stripped. Suite: 27 passing; ruff + mypy --strict clean.

Non-stream create() unwraps the ```json fence Interfaze wraps json_object
content in, but streaming didn't: stream() discarded the strip flag and
_State.build() never unwrapped it, so get_final_completion() leaked the
fence on json_object streams.

Thread the flag from stream() into InterfazeStream/AsyncInterfazeStream and
apply strip_json_fence in build(); scoped to the aggregated result (the live
delta path is untouched). Moved strip_json_fence into _stream.py (shared with
_chat) to avoid a circular import. Forward-compatible with the server-side fix
in InterfazeAI/interfaze#218.
@Abhinavexist

Copy link
Copy Markdown
Collaborator Author

Superseded by #16, which consolidates the #218 spec-compat reconciliation (this fence fix + the max_completion_tokens/models alignment).

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant