Taimwe commited on
Commit
734d7ea
·
verified ·
1 Parent(s): 6a38ac4

Eval v3: Hermes conv support + longest-block AST

Browse files
Files changed (1) hide show
  1. eval_securecoder.py +45 -26
eval_securecoder.py CHANGED
@@ -1,4 +1,4 @@
1
- # /// script
2
  # requires-python = ">=3.10"
3
  # dependencies = [
4
  # "datasets",
@@ -67,7 +67,7 @@ CODE_PROMPTS: list[str] = [
67
 
68
  TOOL_FN_PAT = re.compile(r"<function=([A-Za-z0-9_\.]+)>", re.S)
69
  TOOL_PARAM_PAT = re.compile(r"<parameter=([A-Za-z0-9_]+)>\s*(.*?)\s*</parameter>", re.S)
70
-
71
 
72
 
73
  def parse_args() -> argparse.Namespace:
@@ -81,7 +81,7 @@ def parse_args() -> argparse.Namespace:
81
  p.add_argument("--max-new-tokens", type=int, default=384)
82
  p.add_argument("--seed", type=int, default=3407)
83
  return p.parse_args()
84
-
85
 
86
 
87
  _TYPE_ALIASES = {
@@ -152,7 +152,7 @@ def _messages_from_any(row, kind):
152
  messages.append({"role": "assistant", "content": str(row.get("output") or row.get("response"))})
153
  return messages, tools
154
  return [], tools
155
-
156
 
157
 
158
  def _fetch_first_rows(repo: str, config: str | None, split: str, n: int) -> list[dict]:
@@ -170,10 +170,13 @@ def _fetch_first_rows(repo: str, config: str | None, split: str, n: int) -> list
170
 
171
 
172
  def _build_tool_prompts(rows: list[dict]) -> list[dict]:
 
 
 
 
 
173
  prompts = []
174
  for row in rows:
175
- if not isinstance(row.get("messages"), list):
176
- continue
177
  tools_raw = row.get("tools")
178
  if not tools_raw:
179
  continue
@@ -189,19 +192,27 @@ def _build_tool_prompts(rows: list[dict]) -> list[dict]:
189
  tools.append(n)
190
  if not tools:
191
  continue
192
- messages, _ = _messages_from_any(row, "auto")
193
- if not messages:
194
- continue
195
- user = next((m["content"] for m in messages if m["role"] == "user"), None)
196
- if not user:
 
 
 
 
 
 
 
 
 
197
  continue
198
- prompts.append({"prompt": str(user)[:1200], "tools": tools,
199
- "expected_call": any(m.get("tool_calls") for m in messages)})
 
200
  if len(prompts) >= 200:
201
  break
202
  return prompts
203
-
204
-
205
  def _render_prompt(tokenizer, prompt: str, tools: list[dict]) -> str:
206
  return tokenizer.apply_chat_template(
207
  [{"role": "user", "content": prompt}],
@@ -236,7 +247,7 @@ def _score_call(call: dict, tools: list[dict]) -> dict:
236
  return {"parse": True, "name_ok": True,
237
  "schema_ok": expected.issubset(given) if expected else True,
238
  "expected_keys": sorted(expected), "given_keys": sorted(given)}
239
-
240
 
241
 
242
  def eval_tool_calls(tokenizer, model, args) -> dict:
@@ -294,26 +305,34 @@ def eval_code_sanity(tokenizer, model, args) -> dict:
294
  out.append({"prompt": prompt[:60], "error": repr(exc)[:120]})
295
  continue
296
  block = None
297
- m = re.search(r"```(?:python)?\s*\n(.*?)```", reply, re.S)
298
- if m:
299
- block = m.group(1)
 
 
 
 
300
  else:
301
  start = reply.find("def ")
302
  if start >= 0:
303
  block = reply[start:]
 
 
 
 
304
  parsed = compiles = None
305
  if block:
306
  try:
307
- ast.parse(block); parsed = True
 
308
  except SyntaxError:
309
- parsed = False; block = None
310
- if block:
311
  try:
312
- compile(block, "<eval>", "exec"); compiles = True
 
313
  except Exception:
314
  compiles = False
315
- out.append({"prompt": prompt[:60], "ast_ok": parsed, "compile_ok": compiles,
316
- "reply_first_160": reply[:160]})
317
  if (i + 1) % 5 == 0:
318
  log.info(" code sanity: %d/%d", i + 1, len(prompts))
319
  n = len(out)
@@ -322,7 +341,7 @@ def eval_code_sanity(tokenizer, model, args) -> dict:
322
  return {"section": "code_sanity", "n_prompts": n,
323
  "ast_rate": ast_ok / max(n, 1), "compile_rate": compile_ok / max(n, 1),
324
  "details": out}
325
-
326
 
327
 
328
  def eval_security_mcq(tokenizer, model, n_questions: int = 25) -> dict:
 
1
+ # /// script
2
  # requires-python = ">=3.10"
3
  # dependencies = [
4
  # "datasets",
 
67
 
68
  TOOL_FN_PAT = re.compile(r"<function=([A-Za-z0-9_\.]+)>", re.S)
69
  TOOL_PARAM_PAT = re.compile(r"<parameter=([A-Za-z0-9_]+)>\s*(.*?)\s*</parameter>", re.S)
70
+
71
 
72
 
73
  def parse_args() -> argparse.Namespace:
 
81
  p.add_argument("--max-new-tokens", type=int, default=384)
82
  p.add_argument("--seed", type=int, default=3407)
83
  return p.parse_args()
84
+
85
 
86
 
87
  _TYPE_ALIASES = {
 
152
  messages.append({"role": "assistant", "content": str(row.get("output") or row.get("response"))})
153
  return messages, tools
154
  return [], tools
155
+
156
 
157
 
158
  def _fetch_first_rows(repo: str, config: str | None, split: str, n: int) -> list[dict]:
 
170
 
171
 
172
  def _build_tool_prompts(rows: list[dict]) -> list[dict]:
173
+ """Build (prompt, tools) tuples from a mix of Hermes-style ``conversations``
174
+ (with ``from``/``value`` keys) and OpenAI-style ``messages`` rows.
175
+ The Hermes rows are by far the more common in NousResearch/hermes-function-calling-v1."""
176
+ ROLE_MAP = {"human": "user", "system": "system", "gpt": "assistant",
177
+ "user": "user", "assistant": "assistant"}
178
  prompts = []
179
  for row in rows:
 
 
180
  tools_raw = row.get("tools")
181
  if not tools_raw:
182
  continue
 
192
  tools.append(n)
193
  if not tools:
194
  continue
195
+
196
+ user_content = None
197
+ convo = row.get("messages") or row.get("conversations") or []
198
+ if isinstance(convo, list):
199
+ for turn in convo:
200
+ if not isinstance(turn, dict):
201
+ continue
202
+ role_raw = str(turn.get("from") or turn.get("role") or "").lower()
203
+ role = ROLE_MAP.get(role_raw)
204
+ if role != "user":
205
+ continue
206
+ user_content = turn.get("value", turn.get("content", ""))
207
+ break
208
+ if not user_content:
209
  continue
210
+
211
+ prompts.append({"prompt": str(user_content)[:1200], "tools": tools,
212
+ "expected_call": True})
213
  if len(prompts) >= 200:
214
  break
215
  return prompts
 
 
216
  def _render_prompt(tokenizer, prompt: str, tools: list[dict]) -> str:
217
  return tokenizer.apply_chat_template(
218
  [{"role": "user", "content": prompt}],
 
247
  return {"parse": True, "name_ok": True,
248
  "schema_ok": expected.issubset(given) if expected else True,
249
  "expected_keys": sorted(expected), "given_keys": sorted(given)}
250
+
251
 
252
 
253
  def eval_tool_calls(tokenizer, model, args) -> dict:
 
305
  out.append({"prompt": prompt[:60], "error": repr(exc)[:120]})
306
  continue
307
  block = None
308
+ # Truncation inside a code block is common (max_new_tokens cuts
309
+ # mid-line), so we keep the LONGEST ```python ... ``` block in the
310
+ # reply rather than the first one. Most failures in the first eval
311
+ # run were valid code cut mid-comment, not bad code.
312
+ blocks = re.findall(r"```(?:python)?\s*\n(.*?)```", reply, re.S)
313
+ if blocks:
314
+ block = max(blocks, key=len)
315
  else:
316
  start = reply.find("def ")
317
  if start >= 0:
318
  block = reply[start:]
319
+ if block:
320
+ # Strip anything past a closing fence on the same line
321
+ block = re.split(r"\n```", block, maxsplit=1)[0]
322
+
323
  parsed = compiles = None
324
  if block:
325
  try:
326
+ ast.parse(block)
327
+ parsed = True
328
  except SyntaxError:
329
+ parsed = False
330
+ if block and parsed:
331
  try:
332
+ compile(block, "<eval>", "exec")
333
+ compiles = True
334
  except Exception:
335
  compiles = False
 
 
336
  if (i + 1) % 5 == 0:
337
  log.info(" code sanity: %d/%d", i + 1, len(prompts))
338
  n = len(out)
 
341
  return {"section": "code_sanity", "n_prompts": n,
342
  "ast_rate": ast_ok / max(n, 1), "compile_rate": compile_ok / max(n, 1),
343
  "details": out}
344
+
345
 
346
 
347
  def eval_security_mcq(tokenizer, model, n_questions: int = 25) -> dict: