evalstate HF Staff commited on
Commit
df35eb8
·
verified ·
1 Parent(s): b99177b

Block raw calls for helper-covered endpoint families

Browse files
Files changed (2) hide show
  1. _monty_codegen_shared.md +1 -0
  2. monty_api_tool_v2.py +34 -0
_monty_codegen_shared.md CHANGED
@@ -6,6 +6,7 @@
6
  - Use helper functions first. Use raw `call_api('/api/...')` only if no helper fits.
7
  - `call_api` must receive a raw path starting with `/api/...`; never call helper names through `call_api`.
8
  - `call_api(...)` returns `{ok, status, url, data, error}`. Always check `resp["ok"]` before reading `resp["data"]`. Do not read `resp["items"]` or `resp["meta"]` directly from `call_api(...)`.
 
9
  - Keep final displayed results compact, but do not artificially shrink intermediate helper coverage unless the user explicitly asked for a sample.
10
  - Prefer canonical snake_case keys in generated code and in JSON output.
11
  - When returning a structured dict that includes your own coverage metadata, use the exact top-level keys `results` and `coverage` unless the user explicitly requested different key names.
 
6
  - Use helper functions first. Use raw `call_api('/api/...')` only if no helper fits.
7
  - `call_api` must receive a raw path starting with `/api/...`; never call helper names through `call_api`.
8
  - `call_api(...)` returns `{ok, status, url, data, error}`. Always check `resp["ok"]` before reading `resp["data"]`. Do not read `resp["items"]` or `resp["meta"]` directly from `call_api(...)`.
9
+ - Use `call_api(...)` only for endpoint families that do not already have a helper, such as `/api/daily_papers` or tag metadata endpoints.
10
  - Keep final displayed results compact, but do not artificially shrink intermediate helper coverage unless the user explicitly asked for a sample.
11
  - Prefer canonical snake_case keys in generated code and in JSON output.
12
  - When returning a structured dict that includes your own coverage metadata, use the exact top-level keys `results` and `coverage` unless the user explicitly requested different key names.
monty_api_tool_v2.py CHANGED
@@ -295,6 +295,27 @@ HELPER_EXTERNALS = (
295
  "hf_collection_items",
296
  )
297
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
298
 
299
  def _resolve_helper_functions(namespace: dict[str, Any]) -> dict[str, Callable[..., Any]]:
300
  resolved: dict[str, Callable[..., Any]] = {}
@@ -943,6 +964,12 @@ def _validate_generated_code(code: str) -> None:
943
  if not valid_final_await:
944
  raise ValueError("Generated code must end with `await solve(query, max_calls)`.")
945
 
 
 
 
 
 
 
946
  def _call_api_endpoint_hint(expr: ast.AST | None) -> str | None:
947
  if isinstance(expr, ast.Constant) and isinstance(expr.value, str):
948
  return expr.value
@@ -971,6 +998,10 @@ def _validate_generated_code(code: str) -> None:
971
  endpoint_hint = _call_api_endpoint_hint(endpoint_expr)
972
  if endpoint_hint and "/api/collections/" in endpoint_hint and "/items" in endpoint_hint:
973
  raise ValueError("Use `hf_collection_items(...)` for collection contents instead of guessing `/api/collections/.../items`.")
 
 
 
 
974
 
975
  allowed_external_calls = ["call_api(", *[f"{name}(" for name in HELPER_EXTERNALS]]
976
  if not any(token in code for token in allowed_external_calls):
@@ -990,6 +1021,9 @@ def _validate_generated_code(code: str) -> None:
990
  raise ValueError("Do not call helper names through call_api; call hf_* helpers directly.")
991
  if re.match(r"^/api/collections/(?:[^/]+/)?[^/]+/items$", endpoint_literal):
992
  raise ValueError("Use `hf_collection_items(...)` for collection contents instead of guessing `/api/collections/.../items`.")
 
 
 
993
  if not endpoint_literal.startswith("/api/"):
994
  raise ValueError("call_api endpoint must be a raw path starting with '/api/...'.")
995
 
 
295
  "hf_collection_items",
296
  )
297
 
298
+ HELPER_COVERED_ENDPOINT_PATTERNS: list[tuple[str, str]] = [
299
+ (r"^/api/whoami-v2$", "hf_whoami"),
300
+ (r"^/api/trending$", "hf_trending"),
301
+ (r"^/api/recent-activity$", "hf_recent_activity"),
302
+ (r"^/api/models$", "hf_repo_search"),
303
+ (r"^/api/datasets$", "hf_repo_search"),
304
+ (r"^/api/spaces$", "hf_repo_search"),
305
+ (r"^/api/(models|datasets|spaces)/[^/]+/[^/]+$", "hf_repo_details"),
306
+ (r"^/api/(models|datasets|spaces)/[^/]+/[^/]+/discussions$", "hf_repo_discussions"),
307
+ (r"^/api/(models|datasets|spaces)/[^/]+/[^/]+/discussions/\d+$", "hf_repo_discussion_details"),
308
+ (r"^/api/(models|datasets|spaces)/(?:[^/]+|[^/]+/[^/]+)/likers$", "hf_repo_likers"),
309
+ (r"^/api/users/[^/]+/likes$", "hf_user_likes"),
310
+ (r"^/api/users/[^/]+/(followers|following)$", "hf_user_graph"),
311
+ (r"^/api/organizations/[^/]+/members$", "hf_org_members"),
312
+ (r"^/api/organizations/[^/]+/overview$", "hf_org_overview"),
313
+ (r"^/api/organizations/[^/]+/followers$", "hf_user_graph"),
314
+ (r"^/api/collections$", "hf_collections_search"),
315
+ (r"^/api/collections/[^/]+$", "hf_collection_items"),
316
+ (r"^/api/collections/[^/]+/[^/]+$", "hf_collection_items"),
317
+ ]
318
+
319
 
320
  def _resolve_helper_functions(namespace: dict[str, Any]) -> dict[str, Callable[..., Any]]:
321
  resolved: dict[str, Callable[..., Any]] = {}
 
964
  if not valid_final_await:
965
  raise ValueError("Generated code must end with `await solve(query, max_calls)`.")
966
 
967
+ def _preferred_helper_for_endpoint(endpoint: str) -> str | None:
968
+ for pattern, helper_name in HELPER_COVERED_ENDPOINT_PATTERNS:
969
+ if re.match(pattern, endpoint):
970
+ return helper_name
971
+ return None
972
+
973
  def _call_api_endpoint_hint(expr: ast.AST | None) -> str | None:
974
  if isinstance(expr, ast.Constant) and isinstance(expr.value, str):
975
  return expr.value
 
998
  endpoint_hint = _call_api_endpoint_hint(endpoint_expr)
999
  if endpoint_hint and "/api/collections/" in endpoint_hint and "/items" in endpoint_hint:
1000
  raise ValueError("Use `hf_collection_items(...)` for collection contents instead of guessing `/api/collections/.../items`.")
1001
+ if endpoint_hint:
1002
+ preferred_helper = _preferred_helper_for_endpoint(endpoint_hint)
1003
+ if preferred_helper is not None:
1004
+ raise ValueError(f"Use `{preferred_helper}(...)` instead of `call_api({endpoint_hint!r}, ...)` for this endpoint family.")
1005
 
1006
  allowed_external_calls = ["call_api(", *[f"{name}(" for name in HELPER_EXTERNALS]]
1007
  if not any(token in code for token in allowed_external_calls):
 
1021
  raise ValueError("Do not call helper names through call_api; call hf_* helpers directly.")
1022
  if re.match(r"^/api/collections/(?:[^/]+/)?[^/]+/items$", endpoint_literal):
1023
  raise ValueError("Use `hf_collection_items(...)` for collection contents instead of guessing `/api/collections/.../items`.")
1024
+ preferred_helper = _preferred_helper_for_endpoint(endpoint_literal)
1025
+ if preferred_helper is not None:
1026
+ raise ValueError(f"Use `{preferred_helper}(...)` instead of `call_api({endpoint_literal!r}, ...)` for this endpoint family.")
1027
  if not endpoint_literal.startswith("/api/"):
1028
  raise ValueError("call_api endpoint must be a raw path starting with '/api/...'.")
1029