diff --git a/examples/README.md b/examples/README.md index 08d79a67..7c040920 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 3054bd96..2718eb11 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 5304611f..b1b64507 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 4b3f379c..4ffff9cd 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 9e5e1f1e..bb89211c 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 52fa0445..3e5bf0e4 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 09daae21..00906694 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 511d315f..8492231d 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 131b64a3..104257f3 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 4290c62b..0981105e 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 00000000..a7742437 --- /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 4d3d79e8..8dc542ea 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" },