From 5dc6315d566b77d1e4ae7cb043e80a79ced88728 Mon Sep 17 00:00:00 2001 From: William Brown Date: Mon, 26 Oct 2026 05:18:44 -0700 Subject: [PATCH] fix(deps): depend on oss-harmony, the harmony build vLLM uses openai-harmony and oss-harmony both install the openai_harmony module. vLLM >= 0.32 requires oss-harmony while renderers required openai-harmony, so environments with both held two packages writing the same files, and whichever installed last supplied the native code. oss-harmony renders commentary headers channel-first (<|channel|>commentary to=functions.x). Which order gpt-oss handles best in history is contested, so the renderer keeps the recipient-first header it has always produced for tool calls and tool results. Rendered tokens are unchanged, and identical on either harmony build. The parity oracle is harmony itself, so it applies the same header order; tests/test_gpt_oss.py pins it. Co-Authored-By: Claude Opus 5.5 --- examples/README.md | 4 +- examples/sglang/multiturn_generate_sglang.py | 1 - examples/sglang/online_multiturn_sglang.py | 1 - examples/tinker/multiturn_generate_tinker.py | 1 - .../multiturn_generate_transformers.py | 1 - examples/vllm/multiturn_generate_vllm.py | 1 - pyproject.toml | 17 ++---- renderers/gpt_oss.py | 43 ++++++++++--- tests/reference_continuations.py | 15 +++-- tests/reference_rendering.py | 42 ++++++++++++- tests/test_gpt_oss.py | 61 +++++++++++++++++++ uv.lock | 36 +++++------ 12 files changed, 173 insertions(+), 50 deletions(-) create mode 100644 tests/test_gpt_oss.py diff --git a/examples/README.md b/examples/README.md index 08d79a672..7c040920f 100644 --- a/examples/README.md +++ b/examples/README.md @@ -29,7 +29,9 @@ CUDA_VISIBLE_DEVICES=1 uv run --script examples/sglang/multiturn_generate_sglang ``` The SGLang script uses `input_ids`, so SGLang does not apply a chat template. -It leaves `openai-harmony` at SGLang's pinned version for dependency resolution. +SGLang pins `openai-harmony`, which installs the same `openai_harmony` module +as the `oss-harmony` package renderers depends on, so whichever installs last +wins. The gpt-oss renderer produces the same tokens on either build. ### SGLang HTTP Recipe (online) diff --git a/examples/sglang/multiturn_generate_sglang.py b/examples/sglang/multiturn_generate_sglang.py index 3054bd961..2718eb11b 100755 --- a/examples/sglang/multiturn_generate_sglang.py +++ b/examples/sglang/multiturn_generate_sglang.py @@ -6,7 +6,6 @@ # "sglang==0.5.10.post1", # "flash-attn-4>=4.0.0b4", # "transformers>=5.3.0", -# "openai-harmony==0.0.4", # "openai>=1.108.1", # "tiktoken", # "jinja2", diff --git a/examples/sglang/online_multiturn_sglang.py b/examples/sglang/online_multiturn_sglang.py index 5304611f4..b1b64507e 100644 --- a/examples/sglang/online_multiturn_sglang.py +++ b/examples/sglang/online_multiturn_sglang.py @@ -5,7 +5,6 @@ # "renderers>=0.1.6", # "transformers>=5.3.0", # "httpx>=0.27", -# "openai-harmony==0.0.4", # "tiktoken", # "jinja2", # "numpy", diff --git a/examples/tinker/multiturn_generate_tinker.py b/examples/tinker/multiturn_generate_tinker.py index 4b3f379c6..4ffff9cd8 100644 --- a/examples/tinker/multiturn_generate_tinker.py +++ b/examples/tinker/multiturn_generate_tinker.py @@ -5,7 +5,6 @@ # "renderers>=0.1.6", # "tinker>=0.9.0", # "transformers>=4.57.6,<=5.5.3", -# "openai-harmony>=0.0.8", # "openai>=1.108.1", # "tiktoken", # "jinja2", diff --git a/examples/transformers/multiturn_generate_transformers.py b/examples/transformers/multiturn_generate_transformers.py index 9e5e1f1ef..bb89211c0 100644 --- a/examples/transformers/multiturn_generate_transformers.py +++ b/examples/transformers/multiturn_generate_transformers.py @@ -7,7 +7,6 @@ # "accelerate", # "torch", # "kernels>=0.12.0", -# "openai-harmony>=0.0.8", # "openai>=1.108.1", # "tiktoken", # "jinja2", diff --git a/examples/vllm/multiturn_generate_vllm.py b/examples/vllm/multiturn_generate_vllm.py index 52fa0445b..3e5bf0e4d 100755 --- a/examples/vllm/multiturn_generate_vllm.py +++ b/examples/vllm/multiturn_generate_vllm.py @@ -5,7 +5,6 @@ # "renderers>=0.1.6", # "vllm>=0.20", # "transformers>=4.50.0", -# "openai-harmony>=0.0.8", # "openai>=1.108.1", # "tiktoken", # "jinja2", diff --git a/pyproject.toml b/pyproject.toml index 09daae212..00906694c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,17 +19,12 @@ dependencies = [ "openai>=1.108.1", "tiktoken", "jinja2", - # Used by GptOssRenderer to render and parse harmony tokens. Vendoring - # OpenAI's reference implementation keeps us byte-identical with vLLM - # (which also uses it) and saves us mirroring a 330-line Jinja template. - # - # Floor is ``>=0.0.4`` (not the latest 0.0.8) on purpose: every SGLang - # release through 0.5.12.post1 hard-pins ``openai-harmony==0.0.4``, so a - # higher floor makes ``renderers`` uninstallable alongside SGLang. The - # GptOssRenderer renders byte-identically on 0.0.4 (verified token-for-token - # against 0.0.8) and the Harmony cells in ``tests/test_parity.py`` pass, - # so the older harmony is safe. - "openai-harmony>=0.0.4", + # Used by GptOssRenderer to render harmony tokens. ``oss-harmony`` is the + # maintained fork of OpenAI's ``openai-harmony`` that vLLM depends on. + # Both packages install the ``openai_harmony`` module and overwrite each + # other's files, so ``renderers`` can't share an environment with anything + # that requires ``openai-harmony`` (SGLang pins ``openai-harmony==0.0.4``). + "oss-harmony>=0.0.11", # ``BaseRendererConfig`` inherits from ``pydantic_config.BaseConfig`` so # the typed-config surface stays uniform with prime-rl / verifiers config # bases. Transitively brings pydantic, which ``renderers.configs`` also diff --git a/renderers/gpt_oss.py b/renderers/gpt_oss.py index 511d315fa..8492231dd 100644 --- a/renderers/gpt_oss.py +++ b/renderers/gpt_oss.py @@ -1,9 +1,8 @@ -"""GptOssRenderer — adapter over OpenAI's harmony reference encoder. +"""GptOssRenderer — adapter over the harmony encoder. -Wire format: harmony (channel-based, no BOS). The official renderer ships -in the ``openai-harmony`` package — vLLM uses it directly, so matching -its output guarantees byte-identical tokens with vLLM's serving path -(and with what the model was trained on). +Wire format: harmony (channel-based, no BOS). The encoder ships in the +``oss-harmony`` package, the fork of OpenAI's ``openai-harmony`` that vLLM +depends on. This renderer is a thin adapter that: @@ -18,7 +17,8 @@ - renders one harmony message at a time via ``enc.render(m)`` so each token can be attributed to its source caller index. ``enc.render`` is byte-identical to ``enc.render_conversation`` when concatenated; - verified empirically. + verified empirically. Tool calls and tool results keep the + recipient-first header (``to=functions.x<|channel|>commentary``). Special tokens -------------- @@ -180,6 +180,29 @@ def _encode(self, text: str) -> list[int]: return [] return self._tokenizer.encode(text, add_special_tokens=False) + def _render_message(self, hm: HarmonyMessage) -> list[int]: + """Render one harmony message with its recipient ahead of its channel. + + oss-harmony renders commentary headers channel-first + (``<|channel|>commentary to=functions.x``). openai-harmony and the + gpt-oss chat template put the recipient first, and which order gpt-oss + handles best in conversation history is unsettled, so tool calls and + tool results keep the recipient-first header. + """ + ids = self._enc.render(hm) + if hm.channel != "commentary" or hm.recipient is None: + return ids + author = hm.author.name if hm.author.role == Role.TOOL else hm.author.role.value + header = [ + self._start, + *self._enc.encode(author, disallowed_special=()), + *self._enc.encode(f" to={hm.recipient}", disallowed_special=()), + self._channel, + *self._enc.encode(hm.channel, disallowed_special=()), + self._message, + ] + return header + ids[ids.index(self._message) + 1 :] + def _prefix_content_mask( self, prefix_tokens: list[int], @@ -436,7 +459,9 @@ def emit_harmony_message( for hm in self._to_harmony_messages( msg, preserve_thinking=preserve_thinking ): - emit_harmony_message(self._enc.render(hm), i, is_assistant=is_assistant) + emit_harmony_message( + self._render_message(hm), i, is_assistant=is_assistant + ) # When the conversation ends on an assistant final-channel turn, # ``apply_chat_template`` (and ``render_conversation_for_training``) @@ -547,7 +572,7 @@ def bridge_to_next_turn( ) -> RenderedTokens | None: """Per-message harmony bridge. - Each new message is rendered in isolation via ``enc.render(m)`` — + Each new message is rendered in isolation via ``_render_message`` — same primitive ``render()`` uses — so the bridge can never drift from the full re-render. """ @@ -619,7 +644,7 @@ def bridge_to_next_turn( if role not in ("tool", "user", "system", "developer"): return None for hm in self._to_harmony_messages(msg): - ids = self._enc.render(hm) + ids = self._render_message(hm) try: msg_marker = ids.index(self._message) except ValueError: diff --git a/tests/reference_continuations.py b/tests/reference_continuations.py index 131b64a34..104257f3f 100644 --- a/tests/reference_continuations.py +++ b/tests/reference_continuations.py @@ -10,7 +10,11 @@ import json import re -from reference_rendering import _harmony_messages, render_reference +from reference_rendering import ( + _harmony_messages, + harmony_recipient_first, + render_reference, +) REASONING = "Check the weather carefully." ANSWER = "It is sunny in Tokyo." @@ -96,9 +100,12 @@ def render(conversation, generation=False, **overrides): ), Message.from_role_and_content(Role.ASSISTANT, ANSWER).with_channel("final"), ] - full = encoder.render_conversation_for_training( - Conversation.from_messages(history), - RenderConversationConfig(auto_drop_analysis=False), + full = harmony_recipient_first( + encoder, + encoder.render_conversation_for_training( + Conversation.from_messages(history), + RenderConversationConfig(auto_drop_analysis=False), + ), ) elif case.resolved_renderer == "deepseek-r1" and prompt_text.endswith("\n"): # R1's generation prompt opens thinking, but its history branch removes diff --git a/tests/reference_rendering.py b/tests/reference_rendering.py index 4290c62b8..0981105e5 100644 --- a/tests/reference_rendering.py +++ b/tests/reference_rendering.py @@ -562,6 +562,42 @@ def _harmony_messages( return out +def harmony_recipient_first(encoder: Any, ids: list[int]) -> list[int]: + """Move each header's `` to=recipient`` ahead of its ``<|channel|>``. + + The gpt-oss renderer keeps openai-harmony's recipient-first header for tool + calls and tool results; oss-harmony renders commentary headers channel-first. + """ + start, channel, message = ( + encoder.encode(token, allowed_special="all")[0] + for token in ("<|start|>", "<|channel|>", "<|message|>") + ) + out: list[int] = [] + i = 0 + while i < len(ids): + if ids[i] != start or message not in ids[i:]: + out.append(ids[i]) + i += 1 + continue + end = ids.index(message, i) + header = ids[i + 1 : end] + if channel in header: + pos = header.index(channel) + channel_name, to, recipient = encoder.decode(header[pos + 1 :]).partition( + " to=" + ) + if to: + header = [ + *header[:pos], + *encoder.encode(f" to={recipient}"), + channel, + *encoder.encode(channel_name), + ] + out += [start, *header, message] + i = end + 1 + return out + + def _render_harmony( tokenizer: Any, messages: list[dict[str, Any]], @@ -596,10 +632,12 @@ def _render_harmony( prompt = encoder.render_conversation_for_completion( conversation, next_turn_role=Role.ASSISTANT ) - return prompt + encoder.encode( + return harmony_recipient_first(encoder, prompt) + encoder.encode( "<|channel|>analysis<|message|>", allowed_special="all" ) - return encoder.render_conversation_for_training(conversation) + return harmony_recipient_first( + encoder, encoder.render_conversation_for_training(conversation) + ) REFERENCE_ORACLES: Mapping[str, ReferenceOracle] = MappingProxyType( diff --git a/tests/test_gpt_oss.py b/tests/test_gpt_oss.py new file mode 100644 index 000000000..a77424374 --- /dev/null +++ b/tests/test_gpt_oss.py @@ -0,0 +1,61 @@ +"""GPT-OSS header coverage that the Harmony parity oracle can't provide. + +The parity oracle is harmony itself, so it moves with whichever harmony build +is installed. openai-harmony renders tool-call and tool-result headers +recipient-first; oss-harmony renders them channel-first. The renderer keeps +the recipient-first header on either build, and this test pins it. +""" + +from __future__ import annotations + +from renderers import create_renderer +from renderers.base import load_tokenizer + +_TOOLS = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get weather", + "parameters": { + "type": "object", + "properties": {"location": {"type": "string"}}, + "required": ["location"], + }, + }, + } +] + + +def test_tool_call_and_result_headers_put_recipient_first(): + tok = load_tokenizer("openai/gpt-oss-20b") + renderer = create_renderer(tok) + messages = [ + {"role": "user", "content": "Weather in SF?"}, + { + "role": "assistant", + "content": "", + "tool_calls": [ + { + "id": "c1", + "type": "function", + "function": { + "name": "get_weather", + "arguments": '{"location": "SF"}', + }, + } + ], + }, + {"role": "tool", "tool_call_id": "c1", "name": "get_weather", "content": "20C"}, + ] + + ids = renderer.render_ids(messages, tools=_TOOLS) + expected = tok.encode( + "<|start|>assistant to=functions.get_weather<|channel|>commentary" + '<|message|>{"location": "SF"}<|call|>' + "<|start|>functions.get_weather to=assistant<|channel|>commentary" + "<|message|>20C<|end|>", + add_special_tokens=False, + ) + + assert ids[-len(expected) :] == expected diff --git a/uv.lock b/uv.lock index 4d3d79e81..8dc542eab 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 5 requires-python = ">=3.11, <3.15" [options] @@ -873,26 +873,26 @@ wheels = [ ] [[package]] -name = "openai-harmony" -version = "0.0.8" +name = "oss-harmony" +version = "0.0.11" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3e/92/2d038d096f29179c7c9571b431f9e739f87a487121901725e23fe338dd9d/openai_harmony-0.0.8.tar.gz", hash = "sha256:6e43f98e6c242fa2de6f8ea12eab24af63fa2ed3e89c06341fb9d92632c5cbdf", size = 284777, upload-time = "2025-11-05T19:07:06.727Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/88/ca56e5b17df4763bbb552c7be3abe432b51db3c51f6327a3848b88ad7c08/oss_harmony-0.0.11.tar.gz", hash = "sha256:ccad467b71d61490df1f2204b7a064990833338615ac135536c5b3a8e2d03a15", size = 2198780, upload-time = "2026-05-13T20:49:11.767Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/45/c6/2502f416d46be3ec08bb66d696cccffb57781a499e3ff2e4d7c174af4e8f/openai_harmony-0.0.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:029ec25ca74abe48fdb58eb9fdd2a8c1618581fc33ce8e5653f8a1ffbfbd9326", size = 2627806, upload-time = "2025-11-05T19:06:57.063Z" }, - { url = "https://files.pythonhosted.org/packages/d3/d2/ce6953ca87db9cae3e775024184da7d1c5cb88cead19a2d75b42f00a959c/openai_harmony-0.0.8-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4f709815924ec325b9a890e6ab2bbb0ceec8e319a4e257328eb752cf36b2efc", size = 2948463, upload-time = "2025-11-05T19:06:48.17Z" }, - { url = "https://files.pythonhosted.org/packages/fa/4c/b553c9651662d6ce102ca7f3629d268b23df1abe5841e24bed81e8a8e949/openai_harmony-0.0.8-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5cfcfd963b50a41fc656c84d3440ca6eecdccd6c552158ce790b8f2e33dfb5a9", size = 2704083, upload-time = "2025-11-05T19:06:50.205Z" }, - { url = "https://files.pythonhosted.org/packages/9b/af/4eec8f9ab9c27bcdb444460c72cf43011d176fc44c79d6e113094ca1e152/openai_harmony-0.0.8-cp38-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a3a16972aa1cee38ea958470cd04ac9a2d5ac38fdcf77ab686611246220c158", size = 2959765, upload-time = "2025-11-05T19:06:53.62Z" }, - { url = "https://files.pythonhosted.org/packages/11/3c/33f3374e4624e0e776f6b13b73c45a7ead7f9c4529f8369ed5bfcaa30cac/openai_harmony-0.0.8-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4d5cfa168e74d08f8ba6d58a7e49bc7daef4d58951ec69b66b0d56f4927a68d", size = 3427031, upload-time = "2025-11-05T19:06:51.829Z" }, - { url = "https://files.pythonhosted.org/packages/25/3f/1a192b93bb47c6b44cd98ba8cc1d3d2a9308f1bb700c3017e6352da11bda/openai_harmony-0.0.8-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c007d277218a50db8839e599ed78e0fffe5130f614c3f6d93ae257f282071a29", size = 2953260, upload-time = "2025-11-05T19:06:55.406Z" }, - { url = "https://files.pythonhosted.org/packages/5b/f8/93b582cad3531797c3db7c2db5400fd841538ccddfd9f5e3df61be99a630/openai_harmony-0.0.8-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8565d4f5a0638da1bffde29832ed63c9e695c558611053add3b2dc0b56c92dbc", size = 3127044, upload-time = "2025-11-05T19:06:59.553Z" }, - { url = "https://files.pythonhosted.org/packages/1d/10/4327dbf87f75ae813405fd9a9b4a5cde63d506ffed0a096a440a4cabd89c/openai_harmony-0.0.8-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:cbaa3bda75ef0d8836e1f8cc84af62f971b1d756d740efc95c38c3e04c0bfde2", size = 2932931, upload-time = "2025-11-05T19:07:01.437Z" }, - { url = "https://files.pythonhosted.org/packages/8a/c8/1774eec4f6f360ef57618fb8f52e3d3af245b2491bd0297513aa09eec04b/openai_harmony-0.0.8-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:772922a9bd24e133950fad71eb1550836f415a88e8c77870e12d0c3bd688ddc2", size = 2996140, upload-time = "2025-11-05T19:07:03.438Z" }, - { url = "https://files.pythonhosted.org/packages/60/c3/3d1e01e2dba517a91760e4a03e4f20ffc75039a6fe584d0e6f9b5c78fd15/openai_harmony-0.0.8-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:007b0476a1f331f8130783f901f1da6f5a7057af1a4891f1b6a31dec364189b5", size = 3205080, upload-time = "2025-11-05T19:07:05.078Z" }, - { url = "https://files.pythonhosted.org/packages/14/63/119de431572d7c70a7bf1037034a9be6ed0a7502a7498ba7302bca5b3242/openai_harmony-0.0.8-cp38-abi3-win32.whl", hash = "sha256:a9b5f893326b28d9e935ade14b4f655f5a840942473bc89b201c25f7a15af9cf", size = 2082457, upload-time = "2025-11-05T19:07:09.631Z" }, - { url = "https://files.pythonhosted.org/packages/40/1f/c83cf5a206c263ee70448a5ae4264682555f4d0b5bed0d2cc6ca1108103d/openai_harmony-0.0.8-cp38-abi3-win_amd64.whl", hash = "sha256:39d44f0d8f466bd56698e7ead708bead3141e27b9b87e3ab7d5a6d0e4a869ee5", size = 2438369, upload-time = "2025-11-05T19:07:08.1Z" }, + { url = "https://files.pythonhosted.org/packages/8b/4c/400a7e375943ff06447c8cd3920819d2e37c808d0cfea3296b159e961610/oss_harmony-0.0.11-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:83ca71b85be6e2f8e93527673cd2eff4da9180608ee5d4197bd4f01afc4fa6be", size = 3337401, upload-time = "2026-05-13T20:49:00.051Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e9/0b33d4c592e1422c9faeaf1cfe10731328ecf3504e900dc7921109affd55/oss_harmony-0.0.11-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1f74ef7f8be32fa1dcfe1644839af5f0873acce78b5668c2aa39f26d83218ff", size = 3493239, upload-time = "2026-05-13T20:48:49.588Z" }, + { url = "https://files.pythonhosted.org/packages/de/c3/502b7ba6f36645117b58859b250355cba34d7107ec104ea40087b752977b/oss_harmony-0.0.11-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2221f2790411cfd5d0e517769dc2662b15b2711e44728291961506a367ffc3d1", size = 3415967, upload-time = "2026-05-13T20:48:51.731Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f7/f802d7aba027cd4e1c0ff5509c645de8d70fa674b31d80cdf8fec63ac844/oss_harmony-0.0.11-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f215934b05f3a7658e0cf30cf92bc25f4aed125a693529c53dd97325ecfbf7ef", size = 3707786, upload-time = "2026-05-13T20:48:53.91Z" }, + { url = "https://files.pythonhosted.org/packages/89/68/001d7429a692b2db0c03bd49b756f834716b64afc0bbbece96824e8f0af8/oss_harmony-0.0.11-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d877c6b526cdbdf55068bae8f05c60398e929c9e296c7f69a1d533339708be80", size = 3567858, upload-time = "2026-05-13T20:48:57.904Z" }, + { url = "https://files.pythonhosted.org/packages/3b/5d/edf954b0aace872ec137a9ef7b92c29120bd16a4277fd11c5b83e7d1ed16/oss_harmony-0.0.11-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e451e5dc28b0d883f33aa17a77c923afcab5d26ef3b95d7593b68a07e399706e", size = 3633793, upload-time = "2026-05-13T20:48:56.037Z" }, + { url = "https://files.pythonhosted.org/packages/4b/c9/720aed1527d87f82e07a7f35c3c81c6d9198a8d1223bdbd298b3c66a72db/oss_harmony-0.0.11-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:036505bfb39c6b50edcf717349518d269bb375207f7f8e95f517e938b8f2e0c8", size = 3695703, upload-time = "2026-05-13T20:49:02.857Z" }, + { url = "https://files.pythonhosted.org/packages/94/a8/fb8cad33079ee848514ea70688005ef7b6c32ccee788cc18fcb04e24fa74/oss_harmony-0.0.11-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:952218411c219dc7ca8ee56eb86271d015d0c347896b2009906096422d7a94cf", size = 3703138, upload-time = "2026-05-13T20:49:04.937Z" }, + { url = "https://files.pythonhosted.org/packages/0d/7d/d71b3ee5abfbf9130eea646b9589e8f00a039514cdca2ab6dbffa22f8351/oss_harmony-0.0.11-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:5c7b0cea27e1e4e9fc485bf8039a119d6732718cce6b067d1f7de9e637572348", size = 3790649, upload-time = "2026-05-13T20:49:07.337Z" }, + { url = "https://files.pythonhosted.org/packages/48/9a/ad24d20ea1e8b3167744f5d357d48a116086258ca58540986a0896781864/oss_harmony-0.0.11-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:0d1545203973abb3002dcc6c5cffd14ca4a376b655c14ce190c78eb6aaf5f6fd", size = 3807882, upload-time = "2026-05-13T20:49:09.767Z" }, + { url = "https://files.pythonhosted.org/packages/03/33/2917125287b46255cfc6d7e0bc4bc5e5c90f7ad4e02d3922c2f0f95b696b/oss_harmony-0.0.11-cp38-abi3-win32.whl", hash = "sha256:fd91609eef825f342ae7ac990651cfebcf28aac6b6ad1186bf37c5b593cdf950", size = 3135714, upload-time = "2026-05-13T20:49:15.924Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c9/eedd8d02be8bbaa586f8a323de2fc976e6053582411020b7be1f7c7e1734/oss_harmony-0.0.11-cp38-abi3-win_amd64.whl", hash = "sha256:4db175b6f6264cd60ceb692c0e686937e072ca6ea1239d0d4cace2cfb50f4974", size = 3266750, upload-time = "2026-05-13T20:49:13.404Z" }, ] [[package]] @@ -1347,7 +1347,7 @@ dependencies = [ { name = "jinja2" }, { name = "numpy" }, { name = "openai" }, - { name = "openai-harmony" }, + { name = "oss-harmony" }, { name = "prime-pydantic-config" }, { name = "tiktoken" }, ] @@ -1379,7 +1379,7 @@ requires-dist = [ { name = "jinja2" }, { name = "numpy" }, { name = "openai", specifier = ">=1.108.1" }, - { name = "openai-harmony", specifier = ">=0.0.4" }, + { name = "oss-harmony", specifier = ">=0.0.11" }, { name = "pillow", marker = "extra == 'multimodal'", specifier = ">=12.2.0" }, { name = "prime-pydantic-config", specifier = ">=0.3.0.dev83" }, { name = "tiktoken" },