Spaces:
Running
Running
Block raw calls for helper-covered endpoint families
Browse files- _monty_codegen_shared.md +1 -0
- 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 |
|