3737- https://www.stephendiehl.com/posts/fine_tuning_tools/
3838"""
3939
40+ import json
4041import logging
4142
4243from typing import Any
@@ -94,15 +95,17 @@ def get_config_model(self) -> type[BaseModel] | None:
9495
9596 def validate (self , sample : dict ) -> bool :
9697 """Validate that sample has required fields for TRL format."""
97- # Must have messages
98- if "messages" not in sample or not isinstance (sample ["messages" ], list ):
99- return False
98+ # Accept either messages format OR agent_cot_tools format
10099
101- # Must have at least one message
102- # Return whether there is at least one message
103- # Should have available_tools for tool calling
104- # (though we'll still process samples without it)
105- return len (sample ["messages" ]) != 0
100+ # Check for messages format
101+ if "messages" in sample and isinstance (sample ["messages" ], list ):
102+ return len (sample ["messages" ]) > 0
103+
104+ # Check for agent_cot_tools format (question + tool_used + answer/final_answer)
105+ if "question" in sample and "tool_used" in sample :
106+ return "answer" in sample or "final_answer" in sample
107+
108+ return False
106109
107110 def _format_single_sample (self , sample : dict ) -> dict | None :
108111 """
@@ -124,6 +127,10 @@ def _format_single_sample(self, sample: dict) -> dict | None:
124127 else TRLSFTToolsConfig ()
125128 )
126129
130+ # Convert agent_cot_tools format to messages format if needed
131+ if "messages" not in sample :
132+ sample = self ._convert_agent_to_messages (sample , config )
133+
127134 # Start with a copy of the sample
128135 formatted_sample = sample .copy ()
129136
@@ -172,6 +179,89 @@ def _format_single_sample(self, sample: dict) -> dict | None:
172179
173180 return formatted_sample
174181
182+ def _convert_agent_to_messages (
183+ self , sample : dict , config : TRLSFTToolsConfig
184+ ) -> dict :
185+ """
186+ Convert agent_cot_tools format to messages format.
187+
188+ Args:
189+ sample: Sample in agent_cot_tools format
190+ config: Formatter configuration
191+
192+ Returns:
193+ Sample with messages field
194+ """
195+ messages = []
196+
197+ # Add system message if configured
198+ if config .include_system_prompt :
199+ system_content = config .system_prompt_override or (
200+ "You are a helpful AI assistant with access to various tools and functions. "
201+ "When a user asks a question that requires information or actions you cannot "
202+ "directly provide, use the available tools to help answer the question."
203+ )
204+ messages .append ({"role" : "system" , "content" : system_content })
205+
206+ # Add user question
207+ question = sample .get ("question" , "" )
208+ messages .append ({"role" : "user" , "content" : question })
209+
210+ # Extract tool usage information
211+ tool_used = sample .get ("tool_used" , "" )
212+ tool_input = sample .get ("tool_input" , "{}" )
213+ tool_output = sample .get ("tool_output" , "" )
214+ answer = sample .get ("answer" ) or sample .get ("final_answer" , "" )
215+
216+ # Parse tool input
217+ if isinstance (tool_input , str ):
218+ try :
219+ # First try parsing as standard JSON
220+ tool_args = json .loads (tool_input )
221+ except json .JSONDecodeError :
222+ try :
223+ # Fallback: try replacing single quotes with double quotes
224+ tool_args = json .loads (tool_input .replace ("'" , '"' ))
225+ except json .JSONDecodeError :
226+ # Final fallback: wrap in a simple structure
227+ tool_args = {"input" : tool_input }
228+ else :
229+ tool_args = tool_input
230+
231+ # Add assistant message with tool call
232+ # Using OpenAI-compatible function calling format
233+ tool_call = {
234+ "id" : "call_1" , # Placeholder ID
235+ "type" : "function" ,
236+ "function" : {"name" : tool_used , "arguments" : json .dumps (tool_args )},
237+ }
238+
239+ messages .append (
240+ {
241+ "role" : "assistant" ,
242+ "content" : None ,
243+ "tool_calls" : [tool_call ],
244+ }
245+ )
246+
247+ # Add tool response message
248+ messages .append (
249+ {
250+ "role" : "tool" ,
251+ "content" : str (tool_output ),
252+ "tool_call_id" : "call_1" ,
253+ }
254+ )
255+
256+ # Add final assistant answer
257+ messages .append ({"role" : "assistant" , "content" : answer })
258+
259+ # Return sample with messages and preserve available_tools
260+ return {
261+ "messages" : messages ,
262+ "available_tools" : sample .get ("available_tools" , []),
263+ }
264+
175265 def _validate_tool_schemas (self , tools : list [dict ]) -> None :
176266 """
177267 Validate that tool schemas are properly formatted for TRL.
0 commit comments