60e0ffc959
Upgrade checks / Notify on failure (push) Has been cancelled
Upgrade checks / Close issue on success (push) Has been cancelled
Schema Crash Test / Real-world schema crash test (232K schemas) (push) Has been cancelled
Run static analysis / static_analysis (push) Has been cancelled
Tests / Tests: Python 3.10 on ubuntu-latest (push) Has been cancelled
Tests / Tests: Python 3.13 on ubuntu-latest (push) Has been cancelled
Tests / Tests: Python 3.10 on windows-latest (push) Has been cancelled
Tests / Tests with lowest-direct dependencies (push) Has been cancelled
Tests / MCP conformance tests (push) Has been cancelled
Tests / Integration tests (push) Has been cancelled
Tests / Package install smoke (push) Has been cancelled
Upgrade checks / Static analysis (push) Has been cancelled
Upgrade checks / Tests: Python 3.10 on ubuntu-latest (push) Has been cancelled
Upgrade checks / Tests: Python 3.13 on ubuntu-latest (push) Has been cancelled
Upgrade checks / Tests: Python 3.10 on windows-latest (push) Has been cancelled
Upgrade checks / Integration tests (push) Has been cancelled
Update MCPServerConfig Schema / update-config-schema (push) Has been cancelled
Update SDK Documentation / update-sdk-docs (push) Has been cancelled
111 lines
3.7 KiB
Python
111 lines
3.7 KiB
Python
# /// script
|
|
# dependencies = ["anthropic", "fastmcp", "rich"]
|
|
# ///
|
|
"""
|
|
Structured Output Sampling
|
|
|
|
Demonstrates using `result_type` to get validated Pydantic models from an LLM.
|
|
The server exposes a sentiment analysis tool that returns structured data.
|
|
|
|
Run:
|
|
uv run examples/sampling/structured_output.py
|
|
"""
|
|
|
|
import asyncio
|
|
|
|
from pydantic import BaseModel
|
|
from rich.console import Console
|
|
from rich.panel import Panel
|
|
from rich.table import Table
|
|
|
|
from fastmcp import Client, Context, FastMCP
|
|
from fastmcp.client.sampling import SamplingMessage, SamplingParams
|
|
from fastmcp.client.sampling.handlers.anthropic import AnthropicSamplingHandler
|
|
|
|
console = Console()
|
|
|
|
|
|
class LoggingAnthropicHandler(AnthropicSamplingHandler):
|
|
async def __call__(
|
|
self, messages: list[SamplingMessage], params: SamplingParams, context
|
|
): # type: ignore[override]
|
|
console.print(" [bold blue]SAMPLING[/] Calling Claude API...")
|
|
result = await super().__call__(messages, params, context)
|
|
console.print(" [bold blue]SAMPLING[/] Response received")
|
|
return result
|
|
|
|
|
|
# Define a structured output model
|
|
class SentimentAnalysis(BaseModel):
|
|
sentiment: str # "positive", "negative", or "neutral"
|
|
confidence: float # 0.0 to 1.0
|
|
keywords: list[str] # Keywords that influenced the analysis
|
|
explanation: str # Brief explanation of the analysis
|
|
|
|
|
|
# Create the MCP server
|
|
mcp = FastMCP("Sentiment Analyzer")
|
|
|
|
|
|
@mcp.tool
|
|
async def analyze_sentiment(text: str, ctx: Context) -> dict:
|
|
"""Analyze the sentiment of the given text."""
|
|
console.print(" [bold cyan]SERVER[/] Analyzing sentiment...")
|
|
|
|
result = await ctx.sample(
|
|
messages=f"Analyze the sentiment of this text:\n\n{text}",
|
|
system_prompt="You are a sentiment analysis expert. Analyze text carefully.",
|
|
result_type=SentimentAnalysis,
|
|
)
|
|
|
|
console.print(" [bold cyan]SERVER[/] Analysis complete")
|
|
return result.result.model_dump() # type: ignore[attr-defined]
|
|
|
|
|
|
async def main():
|
|
console.print(
|
|
Panel.fit("[bold]MCP Sampling Flow Demo[/]", subtitle="structured_output.py")
|
|
)
|
|
console.print()
|
|
|
|
handler = LoggingAnthropicHandler(default_model="claude-sonnet-4-5")
|
|
|
|
async with Client(mcp, sampling_handler=handler) as client:
|
|
texts = [
|
|
"I absolutely love this product! It exceeded all my expectations.",
|
|
"The service was okay, nothing special but got the job done.",
|
|
"This is the worst experience I've ever had. Never again.",
|
|
]
|
|
|
|
for text in texts:
|
|
console.print(f"[bold green]CLIENT[/] Analyzing: [italic]{text[:50]}...[/]")
|
|
console.print()
|
|
|
|
result = await client.call_tool("analyze_sentiment", {"text": text})
|
|
data = result.data
|
|
|
|
# Display results in a table
|
|
table = Table(show_header=False, box=None, padding=(0, 2))
|
|
table.add_column(style="bold")
|
|
table.add_column()
|
|
|
|
sentiment_color = {
|
|
"positive": "green",
|
|
"negative": "red",
|
|
"neutral": "yellow",
|
|
}.get(
|
|
data["sentiment"],
|
|
"white", # type: ignore[union-attr]
|
|
)
|
|
table.add_row("Sentiment", f"[{sentiment_color}]{data['sentiment']}[/]") # type: ignore[index]
|
|
table.add_row("Confidence", f"{data['confidence']:.0%}") # type: ignore[index]
|
|
table.add_row("Keywords", ", ".join(data["keywords"])) # type: ignore[index]
|
|
table.add_row("Explanation", data["explanation"]) # type: ignore[index]
|
|
|
|
console.print(Panel(table, border_style=sentiment_color))
|
|
console.print()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|