-
-
Notifications
You must be signed in to change notification settings - Fork 11.7k
[gpt-oss] tool parser supports for /chat/completions [1/n] #22386
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 8 commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
327a154
feat: harmony tool supports for /chat/completions
aarnphm f03ea94
merge: branch 'main' of github.com:vllm-project/vllm into feat/gpt-os…
aarnphm d093940
chore: add test cases for serving chat
aarnphm 72f83a0
chore: update interfaces
aarnphm a891510
fix: linter issue
aarnphm 3d029bd
fix: correct types and lint
aarnphm bd57e40
fix: tests
aarnphm 7b3dce2
Merge branch 'main' into feat/gpt-oss-fc
simon-mo a806f00
fix: address comments
aarnphm 01d3ce8
merge: branch 'main' of github.com:vllm-project/vllm into feat/gpt-os…
aarnphm 12f18c3
chore: enable tests for gpt-oss e2e
aarnphm 62bbad3
revert: remove interfaces change and keep gpt-oss separate
aarnphm 629f974
chore: update test chat
aarnphm 503f98f
chore: cleanup separate path for reasoning contents
aarnphm c871dd0
chore: final styling
aarnphm 6c4f42b
merge: branch 'main' of github.com:vllm-project/vllm into feat/gpt-os…
aarnphm ad75bee
fix: import issue
aarnphm 28764fe
fix: correct tests and remove reasoning content
aarnphm 487ec94
fix: tests
aarnphm f6aa5d5
merge: branch 'main' of github.com:vllm-project/vllm into feat/gpt-os…
aarnphm f0ee88a
merge: branch 'main' of github.com:vllm-project/vllm into feat/gpt-os…
aarnphm 9588a55
fix: correct fixed names
aarnphm d9b3d7f
merge: branch 'main' of github.com:vllm-project/vllm into feat/gpt-os…
aarnphm File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # SPDX-FileCopyrightText: Copyright contributors to the vLLM project | ||
|
|
||
| import json | ||
|
|
||
| import pytest | ||
| from openai_harmony import (HarmonyEncodingName, Message, Role, | ||
| load_harmony_encoding) | ||
|
|
||
| from vllm.entrypoints.openai.protocol import FunctionCall, ToolCall | ||
| from vllm.entrypoints.openai.tool_parsers import OpenAIToolParser | ||
| from vllm.transformers_utils.tokenizer import get_tokenizer | ||
|
|
||
| MODEL = "gpt2" | ||
|
|
||
|
|
||
| @pytest.fixture(scope="module") | ||
| def openai_tokenizer(): | ||
| # The parser does not use the tokenizer, but the constructor requires it. | ||
| return get_tokenizer(MODEL) | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def openai_tool_parser(openai_tokenizer): | ||
| return OpenAIToolParser(openai_tokenizer) | ||
|
|
||
|
|
||
| @pytest.fixture(scope="module") | ||
| def harmony_encoding(): | ||
| return load_harmony_encoding(HarmonyEncodingName.HARMONY_GPT_OSS) | ||
|
|
||
|
|
||
| def assert_tool_calls(actual_tool_calls: list[ToolCall], | ||
| expected_tool_calls: list[ToolCall]): | ||
| assert len(actual_tool_calls) == len(expected_tool_calls) | ||
|
|
||
| for actual_tool_call, expected_tool_call in zip(actual_tool_calls, | ||
| expected_tool_calls): | ||
| assert isinstance(actual_tool_call.id, str) | ||
| assert len(actual_tool_call.id) > 16 # Default from protocol.py | ||
| assert actual_tool_call.type == "function" | ||
| assert actual_tool_call.function == expected_tool_call.function | ||
|
|
||
|
|
||
| def test_extract_tool_calls_no_tools(openai_tool_parser, harmony_encoding): | ||
| msg = Message.from_role_and_content(Role.ASSISTANT, | ||
| "This is a test").with_channel("final") | ||
| stop_token = harmony_encoding.token_from_string("<|return|>") | ||
| token_ids = harmony_encoding.render_message(msg) + [stop_token] | ||
|
|
||
| extracted_info = openai_tool_parser.extract_tool_calls("", | ||
| request=None, | ||
| token_ids=token_ids) | ||
| assert not extracted_info.tools_called | ||
| assert extracted_info.tool_calls == [] | ||
| assert extracted_info.content == "This is a test" | ||
|
|
||
|
|
||
| def test_extract_tool_calls_single_tool(openai_tool_parser, harmony_encoding): | ||
| msg = Message.from_role_and_content( | ||
| Role.ASSISTANT, '{"city": "Dallas"}').with_channel("commentary"). \ | ||
| with_recipient("functions.get_current_weather").with_content_type("json") | ||
| stop_token = harmony_encoding.token_from_string("<|call|>") | ||
| token_ids = harmony_encoding.render_message(msg) + [stop_token] | ||
|
|
||
| extracted_info = openai_tool_parser.extract_tool_calls("", | ||
| request=None, | ||
| token_ids=token_ids) | ||
| assert extracted_info.tools_called | ||
| expected_tool_calls = [ | ||
| ToolCall( | ||
| function=FunctionCall(name="get_current_weather", | ||
| arguments=json.dumps({"city": "Dallas"}))) | ||
| ] | ||
| assert_tool_calls(extracted_info.tool_calls, expected_tool_calls) | ||
| assert extracted_info.content is None | ||
|
|
||
|
|
||
| def test_extract_tool_calls_multiple_tools(openai_tool_parser, | ||
| harmony_encoding): | ||
| msg1 = Message.from_role_and_content( | ||
| Role.ASSISTANT, '{"city": "Dallas"}').with_channel("commentary"). \ | ||
| with_recipient("functions.get_current_weather").with_content_type("json") | ||
| msg2 = Message.from_role_and_content( | ||
| Role.ASSISTANT, '{}').with_channel("commentary"). \ | ||
| with_recipient("functions.get_user_location").with_content_type("json") | ||
| stop_token = harmony_encoding.token_from_string("<|call|>") | ||
| token_ids = harmony_encoding.render_message( | ||
| msg1) + harmony_encoding.render_message(msg2) + [stop_token] | ||
|
|
||
| extracted_info = openai_tool_parser.extract_tool_calls("", | ||
| request=None, | ||
| token_ids=token_ids) | ||
| assert extracted_info.tools_called | ||
| expected_tool_calls = [ | ||
| ToolCall( | ||
| function=FunctionCall(name="get_current_weather", | ||
| arguments=json.dumps({"city": "Dallas"}))), | ||
| ToolCall(function=FunctionCall(name="get_user_location", | ||
| arguments=json.dumps({}))) | ||
| ] | ||
| assert_tool_calls(extracted_info.tool_calls, expected_tool_calls) | ||
| assert extracted_info.content is None | ||
|
|
||
|
|
||
| def test_extract_tool_calls_with_reasoning(openai_tool_parser, | ||
| harmony_encoding): | ||
| msg1 = Message.from_role_and_content( | ||
| Role.ASSISTANT, "Thinking about the weather.").with_channel("analysis") | ||
| msg2 = Message.from_role_and_content( | ||
| Role.ASSISTANT, '{"city": "Dallas"}').with_channel("commentary"). \ | ||
| with_recipient("functions.get_current_weather").with_content_type("json") | ||
| msg3 = Message.from_role_and_content( | ||
| Role.ASSISTANT, "The weather is nice.").with_channel("final") | ||
|
|
||
| stop_token = harmony_encoding.token_from_string("<|return|>") | ||
| token_ids = harmony_encoding.render_message( | ||
| msg1) + harmony_encoding.render_message( | ||
| msg2) + harmony_encoding.render_message(msg3) + [stop_token] | ||
|
|
||
| extracted_info = openai_tool_parser.extract_tool_calls("", | ||
| request=None, | ||
| token_ids=token_ids) | ||
| assert extracted_info.tools_called | ||
| assert extracted_info.reasoning_content == "Thinking about the weather." | ||
| expected_tool_calls = [ | ||
| ToolCall( | ||
| function=FunctionCall(name="get_current_weather", | ||
| arguments=json.dumps({"city": "Dallas"}))) | ||
| ] | ||
| assert_tool_calls(extracted_info.tool_calls, expected_tool_calls) | ||
| assert extracted_info.content == "The weather is nice." |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.