Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:16:46 +08:00
commit 3d2cb201f0
33 changed files with 2911 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
# Changelog
## 0.2.0
- Refactored to Anthropic progressive disclosure pattern
- Updated description with "Use PROACTIVELY when..." format
- Removed version/author/category/tags from frontmatter
## 0.1.0
- Initial release of JSON Outputs Implementer skill
- Complete workflow covering schema design, SDK integration, testing, and production
- Pydantic and Zod integration patterns
- Error handling for refusals, token limits, and validation failures
- Performance optimization guidance (grammar caching, token management)
- Contact and invoice extraction examples

View File

@@ -0,0 +1,90 @@
# JSON Outputs Implementer
Specialized skill for implementing JSON outputs mode with guaranteed schema compliance.
## Purpose
This skill handles **end-to-end implementation** of JSON outputs mode (`output_format`), ensuring Claude's responses strictly match your JSON schema. Covers schema design, SDK integration, testing, and production deployment.
## Use Cases
- **Data Extraction**: Pull structured info from text/images
- **Classification**: Categorize content with guaranteed output format
- **API Formatting**: Generate API-ready JSON responses
- **Report Generation**: Create structured reports
- **Database Operations**: Ensure type-safe inserts/updates
## Prerequisites
- Routed here by `structured-outputs-advisor`
- Model: Claude Sonnet 4.5 or Opus 4.1
- Beta header: `structured-outputs-2025-11-13`
## Quick Start
**Python with Pydantic:**
```python
from pydantic import BaseModel
from anthropic import Anthropic
class Contact(BaseModel):
name: str
email: str
client = Anthropic()
response = client.beta.messages.parse(
model="claude-sonnet-4-5",
betas=["structured-outputs-2025-11-13"],
messages=[{"role": "user", "content": "Extract contact..."}],
output_format=Contact,
)
contact = response.parsed_output # Guaranteed valid
```
**TypeScript with Zod:**
```typescript
import { z } from 'zod';
const ContactSchema = z.object({
name: z.string(),
email: z.string().email(),
});
const response = await client.beta.messages.parse({
model: "claude-sonnet-4-5",
betas: ["structured-outputs-2025-11-13"],
output_format: betaZodOutputFormat(ContactSchema),
messages: [...]
});
```
## What You'll Learn
1. **Schema Design** - Respecting JSON Schema limitations
2. **SDK Integration** - Pydantic/Zod helpers
3. **Error Handling** - Refusals, token limits, validation
4. **Production Optimization** - Caching, monitoring, cost tracking
5. **Testing** - Comprehensive validation strategies
## Examples
- [contact-extraction.py](./examples/contact-extraction.py) - Extract contact info
- [invoice-extraction.py](./examples/invoice-extraction.py) - Complex nested schemas
## Related Skills
- [`structured-outputs-advisor`](../structured-outputs-advisor/) - Choose the right mode
- [`strict-tool-implementer`](../strict-tool-implementer/) - For tool validation
## Reference Materials
- [JSON Schema Limitations](../reference/json-schema-limitations.md)
- [Best Practices](../reference/best-practices.md)
- [API Compatibility](../reference/api-compatibility.md)
## Version
Current version: 0.1.0
See [CHANGELOG.md](./CHANGELOG.md) for version history.

View File

@@ -0,0 +1,93 @@
---
name: json-outputs-implementer
description: >-
Use PROACTIVELY when extracting structured data from text/images, classifying content, or formatting API responses with guaranteed schema compliance.
Implements Anthropic's JSON outputs mode with Pydantic/Zod SDK integration.
Covers schema design, validation, testing, and production optimization.
Not for tool parameter validation or agentic workflows (use strict-tool-implementer instead).
---
# JSON Outputs Implementer
## Overview
This skill implements Anthropic's JSON outputs mode for guaranteed schema compliance. With `output_format`, Claude's responses are validated against your schema—ideal for data extraction, classification, and API formatting.
**What This Skill Provides:**
- Production-ready JSON schema design
- SDK integration (Pydantic for Python, Zod for TypeScript)
- Validation and error handling patterns
- Performance optimization strategies
- Complete implementation examples
**Prerequisites:**
- Decision made via `structured-outputs-advisor`
- Model: Claude Sonnet 4.5 or Opus 4.1
- Beta header: `structured-outputs-2025-11-13`
## When to Use This Skill
**Use for:**
- Extracting structured data from text/images
- Classification tasks with guaranteed categories
- Generating API-ready responses
- Formatting reports with fixed structure
- Database inserts requiring type safety
**NOT for:**
- Validating tool inputs → `strict-tool-implementer`
- Agentic workflows → `strict-tool-implementer`
## Response Style
- **Schema-first**: Design schema before implementation
- **SDK-friendly**: Leverage Pydantic/Zod when available
- **Production-ready**: Consider performance, caching, errors
- **Example-driven**: Provide complete working code
- **Limitation-aware**: Respect JSON Schema constraints
## Workflow
| Phase | Description | Details |
|-------|-------------|---------|
| 1 | Schema Design | → [workflow/phase-1-schema-design.md](workflow/phase-1-schema-design.md) |
| 2 | SDK Integration | → [workflow/phase-2-sdk-integration.md](workflow/phase-2-sdk-integration.md) |
| 3 | Error Handling | → [workflow/phase-3-error-handling.md](workflow/phase-3-error-handling.md) |
| 4 | Testing | → [workflow/phase-4-testing.md](workflow/phase-4-testing.md) |
| 5 | Production Optimization | → [workflow/phase-5-production.md](workflow/phase-5-production.md) |
## Quick Reference
### Python Template
```python
from pydantic import BaseModel
from anthropic import Anthropic
class MySchema(BaseModel):
field: str
response = client.beta.messages.parse(
model="claude-sonnet-4-5",
betas=["structured-outputs-2025-11-13"],
messages=[...],
output_format=MySchema,
)
result = response.parsed_output # Validated!
```
### Supported Schema Features
✅ Basic types, enums, format strings, nested objects/arrays, required fields
❌ Recursive schemas, min/max constraints, string length, complex regex
## Reference Materials
- [Common Use Cases](reference/use-cases.md)
- [Schema Limitations](reference/schema-limitations.md)
## Related Skills
- `structured-outputs-advisor` - Choose the right mode
- `strict-tool-implementer` - For tool validation use cases

View File

@@ -0,0 +1,138 @@
"""
Contact Information Extraction Example
Extracts structured contact information from unstructured text (emails, messages, etc.)
using JSON outputs mode with Pydantic schema validation.
"""
from pydantic import BaseModel, Field, EmailStr
from typing import Optional, List
from anthropic import Anthropic
import os
# Initialize client
client = Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))
# Define schema with Pydantic
class ContactInfo(BaseModel):
"""Structured contact information extracted from text."""
name: str = Field(description="Full name of the contact person")
email: EmailStr = Field(description="Email address")
phone: Optional[str] = Field(
None, description="Phone number in any format"
)
company: Optional[str] = Field(
None, description="Company or organization name"
)
plan_interest: Optional[str] = Field(
None, description="Product plan or tier they're interested in"
)
demo_requested: bool = Field(
False, description="Whether they requested a product demo"
)
tags: List[str] = Field(
default_factory=list,
description="Relevant tags or categories"
)
def extract_contact(text: str) -> Optional[ContactInfo]:
"""
Extract contact information from unstructured text.
Args:
text: Unstructured text containing contact information
Returns:
ContactInfo object with extracted data, or None if request refused
"""
try:
response = client.beta.messages.parse(
model="claude-sonnet-4-5",
max_tokens=1024,
betas=["structured-outputs-2025-11-13"],
messages=[{
"role": "user",
"content": f"Extract contact information from the following text:\n\n{text}"
}],
output_format=ContactInfo,
)
# Handle different stop reasons
if response.stop_reason == "refusal":
print(f"⚠️ Request refused for safety reasons")
return None
if response.stop_reason == "max_tokens":
print(f"⚠️ Response truncated - increase max_tokens")
return None
# Return validated contact info
return response.parsed_output
except Exception as e:
print(f"❌ Error extracting contact: {e}")
raise
def main():
"""Run contact extraction examples."""
examples = [
# Example 1: Complete contact info
"""
Hi, I'm John Smith from Acme Corp. You can reach me at john.smith@acme.com
or call me at (555) 123-4567. I'm interested in your Enterprise plan and
would love to schedule a demo next week.
""",
# Example 2: Minimal info
"""
Contact: jane.doe@example.com
""",
# Example 3: Informal message
"""
Hey! Bob here. Email me at bob@startup.io if you want to chat about
the Pro plan. Thanks!
""",
# Example 4: Multiple contacts (extracts first/primary)
"""
From: alice@company.com
CC: support@company.com
Hi, I'm Alice Johnson, VP of Engineering at TechCo.
We're evaluating your platform for our team of 50 developers.
""",
]
print("=" * 70)
print("Contact Extraction Examples")
print("=" * 70)
for i, text in enumerate(examples, 1):
print(f"\n📧 Example {i}:")
print(f"Input: {text.strip()[:100]}...")
contact = extract_contact(text)
if contact:
print(f"\n✅ Extracted Contact:")
print(f" Name: {contact.name}")
print(f" Email: {contact.email}")
print(f" Phone: {contact.phone or 'N/A'}")
print(f" Company: {contact.company or 'N/A'}")
print(f" Plan Interest: {contact.plan_interest or 'N/A'}")
print(f" Demo Requested: {contact.demo_requested}")
print(f" Tags: {', '.join(contact.tags) if contact.tags else 'None'}")
else:
print(f"\n❌ No contact extracted")
print("-" * 70)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,160 @@
"""
Invoice Data Extraction Example
Extracts structured invoice data from text using JSON outputs with nested schemas.
Demonstrates handling complex nested structures (line items, tax breakdown).
"""
from pydantic import BaseModel, Field
from typing import List
from decimal import Decimal
from anthropic import Anthropic
import os
client = Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))
# Nested schema for line items
class LineItem(BaseModel):
"""Individual line item on an invoice."""
description: str = Field(description="Item description")
quantity: int = Field(description="Quantity ordered")
unit_price: float = Field(description="Price per unit in USD")
total: float = Field(description="Total for this line (quantity * unit_price)")
class Invoice(BaseModel):
"""Complete invoice structure."""
invoice_number: str = Field(description="Invoice ID (format: INV-XXXXX)")
date: str = Field(description="Invoice date in YYYY-MM-DD format")
due_date: str = Field(description="Payment due date in YYYY-MM-DD format")
customer_name: str = Field(description="Customer or company name")
customer_email: str = Field(description="Customer email address")
line_items: List[LineItem] = Field(
description="List of items on the invoice"
)
subtotal: float = Field(description="Subtotal before tax in USD")
tax_rate: float = Field(description="Tax rate as decimal (e.g., 0.08 for 8%)")
tax_amount: float = Field(description="Tax amount in USD")
total_amount: float = Field(description="Final total amount in USD")
notes: str = Field(
default="",
description="Additional notes or payment instructions"
)
def extract_invoice(invoice_text: str) -> Optional[Invoice]:
"""Extract structured invoice data."""
try:
response = client.beta.messages.parse(
model="claude-sonnet-4-5",
max_tokens=2048, # Higher for complex nested structures
betas=["structured-outputs-2025-11-13"],
messages=[{
"role": "user",
"content": f"Extract all invoice data from:\n\n{invoice_text}"
}],
output_format=Invoice,
)
if response.stop_reason != "end_turn":
print(f"⚠️ Unexpected stop reason: {response.stop_reason}")
return None
return response.parsed_output
except Exception as e:
print(f"❌ Error: {e}")
raise
def main():
"""Run invoice extraction example."""
invoice_text = """
INVOICE
Invoice Number: INV-2024-00123
Date: 2024-01-15
Due Date: 2024-02-15
Bill To:
Acme Corporation
John Smith, CFO
john.smith@acme.com
ITEMS:
1. Cloud Hosting - Pro Plan (x3 servers)
Quantity: 3
Unit Price: $299.00
Total: $897.00
2. Database Storage - 500GB
Quantity: 500
Unit Price: $0.50
Total: $250.00
3. API Calls - Premium Tier
Quantity: 1,000,000
Unit Price: $0.001
Total: $1,000.00
4. Support - Enterprise Level
Quantity: 1
Unit Price: $500.00
Total: $500.00
Subtotal: $2,647.00
Tax (8.5%): $224.99
TOTAL: $2,871.99
Payment Terms: Net 30
Please remit payment to accounts@cloudprovider.com
"""
print("=" * 70)
print("Invoice Extraction Example")
print("=" * 70)
invoice = extract_invoice(invoice_text)
if invoice:
print(f"\n✅ Invoice Extracted Successfully\n")
print(f"Invoice #: {invoice.invoice_number}")
print(f"Customer: {invoice.customer_name} ({invoice.customer_email})")
print(f"Date: {invoice.date}")
print(f"Due: {invoice.due_date}")
print(f"\nLine Items:")
for i, item in enumerate(invoice.line_items, 1):
print(f" {i}. {item.description}")
print(f" Qty: {item.quantity} × ${item.unit_price:.2f} = ${item.total:.2f}")
print(f"\nSubtotal: ${invoice.subtotal:.2f}")
print(f"Tax ({invoice.tax_rate * 100:.1f}%): ${invoice.tax_amount:.2f}")
print(f"TOTAL: ${invoice.total_amount:.2f}")
if invoice.notes:
print(f"\nNotes: {invoice.notes}")
# Validation checks
print(f"\n🔍 Validation:")
calculated_subtotal = sum(item.total for item in invoice.line_items)
print(f" Subtotal matches: {abs(calculated_subtotal - invoice.subtotal) < 0.01}")
calculated_tax = invoice.subtotal * invoice.tax_rate
print(f" Tax calculation matches: {abs(calculated_tax - invoice.tax_amount) < 0.01}")
calculated_total = invoice.subtotal + invoice.tax_amount
print(f" Total matches: {abs(calculated_total - invoice.total_amount) < 0.01}")
else:
print("❌ Failed to extract invoice")
if __name__ == "__main__":
from typing import Optional
main()

View File

@@ -0,0 +1,47 @@
# JSON Schema Limitations Reference
## Supported Features
- ✅ All basic types (object, array, string, integer, number, boolean, null)
-`enum` (primitives only)
-`const`, `anyOf`, `allOf`
-`$ref`, `$def`, `definitions` (local)
-`required`, `additionalProperties: false`
- ✅ String formats: date-time, time, date, email, uri, uuid, ipv4, ipv6
-`minItems: 0` or `minItems: 1` for arrays
## NOT Supported
- ❌ Recursive schemas
- ❌ Numerical constraints (minimum, maximum, multipleOf)
- ❌ String constraints (minLength, maxLength, pattern with complex regex)
- ❌ Array constraints (beyond minItems 0/1)
- ❌ External `$ref`
- ❌ Complex types in enums
## SDK Transformation
Python and TypeScript SDKs automatically remove unsupported constraints and add them to descriptions.
## Success Criteria
- [ ] Schema designed with all required fields
- [ ] JSON Schema limitations respected
- [ ] SDK helper integrated (Pydantic/Zod)
- [ ] Beta header included in requests
- [ ] Error handling for refusals and token limits
- [ ] Tested with representative examples
- [ ] Edge cases covered (missing fields, invalid data)
- [ ] Production optimization considered (caching, tokens)
- [ ] Monitoring in place (latency, costs)
- [ ] Documentation provided
## Important Reminders
1. **Use SDK helpers** - `client.beta.messages.parse()` auto-validates
2. **Respect limitations** - No recursive schemas, no min/max constraints
3. **Add descriptions** - Helps Claude understand what to extract
4. **Handle refusals** - Don't retry safety refusals
5. **Monitor performance** - Watch for cache misses and high latency
6. **Set `additionalProperties: false`** - Required for all objects
7. **Test thoroughly** - Edge cases often reveal schema issues

View File

@@ -0,0 +1,86 @@
# Common Use Cases
## Use Case 1: Data Extraction
**Scenario**: Extract invoice data from text/images
```python
from pydantic import BaseModel
from typing import List
class LineItem(BaseModel):
description: str
quantity: int
unit_price: float
total: float
class Invoice(BaseModel):
invoice_number: str
date: str
customer_name: str
line_items: List[LineItem]
subtotal: float
tax: float
total_amount: float
response = client.beta.messages.parse(
model="claude-sonnet-4-5",
betas=["structured-outputs-2025-11-13"],
max_tokens=2048,
messages=[{"role": "user", "content": f"Extract invoice:\n{invoice_text}"}],
output_format=Invoice,
)
invoice = response.parsed_output
# Insert into database with guaranteed types
db.insert_invoice(invoice.model_dump())
```
## Use Case 2: Classification
**Scenario**: Classify support tickets
```python
class TicketClassification(BaseModel):
category: str # "billing", "technical", "sales"
priority: str # "low", "medium", "high", "critical"
confidence: float
requires_human: bool
suggested_assignee: Optional[str] = None
tags: List[str]
response = client.beta.messages.parse(
model="claude-sonnet-4-5",
betas=["structured-outputs-2025-11-13"],
messages=[{"role": "user", "content": f"Classify:\n{ticket}"}],
output_format=TicketClassification,
)
classification = response.parsed_output
if classification.requires_human or classification.confidence < 0.7:
route_to_human(ticket)
else:
auto_assign(ticket, classification.category)
```
## Use Case 3: API Response Formatting
**Scenario**: Generate API-ready responses
```python
class APIResponse(BaseModel):
status: str # "success" or "error"
data: dict
errors: Optional[List[dict]] = None
metadata: dict
response = client.beta.messages.parse(
model="claude-sonnet-4-5",
betas=["structured-outputs-2025-11-13"],
messages=[{"role": "user", "content": f"Process: {request}"}],
output_format=APIResponse,
)
# Directly return as JSON API response
return jsonify(response.parsed_output.model_dump())
```

View File

@@ -0,0 +1,92 @@
# Phase 1: Schema Design
**Objective**: Create a production-ready JSON schema respecting all limitations
## Steps
### 1. Define Output Structure
Ask the user:
- "What fields do you need in the output?"
- "Which fields are required vs. optional?"
- "What are the data types for each field?"
- "Are there nested objects or arrays?"
### 2. Choose Schema Approach
**Option A: Pydantic (Python) - Recommended**
```python
from pydantic import BaseModel
from typing import List, Optional
class ContactInfo(BaseModel):
name: str
email: str
plan_interest: Optional[str] = None
demo_requested: bool = False
tags: List[str] = []
```
**Option B: Zod (TypeScript) - Recommended**
```typescript
import { z } from 'zod';
const ContactInfoSchema = z.object({
name: z.string(),
email: z.string().email(),
plan_interest: z.string().optional(),
demo_requested: z.boolean().default(false),
tags: z.array(z.string()).default([]),
});
```
**Option C: Raw JSON Schema**
```json
{
"type": "object",
"properties": {
"name": {"type": "string", "description": "Full name"},
"email": {"type": "string", "description": "Email address"},
"plan_interest": {"type": "string", "description": "Interested plan"},
"demo_requested": {"type": "boolean"},
"tags": {"type": "array", "items": {"type": "string"}}
},
"required": ["name", "email", "demo_requested"],
"additionalProperties": false
}
```
### 3. Apply JSON Schema Limitations
**✅ Supported Features:**
- All basic types: object, array, string, integer, number, boolean, null
- `enum` (strings, numbers, bools, nulls only)
- `const`
- `anyOf` and `allOf` (limited)
- `$ref`, `$def`, `definitions` (local only)
- `required` and `additionalProperties: false`
- String formats: date-time, time, date, email, uri, uuid, ipv4, ipv6
- Array `minItems` (0 or 1 only)
**❌ NOT Supported (SDK can transform these):**
- Recursive schemas
- Numerical constraints (minimum, maximum)
- String constraints (minLength, maxLength)
- Complex array constraints
- External `$ref`
### 4. Add AI-Friendly Descriptions
```python
class Invoice(BaseModel):
invoice_number: str # Field(description="Invoice ID, format: INV-XXXXX")
date: str # Field(description="Invoice date in YYYY-MM-DD format")
total: float # Field(description="Total amount in USD")
items: List[LineItem] # Field(description="Line items on the invoice")
```
Good descriptions help Claude understand what to extract.
## Output
Production-ready schema following Anthropic's limitations.

View File

@@ -0,0 +1,100 @@
# Phase 2: SDK Integration
**Objective**: Implement using SDK helpers for automatic validation
## Python Implementation
**Recommended: Use `client.beta.messages.parse()`**
```python
from pydantic import BaseModel, Field
from typing import List, Optional
from anthropic import Anthropic
class ContactInfo(BaseModel):
name: str = Field(description="Full name of the contact")
email: str = Field(description="Email address")
plan_interest: Optional[str] = Field(
None, description="Plan tier they're interested in"
)
demo_requested: bool = Field(
False, description="Whether they requested a demo"
)
client = Anthropic()
def extract_contact(text: str) -> ContactInfo:
"""Extract contact information from text."""
response = client.beta.messages.parse(
model="claude-sonnet-4-5",
max_tokens=1024,
betas=["structured-outputs-2025-11-13"],
messages=[{
"role": "user",
"content": f"Extract contact information from: {text}"
}],
output_format=ContactInfo,
)
# Handle edge cases
if response.stop_reason == "refusal":
raise ValueError("Claude refused the request")
if response.stop_reason == "max_tokens":
raise ValueError("Response truncated - increase max_tokens")
# Automatically validated
return response.parsed_output
# Usage
contact = extract_contact("John Smith (john@example.com) wants Enterprise plan")
print(contact.name, contact.email) # Type-safe access
```
## TypeScript Implementation
```typescript
import Anthropic from '@anthropic-ai/sdk';
import { z } from 'zod';
import { betaZodOutputFormat } from '@anthropic-ai/sdk/helpers/beta/zod';
const ContactInfoSchema = z.object({
name: z.string().describe("Full name of the contact"),
email: z.string().email().describe("Email address"),
plan_interest: z.string().optional().describe("Plan tier interested in"),
demo_requested: z.boolean().default(false).describe("Demo requested"),
});
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
async function extractContact(text: string) {
const response = await client.beta.messages.parse({
model: "claude-sonnet-4-5",
max_tokens: 1024,
betas: ["structured-outputs-2025-11-13"],
messages: [{
role: "user",
content: `Extract contact information from: ${text}`
}],
output_format: betaZodOutputFormat(ContactInfoSchema),
});
if (response.stop_reason === "refusal") {
throw new Error("Claude refused the request");
}
if (response.stop_reason === "max_tokens") {
throw new Error("Response truncated - increase max_tokens");
}
return response.parsed_output;
}
// Usage
const contact = await extractContact("John Smith (john@example.com)...");
console.log(contact.name, contact.email); // Fully typed
```
## Output
Working implementation with SDK validation.

View File

@@ -0,0 +1,53 @@
# Phase 3: Error Handling
**Objective**: Handle refusals, token limits, and validation errors
## Key Error Scenarios
### 1. Safety Refusals (`stop_reason: "refusal"`)
```python
if response.stop_reason == "refusal":
logger.warning(f"Request refused: {input_text}")
# Don't retry - respect safety boundaries
return None # or raise exception
```
### 2. Token Limit Reached (`stop_reason: "max_tokens"`)
```python
if response.stop_reason == "max_tokens":
# Retry with higher limit
return extract_with_higher_limit(text, max_tokens * 1.5)
```
### 3. Schema Validation Errors (SDK raises exception)
```python
from pydantic import ValidationError
try:
result = response.parsed_output
except ValidationError as e:
logger.error(f"Schema validation failed: {e}")
# Should be rare - indicates schema mismatch
raise
```
### 4. API Errors (400 - schema too complex)
```python
from anthropic import BadRequestError
try:
response = client.beta.messages.parse(...)
except BadRequestError as e:
if "too complex" in str(e).lower():
# Simplify schema
logger.error("Schema too complex, simplifying...")
raise
```
## Output
Robust error handling for production deployments.

View File

@@ -0,0 +1,56 @@
# Phase 4: Testing
**Objective**: Validate schema works with representative data
## Test Coverage
```python
import pytest
@pytest.fixture
def extractor():
return ContactExtractor()
def test_complete_contact(extractor):
"""Test with all fields present."""
text = "John Smith (john@example.com) interested in Enterprise plan, wants demo"
result = extractor.extract(text)
assert result.name == "John Smith"
assert result.email == "john@example.com"
assert result.plan_interest == "Enterprise"
assert result.demo_requested is True
def test_minimal_contact(extractor):
"""Test with only required fields."""
text = "Contact: jane@example.com"
result = extractor.extract(text)
assert result.email == "jane@example.com"
assert result.name is not None # Claude should infer or extract
assert result.plan_interest is None # Optional field
assert result.demo_requested is False # Default
def test_invalid_input(extractor):
"""Test with insufficient data."""
text = "This has no contact information"
# Depending on requirements, might raise or return partial data
result = extractor.extract(text)
# Define expected behavior
def test_refusal_scenario(extractor):
"""Test that refusals are handled."""
# Test with potentially unsafe content
# Verify graceful handling without crash
pass
def test_token_limit(extractor):
"""Test with very long input."""
text = "..." * 10000 # Very long text
# Verify either succeeds or raises appropriate error
pass
```
## Output
Comprehensive test suite covering edge cases.

View File

@@ -0,0 +1,78 @@
# Phase 5: Production Optimization
**Objective**: Optimize for performance, cost, and reliability
## 1. Grammar Caching Strategy
The first request compiles a grammar from your schema (~extra latency). Subsequent requests use cached grammar (24-hour TTL).
**Cache Invalidation Triggers:**
- Schema structure changes
- Tool set changes (if using tools + JSON outputs together)
- 24 hours of non-use
**Best Practices:**
```python
# ✅ Good: Finalize schema before production
CONTACT_SCHEMA = ContactInfo # Reuse same schema
# ❌ Bad: Dynamic schema generation
def get_schema(include_phone: bool): # Different schemas = cache misses
if include_phone:
class Contact(BaseModel):
phone: str
...
...
```
## 2. Token Cost Management
Structured outputs add tokens via system prompt:
```python
# Monitor token usage
response = client.beta.messages.parse(...)
print(f"Input tokens: {response.usage.input_tokens}")
print(f"Output tokens: {response.usage.output_tokens}")
# Optimize descriptions for token efficiency
# ✅ Good: Concise but clear
name: str = Field(description="Full name")
# ❌ Excessive: Too verbose
name: str = Field(description="The complete full name of the person including first name, middle name if available, and last name")
```
## 3. Monitoring
```python
import time
from dataclasses import dataclass
@dataclass
class StructuredOutputMetrics:
latency_ms: float
input_tokens: int
output_tokens: int
cache_hit: bool # Infer from latency
stop_reason: str
def track_metrics(response, start_time) -> StructuredOutputMetrics:
latency = (time.time() - start_time) * 1000
return StructuredOutputMetrics(
latency_ms=latency,
input_tokens=response.usage.input_tokens,
output_tokens=response.usage.output_tokens,
cache_hit=latency < 500, # Heuristic: fast = cache hit
stop_reason=response.stop_reason,
)
# Track in production
metrics = track_metrics(response, start_time)
if metrics.latency_ms > 1000:
logger.warning(f"Slow structured output: {metrics.latency_ms}ms")
```
## Output
Production-optimized implementation with caching and monitoring.