Feature/tool use support#300
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds comprehensive tool-use support to the data generation pipeline for training speculators. The implementation enables handling of tool-based conversations, which is becoming increasingly important for modern language model applications.
Changes:
- Added support for the 'tool' role in conversation normalization
- Implemented preservation of 'tool_calls' field in conversation turns
- Added tools parameter forwarding to tokenizer's apply_chat_template method
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/speculators/data_generation/preprocessing.py | Added tool role handling, tool_calls field preservation, tools column parsing and forwarding to apply_chat_template |
| tests/datagen/test_preprocessing.py | Added comprehensive test coverage for tool role, tool_calls preservation, tools parameter forwarding, and error handling |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Hey, thanks for the pr. This would be great to support! Could you clarify what dataset you are using as the basis for this? Or provide more information on the format this will support (and why)? It seems like you are expecting something along the lines of: I don't have a ton of experience with tool calling datasets but the first one I found when googling was this one: https://huggingface.co/datasets/Nanbeige/ToolMind/blob/main/open_datasets/ToolACE-query.jsonl which uses a different format (list of dicts containing conversations + tools together). I'm not saying we have to support this (random) dataset, but would like to understand what kinds of dataset this will support. And then I think we should add documentation indicating what's supported and/or automatic warnings if we detect unsupported tool dataset types (for common formats that we don't support). |
|
Hi @fynnsu.
We have mix of multiple datasets. However, the This format can be seen e.g., in llamafactory/reason-tool-use-demo-1500, interstellarninja/hermes_reasoning_tool_use (sorted tool use datasets in HF by most downloads). I am not sure if it can be robust enough to support all the datasets as it depends on the authors. For example Nvidia's nice dataset add the It is similar to the requirement of
Sure, the format requirements in the documentation sounds like a good idea. I can add it. Do you have any suggestion where it would fit the best? Thank you for reply, Ondřej |
Thank you for the examples!
Yeah, it's definitely okay if this doesn't support all types of datasets, especially because as you noted our current handling for messages isn't super general either. I think if you could add a note to the data generation section in |
|
Okey, nice. I added a note to the readme and suggestions from copilot here. Thank you! |
fynnsu
left a comment
There was a problem hiding this comment.
Looks like the quality checks are failing. Could you run this locally and fix the type issues?
uv pip install -e .[dev]
make quality
The link checks are also failing but that maybe be unrelated. I can look into it.
3c68de5 to
2ef74d1
Compare
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThis PR adds tool-related field support to conversation preprocessing. It extends role normalization to handle tool roles and tool_calls, adds batch-level reading and validation of tools columns, parses tool data as JSON, and forwards tools to the tokenizer's chat template. Documentation and comprehensive tests are included. ChangesTool Support in Conversation Preprocessing
Sequence DiagramsequenceDiagram
participant Dataset
participant PreprocessBatch
participant Tokenizer
participant Output
Dataset->>PreprocessBatch: batch with conversations & tools columns
PreprocessBatch->>PreprocessBatch: validate tools column length
PreprocessBatch->>PreprocessBatch: extract & parse tools JSON per conversation
PreprocessBatch->>Tokenizer: apply_chat_template(..., tools=parsed_tools)
Tokenizer->>Tokenizer: render template with tool context
Tokenizer->>Output: input_ids, loss_mask with tool-aware tokens
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested labels
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
This pull request has merge conflicts that must be resolved before it can be |
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/speculators/data_generation/preprocessing.py (1)
292-307:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winFix indentation:
elseblock is paired with wrong statement.The
elseat line 303 is currently indented as part of thetry/exceptblock, which means it executes whenjson.loads()succeeds (no exception raised). However, the warning message says "Non-string value in tools column", which should execute whenconv_toolsis not a string.This causes two problems:
- The "Non-string value" warning fires when JSON parsing succeeds (incorrect)
- No warning fires when
conv_toolsis actually a non-string value (missing warning)The
elseshould be paired with theif isinstance(conv_tools, str):check, not thetry/except.🐛 Proposed fix
# Parse tools JSON string if present; warn and skip tools on invalid JSON parsed_tools = None if conv_tools: if isinstance(conv_tools, str): try: parsed_tools = json.loads(conv_tools) except json.JSONDecodeError as e: log.warning( f"Invalid JSON in tools column for conversation {idx}: {e}, " "proceeding without tools" ) - else: - log.warning( - f"Non-string value in tools column for conversation {idx}: " - f"{type(conv_tools).__name__}, proceeding without tools" - ) + else: + log.warning( + f"Non-string value in tools column for conversation {idx}: " + f"{type(conv_tools).__name__}, proceeding without tools" + )🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/speculators/data_generation/preprocessing.py` around lines 292 - 307, The "Non-string value" warning is currently inside the try/except (paired with json.loads) so it runs on successful parse; move/dedent that else so it pairs with the isinstance(conv_tools, str) check instead. Concretely, inside the block handling conv_tools, keep the try/except around json.loads(conv_tools) to set parsed_tools and log a JSONDecodeError via log.warning, and add an else branch paired with the isinstance(...) check (or an explicit elif/else) that logs the "Non-string value in tools column" message when conv_tools is not a string; reference parsed_tools, conv_tools, json.loads, and log.warning to locate and fix the code.
🧹 Nitpick comments (1)
tests/datagen/test_preprocessing.py (1)
688-840: ⚡ Quick winAdd an explicit test for tools/conversations batch-alignment validation.
These tests cover valid tools JSON, invalid JSON, and HF-mask forwarding well, but they don’t assert behavior when the
toolscolumn length does not matchconversationslength (a key new branch in this PR). Please add one test for this mismatch path (raise/skip behavior as intended) to lock in correctness.Suggested test addition
+@pytest.mark.sanity +def test_preprocess_batch_tools_length_mismatch(): + """Tools column must align 1:1 with conversations in batched preprocessing.""" + tokenizer = AutoTokenizer.from_pretrained(TEST_MODEL_REPO, trust_remote_code=True) + + if not hasattr(tokenizer, "apply_chat_template") or tokenizer.chat_template is None: + pytest.skip("Tokenizer does not support chat templates") + + if tokenizer.pad_token is None: + tokenizer.pad_token = tokenizer.eos_token + + examples = { + "conversations": [ + [{"role": "user", "content": "Hello"}, {"role": "assistant", "content": "Hi"}], + [{"role": "user", "content": "Bye"}, {"role": "assistant", "content": "Cya"}], + ], + # Deliberate mismatch: only one tools entry for two conversations + "tools": [json.dumps([{"type": "function", "function": {"name": "get_weather"}}])], + } + + assistant_pattern = _detect_assistant_pattern(tokenizer) + # Adjust assertion based on intended contract in preprocessing: + with pytest.raises(ValueError): + _preprocess_batch( + examples, tokenizer, max_length=512, assistant_pattern=assistant_pattern + )As per coding guidelines, "Ensure PyTest tests are clear, comprehensive, and cover edge cases specific to speculative decoding ... Verify that new code paths introduced in the PR are covered."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/datagen/test_preprocessing.py` around lines 688 - 840, Add a new pytest that supplies an examples dict where "conversations" and "tools" lengths differ and assert the new mismatch branch in _preprocess_batch is exercised: create a test function (e.g., test_preprocess_batch_tools_conversations_mismatch) that builds a single conversation but a tools list of different length, loads the tokenizer as in the other tests, and use pytest.raises(ValueError) around the call to _preprocess_batch to verify the function raises on mismatched batch lengths (reference the _preprocess_batch symbol and follow the same tokenizer setup/skip guards used in test_preprocess_batch_with_tools).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@scripts/README.md`:
- Around line 32-33: Update the README to explicitly state that the data
generation preprocessing expects the separate "tools" column to contain
JSON-dumped strings per row (i.e., a JSON array/object encoded as a string), not
arbitrary list formats; mention the "conversations" column requirement too, give
a short example of the exact expected row (conversations: "<conversation text>",
tools: '["toolA", {"name":"toolB","args":{}}]') and add a one-line note on how
to convert common alternate layouts (e.g., Python: json.dumps(row["tools"]) or
CSV: ensure proper quoting) so users know to preprocess other formats before
running the script.
---
Duplicate comments:
In `@src/speculators/data_generation/preprocessing.py`:
- Around line 292-307: The "Non-string value" warning is currently inside the
try/except (paired with json.loads) so it runs on successful parse; move/dedent
that else so it pairs with the isinstance(conv_tools, str) check instead.
Concretely, inside the block handling conv_tools, keep the try/except around
json.loads(conv_tools) to set parsed_tools and log a JSONDecodeError via
log.warning, and add an else branch paired with the isinstance(...) check (or an
explicit elif/else) that logs the "Non-string value in tools column" message
when conv_tools is not a string; reference parsed_tools, conv_tools, json.loads,
and log.warning to locate and fix the code.
---
Nitpick comments:
In `@tests/datagen/test_preprocessing.py`:
- Around line 688-840: Add a new pytest that supplies an examples dict where
"conversations" and "tools" lengths differ and assert the new mismatch branch in
_preprocess_batch is exercised: create a test function (e.g.,
test_preprocess_batch_tools_conversations_mismatch) that builds a single
conversation but a tools list of different length, loads the tokenizer as in the
other tests, and use pytest.raises(ValueError) around the call to
_preprocess_batch to verify the function raises on mismatched batch lengths
(reference the _preprocess_batch symbol and follow the same tokenizer setup/skip
guards used in test_preprocess_batch_with_tools).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: c86880b4-cc52-4b58-9dee-485354de9571
📒 Files selected for processing (4)
scripts/README.mdsrc/speculators/data_generation/preprocessing.pytests/datagen/test_preprocessing.pytests/unit/convert/test_eagle3_converter.py
2ef74d1 to
04fddbf
Compare
|
Hi @fynnsu . I totally missed that two commits were not signed before and the branch is blocked. I fixed it and rebased, hopefully everything is fine. Thanks, Ondřej |
|
This pull request has merge conflicts that must be resolved before it can be |
|
The quality checks have failed. Please run |
|
@olisicky Hey we just landed a bigger change to preprocessing to support multi-modal. Would you be able to rebase one more time? |
04fddbf to
0fa0977
Compare
|
Hi @fynnsu . Sure, just rebased. |
|
The quality checks have failed. Please run |
fynnsu
left a comment
There was a problem hiding this comment.
Looks good! Thanks for adding this
|
Okay, looks like you have a type error. This seems like it should pass and mypy just doesn't accept it for some reason, so you can add And then run |
dade443 to
0d62522
Compare
|
My bad. I rebased but didn't synced new pinned versions of ruff and it missed checks which were indicated here. Sorry. |
|
Hi @olisicky, could you rebase/merge main? I think there may also be some duplicated logic in Can you also add an early return to if isinstance(conv_tools, list):
return conv_toolsI think this should be good to merge then. Sorry for the confusion with the other tool calling pr. |
Signed-off-by: Ondřej Lisický <87812906+olisicky@users.noreply.github.com>
Signed-off-by: Ondřej Lisický <87812906+olisicky@users.noreply.github.com>
Signed-off-by: Ondřej Lisický <87812906+olisicky@users.noreply.github.com>
Signed-off-by: Ondřej Lisický <87812906+olisicky@users.noreply.github.com>
Signed-off-by: Ondřej Lisický <87812906+olisicky@users.noreply.github.com>
Signed-off-by: Ondřej Lisický <87812906+olisicky@users.noreply.github.com>
Signed-off-by: Ondřej Lisický <87812906+olisicky@users.noreply.github.com>
Signed-off-by: Ondřej Lisický <87812906+olisicky@users.noreply.github.com>
0d62522 to
2eabdac
Compare
|
Hi @olisicky, I just merged the pr. Sorry this took so long, but glad we were able to get it in. |
This PR adds tool-use support for data generations. Many applications uses tool-use and training speculators with such data might be beneficial for the model.