When You Need Text Classification and Sentiment Analysis
E-commerce reviews, brand mentions on social media, incoming support emails — the need to automatically categorize text and gauge sentiment keeps growing. Traditionally, you'd need to train a dedicated NLP model with hundreds of labeled examples. With the Gemini API's Structured Output, you can build a high-accuracy text classification and sentiment analysis pipeline in Python without any training data.
Setting Up Your Environment
Getting Your API Key
- Visit Google AI Studio
- Click "Get API key" in the left menu
- Create a new project or select an existing one to generate your key
Installing the SDK
pip install google-genaiTesting the Connection
from google import genai
client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
response = client.models.generate_content(
model="gemini-2.5-flash",
contents="Hello, Gemini! Please send a test response."
)
print(response.text)
# Expected output: Hello! Here's your test response. How can I help you today?If your API key is configured correctly, you'll see a response from Gemini.
Building a Text Classifier with Structured Output
Gemini API's Structured Output lets you receive classification labels in a strict JSON format. Instead of parsing free text, you get structured data that flows directly into your downstream processing.
Defining the Classification Schema
from google import genai
from pydantic import BaseModel, Field
from enum import Enum
from typing import Optional
class Category(str, Enum):
PRODUCT_QUALITY = "product_quality"
SHIPPING = "shipping"
CUSTOMER_SERVICE = "customer_service"
PRICING = "pricing"
OTHER = "other"
class Sentiment(str, Enum):
POSITIVE = "positive"
NEUTRAL = "neutral"
NEGATIVE = "negative"
class TextClassification(BaseModel):
category: Category = Field(description="The primary category of the text")
sentiment: Sentiment = Field(description="The sentiment polarity of the text")
confidence: float = Field(description="Classification confidence score (0.0–1.0)", ge=0.0, le=1.0)
keywords: list[str] = Field(description="Key terms extracted from the text (up to 5)")
summary: str = Field(description="One-line summary of the text")By defining Category and Sentiment as Enums, you ensure that the returned labels never contain unexpected values. Pydantic handles validation automatically.
Classifying a Single Text
client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
review = "Arrived in just two days. The packaging was excellent and the product quality met my expectations. The only downside is the manual was only available in Japanese."
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=f"""Analyze the following customer review.
Review: {review}
Return the category, sentiment, confidence, keywords, and summary.""",
config={
"response_mime_type": "application/json",
"response_schema": TextClassification,
}
)
result = TextClassification.model_validate_json(response.text)
print(f"Category: {result.category.value}")
print(f"Sentiment: {result.sentiment.value}")
print(f"Confidence: {result.confidence}")
print(f"Keywords: {result.keywords}")
print(f"Summary: {result.summary}")
# Expected output:
# Category: product_quality
# Sentiment: positive
# Confidence: 0.82
# Keywords: ['packaging', 'quality', 'delivery', 'manual', 'Japanese']
# Summary: Good product quality and fast delivery, but manual lacks English translationJust pass the Pydantic model to response_schema, and Gemini returns a response matching your exact JSON structure. No parsing headaches.
Batch Processing for Large Datasets
In production, you'll often need to process hundreds or thousands of reviews at once. Here's a batch implementation with retry logic.
import time
from google import genai
from google.genai.errors import ClientError
client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
reviews = [
"The product itself is great, but shipping took over a week.",
"Customer support was incredibly helpful and responsive.",
"At this price point, the quality is genuinely surprising. Best value I've found.",
"The item looks nothing like the photos. The return process is frustrating too.",
"It works fine. Nothing special to report.",
]
def classify_review(review_text: str, max_retries: int = 3) -> Optional[TextClassification]:
"""Classify a review with retry logic."""
for attempt in range(max_retries):
try:
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=f"Analyze the following customer review.\n\nReview: {review_text}",
config={
"response_mime_type": "application/json",
"response_schema": TextClassification,
}
)
return TextClassification.model_validate_json(response.text)
except ClientError as e:
if "429" in str(e):
wait = 2 ** attempt
print(f"Rate limited. Retrying in {wait} seconds...")
time.sleep(wait)
else:
print(f"Error: {e}")
return None
return None
# Run batch classification
results = []
for i, review in enumerate(reviews):
print(f"Processing: {i + 1}/{len(reviews)}")
result = classify_review(review)
if result:
results.append({"review": review, "classification": result})
time.sleep(0.5) # Respect rate limits
# Aggregate results
from collections import Counter
sentiment_counts = Counter(r["classification"].sentiment.value for r in results)
category_counts = Counter(r["classification"].category.value for r in results)
print(f"\n--- Sentiment Distribution ---")
for sentiment, count in sentiment_counts.most_common():
print(f" {sentiment}: {count}")
print(f"\n--- Category Distribution ---")
for category, count in category_counts.most_common():
print(f" {category}: {count}")
# Expected output:
# --- Sentiment Distribution ---
# positive: 2
# negative: 2
# neutral: 1
# --- Category Distribution ---
# shipping: 1
# customer_service: 1
# pricing: 1
# product_quality: 2The key points are adding time.sleep(0.5) between requests and using exponential backoff for 429 errors. For large-scale processing, consider using the Batch API approach covered in Gemini API Production Pipeline Architecture.
Multilingual Sentiment Analysis
Gemini handles multiple languages natively, so you can process Japanese, English, Chinese, and other texts through the same pipeline.
class MultilingualAnalysis(BaseModel):
detected_language: str = Field(description="Detected language code (ja, en, zh, etc.)")
sentiment: Sentiment
confidence: float = Field(ge=0.0, le=1.0)
translated_summary: str = Field(description="One-line summary in English")
multilingual_texts = [
"This product exceeded my expectations! Highly recommended.",
"配送が遅すぎます。二度と利用しません。",
"产品质量一般,但价格很合理。",
]
for text in multilingual_texts:
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=f"Analyze the following text. Detect the language and determine the sentiment.\n\nText: {text}",
config={
"response_mime_type": "application/json",
"response_schema": MultilingualAnalysis,
}
)
result = MultilingualAnalysis.model_validate_json(response.text)
print(f"Language: {result.detected_language} | Sentiment: {result.sentiment.value} | Summary: {result.translated_summary}")
# Expected output:
# Language: en | Sentiment: positive | Summary: Product exceeded expectations, strongly recommended
# Language: ja | Sentiment: negative | Summary: Severe dissatisfaction with slow delivery
# Language: zh | Sentiment: neutral | Summary: Average quality but reasonable pricingNo need to switch models for different languages — one prompt handles everything.
Custom Categories for Your Business
You can customize the classification categories to match your specific business needs. Here's an example for automatic support ticket routing.
class TicketPriority(str, Enum):
URGENT = "urgent"
HIGH = "high"
MEDIUM = "medium"
LOW = "low"
class TicketCategory(str, Enum):
BUG_REPORT = "bug_report"
FEATURE_REQUEST = "feature_request"
BILLING = "billing"
ACCOUNT = "account"
GENERAL = "general"
class SupportTicket(BaseModel):
category: TicketCategory
priority: TicketPriority
sentiment: Sentiment
requires_human: bool = Field(description="Whether the ticket needs human escalation")
suggested_response: str = Field(description="Suggested first response (under 50 words)")
ticket_text = "I can't log in to my account. The password reset email isn't arriving either. I need this resolved urgently."
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=f"""Analyze the following support ticket and determine the category, priority, sentiment, escalation need, and suggested response.
Ticket: {ticket_text}""",
config={
"response_mime_type": "application/json",
"response_schema": SupportTicket,
}
)
ticket = SupportTicket.model_validate_json(response.text)
print(f"Category: {ticket.category.value}")
print(f"Priority: {ticket.priority.value}")
print(f"Sentiment: {ticket.sentiment.value}")
print(f"Escalation: {'Required' if ticket.requires_human else 'Not needed'}")
print(f"Suggested Response: {ticket.suggested_response}")
# Expected output:
# Category: account
# Priority: urgent
# Sentiment: negative
# Escalation: Required
# Suggested Response: We're looking into your login issue right away. A team member will follow up shortly.Adding a requires_human field lets you automatically separate tickets that can be handled by bots from those that need human attention.
Prompt Design Tips for Better Accuracy
Classification accuracy depends heavily on how you write your prompts. Here are techniques that make a real difference.
Using System Instructions
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=review_text,
config={
"system_instruction": """You are a text classification expert.
Follow these rules:
- When text fits multiple categories, choose the single most prominent one
- Set confidence conservatively (only use 0.9+ for clear-cut cases)
- Watch for sarcasm and irony (e.g., "Oh great, another delay" is negative)
- Consider emojis and emoticons as sentiment signals""",
"response_mime_type": "application/json",
"response_schema": TextClassification,
}
)Adding Few-Shot Examples
few_shot_prompt = """Classify the following review.
## Classification examples
Review: "Best product ever!" → category: product_quality, sentiment: positive, confidence: 0.95
Review: "It's okay I guess" → category: product_quality, sentiment: neutral, confidence: 0.70
Review: "I want a refund" → category: billing, sentiment: negative, confidence: 0.90
## Text to classify
Review: {review_text}"""Adding just 2–3 few-shot examples significantly improves accuracy on borderline cases like mildly positive-neutral texts. For more details on configuring Structured Output, check out the Gemini API JSON Mode and Structured Output Guide.
Looking back
By combining the Gemini API's Structured Output with Pydantic models, you can build a text classification and sentiment analysis pipeline quickly — no training data required. Defining categories and sentiment labels as Enums ensures consistent, reliable results that plug directly into your data workflows.
The patterns shown here apply well beyond customer reviews: support ticket routing, social media monitoring, survey analysis, and more. Start with the Gemini API Quickstart Guide to get your API key, and try classifying a small dataset first.