Improve documentation overall

This commit is contained in:
Vinicius Brasil
2026-06-18 21:49:54 -07:00
parent 9b8ecc7df5
commit 5fbab5a50a
5 changed files with 90 additions and 119 deletions

View File

@@ -39,7 +39,7 @@ The Enterprise Tools Repository includes:
- **Error Handling**: Incorporates robust error handling mechanisms to ensure smooth operation.
- **Caching Mechanism**: Features intelligent caching to optimize performance and reduce redundant operations.
- **Asynchronous Support**: Handles both synchronous and asynchronous tools, enabling non-blocking operations.
- **Typed Outputs**: Optionally validates tool results with Pydantic models and sends agents a JSON-safe representation while preserving the raw Python value for direct calls and hooks.
- **Typed Outputs**: Uses optional Pydantic models to give agents clear JSON fields while direct Python calls still receive the tool's normal return value.
## Using CrewAI Tools
@@ -187,48 +187,52 @@ class MyCustomTool(BaseTool):
### Typed Tool Outputs
As a best practice, define a Pydantic output model when a tool returns structured data. CrewAI keeps `tool.run(...)` unchanged: it returns the raw Python value from `_run`. During agent execution, CrewAI validates that raw value against the tool's `output_schema` and sends the agent a JSON string.
When a tool returns structured data, define a Pydantic output model. This gives the agent field names it can trust, such as `sku`, `quantity`, or `needs_reorder`.
Direct Python calls still receive the value your tool returns. When an agent uses the tool, CrewAI sends the agent a JSON string based on the output model.
```python Code
from crewai.tools import BaseTool
from pydantic import BaseModel
class SearchResult(BaseModel):
query: str
score: float
class InventoryResult(BaseModel):
sku: str
quantity: int
needs_reorder: bool
class SearchTool(BaseTool):
name: str = "Search"
description: str = "Searches for a query and returns the top match score."
class InventoryTool(BaseTool):
name: str = "Inventory Check"
description: str = "Checks current stock for a product SKU."
def _run(self, query: str) -> SearchResult:
return SearchResult(query=query, score=0.97)
def _run(self, sku: str) -> InventoryResult:
quantity = {"SKU-123": 14, "SKU-456": 0}.get(sku, 0)
return InventoryResult(sku=sku, quantity=quantity, needs_reorder=quantity < 5)
tool = SearchTool()
tool = InventoryTool()
# Direct calls receive the raw Pydantic object.
raw_result = tool.run(query="CrewAI")
print(raw_result.score)
result = tool.run(sku="SKU-123")
print(result.quantity)
```
To send a custom representation to the agent, such as Markdown, override `format_output_for_agent` on your `BaseTool` subclass. This does not change direct execution: `tool.run(...)` still returns the raw Python value.
To send Markdown or another short text format to the agent, override `format_output_for_agent`. Direct calls to `tool.run(...)` still return the normal Python value.
```python Code
class SearchTool(BaseTool):
name: str = "Search"
description: str = "Searches for a query and returns the top match score."
class InventoryTool(BaseTool):
name: str = "Inventory Check"
description: str = "Checks current stock for a product SKU."
def _run(self, query: str) -> SearchResult:
return SearchResult(query=query, score=0.97)
def _run(self, sku: str) -> InventoryResult:
quantity = {"SKU-123": 14, "SKU-456": 0}.get(sku, 0)
return InventoryResult(sku=sku, quantity=quantity, needs_reorder=quantity < 5)
def format_output_for_agent(self, raw_result: object) -> str:
result = SearchResult.model_validate(raw_result)
return f"### Search result\n\n- Query: `{result.query}`\n- Score: {result.score}"
result = InventoryResult.model_validate(raw_result)
status = "reorder needed" if result.needs_reorder else "stock is healthy"
return f"{result.sku}: {result.quantity} units. {status}."
```
If you do not override `format_output_for_agent`, CrewAI uses the default typed-output behavior: Pydantic outputs become JSON for the agent, and untyped outputs use `str(raw_result)`.
If validation or serialization fails during agent execution, CrewAI emits a runtime warning and falls back to `str(raw_result)` for the agent-facing text. Direct tool calls still receive the raw result.
If you do not override `format_output_for_agent`, typed outputs are sent to the agent as JSON. Plain string results work as before.
## Asynchronous Tool Support

View File

@@ -106,11 +106,11 @@ Explicit schemas are recommended for published tools — they produce better age
### Optional: Typed Outputs with `output_schema`
If your tool returns structured data, define a Pydantic output model. This is a best practice for published tools because it gives agents a predictable JSON shape while preserving the raw Python value for direct users of your package.
If your tool returns structured data, define a Pydantic output model. This is a good default for published tools because users and agents can rely on named fields.
CrewAI keeps direct execution unchanged: `tool.run(...)` returns the raw value from your tool. During agent execution, CrewAI validates that raw value against the output schema and sends the agent a JSON string. If validation or serialization fails, CrewAI warns and falls back to `str(raw_result)` for the agent-facing text.
Direct Python calls still receive the value your tool returns. When an agent uses the tool, CrewAI sends the agent JSON based on the output model.
You can let CrewAI infer the output schema from a Pydantic return annotation:
CrewAI can infer the output schema from a Pydantic return annotation:
```python
from crewai.tools import BaseTool
@@ -127,10 +127,12 @@ class GeolocateTool(BaseTool):
description: str = "Converts a street address into latitude/longitude coordinates."
def _run(self, address: str) -> GeolocateResult:
if "1600 Pennsylvania" in address:
return GeolocateResult(latitude=38.8977, longitude=-77.0365)
return GeolocateResult(latitude=40.7128, longitude=-74.0060)
```
Or set `output_schema` explicitly when your implementation returns a dictionary:
Set `output_schema` explicitly when your tool returns a dictionary:
```python
class GeolocateTool(BaseTool):
@@ -139,10 +141,12 @@ class GeolocateTool(BaseTool):
output_schema: type[BaseModel] = GeolocateResult
def _run(self, address: str) -> dict[str, float]:
if "1600 Pennsylvania" in address:
return {"latitude": 38.8977, "longitude": -77.0365}
return {"latitude": 40.7128, "longitude": -74.0060}
```
If agents should receive a custom text format instead of JSON, override `format_output_for_agent` on your `BaseTool` subclass. This is useful when the best agent-facing representation is Markdown, a terse summary, or another format derived from the same raw result.
If agents should receive a short text summary instead of JSON, override `format_output_for_agent` on your `BaseTool` subclass.
```python
class GeolocateTool(BaseTool):
@@ -150,18 +154,16 @@ class GeolocateTool(BaseTool):
description: str = "Converts a street address into latitude/longitude coordinates."
def _run(self, address: str) -> GeolocateResult:
if "1600 Pennsylvania" in address:
return GeolocateResult(latitude=38.8977, longitude=-77.0365)
return GeolocateResult(latitude=40.7128, longitude=-74.0060)
def format_output_for_agent(self, raw_result: object) -> str:
result = GeolocateResult.model_validate(raw_result)
return (
f"### Coordinates\n\n"
f"- Latitude: `{result.latitude}`\n"
f"- Longitude: `{result.longitude}`"
)
return f"Latitude {result.latitude}, longitude {result.longitude}"
```
The override only controls the text sent to the agent. Direct users of your package still receive the raw value from `tool.run(...)`.
The override only changes what the agent sees. Direct users of your package still receive the normal value from `tool.run(...)`.
### Optional: Environment Variables

View File

@@ -55,143 +55,108 @@ def my_simple_tool(question: str) -> str:
### Best Practice: Define Typed Outputs
When a tool returns structured data, define the output as a Pydantic model. This is optional, but recommended because it gives CrewAI a clear contract for the data your tool returns.
When a tool returns structured data, define a Pydantic output model. This helps the agent read the result as clear fields instead of guessing from plain text.
Typed outputs create a clear split between direct Python usage and agent execution:
Typed outputs are useful for results with stable fields, such as IDs, status values, scores, prices, or lists. Plain strings are still fine for short prose results.
- `tool.run(...)` returns the raw Python value from your tool.
- Agent execution validates the raw value with the tool's output schema and sends the agent an LLM-safe string.
- Valid Pydantic outputs are serialized to JSON for the agent.
- If validation or serialization fails, CrewAI emits a runtime warning and falls back to `str(raw_result)` only for the agent-facing text.
Direct Python calls still receive the value your tool returns. When an agent uses a typed tool, CrewAI sends the agent JSON based on the output model.
#### Return a Pydantic Model
CrewAI infers the output schema when your `BaseTool` or `@tool` function has a Pydantic return annotation.
CrewAI infers the output schema when your `BaseTool` has a Pydantic return annotation.
```python Code
from crewai.tools import BaseTool
from pydantic import BaseModel, Field
class SentimentResult(BaseModel):
label: str = Field(description="The sentiment label, such as positive, neutral, or negative.")
confidence: float = Field(description="Confidence score from 0 to 1.")
class InventoryResult(BaseModel):
sku: str = Field(description="The product SKU.")
quantity: int = Field(description="Units available.")
needs_reorder: bool = Field(description="Whether the item should be reordered.")
class SentimentTool(BaseTool):
name: str = "Sentiment Analyzer"
description: str = "Analyze the sentiment of a short text passage."
class InventoryTool(BaseTool):
name: str = "Inventory Check"
description: str = "Check current stock for a product SKU."
def _run(self, text: str) -> SentimentResult:
# Replace this with your model, API call, or business logic.
return SentimentResult(label="positive", confidence=0.92)
def _run(self, sku: str) -> InventoryResult:
quantity = {"SKU-123": 14, "SKU-456": 0}.get(sku, 0)
return InventoryResult(sku=sku, quantity=quantity, needs_reorder=quantity < 5)
tool = SentimentTool()
result = tool.run(text="CrewAI makes multi-agent workflows easier.")
tool = InventoryTool()
result = tool.run(sku="SKU-123")
# Direct Python calls receive the raw Pydantic object.
print(result.label)
print(result.confidence)
print(result.quantity)
```
When an agent calls `SentimentTool`, it receives JSON like this:
When an agent calls `InventoryTool`, it receives JSON like this:
```json
{"label":"positive","confidence":0.92}
{"sku":"SKU-123","quantity":14,"needs_reorder":false}
```
This is easier for the agent to reason over than a Python object representation.
#### Use `output_schema` with Dictionary Results
#### Use `output_schema` for Dictionary Results
If your implementation naturally returns a dictionary, set `output_schema` explicitly. CrewAI validates the dictionary and serializes the validated result to JSON for the agent.
```python Code
from crewai.tools import BaseTool
from pydantic import BaseModel, Field
class ProductLookupResult(BaseModel):
sku: str = Field(description="The product SKU.")
name: str = Field(description="The product name.")
in_stock: bool = Field(description="Whether the product is available.")
class ProductLookupTool(BaseTool):
name: str = "Product Lookup"
description: str = "Look up product availability by SKU."
output_schema: type[BaseModel] = ProductLookupResult
def _run(self, sku: str) -> dict[str, object]:
return {
"sku": sku,
"name": "CrewAI Enterprise License",
"in_stock": True,
}
```
You can use the same pattern with the `@tool` decorator:
If your tool returns a dictionary, set `output_schema` explicitly. You can do this on a `BaseTool` subclass or with the `@tool` decorator:
```python Code
from crewai.tools import tool
from pydantic import BaseModel, Field
class ProductLookupResult(BaseModel):
class ProductResult(BaseModel):
sku: str = Field(description="The product SKU.")
name: str = Field(description="The product name.")
in_stock: bool = Field(description="Whether the product is available.")
@tool("Product Lookup", output_schema=ProductLookupResult)
@tool("Product Lookup", output_schema=ProductResult)
def product_lookup(sku: str) -> dict[str, object]:
"""Look up product availability by SKU."""
catalog = {
"SKU-123": ("Noise-canceling headset", True),
"SKU-456": ("USB-C dock", False),
}
name, in_stock = catalog.get(sku, ("Unknown product", False))
return {
"sku": sku,
"name": "CrewAI Enterprise License",
"in_stock": True,
"name": name,
"in_stock": in_stock,
}
```
#### Customize the Text Sent to the Agent
By default, typed tool outputs are sent to the agent as JSON. If your agent should receive Markdown, XML, or a compact human-readable summary instead, subclass `BaseTool` and override `format_output_for_agent`.
This only changes the agent-facing text. Direct calls to `tool.run(...)` still return the raw Python value from `_run`.
By default, typed tool outputs are sent to the agent as JSON. If the agent should receive a short summary instead, subclass `BaseTool` and override `format_output_for_agent`.
```python Code
from crewai.tools import BaseTool
from pydantic import BaseModel, Field
class ProductLookupResult(BaseModel):
class InventoryResult(BaseModel):
sku: str = Field(description="The product SKU.")
name: str = Field(description="The product name.")
in_stock: bool = Field(description="Whether the product is available.")
quantity: int = Field(description="Units available.")
needs_reorder: bool = Field(description="Whether the item should be reordered.")
class ProductLookupTool(BaseTool):
name: str = "Product Lookup"
description: str = "Look up product availability by SKU."
class InventoryTool(BaseTool):
name: str = "Inventory Check"
description: str = "Check current stock for a product SKU."
def _run(self, sku: str) -> ProductLookupResult:
return ProductLookupResult(
sku=sku,
name="CrewAI Enterprise License",
in_stock=True,
)
def _run(self, sku: str) -> InventoryResult:
quantity = {"SKU-123": 14, "SKU-456": 0}.get(sku, 0)
return InventoryResult(sku=sku, quantity=quantity, needs_reorder=quantity < 5)
def format_output_for_agent(self, raw_result: object) -> str:
result = ProductLookupResult.model_validate(raw_result)
status = "in stock" if result.in_stock else "out of stock"
return (
f"### {result.name}\n\n"
f"- SKU: `{result.sku}`\n"
f"- Status: **{status}**"
)
result = InventoryResult.model_validate(raw_result)
status = "reorder needed" if result.needs_reorder else "stock is healthy"
return f"{result.sku}: {result.quantity} units. {status}."
tool = ProductLookupTool()
result = tool.run(sku="CREW-ENT")
tool = InventoryTool()
result = tool.run(sku="SKU-123")
# Direct Python calls receive the raw Pydantic object.
print(result.name)
print(result.quantity)
```
When an agent calls `ProductLookupTool`, it receives the Markdown returned by `format_output_for_agent`. When you do not override this method, CrewAI uses the default behavior: validate typed outputs and serialize them to JSON, or use `str(raw_result)` for untyped outputs.
Use typed outputs for tool results that have stable fields, nested data, lists, IDs, status values, scores, or any structure the agent should interpret precisely. Plain strings are still fine for simple prose results.
The override only changes what the agent sees. Direct calls to `tool.run(...)` still return the normal Python value.
### Defining a Cache Function for the Tool

View File

@@ -199,7 +199,7 @@ class ToolCallHookContext:
raw_tool_result: Any | None # Raw Python result (after hooks)
```
For typed tool outputs, `tool_result` is the JSON string sent to the agent, while `raw_tool_result` is the original Python value returned by the tool.
For typed tool outputs, `tool_result` is the string the agent sees. By default, this is JSON. If the tool uses custom formatting, it can be Markdown or another string. `raw_tool_result` is the original Python value returned by the tool.
## Common Patterns

View File

@@ -64,7 +64,7 @@ class ToolCallHookContext:
raw_tool_result: Any | None # Raw Python result (after hooks only)
```
For typed tool outputs, `tool_result` is the JSON string sent to the agent, while `raw_tool_result` is the original Python value returned by the tool. Use `raw_tool_result` when your hook needs the typed object or dictionary; return a string from the hook only when you want to change the agent-facing result.
For typed tool outputs, `tool_result` is the string the agent sees. By default, this is JSON. If the tool uses custom formatting, it can be Markdown or another string. Use `raw_tool_result` when your hook needs the typed object or dictionary.
### Modifying Tool Inputs