Skip to content

Review Triage — Structured Prompt Output

Demonstrates # @output_schema on a prompt cell.

The triage cell sends a list of customer reviews to an LLM and gets back a schema-validated JSON array — not free-form text that a downstream cell has to regex. The schema is passed through as native structured-output (OpenAI's response_format: {type: "json_schema"}, or json_object fallback for providers that don't support schemas).

Because the schema is part of the cell's provenance hash, editing the schema invalidates the cached response — exactly what you want when you're iterating on the shape of the output.

Cells

  1. reviews.py — hand-picked list of customer reviews
  2. triage.py — prompt cell with @output_schema enforcing {sentiment, priority, tags} per review
  3. summary.py — pandas aggregation of the structured results

Running

Set an API key (ANTHROPIC_API_KEY, OPENAI_API_KEY, etc.) in the notebook's Runtime panel, then run-all.

reviews

kind python

# A small hand-picked set of customer reviews. Kept inline so the
# notebook runs anywhere — no network, no data file, just enough
# variety to make the triage output interesting.
reviews = [
    "Shipping took two weeks but the product itself works perfectly. Would buy again.",
    "Absolute disaster. Arrived broken, support ignored three emails. Demanding a refund.",
    "It's fine. Does what it says on the box.",
    "Great product and fast shipping, but the packaging was excessive — so much plastic.",
    "Completely failed after 24 hours of use. Battery overheated. Fire hazard.",
    "Best purchase of the year! Setup was a breeze and it integrates with everything.",
    "Missing the power cable. Shop should have caught this before shipping.",
    "Works as advertised. The interface feels a bit dated but the performance is solid.",
]

triage

kind prompt

Prompt cell — response intentionally excluded from export.

# @name triage
# @temperature 0.0
# @output_schema {"type": "object", "properties": {"items": {"type": "array", "items": {"type": "object", "properties": {"review_index": {"type": "integer"}, "sentiment": {"type": "string", "enum": ["positive", "negative", "neutral"]}, "priority": {"type": "string", "enum": ["low", "medium", "high"]}, "tags": {"type": "array", "items": {"type": "string"}, "minItems": 1, "maxItems": 3}}, "required": ["review_index", "sentiment", "priority", "tags"]}}}, "required": ["items"]}
# @system You are a customer-support triage assistant. Keep tags short (1-2 words).
#
# Free-form language models return free-form text — useful for summaries,
# awkward for pipelines. The `@output_schema` annotation pins the shape
# of this cell's output so downstream cells can destructure fields
# without regex-wrangling the response. Schema changes invalidate the
# cache, so iterating on the schema does what you'd expect.

Triage the following customer reviews. For each one, return:
- `review_index` — the 0-based position of the review in the input list
- `sentiment` — positive / negative / neutral
- `priority` — low / medium / high. "high" means the team should
  escalate immediately (safety issues, demands for refunds, etc).
- `tags` — 1–3 short descriptive tags (e.g. "shipping", "hardware
  failure", "packaging")

Reviews:
{{ reviews }}

triage_summary

kind python

# @name triage_summary
# Downstream cells consume the LLM output as structured data — no
# parsing, no fallbacks, no "did the model actually return JSON this
# time?" defensive code. The schema guarantees the shape.
import pandas as pd

rows = triage["items"]
df = pd.DataFrame(rows)

print("Per-review triage:")
for row, review in zip(rows, reviews):
    tags = ", ".join(row["tags"])
    print(
        f"  [{row['review_index']}] "
        f"{row['sentiment']:>8} / {row['priority']:>6} "
        f"({tags}): {review[:60]}..."
    )

print()
print("By priority:")
print(df["priority"].value_counts().to_string())

print()
print("Sentiment distribution:")
print(df["sentiment"].value_counts().to_string())

# The final expression becomes the cell's display value — a compact
# record of what the triage flagged as high-priority.
high_priority = df[df["priority"] == "high"]
{
    "total_reviews": len(df),
    "needs_escalation": len(high_priority),
    "negative_rate": float((df["sentiment"] == "negative").mean()),
}