vanishingradient commited on
Commit
c658700
·
verified ·
1 Parent(s): 0ecc38b

TRL: inject text/JSON tool calls + forced ls into tool loop

Browse files
Files changed (1) hide show
  1. kaggle/train_grpo.py +87 -1
kaggle/train_grpo.py CHANGED
@@ -282,6 +282,90 @@ def _parse_tool_calls(text: str) -> list[dict[str, Any]]:
282
  return calls
283
 
284
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
  def run_heldout_agent_eval(
286
  model,
287
  tokenizer,
@@ -1385,14 +1469,16 @@ def main(cfg: Cfg | None = None) -> None:
1385
  max_eval_turns=cfg.max_eval_turns,
1386
  )
1387
 
 
1388
  _vlog(
1389
  "main.trainer_init",
1390
  environment="DeepAgentSWEEnv",
 
1391
  max_steps=cfg.max_steps,
1392
  num_generations=cfg.num_generations,
1393
  max_tool_calling_iterations=cfg.max_tool_calling_iterations,
1394
  )
1395
- trainer = GRPOTrainer(
1396
  model=model,
1397
  reward_funcs=[log_completions_reward], # logs only; env get_reward still scores SWE
1398
  args=args,
 
282
  return calls
283
 
284
 
285
+ def _parsed_to_openai_tool_calls(parsed: list[dict[str, Any]]) -> list[dict[str, Any]]:
286
+ out = []
287
+ for i, p in enumerate(parsed):
288
+ args = p.get("arguments") if isinstance(p.get("arguments"), dict) else {}
289
+ out.append(
290
+ {
291
+ "id": f"call_text_{i}",
292
+ "type": "function",
293
+ "function": {
294
+ "name": str(p["name"]),
295
+ "arguments": args,
296
+ },
297
+ }
298
+ )
299
+ return out
300
+
301
+
302
+ def _inject_text_tool_calls_into_completions(
303
+ completions: list[Any],
304
+ *,
305
+ force_ls_if_empty: bool = True,
306
+ ) -> tuple[list[Any], int]:
307
+ """TRL only runs tools from native ``tool_calls``. Qwen often emits markdown JSON — promote it."""
308
+ injected = 0
309
+ fixed: list[Any] = []
310
+ for comp in completions:
311
+ # conversational: [[{role, content, tool_calls?}]]
312
+ if (
313
+ isinstance(comp, list)
314
+ and comp
315
+ and isinstance(comp[0], dict)
316
+ and comp[0].get("role") == "assistant"
317
+ ):
318
+ msg = dict(comp[0])
319
+ existing = msg.get("tool_calls") or []
320
+ if existing:
321
+ fixed.append([msg] + list(comp[1:]))
322
+ continue
323
+ content = str(msg.get("content") or "")
324
+ parsed = _parse_tool_calls(content)
325
+ if not parsed and force_ls_if_empty:
326
+ # Last resort so the GRPO group is not all "DONE"/no-tools.
327
+ parsed = [{"name": "ls", "arguments": {"path": "."}}]
328
+ msg["content"] = (content + "\n").strip() + "\n(forced tool: ls path='.')"
329
+ if parsed:
330
+ msg["tool_calls"] = _parsed_to_openai_tool_calls(parsed)
331
+ injected += 1
332
+ _vlog(
333
+ "train.inject_text_tool_calls",
334
+ n=len(parsed),
335
+ names=[p["name"] for p in parsed],
336
+ forced=not bool(_parse_tool_calls(content)),
337
+ )
338
+ fixed.append([msg] + list(comp[1:]))
339
+ else:
340
+ fixed.append(comp)
341
+ return fixed, injected
342
+
343
+
344
+ def make_tool_repair_grpo_trainer(base_cls: type):
345
+ """Subclass GRPOTrainer to execute markdown/JSON tool blobs + forced ls."""
346
+
347
+ class ToolRepairGRPOTrainer(base_cls): # type: ignore[misc,valid-type]
348
+ def _tool_call_loop(self, prompts, prompt_ids, completion_ids, completions, logprobs, images, multimodal_fields):
349
+ force = os.getenv("DARL_FORCE_FIRST_TOOL", "1").strip() not in ("0", "false", "no")
350
+ completions, n_inj = _inject_text_tool_calls_into_completions(
351
+ completions, force_ls_if_empty=force
352
+ )
353
+ if n_inj:
354
+ _vlog("train.tool_repair.injected", n=n_inj, force=force)
355
+ return super()._tool_call_loop(
356
+ prompts,
357
+ prompt_ids,
358
+ completion_ids,
359
+ completions,
360
+ logprobs,
361
+ images,
362
+ multimodal_fields,
363
+ )
364
+
365
+ ToolRepairGRPOTrainer.__name__ = "ToolRepairGRPOTrainer"
366
+ return ToolRepairGRPOTrainer
367
+
368
+
369
  def run_heldout_agent_eval(
370
  model,
371
  tokenizer,
 
1469
  max_eval_turns=cfg.max_eval_turns,
1470
  )
1471
 
1472
+ TrainerCls = make_tool_repair_grpo_trainer(GRPOTrainer)
1473
  _vlog(
1474
  "main.trainer_init",
1475
  environment="DeepAgentSWEEnv",
1476
+ trainer=TrainerCls.__name__,
1477
  max_steps=cfg.max_steps,
1478
  num_generations=cfg.num_generations,
1479
  max_tool_calling_iterations=cfg.max_tool_calling_iterations,
1480
  )
1481
+ trainer = TrainerCls(
1482
  model=model,
1483
  reward_funcs=[log_completions_reward], # logs only; env get_reward still scores SWE
1484
  args=args,