From 5b15c28b8feeecbb6a5eb62bcf4c8623a25be44b Mon Sep 17 00:00:00 2001 From: Nick Clegg Date: Fri, 20 Jun 2025 16:14:43 +0000 Subject: [PATCH 1/4] Update structured output docs for model providers --- .../concepts/model-providers/anthropic.md | 44 +++++++ .../model-providers/custom_model_provider.md | 111 +++++++++++++++++- .../concepts/model-providers/litellm.md | 46 ++++++++ .../concepts/model-providers/llamaapi.md | 47 ++++++++ .../concepts/model-providers/ollama.md | 46 ++++++++ .../concepts/model-providers/openai.md | 36 ++++++ 6 files changed, 329 insertions(+), 1 deletion(-) diff --git a/docs/user-guide/concepts/model-providers/anthropic.md b/docs/user-guide/concepts/model-providers/anthropic.md index 5973b9e4..fd7d2e14 100644 --- a/docs/user-guide/concepts/model-providers/anthropic.md +++ b/docs/user-guide/concepts/model-providers/anthropic.md @@ -58,6 +58,50 @@ The `model_config` configures the underlying model selected for inference. The s If you encounter the error `ModuleNotFoundError: No module named 'anthropic'`, this means you haven't installed the `anthropic` dependency in your environment. To fix, run `pip install 'strands-agents[anthropic]'`. +## Advanced Features + +### Structured Output + +Anthropic's Claude models support structured output through their tool calling capabilities. When you use [`Agent.structured_output()`](../../../api-reference/agent.md#strands.agent.agent.Agent.structured_output), the Strands SDK converts your Pydantic models to Anthropic's tool specification format. + +```python +from pydantic import BaseModel, Field +from strands import Agent +from strands.models.anthropic import AnthropicModel + +class BookAnalysis(BaseModel): + """Analyze a book's key information.""" + title: str = Field(description="The book's title") + author: str = Field(description="The book's author") + genre: str = Field(description="Primary genre or category") + summary: str = Field(description="Brief summary of the book") + rating: int = Field(description="Rating from 1-10", ge=1, le=10) + +model = AnthropicModel( + client_args={"api_key": ""}, + max_tokens=1024, + model_id="claude-3-7-sonnet-20250219", + params={"temperature": 0.2} # Lower temperature for consistent structured output +) + +agent = Agent(model=model) + +# Extract structured book information +result = agent.structured_output( + BookAnalysis, + """ + Analyze this book: "The Hitchhiker's Guide to the Galaxy" by Douglas Adams. + It's a science fiction comedy about Arthur Dent's adventures through space + after Earth is destroyed. It's widely considered a classic of humorous sci-fi. + """ +) + +print(f"Title: {result.title}") +print(f"Author: {result.author}") +print(f"Genre: {result.genre}") +print(f"Rating: {result.rating}") +``` + ## References - [API](../../../api-reference/models.md) diff --git a/docs/user-guide/concepts/model-providers/custom_model_provider.md b/docs/user-guide/concepts/model-providers/custom_model_provider.md index 9b42e80d..365b54f5 100644 --- a/docs/user-guide/concepts/model-providers/custom_model_provider.md +++ b/docs/user-guide/concepts/model-providers/custom_model_provider.md @@ -2,6 +2,38 @@ Strands Agents SDK provides an extensible interface for implementing custom model providers, allowing organizations to integrate their own LLM services while keeping implementation details private to their codebase. +## Model Provider Functionality + +Custom model providers in Strands Agents support two primary interaction modes: + +### Conversational Interaction (`converse`) +The standard conversational mode where agents exchange messages with the model. This is the default interaction pattern used by [`Agent.converse()`](../../../api-reference/agent.md#strands.agent.agent.Agent.converse) and when you call an agent directly: + +```python +agent = Agent(model=your_custom_model) +response = agent("Hello, how can you help me today?") +``` + +### Structured Output (`structured_output`) +A specialized mode that returns type-safe, validated responses using [Pydantic](https://docs.pydantic.dev/latest/concepts/models/) models instead of raw text. This enables reliable data extraction and processing: + +```python +from pydantic import BaseModel + +class PersonInfo(BaseModel): + name: str + age: int + occupation: str + +result = agent.structured_output( + PersonInfo, + "Extract info: John Smith is a 30-year-old software engineer" +) +# Returns a validated PersonInfo object +``` + +Both modes work through the same underlying model provider interface, with structured output using tool calling capabilities to ensure schema compliance. + ## Model Provider Architecture Strands Agents uses an abstract `Model` class that defines the standard interface all model providers must implement: @@ -254,7 +286,84 @@ Now that you have mapped the Strands Agents input to your models request, use th yield chunk ``` -### 5. Use Your Custom Model Provider +### 5. Structured Output Support + +To support structured output in your custom model provider, you need to implement a `structured_output()` method that extracts schema information from Pydantic models, formats requests appropriately, and validates responses using tool calling capabilities. + +```python +from typing import Type, TypeVar, Optional, Callable, Any +from strands.types.content import Messages +from strands.utils.tools import convert_pydantic_to_tool_spec +from strands.utils.streaming import process_stream + +T = TypeVar('T', bound=BaseModel) + +@override +def structured_output( + self, output_model: Type[T], prompt: Messages, callback_handler: Optional[Callable] = None +) -> T: + """Get structured output using tool calling.""" + + # Convert Pydantic model to tool specification + tool_spec = convert_pydantic_to_tool_spec(output_model) + + # Use existing converse method with tool specification + response = self.converse(messages=prompt, tool_specs=[tool_spec]) + + # Process streaming response + for event in process_stream(response, prompt): + if callback_handler and "callback" in event: + callback_handler(**event["callback"]) + else: + stop_reason, messages, _, _ = event["stop"] + + # Validate tool use response + if stop_reason != "tool_use": + raise ValueError("No valid tool use found in the model response.") + + # Extract tool use output + content = messages["content"] + for block in content: + if block.get("toolUse") and block["toolUse"]["name"] == tool_spec["name"]: + return output_model(**block["toolUse"]["input"]) + + raise ValueError("No valid tool use input found in the response.") +``` + +**Implementation Suggestions:** + +1. **Tool Integration**: Use your existing `converse()` method with tool specifications to invoke your model +2. **Response Validation**: Use `output_model(**data)` to validate the response +3. **Error Handling**: Provide clear error messages for parsing and validation failures + + +**Test Your Implementation:** +```python +def test_structured_output(): + """Test structured output implementation.""" + from pydantic import BaseModel, Field + + class PersonInfo(BaseModel): + name: str = Field(description="Full name") + age: int = Field(description="Age in years") + occupation: str = Field(description="Job title") + + model = YourCustomModel(api_key="key", model_id="model") + + result = model.structured_output( + PersonInfo, + [{"role": "user", "content": [{"text": "John Smith is a 30-year-old engineer."}]}] + ) + + assert isinstance(result, PersonInfo) + assert result.name == "John Smith" + print("Structured output test passed!") +``` + + +For detailed structured output usage patterns, see the [Structured Output documentation](../agents/structured-output.md). + +### 6. Use Your Custom Model Provider Once implemented, you can use your custom model provider in your applications: diff --git a/docs/user-guide/concepts/model-providers/litellm.md b/docs/user-guide/concepts/model-providers/litellm.md index 3600ca10..3c929f78 100644 --- a/docs/user-guide/concepts/model-providers/litellm.md +++ b/docs/user-guide/concepts/model-providers/litellm.md @@ -57,6 +57,52 @@ The `model_config` configures the underlying model selected for inference. The s If you encounter the error `ModuleNotFoundError: No module named 'litellm'`, this means you haven't installed the `litellm` dependency in your environment. To fix, run `pip install 'strands-agents[litellm]'`. +## Advanced Features + +### Structured Output + +LiteLLM supports structured output by proxying requests to underlying model providers that support tool calling. The availability of structured output depends on the specific model and provider you're using through LiteLLM. + +```python +from pydantic import BaseModel, Field +from strands import Agent +from strands.models.litellm import LiteLLMModel + +class TaskAnalysis(BaseModel): + """Analyze a task or project.""" + title: str = Field(description="Task title") + priority: str = Field(description="Priority level: low, medium, high") + estimated_hours: float = Field(description="Estimated completion time in hours") + dependencies: List[str] = Field(description="Task dependencies") + status: str = Field(description="Current status") + +model = LiteLLMModel( + model_id="gpt-4o", # OpenAI model through LiteLLM + params={ + "temperature": 0.1, + "max_tokens": 1000 + } +) + +agent = Agent(model=model) + +# Extract structured task information +result = agent.structured_output( + TaskAnalysis, + """ + Analyze this project task: "Implement user authentication system" + This is a high-priority task that requires database setup and security review. + Estimated to take 16 hours. Currently in planning phase. + Depends on database schema design and security policy approval. + """ +) + +print(f"Task: {result.title}") +print(f"Priority: {result.priority}") +print(f"Hours: {result.estimated_hours}") +print(f"Dependencies: {result.dependencies}") +``` + ## References - [API](../../../api-reference/models.md) diff --git a/docs/user-guide/concepts/model-providers/llamaapi.md b/docs/user-guide/concepts/model-providers/llamaapi.md index 8dde1fa9..88a2ab6e 100644 --- a/docs/user-guide/concepts/model-providers/llamaapi.md +++ b/docs/user-guide/concepts/model-providers/llamaapi.md @@ -63,6 +63,53 @@ The `model_config` configures the underlying model selected for inference. The s If you encounter the error `ModuleNotFoundError: No module named 'llamaapi'`, this means you haven't installed the `llamaapi` dependency in your environment. To fix, run `pip install 'strands-agents[llamaapi]'`. +## Advanced Features + +### Structured Output + +Llama API models support structured output through their tool calling capabilities. When you use [`Agent.structured_output()`](../../../api-reference/agent.md#strands.agent.agent.Agent.structured_output), the Strands SDK converts your Pydantic models to tool specifications that Llama models can understand. + +```python +from pydantic import BaseModel, Field +from typing import List, Optional +from strands import Agent +from strands.models.llamaapi import LlamaAPIModel + +class ResearchSummary(BaseModel): + """Summarize research findings.""" + topic: str = Field(description="Main research topic") + key_findings: List[str] = Field(description="Primary research findings") + methodology: str = Field(description="Research methodology used") + confidence_level: float = Field(description="Confidence in findings 0-1", ge=0, le=1) + recommendations: List[str] = Field(description="Actionable recommendations") + +model = LlamaAPIModel( + client_args={"api_key": ""}, + model_id="Llama-4-Maverick-17B-128E-Instruct-FP8", + temperature=0.1, # Low temperature for consistent structured output + max_completion_tokens=2000 +) + +agent = Agent(model=model) + +# Extract structured research summary +result = agent.structured_output( + ResearchSummary, + """ + Analyze this research: A study of 500 remote workers found that + productivity increased by 23% when using structured daily schedules. + The study used time-tracking software and productivity metrics over 6 months. + Researchers recommend implementing structured work blocks and regular breaks. + """ +) + +print(f"Topic: {result.topic}") +print(f"Key Findings: {result.key_findings}") +print(f"Methodology: {result.methodology}") +print(f"Confidence: {result.confidence_level}") +print(f"Recommendations: {result.recommendations}") +``` + ## References - [API](../../../api-reference/models.md) diff --git a/docs/user-guide/concepts/model-providers/ollama.md b/docs/user-guide/concepts/model-providers/ollama.md index 871c5dfe..3e310041 100644 --- a/docs/user-guide/concepts/model-providers/ollama.md +++ b/docs/user-guide/concepts/model-providers/ollama.md @@ -191,6 +191,52 @@ creative_agent = Agent(model=creative_model) factual_agent = Agent(model=factual_model) ``` +### Structured Output + +Ollama supports structured output for models that have tool calling capabilities. When you use [`Agent.structured_output()`](../../../api-reference/agent.md#strands.agent.agent.Agent.structured_output), the Strands SDK converts your Pydantic models to tool specifications that compatible Ollama models can understand. + +```python +from pydantic import BaseModel, Field +from strands import Agent +from strands.models.ollama import OllamaModel + +class CodeAnalysis(BaseModel): + """Analyze code structure and quality.""" + language: str = Field(description="Programming language") + complexity: str = Field(description="Code complexity: low, medium, high") + issues: List[str] = Field(description="Potential issues or improvements") + score: int = Field(description="Code quality score 1-10", ge=1, le=10) + +# Use a model that supports tool calling +ollama_model = OllamaModel( + host="http://localhost:11434", + model_id="llama3.1:8b", # Tool-capable model + temperature=0.1 # Low temperature for consistent structured output +) + +agent = Agent(model=ollama_model) + +# Extract structured code analysis +result = agent.structured_output( + CodeAnalysis, + """ + Analyze this Python function: + + def calculate_fibonacci(n): + if n <= 1: + return n + return calculate_fibonacci(n-1) + calculate_fibonacci(n-2) + + This is a recursive implementation of the Fibonacci sequence. + """ +) + +print(f"Language: {result.language}") +print(f"Complexity: {result.complexity}") +print(f"Issues: {result.issues}") +print(f"Score: {result.score}") +``` + ## Tool Support [Ollama models that support tool use](https://ollama.com/search?c=tools) can use tools through Strands's tool system: diff --git a/docs/user-guide/concepts/model-providers/openai.md b/docs/user-guide/concepts/model-providers/openai.md index 5971dfc6..df377f0e 100644 --- a/docs/user-guide/concepts/model-providers/openai.md +++ b/docs/user-guide/concepts/model-providers/openai.md @@ -69,6 +69,42 @@ The `model_config` configures the underlying model selected for inference. The s If you encounter the error `ModuleNotFoundError: No module named 'openai'`, this means you haven't installed the `openai` dependency in your environment. To fix, run `pip install 'strands-agents[openai]'`. +## Advanced Features + +### Structured Output + +OpenAI models support structured output through their native tool calling capabilities. When you use [`Agent.structured_output()`](../../../api-reference/agent.md#strands.agent.agent.Agent.structured_output), the Strands SDK automatically converts your Pydantic models to OpenAI's function calling format. + +```python +from pydantic import BaseModel, Field +from strands import Agent +from strands.models.openai import OpenAIModel + +class PersonInfo(BaseModel): + """Extract person information from text.""" + name: str = Field(description="Full name of the person") + age: int = Field(description="Age in years") + occupation: str = Field(description="Job or profession") + +model = OpenAIModel( + client_args={"api_key": ""}, + model_id="gpt-4o", + params={"temperature": 0.1} # Lower temperature for more consistent structured output +) + +agent = Agent(model=model) + +# Extract structured information +result = agent.structured_output( + PersonInfo, + "John Smith is a 30-year-old software engineer working at a tech startup." +) + +print(f"Name: {result.name}") # "John Smith" +print(f"Age: {result.age}") # 30 +print(f"Job: {result.occupation}") # "software engineer" +``` + ## References - [API](../../../api-reference/models.md) From cfa7e440fbc07f42f6a1680ea54af786f03aaa51 Mon Sep 17 00:00:00 2001 From: Nick Clegg Date: Fri, 20 Jun 2025 16:14:43 +0000 Subject: [PATCH 2/4] Update structured output docs for model providers --- .../model-providers/amazon-bedrock.md | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/docs/user-guide/concepts/model-providers/amazon-bedrock.md b/docs/user-guide/concepts/model-providers/amazon-bedrock.md index a3b04d26..c83abe1a 100644 --- a/docs/user-guide/concepts/model-providers/amazon-bedrock.md +++ b/docs/user-guide/concepts/model-providers/amazon-bedrock.md @@ -511,6 +511,44 @@ response = agent("If a train travels at 120 km/h and needs to cover 450 km, how > **Note**: Not all models support structured reasoning output. Check the [inference reasoning documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/inference-reasoning.html) for details on supported models. +### Structured Output + +Amazon Bedrock models support structured output through their tool calling capabilities. When you use [`Agent.structured_output()`](../../../api-reference/agent.md#strands.agent.agent.Agent.structured_output), the Strands SDK converts your Pydantic models to Bedrock's tool specification format. + +```python +from pydantic import BaseModel, Field +from strands import Agent +from strands.models import BedrockModel +from typing import List, Optional + +class ProductAnalysis(BaseModel): + """Analyze product information from text.""" + name: str = Field(description="Product name") + category: str = Field(description="Product category") + price: float = Field(description="Price in USD") + features: List[str] = Field(description="Key product features") + rating: Optional[float] = Field(description="Customer rating 1-5", ge=1, le=5) + +bedrock_model = BedrockModel() + +agent = Agent(model=bedrock_model) + +result = agent.structured_output( + ProductAnalysis, + """ + Analyze this product: The UltraBook Pro is a premium laptop computer + priced at $1,299. It features a 15-inch 4K display, 16GB RAM, 512GB SSD, + and 12-hour battery life. Customer reviews average 4.5 stars. + """ +) + +print(f"Product: {result.name}") +print(f"Category: {result.category}") +print(f"Price: ${result.price}") +print(f"Features: {result.features}") +print(f"Rating: {result.rating}") +``` + ## Troubleshooting ### Model access issue @@ -539,7 +577,6 @@ Use: ``` us.anthropic.claude-3-7-sonnet-20250219-v1:0 -``` ## Related Resources From 14e86f4309e39b35cac3036aa83886adadb0e5ea Mon Sep 17 00:00:00 2001 From: Nick Clegg Date: Thu, 26 Jun 2025 14:17:53 +0000 Subject: [PATCH 3/4] Update examples --- .../model-providers/amazon-bedrock.md | 1 + .../concepts/model-providers/anthropic.md | 11 +++-- .../concepts/model-providers/litellm.md | 38 +++++++--------- .../concepts/model-providers/llamaapi.md | 36 +++++++--------- .../concepts/model-providers/ollama.md | 43 ++++++++----------- .../concepts/model-providers/openai.md | 2 - 6 files changed, 57 insertions(+), 74 deletions(-) diff --git a/docs/user-guide/concepts/model-providers/amazon-bedrock.md b/docs/user-guide/concepts/model-providers/amazon-bedrock.md index c83abe1a..ba6a1981 100644 --- a/docs/user-guide/concepts/model-providers/amazon-bedrock.md +++ b/docs/user-guide/concepts/model-providers/amazon-bedrock.md @@ -577,6 +577,7 @@ Use: ``` us.anthropic.claude-3-7-sonnet-20250219-v1:0 +``` ## Related Resources diff --git a/docs/user-guide/concepts/model-providers/anthropic.md b/docs/user-guide/concepts/model-providers/anthropic.md index fd7d2e14..6ca64c2f 100644 --- a/docs/user-guide/concepts/model-providers/anthropic.md +++ b/docs/user-guide/concepts/model-providers/anthropic.md @@ -78,15 +78,18 @@ class BookAnalysis(BaseModel): rating: int = Field(description="Rating from 1-10", ge=1, le=10) model = AnthropicModel( - client_args={"api_key": ""}, - max_tokens=1024, + client_args={ + "api_key": "", + }, + max_tokens=1028, model_id="claude-3-7-sonnet-20250219", - params={"temperature": 0.2} # Lower temperature for consistent structured output + params={ + "temperature": 0.7, + } ) agent = Agent(model=model) -# Extract structured book information result = agent.structured_output( BookAnalysis, """ diff --git a/docs/user-guide/concepts/model-providers/litellm.md b/docs/user-guide/concepts/model-providers/litellm.md index 3c929f78..7b06725e 100644 --- a/docs/user-guide/concepts/model-providers/litellm.md +++ b/docs/user-guide/concepts/model-providers/litellm.md @@ -68,39 +68,33 @@ from pydantic import BaseModel, Field from strands import Agent from strands.models.litellm import LiteLLMModel -class TaskAnalysis(BaseModel): - """Analyze a task or project.""" - title: str = Field(description="Task title") - priority: str = Field(description="Priority level: low, medium, high") - estimated_hours: float = Field(description="Estimated completion time in hours") - dependencies: List[str] = Field(description="Task dependencies") - status: str = Field(description="Current status") +class BookAnalysis(BaseModel): + """Analyze a book's key information.""" + title: str = Field(description="The book's title") + author: str = Field(description="The book's author") + genre: str = Field(description="Primary genre or category") + summary: str = Field(description="Brief summary of the book") + rating: int = Field(description="Rating from 1-10", ge=1, le=10) model = LiteLLMModel( - model_id="gpt-4o", # OpenAI model through LiteLLM - params={ - "temperature": 0.1, - "max_tokens": 1000 - } + model_id="bedrock/us.anthropic.claude-3-7-sonnet-20250219-v1:0" ) agent = Agent(model=model) -# Extract structured task information result = agent.structured_output( - TaskAnalysis, + BookAnalysis, """ - Analyze this project task: "Implement user authentication system" - This is a high-priority task that requires database setup and security review. - Estimated to take 16 hours. Currently in planning phase. - Depends on database schema design and security policy approval. + Analyze this book: "The Hitchhiker's Guide to the Galaxy" by Douglas Adams. + It's a science fiction comedy about Arthur Dent's adventures through space + after Earth is destroyed. It's widely considered a classic of humorous sci-fi. """ ) -print(f"Task: {result.title}") -print(f"Priority: {result.priority}") -print(f"Hours: {result.estimated_hours}") -print(f"Dependencies: {result.dependencies}") +print(f"Title: {result.title}") +print(f"Author: {result.author}") +print(f"Genre: {result.genre}") +print(f"Rating: {result.rating}") ``` ## References diff --git a/docs/user-guide/concepts/model-providers/llamaapi.md b/docs/user-guide/concepts/model-providers/llamaapi.md index 88a2ab6e..e8a3d2f3 100644 --- a/docs/user-guide/concepts/model-providers/llamaapi.md +++ b/docs/user-guide/concepts/model-providers/llamaapi.md @@ -71,43 +71,37 @@ Llama API models support structured output through their tool calling capabiliti ```python from pydantic import BaseModel, Field -from typing import List, Optional from strands import Agent from strands.models.llamaapi import LlamaAPIModel -class ResearchSummary(BaseModel): - """Summarize research findings.""" - topic: str = Field(description="Main research topic") - key_findings: List[str] = Field(description="Primary research findings") - methodology: str = Field(description="Research methodology used") - confidence_level: float = Field(description="Confidence in findings 0-1", ge=0, le=1) - recommendations: List[str] = Field(description="Actionable recommendations") +class BookAnalysis(BaseModel): + """Analyze a book's key information.""" + title: str = Field(description="The book's title") + author: str = Field(description="The book's author") + genre: str = Field(description="Primary genre or category") + summary: str = Field(description="Brief summary of the book") + rating: int = Field(description="Rating from 1-10", ge=1, le=10) model = LlamaAPIModel( client_args={"api_key": ""}, model_id="Llama-4-Maverick-17B-128E-Instruct-FP8", - temperature=0.1, # Low temperature for consistent structured output - max_completion_tokens=2000 ) agent = Agent(model=model) -# Extract structured research summary result = agent.structured_output( - ResearchSummary, + BookAnalysis, """ - Analyze this research: A study of 500 remote workers found that - productivity increased by 23% when using structured daily schedules. - The study used time-tracking software and productivity metrics over 6 months. - Researchers recommend implementing structured work blocks and regular breaks. + Analyze this book: "The Hitchhiker's Guide to the Galaxy" by Douglas Adams. + It's a science fiction comedy about Arthur Dent's adventures through space + after Earth is destroyed. It's widely considered a classic of humorous sci-fi. """ ) -print(f"Topic: {result.topic}") -print(f"Key Findings: {result.key_findings}") -print(f"Methodology: {result.methodology}") -print(f"Confidence: {result.confidence_level}") -print(f"Recommendations: {result.recommendations}") +print(f"Title: {result.title}") +print(f"Author: {result.author}") +print(f"Genre: {result.genre}") +print(f"Rating: {result.rating}") ``` ## References diff --git a/docs/user-guide/concepts/model-providers/ollama.md b/docs/user-guide/concepts/model-providers/ollama.md index 3e310041..6c075208 100644 --- a/docs/user-guide/concepts/model-providers/ollama.md +++ b/docs/user-guide/concepts/model-providers/ollama.md @@ -200,41 +200,34 @@ from pydantic import BaseModel, Field from strands import Agent from strands.models.ollama import OllamaModel -class CodeAnalysis(BaseModel): - """Analyze code structure and quality.""" - language: str = Field(description="Programming language") - complexity: str = Field(description="Code complexity: low, medium, high") - issues: List[str] = Field(description="Potential issues or improvements") - score: int = Field(description="Code quality score 1-10", ge=1, le=10) - -# Use a model that supports tool calling +class BookAnalysis(BaseModel): + """Analyze a book's key information.""" + title: str = Field(description="The book's title") + author: str = Field(description="The book's author") + genre: str = Field(description="Primary genre or category") + summary: str = Field(description="Brief summary of the book") + rating: int = Field(description="Rating from 1-10", ge=1, le=10) + ollama_model = OllamaModel( host="http://localhost:11434", - model_id="llama3.1:8b", # Tool-capable model - temperature=0.1 # Low temperature for consistent structured output + model_id="llama3", ) agent = Agent(model=ollama_model) -# Extract structured code analysis result = agent.structured_output( - CodeAnalysis, + BookAnalysis, """ - Analyze this Python function: - - def calculate_fibonacci(n): - if n <= 1: - return n - return calculate_fibonacci(n-1) + calculate_fibonacci(n-2) - - This is a recursive implementation of the Fibonacci sequence. + Analyze this book: "The Hitchhiker's Guide to the Galaxy" by Douglas Adams. + It's a science fiction comedy about Arthur Dent's adventures through space + after Earth is destroyed. It's widely considered a classic of humorous sci-fi. """ ) -print(f"Language: {result.language}") -print(f"Complexity: {result.complexity}") -print(f"Issues: {result.issues}") -print(f"Score: {result.score}") +print(f"Title: {result.title}") +print(f"Author: {result.author}") +print(f"Genre: {result.genre}") +print(f"Rating: {result.rating}") ``` ## Tool Support @@ -249,7 +242,7 @@ from strands_tools import calculator, current_time # Create an Ollama model ollama_model = OllamaModel( host="http://localhost:11434", - model_id="llama3" + model_id="llama3.3" ) # Create an agent with tools diff --git a/docs/user-guide/concepts/model-providers/openai.md b/docs/user-guide/concepts/model-providers/openai.md index df377f0e..1b9cba7c 100644 --- a/docs/user-guide/concepts/model-providers/openai.md +++ b/docs/user-guide/concepts/model-providers/openai.md @@ -89,12 +89,10 @@ class PersonInfo(BaseModel): model = OpenAIModel( client_args={"api_key": ""}, model_id="gpt-4o", - params={"temperature": 0.1} # Lower temperature for more consistent structured output ) agent = Agent(model=model) -# Extract structured information result = agent.structured_output( PersonInfo, "John Smith is a 30-year-old software engineer working at a tech startup." From 343e8e999cbe51dcd2352cabaebc8d24d63fe153 Mon Sep 17 00:00:00 2001 From: Nick Clegg Date: Fri, 27 Jun 2025 14:31:13 +0000 Subject: [PATCH 4/4] Refactor custom_model_provider a bit more --- .../model-providers/custom_model_provider.md | 123 +++++++++--------- .../concepts/model-providers/ollama.md | 2 +- 2 files changed, 61 insertions(+), 64 deletions(-) diff --git a/docs/user-guide/concepts/model-providers/custom_model_provider.md b/docs/user-guide/concepts/model-providers/custom_model_provider.md index 365b54f5..4eb634be 100644 --- a/docs/user-guide/concepts/model-providers/custom_model_provider.md +++ b/docs/user-guide/concepts/model-providers/custom_model_provider.md @@ -6,15 +6,17 @@ Strands Agents SDK provides an extensible interface for implementing custom mode Custom model providers in Strands Agents support two primary interaction modes: -### Conversational Interaction (`converse`) -The standard conversational mode where agents exchange messages with the model. This is the default interaction pattern used by [`Agent.converse()`](../../../api-reference/agent.md#strands.agent.agent.Agent.converse) and when you call an agent directly: +### Conversational Interaction +The standard conversational mode where agents exchange messages with the model. This is the default interaction pattern that is used when you call an agent directly: ```python agent = Agent(model=your_custom_model) response = agent("Hello, how can you help me today?") ``` -### Structured Output (`structured_output`) +This invokes the underlying model provided to the agent. + +### Structured Output A specialized mode that returns type-safe, validated responses using [Pydantic](https://docs.pydantic.dev/latest/concepts/models/) models instead of raw text. This enables reliable data extraction and processing: ```python @@ -288,46 +290,42 @@ Now that you have mapped the Strands Agents input to your models request, use th ### 5. Structured Output Support -To support structured output in your custom model provider, you need to implement a `structured_output()` method that extracts schema information from Pydantic models, formats requests appropriately, and validates responses using tool calling capabilities. +To support structured output in your custom model provider, you need to implement a `structured_output()` method that invokes your model, and has it return a json output. Below is an example of what this might look like for a Bedrock model, where we invoke the model with a tool spec, and check if the response contains a `toolUse` response. ```python -from typing import Type, TypeVar, Optional, Callable, Any -from strands.types.content import Messages -from strands.utils.tools import convert_pydantic_to_tool_spec -from strands.utils.streaming import process_stream - -T = TypeVar('T', bound=BaseModel) - -@override -def structured_output( - self, output_model: Type[T], prompt: Messages, callback_handler: Optional[Callable] = None -) -> T: - """Get structured output using tool calling.""" - - # Convert Pydantic model to tool specification - tool_spec = convert_pydantic_to_tool_spec(output_model) - - # Use existing converse method with tool specification - response = self.converse(messages=prompt, tool_specs=[tool_spec]) - - # Process streaming response - for event in process_stream(response, prompt): - if callback_handler and "callback" in event: - callback_handler(**event["callback"]) - else: - stop_reason, messages, _, _ = event["stop"] + + T = TypeVar('T', bound=BaseModel) + + @override + def structured_output( + self, output_model: Type[T], prompt: Messages, callback_handler: Optional[Callable] = None + ) -> T: + """Get structured output using tool calling.""" - # Validate tool use response - if stop_reason != "tool_use": - raise ValueError("No valid tool use found in the model response.") + # Convert Pydantic model to tool specification + tool_spec = convert_pydantic_to_tool_spec(output_model) + + # Use existing converse method with tool specification + response = self.converse(messages=prompt, tool_specs=[tool_spec]) - # Extract tool use output - content = messages["content"] - for block in content: - if block.get("toolUse") and block["toolUse"]["name"] == tool_spec["name"]: - return output_model(**block["toolUse"]["input"]) + # Process streaming response + for event in process_stream(response, prompt): + if callback_handler and "callback" in event: + callback_handler(**event["callback"]) + else: + stop_reason, messages, _, _ = event["stop"] - raise ValueError("No valid tool use input found in the response.") + # Validate tool use response + if stop_reason != "tool_use": + raise ValueError("No valid tool use found in the model response.") + + # Extract tool use output + content = messages["content"] + for block in content: + if block.get("toolUse") and block["toolUse"]["name"] == tool_spec["name"]: + return output_model(**block["toolUse"]["input"]) + + raise ValueError("No valid tool use input found in the response.") ``` **Implementation Suggestions:** @@ -337,35 +335,11 @@ def structured_output( 3. **Error Handling**: Provide clear error messages for parsing and validation failures -**Test Your Implementation:** -```python -def test_structured_output(): - """Test structured output implementation.""" - from pydantic import BaseModel, Field - - class PersonInfo(BaseModel): - name: str = Field(description="Full name") - age: int = Field(description="Age in years") - occupation: str = Field(description="Job title") - - model = YourCustomModel(api_key="key", model_id="model") - - result = model.structured_output( - PersonInfo, - [{"role": "user", "content": [{"text": "John Smith is a 30-year-old engineer."}]}] - ) - - assert isinstance(result, PersonInfo) - assert result.name == "John Smith" - print("Structured output test passed!") -``` - - For detailed structured output usage patterns, see the [Structured Output documentation](../agents/structured-output.md). ### 6. Use Your Custom Model Provider -Once implemented, you can use your custom model provider in your applications: +Once implemented, you can use your custom model provider in your applications for regular agent invocation: ```python from strands import Agent @@ -389,6 +363,29 @@ agent = Agent(model=custom_model) response = agent("Hello, how are you today?") ``` +Or you can use the `structured_output` feature to generate structured output: + +```python +from strands import Agent +from your_org.models.custom_model import Model as CustomModel +from pydantic import BaseModel, Field + +class PersonInfo(BaseModel): + name: str = Field(description="Full name") + age: int = Field(description="Age in years") + occupation: str = Field(description="Job title") + +model = CustomModel(api_key="key", model_id="model") + +agent = Agent(model=model) + +result = agent.structured_output(PersonInfo, "John Smith is a 30-year-old engineer.") + +print(f"Name: {result.name}") +print(f"Age: {result.age}") +print(f"Occupation: {result.occupation}") +``` + ## Key Implementation Considerations ### 1. Message Formatting diff --git a/docs/user-guide/concepts/model-providers/ollama.md b/docs/user-guide/concepts/model-providers/ollama.md index 6c075208..46ca56b5 100644 --- a/docs/user-guide/concepts/model-providers/ollama.md +++ b/docs/user-guide/concepts/model-providers/ollama.md @@ -242,7 +242,7 @@ from strands_tools import calculator, current_time # Create an Ollama model ollama_model = OllamaModel( host="http://localhost:11434", - model_id="llama3.3" + model_id="llama3" ) # Create an agent with tools