docs(rust): describe the null arguments mapping and decline coverage

Fix the tool call doc comment that still claimed a non-string arguments
declines, and spell out the null-to-None mapping it now shares with the
deserializer. Note the deliberate absence of deny_unknown_fields, since
python reads only function.arguments and the extra id, type and name keys
are ignored rather than declined. Restore decline coverage for malformed
tool call shapes: tool_calls that is not a list, a tool call with no
function key, and a non-string non-null arguments such as a number or an
object, all surfaced as the crate parse error so the host falls back to
the python counter
This commit is contained in:
James 2026-09-25 11:57:33 -07:00
parent be79444f9c
commit bc7cce5920
2 changed files with 21 additions and 3 deletions

View file

@ -139,9 +139,15 @@ impl<'de> Visitor<'de> for TextValueVisitor {
/// The parts of an assistant tool call Python counts: only the `arguments`
/// string contributes (`_count_function_call_tokens` in
/// `litellm_core_utils/token_counter.py`). Absent arguments count as the empty
/// string; a non-string `arguments` declines so Python handles the fallback
/// instead of miscounting.
/// `litellm_core_utils/token_counter.py`). An absent arguments key counts as the
/// empty string, while an explicit null counts as the string "None" the way
/// Python's `str(None)` does; any other non-string `arguments` declines so
/// Python handles the fallback instead of miscounting.
///
/// `deny_unknown_fields` is deliberately absent from this shape, and from
/// `ToolCallFunction` and `LegacyFunctionCall`: Python reads only
/// `function.arguments` off a tool call, so the extra `id`, `type` and `name`
/// keys the OpenAI shape carries are ignored rather than declined.
#[derive(Clone, Debug, Deserialize, PartialEq)]
pub(crate) struct ToolCall {
pub(crate) function: ToolCallFunction,

View file

@ -262,6 +262,18 @@ mod json {
#[rstest]
#[case::not_json(b"not json" as &[u8])]
#[case::messages_not_a_list(br#"{"model":"m","messages":"hi"}"#)]
#[case::tool_calls_not_a_list(
br#"{"model":"m","messages":[{"role":"assistant","tool_calls":"nope"}]}"#
)]
#[case::tool_call_without_function(
br#"{"model":"m","messages":[{"role":"assistant","tool_calls":[{"id":"1","type":"function"}]}]}"#
)]
#[case::tool_call_arguments_is_a_number(
br#"{"model":"m","messages":[{"role":"assistant","tool_calls":[{"id":"1","type":"function","function":{"name":"f","arguments":42}}]}]}"#
)]
#[case::tool_call_arguments_is_an_object(
br#"{"model":"m","messages":[{"role":"assistant","tool_calls":[{"id":"1","type":"function","function":{"name":"f","arguments":{"city":"Tokyo"}}}]}]}"#
)]
#[case::dict_content(
br#"{"model":"m","messages":[{"role":"user","content":{"type":"text","text":"x"}}]}"#
)]