## Before
### Tool: ``GoogleNews.SearchNewsStories``
```python
@tool(requires_secrets=["SERP_API_KEY"])
async def search_news_stories(
context: ToolContext,
keywords: Annotated[
str,
"Keywords to search for news articles. E.g. 'Apple launches new iPhone'.",
],
country_code: Annotated[
CountryCode | None,
"2-character country code to search for news articles. "
"E.g. 'us' (United States). "
f"Defaults to '{DEFAULT_GOOGLE_NEWS_COUNTRY}'.",
] = None,
language_code: Annotated[
LanguageCode,
"2-character language code to search for news articles. E.g. 'en' (English). "
f"Defaults to '{DEFAULT_GOOGLE_NEWS_LANGUAGE}'.",
] = DEFAULT_GOOGLE_NEWS_LANGUAGE,
limit: Annotated[
int | None,
"Maximum number of news articles to return. Defaults to None "
"(returns all results found by the API).",
] = None,
) -> Annotated[dict[str, Any]]:
"""Search for news articles related to a given query."""
...
```
### Tool Definition: ``GoogleNews.SearchNewsStories``
```
{
"name": "SearchNewsStories",
"fully_qualified_name": "GoogleNews.SearchNewsStories",
"description": "Search for news articles related to a given query.",
"toolkit": {
"name": "GoogleNews",
"description": "Arcade.dev LLM tools for getting new via Google News",
"version": "2.0.0"
},
"input": {
"parameters": [
{
"name": "keywords",
"required": true,
"description": "Keywords to search for news articles. E.g. 'Apple launches new iPhone'.",
"value_schema": {
"val_type": "string",
"inner_val_type": null,
"enum": null,
},
"inferrable": true
},
{
"name": "country_code",
"required": false,
"description": "2-character country code to search for news articles. E.g. 'us' (United States). Defaults to 'None'.",
"value_schema": {
"val_type": "string",
"inner_val_type": null,
"enum": null,
},
"inferrable": true
},
{
"name": "language_code",
"required": false,
"description": "2-character language code to search for news articles. E.g. 'en' (English). Defaults to 'en'.",
"value_schema": {
"val_type": "string",
"inner_val_type": null,
"enum": null,
},
"inferrable": true
},
{
"name": "limit",
"required": false,
"description": "Maximum number of news articles to return. Defaults to None (returns all results found by the API).",
"value_schema": {
"val_type": "integer",
"inner_val_type": null,
"enum": null,
},
"inferrable": true
}
]
},
"output": {
"description": "News search results with article details.",
"available_modes": [
"value",
"error"
],
"value_schema": {
"val_type": "json"
}
},
"requirements": {
"authorization": null,
"secrets": [
{
"key": "serp_api_key"
}
],
"metadata": null
},
"deprecation_message": null
},
```
## After
### Enhanced Tool: ``GoogleNews.SearchNewsStories``
```python
"""Type definitions for Google News API responses and parameters."""
from typing_extensions import TypedDict
CountryCode = str
LanguageCode = str
class SearchNewsParams(TypedDict):
"""Input parameters for searching news articles."""
keywords: str
"""Search query terms to find relevant news articles \
(e.g., 'Apple launches new iPhone')."""
country_code: CountryCode | None
"""Optional 2-letter country code to filter news by region \
(e.g., 'us' for United States, 'uk' for United Kingdom)."""
language_code: LanguageCode | None
"""Optional 2-letter language code to filter news by language \
(e.g., 'en' for English, 'es' for Spanish)."""
limit: int | None
"""Optional maximum number of news articles to return. \
If not specified, returns all results from the API."""
class SourceInfo(TypedDict, total=False):
"""Information about the news source/publication."""
name: str
"""Name of the publication (e.g., 'CNN', 'BBC News', 'The New York Times')."""
icon: str
"""URL to the source's favicon or logo image."""
authors: list[str]
"""List of author names for the article, if available."""
class NewsResult(TypedDict, total=False):
"""Individual news article from the Google News API response."""
position: int
"""Ranking position of this result in the search results."""
title: str
"""Headline or title of the news article."""
link: str
"""Full URL to the original news article."""
source: SourceInfo
"""Information about the publication source."""
date: str
"""Publication date and time (e.g., '2 hours ago', 'Dec 15, 2023')."""
snippet: str
"""Brief excerpt or summary from the article content."""
thumbnail: str
"""URL to a high-resolution thumbnail image for the article."""
thumbnail_small: str
"""URL to a low-resolution thumbnail image for the article."""
story_token: str
"""Token for accessing full coverage of this news story across multiple sources."""
stories: list["NewsResult"]
"""Related news stories from other sources covering the same topic."""
highlight: dict
"""Additional highlighted information about the story."""
class SearchMetadata(TypedDict, total=False):
"""Metadata about the search request and processing."""
id: str
"""Unique identifier for this search request within SerpApi."""
status: str
"""Current processing status ('Processing', 'Success', or 'Error')."""
json_endpoint: str
"""URL to retrieve the JSON results for this search."""
created_at: str
"""Timestamp when the search request was created."""
processed_at: str
"""Timestamp when the search request was processed."""
google_news_url: str
"""Original Google News URL that would return these results."""
total_time_taken: float
"""Total time in seconds taken to process this search."""
class SearchParameters(TypedDict, total=False):
"""Parameters used for the search request."""
engine: str
"""Search engine used (always 'google_news' for this API)."""
q: str
"""Search query string."""
gl: str
"""Country code used for geographic filtering."""
hl: str
"""Language code used for language filtering."""
topic_token: str
"""Token for accessing specific news topics (e.g., 'World', 'Business', 'Technology')."""
publication_token: str
"""Token for accessing news from specific publishers."""
class MenuLink(TypedDict):
"""Navigation link for news categories or topics."""
title: str
"""Display text for the menu item (e.g., 'Technology', 'Sports', 'Business')."""
topic_token: str
"""Token to access this specific topic or category."""
serpapi_link: str
"""SerpApi URL to search within this topic."""
class TopStoriesLink(TypedDict):
"""Link to top stories section."""
topic_token: str
"""Token to access top stories."""
serpapi_link: str
"""SerpApi URL to retrieve top stories."""
class GoogleNewsResponse(TypedDict, total=False):
"""Complete response from the Google News API."""
search_metadata: SearchMetadata
"""Metadata about the search request and processing."""
search_parameters: SearchParameters
"""Parameters that were used for this search."""
news_results: list[NewsResult]
"""List of news articles matching the search criteria."""
menu_links: list[MenuLink]
"""Navigation links to different news categories and topics."""
top_stories_link: TopStoriesLink
"""Link to access top stories."""
title: str
"""Title of the page or topic being displayed."""
class SimplifiedNewsResult(TypedDict):
"""Simplified news article format for tool output."""
title: str
"""Headline of the news article."""
link: str
"""URL to the full article."""
source: str | None
"""Name of the publication source."""
date: str | None
"""When the article was published."""
snippet: str | None
"""Brief excerpt from the article."""
class SearchNewsOutput(TypedDict):
"""Output format for the search_news_stories tool."""
news_results: list[SimplifiedNewsResult]
"""List of news articles in simplified format."""
@tool(requires_secrets=["SERP_API_KEY"])
async def search_news_stories(
context: ToolContext,
keywords: Annotated[
str,
"Keywords to search for news articles. E.g. 'Apple launches new iPhone'.",
],
country_code: Annotated[
CountryCode | None,
"2-character country code to search for news articles. "
"E.g. 'us' (United States). "
f"Defaults to '{DEFAULT_GOOGLE_NEWS_COUNTRY}'.",
] = None,
language_code: Annotated[
LanguageCode,
"2-character language code to search for news articles. E.g. 'en' (English). "
f"Defaults to '{DEFAULT_GOOGLE_NEWS_LANGUAGE}'.",
] = DEFAULT_GOOGLE_NEWS_LANGUAGE,
limit: Annotated[
int | None,
"Maximum number of news articles to return. Defaults to None "
"(returns all results found by the API).",
] = None,
) -> Annotated[SearchNewsOutput, "News search results with article details."]:
"""Search for news articles related to a given query."""
...
```
### Enhanced Tool Definition: ``GoogleNews.SearchNewsStories``
```json
{
"name": "SearchNewsStories",
"fully_qualified_name": "GoogleNews.SearchNewsStories",
"description": "Search for news articles related to a given query.",
"toolkit": {
"name": "GoogleNews",
"description": "Arcade.dev LLM tools for getting new via Google News",
"version": "2.0.0"
},
"input": {
"parameters": [
{
"name": "keywords",
"required": true,
"description": "Keywords to search for news articles. E.g. 'Apple launches new iPhone'.",
"value_schema": {
"val_type": "string",
"inner_val_type": null,
"enum": null,
"properties": null,
"inner_properties": null,
"description": null
},
"inferrable": true
},
{
"name": "country_code",
"required": false,
"description": "2-character country code to search for news articles. E.g. 'us' (United States). Defaults to 'None'.",
"value_schema": {
"val_type": "string",
"inner_val_type": null,
"enum": null,
"properties": null,
"inner_properties": null,
"description": null
},
"inferrable": true
},
{
"name": "language_code",
"required": false,
"description": "2-character language code to search for news articles. E.g. 'en' (English). Defaults to 'en'.",
"value_schema": {
"val_type": "string",
"inner_val_type": null,
"enum": null,
"properties": null,
"inner_properties": null,
"description": null
},
"inferrable": true
},
{
"name": "limit",
"required": false,
"description": "Maximum number of news articles to return. Defaults to None (returns all results found by the API).",
"value_schema": {
"val_type": "integer",
"inner_val_type": null,
"enum": null,
"properties": null,
"inner_properties": null,
"description": null
},
"inferrable": true
}
]
},
"output": {
"description": "News search results with article details.",
"available_modes": [
"value",
"error"
],
"value_schema": {
"val_type": "json",
"inner_val_type": null,
"enum": null,
"properties": {
"news_results": {
"val_type": "array",
"inner_val_type": "json",
"enum": null,
"properties": null,
"inner_properties": {
"title": {
"val_type": "string",
"inner_val_type": null,
"enum": null,
"properties": null,
"inner_properties": null,
"description": "Headline of the news article."
},
"link": {
"val_type": "string",
"inner_val_type": null,
"enum": null,
"properties": null,
"inner_properties": null,
"description": "URL to the full article."
},
"source": {
"val_type": "string",
"inner_val_type": null,
"enum": null,
"properties": null,
"inner_properties": null,
"description": "Name of the publication source."
},
"date": {
"val_type": "string",
"inner_val_type": null,
"enum": null,
"properties": null,
"inner_properties": null,
"description": "When the article was published."
},
"snippet": {
"val_type": "string",
"inner_val_type": null,
"enum": null,
"properties": null,
"inner_properties": null,
"description": "Brief excerpt from the article."
}
},
"description": "List of news articles in simplified format."
}
},
"inner_properties": null,
"description": null
}
},
"requirements": {
"authorization": null,
"secrets": [
{
"key": "serp_api_key"
}
],
"metadata": null
},
"deprecation_message": null
},
```
---------
Co-authored-by: Eric Gustin <eric@arcade.dev>
436 lines
16 KiB
Python
436 lines
16 KiB
Python
from typing import Annotated
|
|
|
|
import pytest
|
|
from arcade_core.catalog import ToolCatalog
|
|
from arcade_core.schema import (
|
|
InputParameter,
|
|
ToolInput,
|
|
ToolOutput,
|
|
ValueSchema,
|
|
)
|
|
from arcade_tdk import tool
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class ProductOutputModel(BaseModel):
|
|
product_name: str
|
|
"""The name of the product"""
|
|
price: int
|
|
"""The price of the product"""
|
|
stock_quantity: int
|
|
"""The stock quantity of the product"""
|
|
|
|
class Config:
|
|
extra = "forbid"
|
|
|
|
|
|
@tool(desc="A function that returns a Pydantic model")
|
|
def func_returns_pydantic_model() -> Annotated[
|
|
ProductOutputModel, "The product, price, and quantity"
|
|
]:
|
|
"""
|
|
Returns a ProductOutput Pydantic model with sample data.
|
|
|
|
Returns:
|
|
ProductOutput: The product, price, and quantity.
|
|
|
|
Example:
|
|
>>> func_returns_pydantic_model()
|
|
ProductOutput(product_name='Product 1', price=100, stock_quantity=1000)
|
|
"""
|
|
return ProductOutputModel(
|
|
product_name="Product 1",
|
|
price=100,
|
|
stock_quantity=1000,
|
|
)
|
|
|
|
|
|
@tool(desc="A function that accepts a required Pydantic Field with a description")
|
|
def func_takes_pydantic_field_with_description(
|
|
product_name: str = Field(..., description="The name of the product"),
|
|
) -> str:
|
|
return product_name
|
|
|
|
|
|
@tool(desc="A function that accepts an optional Pydantic Field")
|
|
def func_takes_pydantic_field_optional(
|
|
product_name: str | None = Field(None, description="The name of the product"),
|
|
) -> str:
|
|
return product_name if product_name is not None else "Product 1"
|
|
|
|
|
|
@tool(desc="A function that accepts an optional Pydantic Field with bar syntax")
|
|
def func_takes_pydantic_field_optional_bar_syntax(
|
|
product_name: str | None = Field(None, description="The name of the product"),
|
|
) -> str | None:
|
|
return product_name if product_name is not None else None
|
|
|
|
|
|
@tool(desc="A function that accepts an optional Pydantic Field with union syntax")
|
|
def func_takes_pydantic_field_optional_union_syntax(
|
|
product_name: str | None = Field(None, description="The name of the product"),
|
|
) -> str:
|
|
return product_name if product_name is not None else "Product 1"
|
|
|
|
|
|
# Annotated[] takes precedence over Field() properties
|
|
@tool(desc="A function that accepts an annotated Pydantic Field")
|
|
def func_takes_pydantic_field_annotated_description(
|
|
product_name: Annotated[str, "The name of the product"] = Field(
|
|
..., description="The name of the product???"
|
|
),
|
|
) -> str:
|
|
return product_name
|
|
|
|
|
|
# Annotated[] takes precedence over Field() properties
|
|
@tool(desc="A function that accepts an annotated Pydantic Field")
|
|
def func_takes_pydantic_field_annotated_name_and_description(
|
|
product_name: Annotated[str, "ProductName", "The name of the product"] = Field(
|
|
..., title="The name of the product???"
|
|
),
|
|
) -> str:
|
|
return product_name
|
|
|
|
|
|
@tool(desc="A function that accepts a Pydantic Field with a default value")
|
|
def func_takes_pydantic_field_default(
|
|
product_name: str = Field(description="The name of the product", default="Product 1"),
|
|
) -> str:
|
|
return product_name
|
|
|
|
|
|
@tool(desc="A function that accepts a Pydantic Field with a default value factory")
|
|
def func_takes_pydantic_field_default_factory(
|
|
product_name: str = Field(
|
|
default_factory=lambda: "Product 1", description="The name of the product"
|
|
),
|
|
) -> str:
|
|
"""
|
|
Accepts a product name with a default value provided by a factory.
|
|
|
|
Parameters:
|
|
product_name: The name of the product. Defaults to "Product 1" if not provided.
|
|
|
|
Returns:
|
|
str: The product name.
|
|
|
|
Example:
|
|
>>> func_takes_pydantic_field_default_factory()
|
|
'Product 1'
|
|
"""
|
|
return product_name
|
|
|
|
|
|
# TODO: Function that takes a Pydantic model as an argument: break it down into components? Look at OpenAPI, do they represent nested arguments?
|
|
# TODO: Should title and default_value be added to JSON schema?
|
|
# TODO: Pydantic Field() properties stretch goal: gt, ge, lt, le, multiple_of, range, regex, max_length, min_length, max_items, min_items, unique_items, exclusive_maximum, exclusive_minimum, title?
|
|
|
|
|
|
### A complex, real-world example
|
|
class ProductFilter(BaseModel):
|
|
column: str = Field(..., description="The column to filter on")
|
|
|
|
|
|
class FilterRating(ProductFilter):
|
|
greater_than: int = Field(..., description="The rating to filter greater than", gt=0, lt=5)
|
|
|
|
|
|
class FilterPriceGreaterThan(ProductFilter):
|
|
price: int = Field(..., description="The price to filter greater than", gt=0)
|
|
|
|
|
|
class FilterPriceLessThan(ProductFilter):
|
|
price: int = Field(..., description="The price to filter less than", gt=0)
|
|
|
|
|
|
class ProductSearch(BaseModel):
|
|
column: str = Field(..., description="The column to search in")
|
|
query: str = Field(..., description="The query to search for")
|
|
filter_operation: FilterRating | None = Field(
|
|
default=None,
|
|
description="The filter operation to apply (rating or price filter).",
|
|
)
|
|
highest_price: FilterPriceGreaterThan | None = Field(
|
|
default=None, description="The highest price to filter by"
|
|
)
|
|
lowest_price: FilterPriceLessThan | None = Field(
|
|
default=None, description="The lowest price to filter by"
|
|
)
|
|
|
|
|
|
class ProductOutput(BaseModel):
|
|
product_name: str = Field(..., description="The name of the product")
|
|
price: int = Field(..., description="The price of the product")
|
|
stock_quantity: int = Field(..., description="The stock quantity of the product")
|
|
|
|
|
|
@tool
|
|
def read_products(
|
|
action: Annotated[ProductSearch, "The search query to perform"],
|
|
cols: list[str] = Field(
|
|
default_factory=lambda: ["Product Name", "Price", "Stock Quantity"],
|
|
description="The columns to return",
|
|
),
|
|
) -> Annotated[list[ProductOutput], "Data with the selected columns"]:
|
|
"""
|
|
Used to search through products by name and filter by rating or price.
|
|
|
|
Parameters:
|
|
action: The search query to perform, as a ProductSearch model.
|
|
cols: The columns to return. Defaults to ["Product Name", "Price", "Stock Quantity"].
|
|
|
|
Returns:
|
|
list[ProductOutput]: Data with the selected columns.
|
|
|
|
Raises:
|
|
None
|
|
|
|
Example:
|
|
>>> await read_products(ProductSearch(query="Widget"), ["Product Name", "Price"])
|
|
"""
|
|
# This is a stub implementation for testing; in real code, this would query a database or service.
|
|
return [
|
|
ProductOutput(product_name="Widget", price=100, stock_quantity=50),
|
|
ProductOutput(product_name="Gadget", price=150, stock_quantity=20),
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"func_under_test, expected_tool_def_fields",
|
|
[
|
|
pytest.param(
|
|
func_returns_pydantic_model,
|
|
{
|
|
"output": ToolOutput(
|
|
value_schema=ValueSchema(
|
|
val_type="json",
|
|
enum=None,
|
|
properties={
|
|
"product_name": ValueSchema(val_type="string", enum=None),
|
|
"price": ValueSchema(val_type="integer", enum=None),
|
|
"stock_quantity": ValueSchema(val_type="integer", enum=None),
|
|
},
|
|
),
|
|
available_modes=["value", "error"],
|
|
description="The product, price, and quantity",
|
|
)
|
|
},
|
|
id="func_returns_pydantic_model",
|
|
),
|
|
pytest.param(
|
|
func_takes_pydantic_field_with_description,
|
|
{
|
|
"input": ToolInput(
|
|
parameters=[
|
|
InputParameter(
|
|
name="product_name",
|
|
description="The name of the product",
|
|
required=True,
|
|
inferrable=True,
|
|
value_schema=ValueSchema(val_type="string", enum=None),
|
|
)
|
|
]
|
|
)
|
|
},
|
|
id="func_takes_pydantic_field_with_description",
|
|
),
|
|
pytest.param(
|
|
func_takes_pydantic_field_optional,
|
|
{
|
|
"input": ToolInput(
|
|
parameters=[
|
|
InputParameter(
|
|
name="product_name",
|
|
description="The name of the product",
|
|
required=False,
|
|
inferrable=True,
|
|
value_schema=ValueSchema(val_type="string", enum=None),
|
|
)
|
|
]
|
|
)
|
|
},
|
|
id="func_takes_pydantic_field_optional",
|
|
),
|
|
pytest.param(
|
|
func_takes_pydantic_field_optional_bar_syntax,
|
|
{
|
|
"input": ToolInput(
|
|
parameters=[
|
|
InputParameter(
|
|
name="product_name",
|
|
description="The name of the product",
|
|
required=False,
|
|
inferrable=True,
|
|
value_schema=ValueSchema(val_type="string", enum=None),
|
|
)
|
|
]
|
|
)
|
|
},
|
|
id="func_takes_pydantic_field_optional_bar_syntax",
|
|
),
|
|
pytest.param(
|
|
func_takes_pydantic_field_optional_union_syntax,
|
|
{
|
|
"input": ToolInput(
|
|
parameters=[
|
|
InputParameter(
|
|
name="product_name",
|
|
description="The name of the product",
|
|
required=False,
|
|
inferrable=True,
|
|
value_schema=ValueSchema(val_type="string", enum=None),
|
|
)
|
|
]
|
|
)
|
|
},
|
|
id="func_takes_pydantic_field_optional_union_syntax",
|
|
),
|
|
pytest.param(
|
|
func_takes_pydantic_field_annotated_description,
|
|
{
|
|
"input": ToolInput(
|
|
parameters=[
|
|
InputParameter(
|
|
name="product_name",
|
|
description="The name of the product", # Annotated[] takes precedence over Field() properties
|
|
required=True,
|
|
inferrable=True,
|
|
value_schema=ValueSchema(val_type="string", enum=None),
|
|
)
|
|
]
|
|
)
|
|
},
|
|
id="func_takes_pydantic_field_annotated_description",
|
|
),
|
|
pytest.param(
|
|
func_takes_pydantic_field_annotated_name_and_description,
|
|
{
|
|
"input": ToolInput(
|
|
parameters=[
|
|
InputParameter(
|
|
name="ProductName",
|
|
description="The name of the product", # Annotated[] takes precedence over Field() properties
|
|
required=True,
|
|
inferrable=True,
|
|
value_schema=ValueSchema(val_type="string", enum=None),
|
|
)
|
|
]
|
|
)
|
|
},
|
|
id="func_takes_pydantic_field_annotated_name_and_description",
|
|
),
|
|
pytest.param(
|
|
func_takes_pydantic_field_default,
|
|
{
|
|
"input": ToolInput(
|
|
parameters=[
|
|
InputParameter(
|
|
name="product_name",
|
|
description="The name of the product",
|
|
required=False, # Because it has a default value
|
|
inferrable=True,
|
|
value_schema=ValueSchema(val_type="string", enum=None),
|
|
)
|
|
]
|
|
),
|
|
},
|
|
id="func_takes_pydantic_field_default",
|
|
),
|
|
pytest.param(
|
|
func_takes_pydantic_field_default_factory,
|
|
{
|
|
"input": ToolInput(
|
|
parameters=[
|
|
InputParameter(
|
|
name="product_name",
|
|
description="The name of the product",
|
|
required=False, # Because it has a default value factory
|
|
inferrable=True,
|
|
value_schema=ValueSchema(val_type="string", enum=None),
|
|
)
|
|
]
|
|
),
|
|
},
|
|
id="func_takes_pydantic_field_default_factory",
|
|
),
|
|
pytest.param(
|
|
read_products,
|
|
{
|
|
"input": ToolInput(
|
|
parameters=[
|
|
InputParameter(
|
|
name="action",
|
|
description="The search query to perform",
|
|
required=True,
|
|
inferrable=True,
|
|
value_schema=ValueSchema(
|
|
val_type="json",
|
|
enum=None,
|
|
properties={
|
|
"column": ValueSchema(val_type="string", enum=None),
|
|
"query": ValueSchema(val_type="string", enum=None),
|
|
"filter_operation": ValueSchema(
|
|
val_type="json",
|
|
enum=None,
|
|
properties={
|
|
"column": ValueSchema(val_type="string", enum=None),
|
|
"greater_than": ValueSchema(
|
|
val_type="integer", enum=None
|
|
),
|
|
},
|
|
),
|
|
"highest_price": ValueSchema(
|
|
val_type="json",
|
|
enum=None,
|
|
properties={
|
|
"column": ValueSchema(val_type="string", enum=None),
|
|
"price": ValueSchema(val_type="integer", enum=None),
|
|
},
|
|
),
|
|
"lowest_price": ValueSchema(
|
|
val_type="json",
|
|
enum=None,
|
|
properties={
|
|
"column": ValueSchema(val_type="string", enum=None),
|
|
"price": ValueSchema(val_type="integer", enum=None),
|
|
},
|
|
),
|
|
},
|
|
),
|
|
),
|
|
InputParameter(
|
|
name="cols",
|
|
description="The columns to return",
|
|
required=False,
|
|
inferrable=True,
|
|
value_schema=ValueSchema(
|
|
val_type="array", inner_val_type="string", enum=None
|
|
),
|
|
),
|
|
]
|
|
),
|
|
"output": ToolOutput(
|
|
value_schema=ValueSchema(
|
|
val_type="array",
|
|
inner_val_type="json",
|
|
enum=None,
|
|
inner_properties={
|
|
"product_name": ValueSchema(val_type="string", enum=None),
|
|
"price": ValueSchema(val_type="integer", enum=None),
|
|
"stock_quantity": ValueSchema(val_type="integer", enum=None),
|
|
},
|
|
),
|
|
available_modes=["value", "error"],
|
|
description="Data with the selected columns",
|
|
),
|
|
},
|
|
id="read_products",
|
|
),
|
|
],
|
|
)
|
|
def test_create_tool_def_from_pydantic(func_under_test, expected_tool_def_fields):
|
|
tool_def = ToolCatalog.create_tool_definition(func_under_test, "1.0")
|
|
|
|
for field, expected_value in expected_tool_def_fields.items():
|
|
assert getattr(tool_def, field) == expected_value
|