Skip to content

Commit 509e20b

Browse files
adiberkdirkbrnd
andauthored
Toolkit: Add new brandfetch toolkit with async and sync support (#4141)
## Summary Adds brandfetch as a toolkit Supports brand search and regular search Supports async and sync tools (user can configure which they want) (If applicable, issue number: #\_\_\_\_) ## Type of change - [ ] Bug fix - [x] New feature - [ ] Breaking change - [ ] Improvement - [ ] Model update - [ ] Other: --- ## Checklist - [x] Code complies with style guidelines - [x] Ran format/validation scripts (`./scripts/format.sh` and `./scripts/validate.sh`) - [x] Self-review completed - [x] Documentation updated (comments, docstrings) - [x] Examples and guides: Relevant cookbook examples have been included or updated (if applicable) - [x] Tested in clean environment - [ ] Tests added/updated (if applicable) --- ## Additional Notes Add any important context (deployment instructions, screenshots, security considerations, etc.) --------- Co-authored-by: Dirk Brand <dirkbrnd@gmail.com>
1 parent 7043ecf commit 509e20b

File tree

2 files changed

+253
-0
lines changed

2 files changed

+253
-0
lines changed

cookbook/tools/brandfetch_tools.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""
2+
You can use the Brandfetch API to retrieve the company's brand information.
3+
4+
Register an account at: https://developers.brandfetch.com/register
5+
6+
For the Brand API, you can use the `brand` parameter to True. (default is True)
7+
For the Brand Search API, you can use the `search` parameter to True. (default is False)
8+
9+
-- Brand API
10+
11+
Export your API key as an environment variable:
12+
export BRANDFETCH_API_KEY=your_api_key
13+
14+
-- Brand Search API
15+
16+
Export your Client ID as an environment variable:
17+
export BRANDFETCH_CLIENT_KEY=your_client_id
18+
19+
You can find it on https://developers.brandfetch.com/dashboard/brand-search-api in the provided URL after `c=...`
20+
21+
"""
22+
23+
from agno.agent import Agent
24+
from agno.tools.brandfetch import BrandfetchTools
25+
26+
# Brand API
27+
28+
# agent = Agent(
29+
# tools=[BrandfetchTools()],
30+
# show_tool_calls=True,
31+
# description="You are a Brand research agent. Given a company name or company domain, you will use the Brandfetch API to retrieve the company's brand information.",
32+
# )
33+
# agent.print_response("What is the brand information of Google?", markdown=True)
34+
35+
36+
# Brand Search API
37+
38+
agent = Agent(
39+
tools=[BrandfetchTools(search=True)],
40+
show_tool_calls=True,
41+
description="You are a Brand research agent. Given a company name or company domain, you will use the Brandfetch API to retrieve the company's brand information.",
42+
)
43+
agent.print_response("What is the brand information of Agno?", markdown=True)

libs/agno/agno/tools/brandfetch.py

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
"""
2+
Going to contribute this to agno toolkits.
3+
"""
4+
5+
from os import getenv
6+
from typing import Any, Optional
7+
8+
try:
9+
import httpx
10+
except ImportError:
11+
raise ImportError("`httpx` not installed.")
12+
13+
from agno.tools import Toolkit
14+
15+
16+
class BrandfetchTools(Toolkit):
17+
"""
18+
Brandfetch API toolkit for retrieving brand data and searching brands.
19+
20+
Supports both Brand API (retrieve comprehensive brand data) and
21+
Brand Search API (find and search brands by name).
22+
23+
-- Brand API
24+
25+
api_key: str - your Brandfetch API key
26+
27+
-- Brand Search API
28+
29+
client_id: str - your Brandfetch Client ID
30+
31+
async_tools: bool = True - if True, will use async tools, if False, will use sync tools
32+
brand: bool = False - if True, will use brand api, if False, will not use brand api
33+
search: bool = False - if True, will use brand search api, if False, will not use brand search api
34+
"""
35+
36+
def __init__(
37+
self,
38+
api_key: Optional[str] = None,
39+
client_id: Optional[str] = None,
40+
base_url: str = "https://api.brandfetch.io/v2",
41+
timeout: Optional[float] = 20.0,
42+
async_tools: bool = False,
43+
brand: bool = True,
44+
search: bool = False,
45+
**kwargs,
46+
):
47+
self.api_key = api_key or getenv("BRANDFETCH_API_KEY")
48+
self.client_id = client_id or getenv("BRANDFETCH_CLIENT_ID")
49+
self.base_url = base_url
50+
self.timeout = httpx.Timeout(timeout)
51+
self.async_tools = async_tools
52+
self.search_url = f"{self.base_url}/search"
53+
self.brand_url = f"{self.base_url}/brands"
54+
55+
tools: list[Any] = []
56+
if self.async_tools:
57+
if brand:
58+
tools.append(self.asearch_by_identifier)
59+
if search:
60+
tools.append(self.asearch_by_brand)
61+
else:
62+
if brand:
63+
tools.append(self.search_by_identifier)
64+
if search:
65+
tools.append(self.search_by_brand)
66+
name = kwargs.pop("name", "brandfetch_tools")
67+
super().__init__(name=name, tools=tools, **kwargs)
68+
69+
async def asearch_by_identifier(self, identifier: str) -> dict[str, Any]:
70+
"""
71+
Search for brand data by identifier (domain, brand id, isin, stock ticker).
72+
73+
Args:
74+
identifier: Options are you can use: Domain (nike.com), Brand ID (id_0dwKPKT), ISIN (US6541061031), Stock Ticker (NKE)
75+
Returns:
76+
Dict containing brand data including logos, colors, fonts, and other brand assets
77+
78+
Raises:
79+
ValueError: If no API key is provided
80+
"""
81+
if not self.api_key:
82+
raise ValueError("API key is required for brand search by identifier")
83+
84+
url = f"{self.brand_url}/{identifier}"
85+
headers = {"Authorization": f"Bearer {self.api_key}"}
86+
87+
try:
88+
async with httpx.AsyncClient(timeout=self.timeout) as client:
89+
response = await client.get(url, headers=headers)
90+
response.raise_for_status()
91+
return response.json()
92+
except httpx.HTTPStatusError as e:
93+
if e.response.status_code == 404:
94+
return {"error": f"Brand not found for identifier: {identifier}"}
95+
elif e.response.status_code == 401:
96+
return {"error": "Invalid API key"}
97+
elif e.response.status_code == 429:
98+
return {"error": "Rate limit exceeded"}
99+
else:
100+
return {"error": f"API error: {e.response.status_code}"}
101+
except httpx.RequestError as e:
102+
return {"error": f"Request failed: {str(e)}"}
103+
104+
def search_by_identifier(self, identifier: str) -> dict[str, Any]:
105+
"""
106+
Search for brand data by identifier (domain, brand id, isin, stock ticker).
107+
108+
Args:
109+
identifier: Options are you can use: Domain (nike.com), Brand ID (id_0dwKPKT), ISIN (US6541061031), Stock Ticker (NKE)
110+
111+
Returns:
112+
Dict containing brand data including logos, colors, fonts, and other brand assets
113+
114+
Raises:
115+
ValueError: If no API key is provided
116+
"""
117+
if not self.api_key:
118+
raise ValueError("API key is required for brand search by identifier")
119+
120+
url = f"{self.brand_url}/{identifier}"
121+
headers = {"Authorization": f"Bearer {self.api_key}"}
122+
123+
try:
124+
with httpx.Client(timeout=self.timeout) as client:
125+
response = client.get(url, headers=headers)
126+
response.raise_for_status()
127+
return response.json()
128+
except httpx.HTTPStatusError as e:
129+
if e.response.status_code == 404:
130+
return {"error": f"Brand not found for identifier: {identifier}"}
131+
elif e.response.status_code == 401:
132+
return {"error": "Invalid API key"}
133+
elif e.response.status_code == 429:
134+
return {"error": "Rate limit exceeded"}
135+
else:
136+
return {"error": f"API error: {e.response.status_code}"}
137+
except httpx.RequestError as e:
138+
return {"error": f"Request failed: {str(e)}"}
139+
140+
async def asearch_by_brand(self, name: str) -> dict[str, Any]:
141+
"""
142+
Search for brands by name using the Brand Search API - can give you the right brand id to use for the brand api.
143+
144+
Args:
145+
name: Brand name to search for (e.g., 'Google', 'Apple')
146+
147+
Returns:
148+
Dict containing search results with brand matches
149+
150+
Raises:
151+
ValueError: If no client ID is provided
152+
"""
153+
if not self.client_id:
154+
raise ValueError("Client ID is required for brand search by name")
155+
156+
url = f"{self.search_url}/{name}"
157+
params = {"c": self.client_id}
158+
159+
try:
160+
async with httpx.AsyncClient(timeout=self.timeout) as client:
161+
response = await client.get(url, params=params)
162+
response.raise_for_status()
163+
return response.json()
164+
except httpx.HTTPStatusError as e:
165+
if e.response.status_code == 404:
166+
return {"error": f"No brands found for name: {name}"}
167+
elif e.response.status_code == 401:
168+
return {"error": "Invalid client ID"}
169+
elif e.response.status_code == 429:
170+
return {"error": "Rate limit exceeded"}
171+
else:
172+
return {"error": f"API error: {e.response.status_code}"}
173+
except httpx.RequestError as e:
174+
return {"error": f"Request failed: {str(e)}"}
175+
176+
def search_by_brand(self, name: str) -> dict[str, Any]:
177+
"""
178+
Search for brands by name using the Brand Search API - can give you the right brand id to use for the brand api.
179+
180+
Args:
181+
name: Brand name to search for (e.g., 'Google', 'Apple')
182+
183+
Returns:
184+
Dict containing search results with brand matches
185+
186+
Raises:
187+
ValueError: If no client ID is provided
188+
"""
189+
if not self.client_id:
190+
raise ValueError("Client ID is required for brand search by name")
191+
192+
url = f"{self.search_url}/{name}"
193+
params = {"c": self.client_id}
194+
195+
try:
196+
with httpx.Client(timeout=self.timeout) as client:
197+
response = client.get(url, params=params)
198+
response.raise_for_status()
199+
return response.json()
200+
except httpx.HTTPStatusError as e:
201+
if e.response.status_code == 404:
202+
return {"error": f"No brands found for name: {name}"}
203+
elif e.response.status_code == 401:
204+
return {"error": "Invalid client ID"}
205+
elif e.response.status_code == 429:
206+
return {"error": "Rate limit exceeded"}
207+
else:
208+
return {"error": f"API error: {e.response.status_code}"}
209+
except httpx.RequestError as e:
210+
return {"error": f"Request failed: {str(e)}"}

0 commit comments

Comments
 (0)