Taimwe commited on
Commit
57da0ae
·
verified ·
1 Parent(s): 734d7ea

Eval v4: Hermes JSON-string tools, merged-repo loader, eos_token_id

Browse files
Files changed (1) hide show
  1. eval_securecoder.py +66 -82
eval_securecoder.py CHANGED
@@ -5,8 +5,7 @@
5
  # "huggingface_hub",
6
  # "torch",
7
  # "transformers",
8
- # "peft",
9
- # "unsloth",
10
  # ]
11
  # ///
12
  """Cheap evaluations for SecureCoder.
@@ -94,18 +93,39 @@ _TYPE_ALIASES = {
94
 
95
 
96
  def _normalise_tool_schema(tool):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  if not isinstance(tool, dict):
98
  return None
99
  fn = tool.get("function") if isinstance(tool.get("function"), dict) else tool
100
  if not isinstance(fn, dict) or not fn.get("name"):
101
  return None
102
  params = fn.get("parameters") or {}
 
 
 
 
 
103
  if isinstance(params, dict) and params and "properties" not in params:
104
  properties: dict[str, Any] = {}
105
  required: list[str] = []
106
  for name, spec in params.items():
107
  if isinstance(spec, dict):
108
- cleaned = {k: v for k, v in spec.items() if k in ("type", "description", "enum", "default", "title", "items")}
 
109
  cleaned["type"] = _TYPE_ALIASES.get(str(cleaned.get("type", "")).lower(), "string")
110
  properties[name] = cleaned
111
  if "default" not in spec:
@@ -120,95 +140,55 @@ def _normalise_tool_schema(tool):
120
 
121
 
122
  def _messages_from_any(row, kind):
 
 
 
 
 
 
 
 
 
123
  tools: list[dict] = []
124
- raw = row.get("tools")
125
- candidates = [raw] if isinstance(raw, dict) else (raw if isinstance(raw, list) else [])
126
  for c in candidates:
127
  n = _normalise_tool_schema(c)
128
  if n:
129
  tools.append(n)
 
130
  messages: list[dict] = []
131
- if isinstance(row.get("messages"), list):
132
- for turn in row["messages"]:
 
 
 
 
 
 
 
 
133
  if not isinstance(turn, dict):
134
  continue
135
- role = turn.get("role")
136
- content = turn.get("content", "")
137
- if role in {"user", "assistant", "system"} and content:
138
- messages.append({"role": role, "content": str(content)})
139
- return messages, tools
140
- if row.get("query") and row.get("answers"):
141
- messages.append({"role": "user", "content": str(row["query"])})
142
- messages.append({"role": "assistant", "content": str(row["answers"])})
143
- return messages, tools
144
- if row.get("user") and row.get("assistant"):
145
- if row.get("system"):
146
- messages.append({"role": "system", "content": str(row["system"])})
147
- messages.append({"role": "user", "content": str(row["user"])})
148
- messages.append({"role": "assistant", "content": str(row["assistant"])})
149
- return messages, tools
150
- if row.get("instruction") and (row.get("output") or row.get("response")):
151
- messages.append({"role": "user", "content": str(row["instruction"])})
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]:
159
- from datasets import load_dataset
160
- kwargs: dict[str, Any] = {"split": split, "streaming": True}
161
- if config:
162
- kwargs["name"] = config
163
- ds = load_dataset(repo, token=os.environ.get("HF_TOKEN"), **kwargs)
164
- out = []
165
- for row in ds:
166
- out.append(dict(row))
167
- if len(out) >= n:
168
- break
169
- return out
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
183
- tools: list[dict] = []
184
- if isinstance(tools_raw, list):
185
- for t in tools_raw:
186
- n = _normalise_tool_schema(t)
187
- if n:
188
- tools.append(n)
189
- elif isinstance(tools_raw, dict):
190
- n = _normalise_tool_schema(tools_raw)
191
- if n:
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
@@ -266,7 +246,7 @@ def eval_tool_calls(tokenizer, model, args) -> dict:
266
  text = _render_prompt(tokenizer, p["prompt"], p["tools"])
267
  ids = tokenizer(text, return_tensors="pt", add_special_tokens=False).input_ids.to(model.device)
268
  with torch.no_grad():
269
- generated = model.generate(ids, max_new_tokens=args.max_new_tokens, do_sample=False)
270
  reply = tokenizer.decode(generated[0, ids.shape[-1]:], skip_special_tokens=True)
271
  except Exception as exc:
272
  out.append({"prompt": p["prompt"][:60], "error": repr(exc)[:120]})
@@ -299,7 +279,7 @@ def eval_code_sanity(tokenizer, model, args) -> dict:
299
  ids = tokenizer(text, return_tensors="pt", add_special_tokens=False).input_ids.to(model.device)
300
  try:
301
  with torch.no_grad():
302
- generated = model.generate(ids, max_new_tokens=384, do_sample=False)
303
  reply = tokenizer.decode(generated[0, ids.shape[-1]:], skip_special_tokens=True)
304
  except Exception as exc:
305
  out.append({"prompt": prompt[:60], "error": repr(exc)[:120]})
@@ -372,7 +352,7 @@ def eval_security_mcq(tokenizer, model, n_questions: int = 25) -> dict:
372
  ids = tokenizer(text, return_tensors="pt", add_special_tokens=False).input_ids.to(model.device)
373
  try:
374
  with torch.no_grad():
375
- generated = model.generate(ids, max_new_tokens=8, do_sample=False)
376
  reply = tokenizer.decode(generated[0, ids.shape[-1]:], skip_special_tokens=True).strip()
377
  except Exception:
378
  continue
@@ -448,3 +428,7 @@ def main() -> int:
448
 
449
  if __name__ == "__main__":
450
  raise SystemExit(main())
 
 
 
 
 
5
  # "huggingface_hub",
6
  # "torch",
7
  # "transformers",
8
+ # "bitsandbytes",
 
9
  # ]
10
  # ///
11
  """Cheap evaluations for SecureCoder.
 
93
 
94
 
95
  def _normalise_tool_schema(tool):
96
+ """Accept tool specs as a dict, a list of dicts, or a JSON string. Hermes
97
+ ships ``tools`` as a JSON string; xLAM and most others use a list/dict."""
98
+ if tool is None:
99
+ return None
100
+ if isinstance(tool, str):
101
+ try:
102
+ tool = json.loads(tool)
103
+ except (json.JSONDecodeError, TypeError):
104
+ return None
105
+ if isinstance(tool, list):
106
+ for entry in tool:
107
+ norm = _normalise_tool_schema(entry)
108
+ if norm:
109
+ return norm
110
+ return None
111
  if not isinstance(tool, dict):
112
  return None
113
  fn = tool.get("function") if isinstance(tool.get("function"), dict) else tool
114
  if not isinstance(fn, dict) or not fn.get("name"):
115
  return None
116
  params = fn.get("parameters") or {}
117
+ if isinstance(params, str):
118
+ try:
119
+ params = json.loads(params)
120
+ except (json.JSONDecodeError, TypeError):
121
+ params = {}
122
  if isinstance(params, dict) and params and "properties" not in params:
123
  properties: dict[str, Any] = {}
124
  required: list[str] = []
125
  for name, spec in params.items():
126
  if isinstance(spec, dict):
127
+ cleaned = {k: v for k, v in spec.items()
128
+ if k in ("type", "description", "enum", "default", "title", "items")}
129
  cleaned["type"] = _TYPE_ALIASES.get(str(cleaned.get("type", "")).lower(), "string")
130
  properties[name] = cleaned
131
  if "default" not in spec:
 
140
 
141
 
142
  def _messages_from_any(row, kind):
143
+ """Extract a single user turn from a Hermes ``conversations`` row (from/value)
144
+ or an OpenAI-style ``messages`` row (role/content). Tools can be a list, dict,
145
+ or JSON string - we keep the first valid schema."""
146
+ tools_raw = row.get("tools")
147
+ if isinstance(tools_raw, str):
148
+ try:
149
+ tools_raw = json.loads(tools_raw)
150
+ except (json.JSONDecodeError, TypeError):
151
+ tools_raw = None
152
  tools: list[dict] = []
153
+ candidates = tools_raw if isinstance(tools_raw, list) else ([tools_raw] if isinstance(tools_raw, dict) else [])
 
154
  for c in candidates:
155
  n = _normalise_tool_schema(c)
156
  if n:
157
  tools.append(n)
158
+
159
  messages: list[dict] = []
160
+ convo = row.get("messages") or row.get("conversations") or []
161
+ if isinstance(convo, str):
162
+ try:
163
+ convo = json.loads(convo)
164
+ except (json.JSONDecodeError, TypeError):
165
+ convo = []
166
+ ROLE_MAP = {"human": "user", "system": "system", "gpt": "assistant",
167
+ "user": "user", "assistant": "assistant"}
168
+ if isinstance(convo, list):
169
+ for turn in convo:
170
  if not isinstance(turn, dict):
171
  continue
172
+ role_raw = str(turn.get("from") or turn.get("role") or "").lower()
173
+ role = ROLE_MAP.get(role_raw)
174
+ if role == "user":
175
+ content = turn.get("value", turn.get("content", ""))
176
+ messages.append({"role": "user", "content": str(content)})
177
+ break
178
+ return messages, tools
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  def _build_tool_prompts(rows: list[dict]) -> list[dict]:
180
+ """Build (prompt, tools) tuples via the shared _messages_from_any helper.
181
+ Hermes rows use ``conversations`` (from/value); OpenAI-style use ``messages``
182
+ (role/content). Both are accepted transparently."""
 
 
183
  prompts = []
184
  for row in rows:
185
+ messages, tools = _messages_from_any(row, "auto")
186
+ if not tools or not messages:
 
 
 
 
 
 
 
 
 
 
 
 
187
  continue
188
+ user = next((m["content"] for m in messages if m["role"] == "user"), None)
189
+ if not user:
 
 
 
 
 
 
 
 
 
 
 
 
190
  continue
191
+ prompts.append({"prompt": str(user)[:1200], "tools": tools,
 
192
  "expected_call": True})
193
  if len(prompts) >= 200:
194
  break
 
246
  text = _render_prompt(tokenizer, p["prompt"], p["tools"])
247
  ids = tokenizer(text, return_tensors="pt", add_special_tokens=False).input_ids.to(model.device)
248
  with torch.no_grad():
249
+ generated = model.generate(ids, max_new_tokens=args.max_new_tokens, do_sample=False, eos_token_id=tokenizer.eos_token_id, pad_token_id=tokenizer.eos_token_id)
250
  reply = tokenizer.decode(generated[0, ids.shape[-1]:], skip_special_tokens=True)
251
  except Exception as exc:
252
  out.append({"prompt": p["prompt"][:60], "error": repr(exc)[:120]})
 
279
  ids = tokenizer(text, return_tensors="pt", add_special_tokens=False).input_ids.to(model.device)
280
  try:
281
  with torch.no_grad():
282
+ generated = model.generate(ids, max_new_tokens=384, do_sample=False, eos_token_id=tokenizer.eos_token_id, pad_token_id=tokenizer.eos_token_id)
283
  reply = tokenizer.decode(generated[0, ids.shape[-1]:], skip_special_tokens=True)
284
  except Exception as exc:
285
  out.append({"prompt": prompt[:60], "error": repr(exc)[:120]})
 
352
  ids = tokenizer(text, return_tensors="pt", add_special_tokens=False).input_ids.to(model.device)
353
  try:
354
  with torch.no_grad():
355
+ generated = model.generate(ids, max_new_tokens=8, do_sample=False, eos_token_id=tokenizer.eos_token_id, pad_token_id=tokenizer.eos_token_id)
356
  reply = tokenizer.decode(generated[0, ids.shape[-1]:], skip_special_tokens=True).strip()
357
  except Exception:
358
  continue
 
428
 
429
  if __name__ == "__main__":
430
  raise SystemExit(main())
431
+
432
+
433
+
434
+