tool_get_answer_groups
Retrieve clustered student answer groups for AI-assisted grading to grade similar responses collectively instead of individually.
Instructions
List all answer groups for a question (AI-Assisted Grading).
Shows clusters of similar student answers. Grade one group to
grade all members at once — much more efficient than 1-by-1.
Args:
course_id: The Gradescope course ID.
question_id: The question ID.
output_format: "markdown" or "json" for structured output.Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| course_id | Yes | ||
| question_id | Yes | ||
| output_format | No | markdown |
Implementation Reference
- src/gradescope_mcp/server.py:553-568 (handler)Registration of the tool tool_get_answer_groups.
def tool_get_answer_groups( course_id: str, question_id: str, output_format: str = "markdown", ) -> str: """List all answer groups for a question (AI-Assisted Grading). Shows clusters of similar student answers. Grade one group to grade all members at once — much more efficient than 1-by-1. Args: course_id: The Gradescope course ID. question_id: The question ID. output_format: "markdown" or "json" for structured output. """ return get_answer_groups(course_id, question_id, output_format) - The actual handler function for getting answer groups.
def get_answer_groups( course_id: str, question_id: str, output_format: str = "markdown", ) -> str: """List all answer groups for a question. Answer groups cluster similar student answers together for efficient batch grading. Instead of grading each submission individually, you can grade one group and the score applies to all members. Args: course_id: The Gradescope course ID. question_id: The question ID. output_format: "markdown" (default) or "json" for structured output. """ if not course_id or not question_id: return "Error: course_id and question_id are required." try: data = _fetch_answer_groups_json(course_id, question_id) except AuthError as e: return f"Authentication error: {e}" except ValueError as e: return f"Error: {e}" except Exception as e: return f"Error fetching answer groups: {e}" groups = data.get("groups", []) submissions = data.get("submissions", []) question = data.get("question", {}) status = data.get("status", "unknown") # Count submissions per group group_counts: dict[int, dict[str, int]] = {} for sub in submissions: gid = sub.get("confirmed_group_id") or sub.get("unconfirmed_group_id") if gid is not None: if gid not in group_counts: group_counts[gid] = {"total": 0, "graded": 0} group_counts[gid]["total"] += 1 if sub.get("graded"): group_counts[gid]["graded"] += 1 # Count ungrouped ungrouped = [ s for s in submissions if not s.get("confirmed_group_id") and not s.get("unconfirmed_group_id") ] if output_format == "json": result = { "question_id": question_id, "question_title": question.get("numbered_title", ""), "assisted_grading_type": question.get("assisted_grading_type"), "status": status, "num_groups": len(groups), "num_submissions": len(submissions), "num_ungrouped": len(ungrouped), "groups": [], } for g in groups: gid = g["id"] counts = group_counts.get(gid, {"total": 0, "graded": 0}) result["groups"].append({ "id": str(gid), "title": g.get("title", ""), "size": counts["total"], "graded": counts["graded"], "hidden": g.get("hidden", False), "question_type": g.get("question_type", ""), }) return json.dumps(result, indent=2) # Markdown output ag_type = question.get('assisted_grading_type') # Resolve type: use assisted_grading_type first, fall back to per-group types if not ag_type and groups: group_types = {g.get('question_type', '') for g in groups if g.get('question_type')} ag_type = ', '.join(sorted(group_types)) if group_types else None ag_type_display = ag_type or '(not set)' group_word = 'group' if len(groups) == 1 else 'groups' lines = [ f"## Answer Groups — {question.get('numbered_title', question_id)}", f"**Type:** {ag_type_display}", f"**Status:** {status}", f"**Total:** {len(submissions)} submissions across {len(groups)} {group_word}" + (f" + {len(ungrouped)} ungrouped" if ungrouped else ""), "", "| # | Group ID | Title | Type | Size | Graded | Hidden |", "|---|----------|-------|------|------|--------|--------|", ] for i, g in enumerate(groups, 1): gid = g["id"] counts = group_counts.get(gid, {"total": 0, "graded": 0}) title = g.get("title", "(untitled)") # Truncate long LaTeX titles if len(title) > 60: title = title[:57] + "..." g_type = g.get("question_type", "") or "" hidden = "🙈" if g.get("hidden") else "" graded_str = f"{counts['graded']}/{counts['total']}" lines.append( f"| {i} | `{gid}` | {title} | {g_type} | {counts['total']} | {graded_str} | {hidden} |" ) if ungrouped: lines.append(f"\n**Ungrouped:** {len(ungrouped)} submissions need manual grouping") return "\n".join(lines)