evalstate HF Staff commited on
Commit
c072a80
·
verified ·
1 Parent(s): 536880e

Deploy latest python changes

Browse files
Files changed (2) hide show
  1. _monty_codegen_shared.md +12 -0
  2. monty_api_tool_v2.py +116 -6
_monty_codegen_shared.md CHANGED
@@ -171,6 +171,12 @@ await hf_repo_discussions(
171
  limit: int = 20,
172
  )
173
 
 
 
 
 
 
 
174
  await hf_whoami()
175
  await call_api(endpoint: str, params: dict | None = None, method: str = "GET", json_body: dict | None = None)
176
  ```
@@ -207,6 +213,8 @@ Search/detail/trending repo rows commonly include:
207
  - `created_at`
208
  - `last_modified`
209
  - `pipeline_tag`
 
 
210
  - `private`
211
  - `repo_url`
212
  - `tags`
@@ -253,6 +261,7 @@ Choose the helper based on the **subject of the question** and the **smallest he
253
  - Search/discovery/list/top repos → `hf_repo_search(...)`
254
  - True trending requests → `hf_trending(...)`
255
  - Repo discussions → `hf_repo_discussions(...)`
 
256
  - Users who liked a specific repo / liker filtering / liker counts → `hf_repo_likers(...)`
257
 
258
  ### User-centric questions
@@ -291,6 +300,8 @@ Pick the helper that already matches the direction of the question instead of tr
291
  - `hf_repo_search(...)` defaults to `repo_type="model"` when no repo type is specified. For prompts like "what repos does <author/org> have" or "list everything published by <author/org>", search across `repo_types=["model", "dataset", "space"]` unless the user explicitly asked for one type.
292
  - Use `hf_repo_details(repo_type="auto", ...)` for `owner/name` detail lookups unless the type is explicit.
293
  - Use `hf_trending(...)` only for true trending requests.
 
 
294
  - `hf_trending(...)` does not accept extra filters like tag/author/task. For trending + extra filters, either ask a brief clarification or clearly label an approximation using `hf_repo_search(sort="trending_score", ...)`.
295
  - Use `hf_user_summary(...)` for common "tell me about user X" prompts. It returns a fixed structured object (no `fields=` projection) with overview data and optional sampled followers/following/likes/activity sections. Read profile and social-link fields such as `websiteUrl`, `twitter`, `github`, `linkedin`, and `bluesky` from `summary["item"]["overview"]`.
296
  - For "my/me" prompts, prefer current-user forms first: `hf_user_summary(username=None)`, `hf_user_graph(username=None, ...)`, and `hf_user_likes(username=None, ...)`. Use `hf_whoami()` when you need the resolved username explicitly.
@@ -314,6 +325,7 @@ Pick the helper that already matches the direction of the question instead of tr
314
  - For user Spaces, use `hf_repo_search(author=..., repo_type="space", ...)`. Do not look for a special spaces-by-author helper.
315
  - Organizations are valid `author=` values for `hf_repo_search(...)`. To inventory an organization's repos, use `author="<org>"` with `repo_types=["model", "dataset", "space"]` and then project to the requested fields.
316
  - Use `hf_repo_discussions(...)` for model/dataset/space discussion listings. Do not guess raw discussion endpoints through `call_api`.
 
317
  - For ambiguous discovery, either ask a brief clarification or search across `repo_types=["model", "dataset", "space"]`.
318
  - For Spaces, `filters` are broader Hub tag-style filters rather than a standardized task taxonomy like model `pipeline_tag`.
319
  - For semantic Space queries (for example image-generation, audio, chat), prefer a broad search with rich fields and then narrow locally.
 
171
  limit: int = 20,
172
  )
173
 
174
+ await hf_repo_discussion_details(
175
+ repo_type: str,
176
+ repo_id: str, # owner/name
177
+ discussion_num: int,
178
+ )
179
+
180
  await hf_whoami()
181
  await call_api(endpoint: str, params: dict | None = None, method: str = "GET", json_body: dict | None = None)
182
  ```
 
213
  - `created_at`
214
  - `last_modified`
215
  - `pipeline_tag`
216
+ - `trending_score`
217
+ - `trending_rank`
218
  - `private`
219
  - `repo_url`
220
  - `tags`
 
261
  - Search/discovery/list/top repos → `hf_repo_search(...)`
262
  - True trending requests → `hf_trending(...)`
263
  - Repo discussions → `hf_repo_discussions(...)`
264
+ - Specific discussion details / latest comment text → `hf_repo_discussion_details(...)`
265
  - Users who liked a specific repo / liker filtering / liker counts → `hf_repo_likers(...)`
266
 
267
  ### User-centric questions
 
300
  - `hf_repo_search(...)` defaults to `repo_type="model"` when no repo type is specified. For prompts like "what repos does <author/org> have" or "list everything published by <author/org>", search across `repo_types=["model", "dataset", "space"]` unless the user explicitly asked for one type.
301
  - Use `hf_repo_details(repo_type="auto", ...)` for `owner/name` detail lookups unless the type is explicit.
302
  - Use `hf_trending(...)` only for true trending requests.
303
+ - `hf_trending(...)` returns the Hub's ordered trending list. Numeric `trending_score` may be unavailable from the upstream API; when that field is missing, use `trending_rank` / ordering instead of inventing a score.
304
+ - If the user explicitly asks for numeric trending scores and the returned rows have `trending_score=None`, say the numeric scores are unavailable from the upstream API and return the ordered repos with `trending_rank` instead of emitting null score values.
305
  - `hf_trending(...)` does not accept extra filters like tag/author/task. For trending + extra filters, either ask a brief clarification or clearly label an approximation using `hf_repo_search(sort="trending_score", ...)`.
306
  - Use `hf_user_summary(...)` for common "tell me about user X" prompts. It returns a fixed structured object (no `fields=` projection) with overview data and optional sampled followers/following/likes/activity sections. Read profile and social-link fields such as `websiteUrl`, `twitter`, `github`, `linkedin`, and `bluesky` from `summary["item"]["overview"]`.
307
  - For "my/me" prompts, prefer current-user forms first: `hf_user_summary(username=None)`, `hf_user_graph(username=None, ...)`, and `hf_user_likes(username=None, ...)`. Use `hf_whoami()` when you need the resolved username explicitly.
 
325
  - For user Spaces, use `hf_repo_search(author=..., repo_type="space", ...)`. Do not look for a special spaces-by-author helper.
326
  - Organizations are valid `author=` values for `hf_repo_search(...)`. To inventory an organization's repos, use `author="<org>"` with `repo_types=["model", "dataset", "space"]` and then project to the requested fields.
327
  - Use `hf_repo_discussions(...)` for model/dataset/space discussion listings. Do not guess raw discussion endpoints through `call_api`.
328
+ - Use `hf_repo_discussion_details(...)` when the user asks for the latest comment text, discussion body, or details for a known discussion number.
329
  - For ambiguous discovery, either ask a brief clarification or search across `repo_types=["model", "dataset", "space"]`.
330
  - For Spaces, `filters` are broader Hub tag-style filters rather than a standardized task taxonomy like model `pipeline_tag`.
331
  - For semantic Space queries (for example image-generation, audio, chat), prefer a broad search with rich fields and then narrow locally.
monty_api_tool_v2.py CHANGED
@@ -256,6 +256,7 @@ HELPER_EXTERNALS = [
256
  "hf_user_likes",
257
  "hf_recent_activity",
258
  "hf_repo_discussions",
 
259
  "hf_repo_details",
260
  "hf_trending",
261
  "hf_collections_search",
@@ -316,7 +317,7 @@ class MontyExecutionError(RuntimeError):
316
  self.trace = trace
317
 
318
 
319
- def _load_token() -> str | None:
320
  try:
321
  from fast_agent.mcp.auth.context import request_bearer_token # type: ignore
322
 
@@ -325,6 +326,13 @@ def _load_token() -> str | None:
325
  return token
326
  except Exception:
327
  pass
 
 
 
 
 
 
 
328
  return os.getenv("HF_TOKEN") or None
329
 
330
 
@@ -644,13 +652,13 @@ def _normalize_repo_detail_row(detail: Any, repo_type: str, repo_id: str) -> dic
644
  return row
645
 
646
 
647
- def _normalize_trending_row(repo: dict[str, Any], default_repo_type: str) -> dict[str, Any]:
648
  repo_id = repo.get("id")
649
  repo_type = _canonical_repo_type(repo.get("type") or default_repo_type, default=default_repo_type)
650
  author = repo.get("author")
651
  if not isinstance(author, str) and isinstance(repo_id, str) and "/" in repo_id:
652
  author = repo_id.split("/", 1)[0]
653
- return {
654
  "repo_id": repo_id,
655
  "repo_type": repo_type,
656
  "author": author,
@@ -673,6 +681,9 @@ def _normalize_trending_row(repo: dict[str, Any], default_repo_type: str) -> dic
673
  "datasets": _optional_str_list(repo.get("datasets")),
674
  "subdomain": repo.get("subdomain"),
675
  }
 
 
 
676
 
677
 
678
  def _sort_repo_rows(rows: list[dict[str, Any]], sort_key: str | None) -> list[dict[str, Any]]:
@@ -1297,8 +1308,21 @@ async def _run_with_monty(
1297
  async def hf_whoami() -> dict[str, Any]:
1298
  start_calls = call_count["n"]
1299
  endpoint = "/api/whoami-v2"
 
 
 
 
 
 
 
 
 
 
1300
  try:
1301
- payload = _host_hf_call(endpoint, lambda: _get_hf_api_client().whoami(cache=True))
 
 
 
1302
  except Exception as e:
1303
  return _helper_error(start_calls=start_calls, source=endpoint, error=e)
1304
 
@@ -2890,6 +2914,87 @@ async def _run_with_monty(
2890
  total_count=None,
2891
  )
2892
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2893
  def _resolve_repo_detail_row(
2894
  api: HfApi,
2895
  repo_id: str,
@@ -3032,11 +3137,11 @@ async def _run_with_monty(
3032
 
3033
  items: list[dict[str, Any]] = []
3034
  default_row_type = requested_type if requested_type != "all" else "model"
3035
- for row in rows[:lim]:
3036
  if not isinstance(row, dict):
3037
  continue
3038
  repo = row.get("repoData") if isinstance(row.get("repoData"), dict) else {}
3039
- items.append(_normalize_trending_row(repo, default_row_type))
3040
 
3041
  api = _get_hf_api_client()
3042
  enriched_items: list[dict[str, Any]] = []
@@ -3064,6 +3169,8 @@ async def _run_with_monty(
3064
  trending_score = item.get("trending_score")
3065
  if trending_score is not None:
3066
  merged["trending_score"] = trending_score
 
 
3067
  enriched_items.append(merged)
3068
 
3069
  items = enriched_items
@@ -3080,6 +3187,8 @@ async def _run_with_monty(
3080
  scanned=len(rows),
3081
  matched=matched,
3082
  returned=len(items),
 
 
3083
  failures=enrichment_failures or None,
3084
  )
3085
 
@@ -3188,6 +3297,7 @@ async def _run_with_monty(
3188
  "hf_user_likes": _collecting_wrapper("hf_user_likes", hf_user_likes),
3189
  "hf_recent_activity": _collecting_wrapper("hf_recent_activity", hf_recent_activity),
3190
  "hf_repo_discussions": _collecting_wrapper("hf_repo_discussions", hf_repo_discussions),
 
3191
  "hf_repo_details": _collecting_wrapper("hf_repo_details", hf_repo_details),
3192
  "hf_trending": _collecting_wrapper("hf_trending", hf_trending),
3193
  "hf_collections_search": _collecting_wrapper("hf_collections_search", hf_collections_search),
 
256
  "hf_user_likes",
257
  "hf_recent_activity",
258
  "hf_repo_discussions",
259
+ "hf_repo_discussion_details",
260
  "hf_repo_details",
261
  "hf_trending",
262
  "hf_collections_search",
 
317
  self.trace = trace
318
 
319
 
320
+ def _load_request_token() -> str | None:
321
  try:
322
  from fast_agent.mcp.auth.context import request_bearer_token # type: ignore
323
 
 
326
  return token
327
  except Exception:
328
  pass
329
+ return None
330
+
331
+
332
+ def _load_token() -> str | None:
333
+ token = _load_request_token()
334
+ if token:
335
+ return token
336
  return os.getenv("HF_TOKEN") or None
337
 
338
 
 
652
  return row
653
 
654
 
655
+ def _normalize_trending_row(repo: dict[str, Any], default_repo_type: str, rank: int | None = None) -> dict[str, Any]:
656
  repo_id = repo.get("id")
657
  repo_type = _canonical_repo_type(repo.get("type") or default_repo_type, default=default_repo_type)
658
  author = repo.get("author")
659
  if not isinstance(author, str) and isinstance(repo_id, str) and "/" in repo_id:
660
  author = repo_id.split("/", 1)[0]
661
+ row = {
662
  "repo_id": repo_id,
663
  "repo_type": repo_type,
664
  "author": author,
 
681
  "datasets": _optional_str_list(repo.get("datasets")),
682
  "subdomain": repo.get("subdomain"),
683
  }
684
+ if rank is not None:
685
+ row["trending_rank"] = rank
686
+ return row
687
 
688
 
689
  def _sort_repo_rows(rows: list[dict[str, Any]], sort_key: str | None) -> list[dict[str, Any]]:
 
1308
  async def hf_whoami() -> dict[str, Any]:
1309
  start_calls = call_count["n"]
1310
  endpoint = "/api/whoami-v2"
1311
+ request_token = _load_request_token()
1312
+ if request_token is None:
1313
+ return _helper_error(
1314
+ start_calls=start_calls,
1315
+ source=endpoint,
1316
+ error=(
1317
+ "Current authenticated user is unavailable for this request. "
1318
+ "No request bearer token was found."
1319
+ ),
1320
+ )
1321
  try:
1322
+ payload = _host_hf_call(
1323
+ endpoint,
1324
+ lambda: _get_hf_api_client().whoami(token=request_token, cache=True),
1325
+ )
1326
  except Exception as e:
1327
  return _helper_error(start_calls=start_calls, source=endpoint, error=e)
1328
 
 
2914
  total_count=None,
2915
  )
2916
 
2917
+ async def hf_repo_discussion_details(repo_type: str, repo_id: str, discussion_num: int) -> dict[str, Any]:
2918
+ start_calls = call_count["n"]
2919
+ rt = _canonical_repo_type(repo_type)
2920
+ rid = str(repo_id or "").strip()
2921
+ if "/" not in rid:
2922
+ return _helper_error(start_calls=start_calls, source="/api/.../discussions/<num>", error="repo_id must be owner/name")
2923
+
2924
+ num = _as_int(discussion_num)
2925
+ if num is None:
2926
+ return _helper_error(
2927
+ start_calls=start_calls,
2928
+ source=f"/api/{rt}s/{rid}/discussions/<num>",
2929
+ error="discussion_num must be an integer",
2930
+ )
2931
+
2932
+ endpoint = f"/api/{rt}s/{rid}/discussions/{num}"
2933
+ try:
2934
+ detail = _host_hf_call(
2935
+ endpoint,
2936
+ lambda: _get_hf_api_client().get_discussion_details(
2937
+ repo_id=rid,
2938
+ discussion_num=int(num),
2939
+ repo_type=rt,
2940
+ ),
2941
+ )
2942
+ except Exception as e:
2943
+ return _helper_error(start_calls=start_calls, source=endpoint, error=e)
2944
+
2945
+ comment_events: list[dict[str, Any]] = []
2946
+ raw_events = getattr(detail, "events", None)
2947
+ if isinstance(raw_events, list):
2948
+ for event in raw_events:
2949
+ if str(getattr(event, "type", "")).strip().lower() != "comment":
2950
+ continue
2951
+ comment_events.append(
2952
+ {
2953
+ "author": getattr(event, "author", None),
2954
+ "createdAt": _dt_to_str(getattr(event, "created_at", None)),
2955
+ "text": getattr(event, "content", None),
2956
+ "rendered": getattr(event, "rendered", None),
2957
+ }
2958
+ )
2959
+
2960
+ latest_comment: dict[str, Any] | None = None
2961
+ if comment_events:
2962
+ latest_comment = max(comment_events, key=lambda row: str(row.get("createdAt") or ""))
2963
+
2964
+ item: dict[str, Any] = {
2965
+ "num": num,
2966
+ "number": num,
2967
+ "discussionNum": num,
2968
+ "id": num,
2969
+ "repo_id": rid,
2970
+ "repo_type": rt,
2971
+ "title": getattr(detail, "title", None),
2972
+ "author": getattr(detail, "author", None),
2973
+ "createdAt": _dt_to_str(getattr(detail, "created_at", None)),
2974
+ "status": getattr(detail, "status", None),
2975
+ "url": getattr(detail, "url", None),
2976
+ "commentCount": len(comment_events),
2977
+ "latestCommentAuthor": latest_comment.get("author") if latest_comment else None,
2978
+ "latestCommentCreatedAt": latest_comment.get("createdAt") if latest_comment else None,
2979
+ "latestCommentText": latest_comment.get("text") if latest_comment else None,
2980
+ "latestCommentHtml": latest_comment.get("rendered") if latest_comment else None,
2981
+ "latest_comment_author": latest_comment.get("author") if latest_comment else None,
2982
+ "latest_comment_created_at": latest_comment.get("createdAt") if latest_comment else None,
2983
+ "latest_comment_text": latest_comment.get("text") if latest_comment else None,
2984
+ "latest_comment_html": latest_comment.get("rendered") if latest_comment else None,
2985
+ }
2986
+
2987
+ return _helper_success(
2988
+ start_calls=start_calls,
2989
+ source=endpoint,
2990
+ items=[item],
2991
+ scanned=len(comment_events),
2992
+ matched=1,
2993
+ returned=1,
2994
+ truncated=False,
2995
+ total_comments=len(comment_events),
2996
+ )
2997
+
2998
  def _resolve_repo_detail_row(
2999
  api: HfApi,
3000
  repo_id: str,
 
3137
 
3138
  items: list[dict[str, Any]] = []
3139
  default_row_type = requested_type if requested_type != "all" else "model"
3140
+ for idx, row in enumerate(rows[:lim], start=1):
3141
  if not isinstance(row, dict):
3142
  continue
3143
  repo = row.get("repoData") if isinstance(row.get("repoData"), dict) else {}
3144
+ items.append(_normalize_trending_row(repo, default_row_type, rank=idx))
3145
 
3146
  api = _get_hf_api_client()
3147
  enriched_items: list[dict[str, Any]] = []
 
3169
  trending_score = item.get("trending_score")
3170
  if trending_score is not None:
3171
  merged["trending_score"] = trending_score
3172
+ if item.get("trending_rank") is not None:
3173
+ merged["trending_rank"] = item.get("trending_rank")
3174
  enriched_items.append(merged)
3175
 
3176
  items = enriched_items
 
3187
  scanned=len(rows),
3188
  matched=matched,
3189
  returned=len(items),
3190
+ trending_score_available=any(item.get("trending_score") is not None for item in items),
3191
+ ordered_ranking=True,
3192
  failures=enrichment_failures or None,
3193
  )
3194
 
 
3297
  "hf_user_likes": _collecting_wrapper("hf_user_likes", hf_user_likes),
3298
  "hf_recent_activity": _collecting_wrapper("hf_recent_activity", hf_recent_activity),
3299
  "hf_repo_discussions": _collecting_wrapper("hf_repo_discussions", hf_repo_discussions),
3300
+ "hf_repo_discussion_details": _collecting_wrapper("hf_repo_discussion_details", hf_repo_discussion_details),
3301
  "hf_repo_details": _collecting_wrapper("hf_repo_details", hf_repo_details),
3302
  "hf_trending": _collecting_wrapper("hf_trending", hf_trending),
3303
  "hf_collections_search": _collecting_wrapper("hf_collections_search", hf_collections_search),