-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
PR 2634 CI - Fix the tool_choice format for named choice by adapting OpenAIs scheme #2645
Conversation
@linusbierhoff thank you for the contribution! 🙏 I've opened this PR to run CI, and add some other tool calling improvements along with your changes. This PR now removes the old |
37a1239
to
970d8dc
Compare
router/src/lib.rs
Outdated
|
||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema, Default)] | ||
#[serde(from = "ToolTypeDeserializer")] | ||
pub enum ChatCompletionToolChoiceOption { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we avoid java naming please ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yea I can revert the name change if thats better.
The name change is to align with the naming that OpenAI is using in their spec. https://github.com/openai/openai-openapi/blob/d033c364c6574068ee89f3d5f845a4830bddd503/openapi.yaml#L9581, I aligned the names because I thought it may be helpful in automatically checking the spec, however it is a very long name..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought it may be helpful in automatically checking the spec
Then let's wait for it to be actually relevant, it's not usually a good thing to do things "because we might need it". Either you need it or you don't.
Personally I don't think it has any relevance to use their names.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
got it that make sense, I've changed the name to ToolChoice
in the latest commit. (its much cleaner 👍)
router/src/lib.rs
Outdated
NoTool, | ||
/// Means the model must call one or more tools. | ||
#[schema(rename = "required")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
schema rename
is not what you want, you want serde rename most likely.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh I actually included it to improve the generated schema
this macro changes the enum
on the schema to show the expected lowercase text instead of Required
{
"type": "string",
"description": "Means the model must call one or more tools.",
"enum": [
"required"
]
},
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, and serde(renamed) actually modifies what the input is from the user.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually a better serde(rename) on the whole struct should fix every field (both schema and actual JSON parsing)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh yea thats much more concise 🙏, updated in the latest commit
router/src/lib.rs
Outdated
/// Forces the model to call a specific tool. | ||
#[schema(rename = "function")] | ||
#[serde(alias = "function")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we have link in rustdoc to where all these attributes are explained, possibly in the original openai spec (or anthropic)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, just added a useful link above the enum that explains all 4 of the options in OpenAI's docs this link will also be included in the openapi schema for the ChatCompletionToolChoiceOption
description
router/src/lib.rs
Outdated
|
||
let named = r#"{"tool_choice":"myfn"}"#; | ||
let de_named: TestRequest = serde_json::from_str(named).unwrap(); | ||
assert_eq!(de_named.tool_choice, ref_choice); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assert_eq!(de_named.tool_choice, ref_choice); | |
assert_eq!(de_named.tool_choice,ChatCompletionToolChoiceOption::Function(FunctionName { | |
name: "myfn".to_string(), | |
}) ); |
Please avoid indirection in tests. Tests are about being as dumb as possible, so a reader can easily assess if the test is correct, or outdated. Every indirection makes that harder to reason about.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
great points, indirection removed in the latest commit
router/src/infer/tool_grammar.rs
Outdated
tools.push(no_tool); | ||
// add the no_tool function to the tools as long as we are not required to use a specific tool | ||
if tool_choice != ChatCompletionToolChoiceOption::Required { | ||
let no_tool = Tool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this only ever used in the auto
branch ? Wouldn't putting it in the match branch make more sense ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep good point. Just moved into the match branch and removed the let mut tools
and only mutate tools if the Auto option is chosen. thanks
router/src/infer/tool_grammar.rs
Outdated
ToolType::OneOf => tools.clone(), | ||
ToolType::NoTool => return Ok((tools, None)), | ||
ChatCompletionToolChoiceOption::Required => tools.clone(), | ||
ChatCompletionToolChoiceOption::Auto => tools.clone(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this .clone
needed ? I know it was there before, but since we're upgrading this, couldn't we get rid of it ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes agreed they should be removed. Just removed all of the tool clones above and improved the match in general
router/src/infer/tool_grammar.rs
Outdated
ToolType::NoTool => return Ok((tools, None)), | ||
ChatCompletionToolChoiceOption::Required => tools.clone(), | ||
ChatCompletionToolChoiceOption::Auto => tools.clone(), | ||
ChatCompletionToolChoiceOption::NoTool => return Ok((tools, None)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also I think early returns in match
branches are bug nests, it's easy to overlook those branches, returning an empty slice and letting the rest roll seems slightly better (code reading wise)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good point, updated in the latest to commit to discard the tools and continue executing the function normally
router/src/lib.rs
Outdated
#[schema(nullable = true, example = "null")] | ||
pub tool_choice: ToolChoice, | ||
#[schema(nullable = true, default = "null", example = "null")] | ||
pub tool_choice: Option<ChatCompletionToolChoiceOption>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the change to Option
you have the NoTool
choice, there shouldn't be a need to Option
wrapping is there ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Option
is needed so the user can send a http request without a tool_choice
param included. If tool_choice
is None
(user did not include a tool_choice in the request), ChatCompletionToolChoiceOption
defaults to NoTool
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't a default take care of that ? And shouldn't the default be "auto"?
router/src/lib.rs
Outdated
@@ -906,8 +906,18 @@ impl ChatRequest { | |||
let (inputs, grammar, using_tools) = prepare_chat_input( | |||
infer, | |||
response_format, | |||
tools, | |||
tool_choice, | |||
tools.clone(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No clone.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed, it was previously required because of a check of the tools
vec that was below this line (moved it up and removed the clone). thanks
router/src/lib.rs
Outdated
tool_choice, | ||
tools.clone(), | ||
// unwrap or default (use "auto" if tools are present, and "none" if not) | ||
tool_choice.map_or_else( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That should be already done in the deserialization, there are about 3 different function calls to preprare_chat_input
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good point, in the latest commit i've moved the logic from prepare_chat_input
into try_into_generate
and remove the prepare_chat_input
all together.
Can you point to the original references for the things we're adding. Why are we dropping support for the old way, if it was there, is it documented anywhere ? Should we update the old doc ? This PR is also seriously lacking integration tests for all of these. The deserialization tests are nice, but not enough. |
yea
yea regarding the removed code - as the original PR noted, the regarding flagging the breaking changes, would it be best to include the old API but throw a helpful error if it is used? Currently I've removed the path in favor of the corrected one.
we're dropping support since it was incorrectly implemented, we do not have documentation specific to this tool choice, however I'm going to push another commit with updated docs for the supported tool choice types
just added a test for |
e1d1706
to
79a67aa
Compare
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
router/src/lib.rs
Outdated
ChatCompletionToolChoiceOption::Auto | ||
} else { | ||
ChatCompletionToolChoiceOption::NoTool | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shoudln't Auto be enough in all cases ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if tool_choice is "required" and no tools are sent ?
("auto", tools = []) we should just ignore the grammar right ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
great point! ToolChoice::Auto
should be enough and in the case there are no tools we'll ignore the grammar/tool logic. updated in the latest commit
"tools": tools, | ||
"tool_choice": { | ||
"type": "function", | ||
"function": {"name": "get_current_weather"}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't choosing the other tools be a better test ? (Forcing to check that the other tool was used that in the required test case ?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agreed, just updated to choose get_n_day_weather_forecast
instead
last_response = response | ||
|
||
assert count == 30 | ||
print(tool_calls_generated) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oops 😬, removed in latest commit. thanks!
router/src/infer/tool_grammar.rs
Outdated
if tools.is_empty() { | ||
return Ok((tools, None)); | ||
return Ok((Vec::with_capacity(0), None)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
vec![]
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated in latest commit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let tools_to_use = match tool_choice
was updated to return a vec![]
rather than a Vec::with_capacity(0)
, and the conditional was moved below tools_to_use
to return none in both cases (no tools supplied, or if NoTools specified)
ToolChoice::NoTool => vec![], |
router/src/lib.rs
Outdated
@@ -850,13 +850,13 @@ pub(crate) struct ChatRequest { | |||
/// A specific tool to use. If not provided, the model will default to use any of the tools provided in the tools parameter. | |||
#[serde(default)] | |||
#[schema(nullable = true, default = "null", example = "null")] | |||
pub tool_choice: Option<ChatCompletionToolChoiceOption>, | |||
pub tool_choice: Option<ToolChoice>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't default "auto" allow removing the Option
here ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh yes that worked, I was originally under the impression the Option was still needed to handle all http request but this is much cleaner. Thank you
router/src/lib.rs
Outdated
|
||
/// Response format constraints for the generation. | ||
/// | ||
/// NOTE: A request can use `response_format` OR `tools` but not both. | ||
#[serde(default)] | ||
#[schema(nullable = true, default = "null", example = "null")] | ||
#[schema(nullable = true, default = "auto", example = "auto")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you made the change to the wrong item, no ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes thank you, updated the correct argument in the latest commit
Auto, | ||
/// Means the model will not call any tool and instead generates a message. | ||
#[schema(rename = "none")] | ||
#[default] | ||
NoTool, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this no_tool
with snake_case
?This should mean a rename of this property or None
, no ?
I don't think schema(rename)
imples a serde rename
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good catch, updated to serde(rename)
and added a test to ensure that the "none"
values is respected correctly
…pport and refactor
193ad66
to
c1eab6c
Compare
merging optimistically as CI is green and issues have been resolved. Also mentioned in the internal #text-generation-inference Slack channel. Will watch for any regressions and revert if needed |
This PR re opens #2634 to run CI