Skip to content

fix(realtime): keep item ids within OpenAI's 32-char limit - #7958

Merged
russellmartin-livekit merged 1 commit into
mainfrom
rm/realtime-shorten-item-ids
Oct 28, 2026
Merged

russellmartin-livekit merged 1 commit into
mainfrom
rm/realtime-shorten-item-ids

Conversation

@russellmartin-livekit

Copy link
Copy Markdown
Contributor

The OpenAI Realtime API and Azure OpenAI reject item ids longer than 32 characters (string_above_max_length). Agents that replay history under their own ids, such as UUIDs or item_<uuid>_N, lose that item, and every item after it fails with item_create_invalid_previous_item_id because it is anchored to the rejected one. The session only logs "rejected part of a chat context update" and retries on every turn, so the model runs with no history.

Now the session shortens over-long item ids where events go out, with the same sha256 hash it already uses for call_id, and restores the original ids on every server event it receives. The chat context, the remote chat context, and the create and delete waiters keep the ids the agent chose. Ids of 32 characters or fewer go out unchanged. The reconnection replay, which writes to the socket directly, is shortened too.

Checked live by replaying history whose first item, with a 36-character id, holds a code word, then asking for the word. Without the fix, the model on direct OpenAI, direct Azure, and LiveKit Inference (Azure and OpenAI) had none of the history and only greeted. With the fix all four, plus xAI through LiveKit Inference, answered with the word, and deleting the item by its long id worked.

Initial prompt and agent context

Model: Claude Opus 5.5

Lets do the SDK fix, thats the correct surface right?

Investigation of Azure realtime sessions rejecting 304 items with ids over 32 characters across 10 projects, plus 121 knock-on "previous item does not exist" errors.

@russellmartin-livekit russellmartin-livekit self-assigned this Oct 19, 2026
@russellmartin-livekit
russellmartin-livekit marked this pull request as ready for review October 19, 2026 18:50
@russellmartin-livekit
russellmartin-livekit requested a review from a team as a code owner October 19, 2026 18:50

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

Comment on lines +153 to +158
def _shorten(self, id: Any) -> Any:
if not isinstance(id, str) or len(id) <= _MAX_ID_LEN:
return id
short = _shorten_id(id)
self._original[short] = id
return short

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Distinct history items share one wire ID

When a 32-character item ID equals _shorten_id of a longer ID, both items use the same wire ID. One create fails as a duplicate, and _original can attribute replies to the wrong item.

Learn more

The item-ID map translates long local IDs to 32-character wire IDs. It leaves existing 32-character IDs unchanged, but _shorten does not reserve those IDs when selecting a hash. The provider therefore cannot distinguish a long ID from a valid short ID matching its hash. Server replies also use the map, so the short ID can be restored as the wrong local ID. update_chat_ctx waits for separate create confirmations, and the added-item handler looks up the restored ID.

Example: Give one item ID long_item_id_abcdefghijklmnopqrstuvwxyz and another item ID equal to _shorten_id('long_item_id_abcdefghijklmnopqrstuvwxyz'). The first ID is hashed, the second stays unchanged, and both creates target the same provider ID instead of creating two history items.

Recommended fix: Maintain a collision-free mapping across all IDs used in a session, including conforming IDs. Reserve or preflight the full history before assigning wire IDs; ensure every wire ID maps to exactly one local ID while keeping IDs of 32 characters or fewer unchanged where possible.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

self._original[short] = id
return short

def shorten(self, event: dict[str, Any]) -> None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 New ID translation methods lack documentation

The new _shorten, shorten, and restore methods have no docstrings. Repository rules require documentation for every new method.

Learn more

Example: API documentation generated for _ItemIdMap.shorten cannot describe which event fields it rewrites or that it modifies events in place.

Recommended fix: Add concise docstrings to _shorten, shorten, and restore documenting their input, in-place changes, and mapping behavior.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

@russellmartin-livekit
russellmartin-livekit merged commit 0737686 into main Oct 28, 2026
28 checks passed
@russellmartin-livekit
russellmartin-livekit deleted the rm/realtime-shorten-item-ids branch October 28, 2026 21:42
@tinalenguyen tinalenguyen mentioned this pull request Nov 12, 2026
7 tasks done
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.

2 participants