agno 2.1.2__py3-none-any.whl → 2.3.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agno/agent/agent.py +5540 -2273
- agno/api/api.py +2 -0
- agno/api/os.py +1 -1
- agno/compression/__init__.py +3 -0
- agno/compression/manager.py +247 -0
- agno/culture/__init__.py +3 -0
- agno/culture/manager.py +956 -0
- agno/db/async_postgres/__init__.py +3 -0
- agno/db/base.py +689 -6
- agno/db/dynamo/dynamo.py +933 -37
- agno/db/dynamo/schemas.py +174 -10
- agno/db/dynamo/utils.py +63 -4
- agno/db/firestore/firestore.py +831 -9
- agno/db/firestore/schemas.py +51 -0
- agno/db/firestore/utils.py +102 -4
- agno/db/gcs_json/gcs_json_db.py +660 -12
- agno/db/gcs_json/utils.py +60 -26
- agno/db/in_memory/in_memory_db.py +287 -14
- agno/db/in_memory/utils.py +60 -2
- agno/db/json/json_db.py +590 -14
- agno/db/json/utils.py +60 -26
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/v1_to_v2.py +43 -13
- agno/db/migrations/versions/__init__.py +0 -0
- agno/db/migrations/versions/v2_3_0.py +938 -0
- agno/db/mongo/__init__.py +15 -1
- agno/db/mongo/async_mongo.py +2760 -0
- agno/db/mongo/mongo.py +879 -11
- agno/db/mongo/schemas.py +42 -0
- agno/db/mongo/utils.py +80 -8
- agno/db/mysql/__init__.py +2 -1
- agno/db/mysql/async_mysql.py +2912 -0
- agno/db/mysql/mysql.py +946 -68
- agno/db/mysql/schemas.py +72 -10
- agno/db/mysql/utils.py +198 -7
- agno/db/postgres/__init__.py +2 -1
- agno/db/postgres/async_postgres.py +2579 -0
- agno/db/postgres/postgres.py +942 -57
- agno/db/postgres/schemas.py +81 -18
- agno/db/postgres/utils.py +164 -2
- agno/db/redis/redis.py +671 -7
- agno/db/redis/schemas.py +50 -0
- agno/db/redis/utils.py +65 -7
- agno/db/schemas/__init__.py +2 -1
- agno/db/schemas/culture.py +120 -0
- agno/db/schemas/evals.py +1 -0
- agno/db/schemas/memory.py +17 -2
- agno/db/singlestore/schemas.py +63 -0
- agno/db/singlestore/singlestore.py +949 -83
- agno/db/singlestore/utils.py +60 -2
- agno/db/sqlite/__init__.py +2 -1
- agno/db/sqlite/async_sqlite.py +2911 -0
- agno/db/sqlite/schemas.py +62 -0
- agno/db/sqlite/sqlite.py +965 -46
- agno/db/sqlite/utils.py +169 -8
- agno/db/surrealdb/__init__.py +3 -0
- agno/db/surrealdb/metrics.py +292 -0
- agno/db/surrealdb/models.py +334 -0
- agno/db/surrealdb/queries.py +71 -0
- agno/db/surrealdb/surrealdb.py +1908 -0
- agno/db/surrealdb/utils.py +147 -0
- agno/db/utils.py +2 -0
- agno/eval/__init__.py +10 -0
- agno/eval/accuracy.py +75 -55
- agno/eval/agent_as_judge.py +861 -0
- agno/eval/base.py +29 -0
- agno/eval/performance.py +16 -7
- agno/eval/reliability.py +28 -16
- agno/eval/utils.py +35 -17
- agno/exceptions.py +27 -2
- agno/filters.py +354 -0
- agno/guardrails/prompt_injection.py +1 -0
- agno/hooks/__init__.py +3 -0
- agno/hooks/decorator.py +164 -0
- agno/integrations/discord/client.py +1 -1
- agno/knowledge/chunking/agentic.py +13 -10
- agno/knowledge/chunking/fixed.py +4 -1
- agno/knowledge/chunking/semantic.py +9 -4
- agno/knowledge/chunking/strategy.py +59 -15
- agno/knowledge/embedder/fastembed.py +1 -1
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/embedder/ollama.py +8 -0
- agno/knowledge/embedder/openai.py +8 -8
- agno/knowledge/embedder/sentence_transformer.py +6 -2
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/knowledge.py +1618 -318
- agno/knowledge/reader/base.py +6 -2
- agno/knowledge/reader/csv_reader.py +8 -10
- agno/knowledge/reader/docx_reader.py +5 -6
- agno/knowledge/reader/field_labeled_csv_reader.py +16 -20
- agno/knowledge/reader/json_reader.py +5 -4
- agno/knowledge/reader/markdown_reader.py +8 -8
- agno/knowledge/reader/pdf_reader.py +17 -19
- agno/knowledge/reader/pptx_reader.py +101 -0
- agno/knowledge/reader/reader_factory.py +32 -3
- agno/knowledge/reader/s3_reader.py +3 -3
- agno/knowledge/reader/tavily_reader.py +193 -0
- agno/knowledge/reader/text_reader.py +22 -10
- agno/knowledge/reader/web_search_reader.py +1 -48
- agno/knowledge/reader/website_reader.py +10 -10
- agno/knowledge/reader/wikipedia_reader.py +33 -1
- agno/knowledge/types.py +1 -0
- agno/knowledge/utils.py +72 -7
- agno/media.py +22 -6
- agno/memory/__init__.py +14 -1
- agno/memory/manager.py +544 -83
- agno/memory/strategies/__init__.py +15 -0
- agno/memory/strategies/base.py +66 -0
- agno/memory/strategies/summarize.py +196 -0
- agno/memory/strategies/types.py +37 -0
- agno/models/aimlapi/aimlapi.py +17 -0
- agno/models/anthropic/claude.py +515 -40
- agno/models/aws/bedrock.py +102 -21
- agno/models/aws/claude.py +131 -274
- agno/models/azure/ai_foundry.py +41 -19
- agno/models/azure/openai_chat.py +39 -8
- agno/models/base.py +1249 -525
- agno/models/cerebras/cerebras.py +91 -21
- agno/models/cerebras/cerebras_openai.py +21 -2
- agno/models/cohere/chat.py +40 -6
- agno/models/cometapi/cometapi.py +18 -1
- agno/models/dashscope/dashscope.py +2 -3
- agno/models/deepinfra/deepinfra.py +18 -1
- agno/models/deepseek/deepseek.py +69 -3
- agno/models/fireworks/fireworks.py +18 -1
- agno/models/google/gemini.py +877 -80
- agno/models/google/utils.py +22 -0
- agno/models/groq/groq.py +51 -18
- agno/models/huggingface/huggingface.py +17 -6
- agno/models/ibm/watsonx.py +16 -6
- agno/models/internlm/internlm.py +18 -1
- agno/models/langdb/langdb.py +13 -1
- agno/models/litellm/chat.py +44 -9
- agno/models/litellm/litellm_openai.py +18 -1
- agno/models/message.py +28 -5
- agno/models/meta/llama.py +47 -14
- agno/models/meta/llama_openai.py +22 -17
- agno/models/mistral/mistral.py +8 -4
- agno/models/nebius/nebius.py +6 -7
- agno/models/nvidia/nvidia.py +20 -3
- agno/models/ollama/chat.py +24 -8
- agno/models/openai/chat.py +104 -29
- agno/models/openai/responses.py +101 -81
- agno/models/openrouter/openrouter.py +60 -3
- agno/models/perplexity/perplexity.py +17 -1
- agno/models/portkey/portkey.py +7 -6
- agno/models/requesty/requesty.py +24 -4
- agno/models/response.py +73 -2
- agno/models/sambanova/sambanova.py +20 -3
- agno/models/siliconflow/siliconflow.py +19 -2
- agno/models/together/together.py +20 -3
- agno/models/utils.py +254 -8
- agno/models/vercel/v0.py +20 -3
- agno/models/vertexai/__init__.py +0 -0
- agno/models/vertexai/claude.py +190 -0
- agno/models/vllm/vllm.py +19 -14
- agno/models/xai/xai.py +19 -2
- agno/os/app.py +549 -152
- agno/os/auth.py +190 -3
- agno/os/config.py +23 -0
- agno/os/interfaces/a2a/router.py +8 -11
- agno/os/interfaces/a2a/utils.py +1 -1
- agno/os/interfaces/agui/router.py +18 -3
- agno/os/interfaces/agui/utils.py +152 -39
- agno/os/interfaces/slack/router.py +55 -37
- agno/os/interfaces/slack/slack.py +9 -1
- agno/os/interfaces/whatsapp/router.py +0 -1
- agno/os/interfaces/whatsapp/security.py +3 -1
- agno/os/mcp.py +110 -52
- agno/os/middleware/__init__.py +2 -0
- agno/os/middleware/jwt.py +676 -112
- agno/os/router.py +40 -1478
- agno/os/routers/agents/__init__.py +3 -0
- agno/os/routers/agents/router.py +599 -0
- agno/os/routers/agents/schema.py +261 -0
- agno/os/routers/evals/evals.py +96 -39
- agno/os/routers/evals/schemas.py +65 -33
- agno/os/routers/evals/utils.py +80 -10
- agno/os/routers/health.py +10 -4
- agno/os/routers/knowledge/knowledge.py +196 -38
- agno/os/routers/knowledge/schemas.py +82 -22
- agno/os/routers/memory/memory.py +279 -52
- agno/os/routers/memory/schemas.py +46 -17
- agno/os/routers/metrics/metrics.py +20 -8
- agno/os/routers/metrics/schemas.py +16 -16
- agno/os/routers/session/session.py +462 -34
- agno/os/routers/teams/__init__.py +3 -0
- agno/os/routers/teams/router.py +512 -0
- agno/os/routers/teams/schema.py +257 -0
- agno/os/routers/traces/__init__.py +3 -0
- agno/os/routers/traces/schemas.py +414 -0
- agno/os/routers/traces/traces.py +499 -0
- agno/os/routers/workflows/__init__.py +3 -0
- agno/os/routers/workflows/router.py +624 -0
- agno/os/routers/workflows/schema.py +75 -0
- agno/os/schema.py +256 -693
- agno/os/scopes.py +469 -0
- agno/os/utils.py +514 -36
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/openai.py +5 -0
- agno/reasoning/vertexai.py +76 -0
- agno/run/__init__.py +6 -0
- agno/run/agent.py +155 -32
- agno/run/base.py +55 -3
- agno/run/requirement.py +181 -0
- agno/run/team.py +125 -38
- agno/run/workflow.py +72 -18
- agno/session/agent.py +102 -89
- agno/session/summary.py +56 -15
- agno/session/team.py +164 -90
- agno/session/workflow.py +405 -40
- agno/table.py +10 -0
- agno/team/team.py +3974 -1903
- agno/tools/dalle.py +2 -4
- agno/tools/eleven_labs.py +23 -25
- agno/tools/exa.py +21 -16
- agno/tools/file.py +153 -23
- agno/tools/file_generation.py +16 -10
- agno/tools/firecrawl.py +15 -7
- agno/tools/function.py +193 -38
- agno/tools/gmail.py +238 -14
- agno/tools/google_drive.py +271 -0
- agno/tools/googlecalendar.py +36 -8
- agno/tools/googlesheets.py +20 -5
- agno/tools/jira.py +20 -0
- agno/tools/mcp/__init__.py +10 -0
- agno/tools/mcp/mcp.py +331 -0
- agno/tools/mcp/multi_mcp.py +347 -0
- agno/tools/mcp/params.py +24 -0
- agno/tools/mcp_toolbox.py +3 -3
- agno/tools/models/nebius.py +5 -5
- agno/tools/models_labs.py +20 -10
- agno/tools/nano_banana.py +151 -0
- agno/tools/notion.py +204 -0
- agno/tools/parallel.py +314 -0
- agno/tools/postgres.py +76 -36
- agno/tools/redshift.py +406 -0
- agno/tools/scrapegraph.py +1 -1
- agno/tools/shopify.py +1519 -0
- agno/tools/slack.py +18 -3
- agno/tools/spotify.py +919 -0
- agno/tools/tavily.py +146 -0
- agno/tools/toolkit.py +25 -0
- agno/tools/workflow.py +8 -1
- agno/tools/yfinance.py +12 -11
- agno/tracing/__init__.py +12 -0
- agno/tracing/exporter.py +157 -0
- agno/tracing/schemas.py +276 -0
- agno/tracing/setup.py +111 -0
- agno/utils/agent.py +938 -0
- agno/utils/cryptography.py +22 -0
- agno/utils/dttm.py +33 -0
- agno/utils/events.py +151 -3
- agno/utils/gemini.py +15 -5
- agno/utils/hooks.py +118 -4
- agno/utils/http.py +113 -2
- agno/utils/knowledge.py +12 -5
- agno/utils/log.py +1 -0
- agno/utils/mcp.py +92 -2
- agno/utils/media.py +187 -1
- agno/utils/merge_dict.py +3 -3
- agno/utils/message.py +60 -0
- agno/utils/models/ai_foundry.py +9 -2
- agno/utils/models/claude.py +49 -14
- agno/utils/models/cohere.py +9 -2
- agno/utils/models/llama.py +9 -2
- agno/utils/models/mistral.py +4 -2
- agno/utils/print_response/agent.py +109 -16
- agno/utils/print_response/team.py +223 -30
- agno/utils/print_response/workflow.py +251 -34
- agno/utils/streamlit.py +1 -1
- agno/utils/team.py +98 -9
- agno/utils/tokens.py +657 -0
- agno/vectordb/base.py +39 -7
- agno/vectordb/cassandra/cassandra.py +21 -5
- agno/vectordb/chroma/chromadb.py +43 -12
- agno/vectordb/clickhouse/clickhousedb.py +21 -5
- agno/vectordb/couchbase/couchbase.py +29 -5
- agno/vectordb/lancedb/lance_db.py +92 -181
- agno/vectordb/langchaindb/langchaindb.py +24 -4
- agno/vectordb/lightrag/lightrag.py +17 -3
- agno/vectordb/llamaindex/llamaindexdb.py +25 -5
- agno/vectordb/milvus/milvus.py +50 -37
- agno/vectordb/mongodb/__init__.py +7 -1
- agno/vectordb/mongodb/mongodb.py +36 -30
- agno/vectordb/pgvector/pgvector.py +201 -77
- agno/vectordb/pineconedb/pineconedb.py +41 -23
- agno/vectordb/qdrant/qdrant.py +67 -54
- agno/vectordb/redis/__init__.py +9 -0
- agno/vectordb/redis/redisdb.py +682 -0
- agno/vectordb/singlestore/singlestore.py +50 -29
- agno/vectordb/surrealdb/surrealdb.py +31 -41
- agno/vectordb/upstashdb/upstashdb.py +34 -6
- agno/vectordb/weaviate/weaviate.py +53 -14
- agno/workflow/__init__.py +2 -0
- agno/workflow/agent.py +299 -0
- agno/workflow/condition.py +120 -18
- agno/workflow/loop.py +77 -10
- agno/workflow/parallel.py +231 -143
- agno/workflow/router.py +118 -17
- agno/workflow/step.py +609 -170
- agno/workflow/steps.py +73 -6
- agno/workflow/types.py +96 -21
- agno/workflow/workflow.py +2039 -262
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/METADATA +201 -66
- agno-2.3.13.dist-info/RECORD +613 -0
- agno/tools/googlesearch.py +0 -98
- agno/tools/mcp.py +0 -679
- agno/tools/memori.py +0 -339
- agno-2.1.2.dist-info/RECORD +0 -543
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/WHEEL +0 -0
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/licenses/LICENSE +0 -0
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/top_level.txt +0 -0
agno/tools/dalle.py
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
from os import getenv
|
|
2
|
-
from typing import Any, List, Literal, Optional
|
|
2
|
+
from typing import Any, List, Literal, Optional
|
|
3
3
|
from uuid import uuid4
|
|
4
4
|
|
|
5
|
-
from agno.agent import Agent
|
|
6
5
|
from agno.media import Image
|
|
7
|
-
from agno.team.team import Team
|
|
8
6
|
from agno.tools import Toolkit
|
|
9
7
|
from agno.tools.function import ToolResult
|
|
10
8
|
from agno.utils.log import log_debug, logger
|
|
@@ -64,7 +62,7 @@ class DalleTools(Toolkit):
|
|
|
64
62
|
# - Add support for saving images
|
|
65
63
|
# - Add support for editing images
|
|
66
64
|
|
|
67
|
-
def create_image(self,
|
|
65
|
+
def create_image(self, prompt: str) -> ToolResult:
|
|
68
66
|
"""Use this function to generate an image for a prompt.
|
|
69
67
|
|
|
70
68
|
Args:
|
agno/tools/eleven_labs.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from base64 import b64encode
|
|
2
1
|
from io import BytesIO
|
|
3
2
|
from os import getenv, path
|
|
4
3
|
from pathlib import Path
|
|
@@ -10,7 +9,7 @@ from agno.media import Audio
|
|
|
10
9
|
from agno.team.team import Team
|
|
11
10
|
from agno.tools import Toolkit
|
|
12
11
|
from agno.tools.function import ToolResult
|
|
13
|
-
from agno.utils.log import
|
|
12
|
+
from agno.utils.log import log_error, log_info
|
|
14
13
|
|
|
15
14
|
try:
|
|
16
15
|
from elevenlabs import ElevenLabs # type: ignore
|
|
@@ -48,7 +47,7 @@ class ElevenLabsTools(Toolkit):
|
|
|
48
47
|
):
|
|
49
48
|
self.api_key = api_key or getenv("ELEVEN_LABS_API_KEY")
|
|
50
49
|
if not self.api_key:
|
|
51
|
-
|
|
50
|
+
log_error("ELEVEN_LABS_API_KEY not set. Please set the ELEVEN_LABS_API_KEY environment variable.")
|
|
52
51
|
|
|
53
52
|
self.target_directory = target_directory
|
|
54
53
|
self.voice_id = voice_id
|
|
@@ -73,7 +72,7 @@ class ElevenLabsTools(Toolkit):
|
|
|
73
72
|
|
|
74
73
|
def get_voices(self) -> str:
|
|
75
74
|
"""
|
|
76
|
-
|
|
75
|
+
Get all the voices available.
|
|
77
76
|
|
|
78
77
|
Returns:
|
|
79
78
|
result (list): A list of voices that have an ID, name and description.
|
|
@@ -94,20 +93,19 @@ class ElevenLabsTools(Toolkit):
|
|
|
94
93
|
return str(response)
|
|
95
94
|
|
|
96
95
|
except Exception as e:
|
|
97
|
-
|
|
96
|
+
log_error(f"Failed to fetch voices: {e}")
|
|
98
97
|
return f"Error: {e}"
|
|
99
98
|
|
|
100
|
-
def _process_audio(self, audio_generator: Iterator[bytes]) ->
|
|
101
|
-
# Step 1: Write audio data to BytesIO
|
|
99
|
+
def _process_audio(self, audio_generator: Iterator[bytes]) -> bytes:
|
|
102
100
|
audio_bytes = BytesIO()
|
|
103
101
|
for chunk in audio_generator:
|
|
104
102
|
audio_bytes.write(chunk)
|
|
105
|
-
audio_bytes.seek(0) # Rewind the stream
|
|
106
103
|
|
|
107
|
-
#
|
|
108
|
-
|
|
104
|
+
# Read bytes
|
|
105
|
+
audio_bytes.seek(0)
|
|
106
|
+
audio_data = audio_bytes.read()
|
|
109
107
|
|
|
110
|
-
#
|
|
108
|
+
# Save to disk if target_directory exists
|
|
111
109
|
if self.target_directory:
|
|
112
110
|
# Determine file extension based on output format
|
|
113
111
|
if self.output_format.startswith("mp3"):
|
|
@@ -122,19 +120,19 @@ class ElevenLabsTools(Toolkit):
|
|
|
122
120
|
output_filename = f"{uuid4()}.{extension}"
|
|
123
121
|
output_path = path.join(self.target_directory, output_filename)
|
|
124
122
|
|
|
125
|
-
# Write from BytesIO to disk
|
|
126
|
-
audio_bytes.seek(0) # Reset the BytesIO stream again
|
|
127
123
|
with open(output_path, "wb") as f:
|
|
128
|
-
f.write(
|
|
124
|
+
f.write(audio_data)
|
|
129
125
|
|
|
130
|
-
|
|
126
|
+
log_info(f"Audio saved to: {output_path}")
|
|
127
|
+
|
|
128
|
+
return audio_data
|
|
131
129
|
|
|
132
130
|
def generate_sound_effect(self, prompt: str, duration_seconds: Optional[float] = None) -> ToolResult:
|
|
133
131
|
"""
|
|
134
|
-
|
|
132
|
+
Generate a sound effect from a text description.
|
|
135
133
|
|
|
136
134
|
Args:
|
|
137
|
-
prompt (str):
|
|
135
|
+
prompt (str): Description of the sound effect
|
|
138
136
|
duration_seconds (Optional[float]): Duration in seconds to generate audio from. Has to be between 0.5 and 22.
|
|
139
137
|
Returns:
|
|
140
138
|
ToolResult: A ToolResult containing the generated audio or error message.
|
|
@@ -144,27 +142,27 @@ class ElevenLabsTools(Toolkit):
|
|
|
144
142
|
text=prompt, duration_seconds=duration_seconds
|
|
145
143
|
)
|
|
146
144
|
|
|
147
|
-
|
|
145
|
+
audio_data = self._process_audio(audio_generator)
|
|
148
146
|
|
|
149
147
|
# Create AudioArtifact
|
|
150
148
|
audio_artifact = Audio(
|
|
151
149
|
id=str(uuid4()),
|
|
152
|
-
|
|
150
|
+
content=audio_data,
|
|
153
151
|
mime_type="audio/mpeg",
|
|
154
152
|
)
|
|
155
153
|
|
|
156
154
|
return ToolResult(
|
|
157
|
-
content="
|
|
155
|
+
content="Sound effect generated successfully",
|
|
158
156
|
audios=[audio_artifact],
|
|
159
157
|
)
|
|
160
158
|
|
|
161
159
|
except Exception as e:
|
|
162
|
-
|
|
160
|
+
log_error(f"Failed to generate sound effect: {e}")
|
|
163
161
|
return ToolResult(content=f"Error: {e}")
|
|
164
162
|
|
|
165
163
|
def text_to_speech(self, agent: Union[Agent, Team], prompt: str) -> ToolResult:
|
|
166
164
|
"""
|
|
167
|
-
|
|
165
|
+
Convert text to speech.
|
|
168
166
|
|
|
169
167
|
Args:
|
|
170
168
|
prompt (str): Text to generate audio from.
|
|
@@ -179,12 +177,12 @@ class ElevenLabsTools(Toolkit):
|
|
|
179
177
|
output_format=self.output_format,
|
|
180
178
|
)
|
|
181
179
|
|
|
182
|
-
|
|
180
|
+
audio_data = self._process_audio(audio_generator)
|
|
183
181
|
|
|
184
182
|
# Create AudioArtifact
|
|
185
183
|
audio_artifact = Audio(
|
|
186
184
|
id=str(uuid4()),
|
|
187
|
-
|
|
185
|
+
content=audio_data,
|
|
188
186
|
mime_type="audio/mpeg",
|
|
189
187
|
)
|
|
190
188
|
|
|
@@ -194,5 +192,5 @@ class ElevenLabsTools(Toolkit):
|
|
|
194
192
|
)
|
|
195
193
|
|
|
196
194
|
except Exception as e:
|
|
197
|
-
|
|
195
|
+
log_error(f"Failed to generate audio: {e}")
|
|
198
196
|
return ToolResult(content=f"Error: {e}")
|
agno/tools/exa.py
CHANGED
|
@@ -27,14 +27,14 @@ class ExaTools(Toolkit):
|
|
|
27
27
|
all (bool): Enable all tools. Overrides individual flags when True. Default is False.
|
|
28
28
|
text (bool): Retrieve text content from results. Default is True.
|
|
29
29
|
text_length_limit (int): Max length of text content per result. Default is 1000.
|
|
30
|
-
highlights (bool): Include highlighted snippets.
|
|
30
|
+
highlights (bool): Include highlighted snippets. Deprecated since it was removed in the Exa API. It will be removed from Agno in a future release.
|
|
31
31
|
api_key (Optional[str]): Exa API key. Retrieved from `EXA_API_KEY` env variable if not provided.
|
|
32
32
|
num_results (Optional[int]): Default number of search results. Overrides individual searches if set.
|
|
33
33
|
start_crawl_date (Optional[str]): Include results crawled on/after this date (`YYYY-MM-DD`).
|
|
34
34
|
end_crawl_date (Optional[str]): Include results crawled on/before this date (`YYYY-MM-DD`).
|
|
35
35
|
start_published_date (Optional[str]): Include results published on/after this date (`YYYY-MM-DD`).
|
|
36
36
|
end_published_date (Optional[str]): Include results published on/before this date (`YYYY-MM-DD`).
|
|
37
|
-
use_autoprompt (Optional[bool]): Enable autoprompt features in queries.
|
|
37
|
+
use_autoprompt (Optional[bool]): Enable autoprompt features in queries. Deprecated since it was removed in the Exa API. It will be removed from Agno in a future release.
|
|
38
38
|
type (Optional[str]): Specify content type (e.g., article, blog, video).
|
|
39
39
|
category (Optional[str]): Filter results by category. Options are "company", "research paper", "news", "pdf", "github", "tweet", "personal site", "linkedin profile", "financial report".
|
|
40
40
|
include_domains (Optional[List[str]]): Restrict results to these domains.
|
|
@@ -54,7 +54,7 @@ class ExaTools(Toolkit):
|
|
|
54
54
|
all: bool = False,
|
|
55
55
|
text: bool = True,
|
|
56
56
|
text_length_limit: int = 1000,
|
|
57
|
-
highlights: bool =
|
|
57
|
+
highlights: Optional[bool] = None, # Deprecated
|
|
58
58
|
summary: bool = False,
|
|
59
59
|
api_key: Optional[str] = None,
|
|
60
60
|
num_results: Optional[int] = None,
|
|
@@ -84,7 +84,24 @@ class ExaTools(Toolkit):
|
|
|
84
84
|
|
|
85
85
|
self.text: bool = text
|
|
86
86
|
self.text_length_limit: int = text_length_limit
|
|
87
|
-
|
|
87
|
+
|
|
88
|
+
if highlights:
|
|
89
|
+
import warnings
|
|
90
|
+
|
|
91
|
+
warnings.warn(
|
|
92
|
+
"The 'highlights' parameter is deprecated since it was removed in the Exa API. It will be removed from Agno in a future release.",
|
|
93
|
+
DeprecationWarning,
|
|
94
|
+
stacklevel=2,
|
|
95
|
+
)
|
|
96
|
+
if use_autoprompt:
|
|
97
|
+
import warnings
|
|
98
|
+
|
|
99
|
+
warnings.warn(
|
|
100
|
+
"The 'use_autoprompt' parameter is deprecated since it was removed in the Exa API. It will be removed from Agno in a future release.",
|
|
101
|
+
DeprecationWarning,
|
|
102
|
+
stacklevel=2,
|
|
103
|
+
)
|
|
104
|
+
|
|
88
105
|
self.summary: bool = summary
|
|
89
106
|
self.num_results: Optional[int] = num_results
|
|
90
107
|
self.livecrawl: str = livecrawl
|
|
@@ -92,7 +109,6 @@ class ExaTools(Toolkit):
|
|
|
92
109
|
self.end_crawl_date: Optional[str] = end_crawl_date
|
|
93
110
|
self.start_published_date: Optional[str] = start_published_date
|
|
94
111
|
self.end_published_date: Optional[str] = end_published_date
|
|
95
|
-
self.use_autoprompt: Optional[bool] = use_autoprompt
|
|
96
112
|
self.type: Optional[str] = type
|
|
97
113
|
self.category: Optional[str] = category
|
|
98
114
|
self.include_domains: Optional[List[str]] = include_domains
|
|
@@ -140,13 +156,6 @@ class ExaTools(Toolkit):
|
|
|
140
156
|
if self.text_length_limit:
|
|
141
157
|
_text = _text[: self.text_length_limit]
|
|
142
158
|
result_dict["text"] = _text
|
|
143
|
-
if self.highlights:
|
|
144
|
-
try:
|
|
145
|
-
if result.highlights: # type: ignore
|
|
146
|
-
result_dict["highlights"] = result.highlights # type: ignore
|
|
147
|
-
except Exception as e:
|
|
148
|
-
log_debug(f"Failed to get highlights {e}")
|
|
149
|
-
result_dict["highlights"] = f"Failed to get highlights {e}"
|
|
150
159
|
exa_results_parsed.append(result_dict)
|
|
151
160
|
return json.dumps(exa_results_parsed, indent=4, ensure_ascii=False)
|
|
152
161
|
|
|
@@ -168,14 +177,12 @@ class ExaTools(Toolkit):
|
|
|
168
177
|
log_info(f"Searching exa for: {query}")
|
|
169
178
|
search_kwargs: Dict[str, Any] = {
|
|
170
179
|
"text": self.text,
|
|
171
|
-
"highlights": self.highlights,
|
|
172
180
|
"summary": self.summary,
|
|
173
181
|
"num_results": self.num_results or num_results,
|
|
174
182
|
"start_crawl_date": self.start_crawl_date,
|
|
175
183
|
"end_crawl_date": self.end_crawl_date,
|
|
176
184
|
"start_published_date": self.start_published_date,
|
|
177
185
|
"end_published_date": self.end_published_date,
|
|
178
|
-
"use_autoprompt": self.use_autoprompt,
|
|
179
186
|
"type": self.type,
|
|
180
187
|
"category": self.category or category, # Prefer a user-set category
|
|
181
188
|
"include_domains": self.include_domains,
|
|
@@ -212,7 +219,6 @@ class ExaTools(Toolkit):
|
|
|
212
219
|
|
|
213
220
|
query_kwargs: Dict[str, Any] = {
|
|
214
221
|
"text": self.text,
|
|
215
|
-
"highlights": self.highlights,
|
|
216
222
|
"summary": self.summary,
|
|
217
223
|
}
|
|
218
224
|
|
|
@@ -249,7 +255,6 @@ class ExaTools(Toolkit):
|
|
|
249
255
|
|
|
250
256
|
query_kwargs: Dict[str, Any] = {
|
|
251
257
|
"text": self.text,
|
|
252
|
-
"highlights": self.highlights,
|
|
253
258
|
"summary": self.summary,
|
|
254
259
|
"include_domains": self.include_domains,
|
|
255
260
|
"exclude_domains": self.exclude_domains,
|
agno/tools/file.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from pathlib import Path
|
|
3
|
-
from typing import Any, List, Optional
|
|
3
|
+
from typing import Any, List, Optional, Tuple
|
|
4
4
|
|
|
5
5
|
from agno.tools import Toolkit
|
|
6
|
-
from agno.utils.log import log_debug, log_error
|
|
6
|
+
from agno.utils.log import log_debug, log_error
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class FileTools(Toolkit):
|
|
@@ -12,14 +12,26 @@ class FileTools(Toolkit):
|
|
|
12
12
|
base_dir: Optional[Path] = None,
|
|
13
13
|
enable_save_file: bool = True,
|
|
14
14
|
enable_read_file: bool = True,
|
|
15
|
+
enable_delete_file: bool = False,
|
|
15
16
|
enable_list_files: bool = True,
|
|
16
17
|
enable_search_files: bool = True,
|
|
18
|
+
enable_read_file_chunk: bool = True,
|
|
19
|
+
enable_replace_file_chunk: bool = True,
|
|
20
|
+
expose_base_directory: bool = False,
|
|
21
|
+
max_file_length: int = 10000000,
|
|
22
|
+
max_file_lines: int = 100000,
|
|
23
|
+
line_separator: str = "\n",
|
|
17
24
|
all: bool = False,
|
|
18
25
|
**kwargs,
|
|
19
26
|
):
|
|
20
27
|
self.base_dir: Path = base_dir or Path.cwd()
|
|
28
|
+
self.base_dir = self.base_dir.resolve()
|
|
21
29
|
|
|
22
30
|
tools: List[Any] = []
|
|
31
|
+
self.max_file_length = max_file_length
|
|
32
|
+
self.max_file_lines = max_file_lines
|
|
33
|
+
self.line_separator = line_separator
|
|
34
|
+
self.expose_base_directory = expose_base_directory
|
|
23
35
|
if all or enable_save_file:
|
|
24
36
|
tools.append(self.save_file)
|
|
25
37
|
if all or enable_read_file:
|
|
@@ -28,10 +40,16 @@ class FileTools(Toolkit):
|
|
|
28
40
|
tools.append(self.list_files)
|
|
29
41
|
if all or enable_search_files:
|
|
30
42
|
tools.append(self.search_files)
|
|
43
|
+
if all or enable_delete_file:
|
|
44
|
+
tools.append(self.delete_file)
|
|
45
|
+
if all or enable_read_file_chunk:
|
|
46
|
+
tools.append(self.read_file_chunk)
|
|
47
|
+
if all or enable_replace_file_chunk:
|
|
48
|
+
tools.append(self.replace_file_chunk)
|
|
31
49
|
|
|
32
50
|
super().__init__(name="file_tools", tools=tools, **kwargs)
|
|
33
51
|
|
|
34
|
-
def save_file(self, contents: str, file_name: str, overwrite: bool = True) -> str:
|
|
52
|
+
def save_file(self, contents: str, file_name: str, overwrite: bool = True, encoding: str = "utf-8") -> str:
|
|
35
53
|
"""Saves the contents to a file called `file_name` and returns the file name if successful.
|
|
36
54
|
|
|
37
55
|
:param contents: The contents to save.
|
|
@@ -40,42 +58,146 @@ class FileTools(Toolkit):
|
|
|
40
58
|
:return: The file name if successful, otherwise returns an error message.
|
|
41
59
|
"""
|
|
42
60
|
try:
|
|
43
|
-
file_path = self.
|
|
61
|
+
safe, file_path = self.check_escape(file_name)
|
|
62
|
+
if not (safe):
|
|
63
|
+
log_error(f"Attempted to save file: {file_name}")
|
|
64
|
+
return "Error saving file"
|
|
44
65
|
log_debug(f"Saving contents to {file_path}")
|
|
45
66
|
if not file_path.parent.exists():
|
|
46
67
|
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
47
68
|
if file_path.exists() and not overwrite:
|
|
48
69
|
return f"File {file_name} already exists"
|
|
49
|
-
file_path.write_text(contents)
|
|
50
|
-
|
|
70
|
+
file_path.write_text(contents, encoding=encoding)
|
|
71
|
+
log_debug(f"Saved: {file_path}")
|
|
51
72
|
return str(file_name)
|
|
52
73
|
except Exception as e:
|
|
53
74
|
log_error(f"Error saving to file: {e}")
|
|
54
75
|
return f"Error saving to file: {e}"
|
|
55
76
|
|
|
56
|
-
def
|
|
77
|
+
def read_file_chunk(self, file_name: str, start_line: int, end_line: int, encoding: str = "utf-8") -> str:
|
|
78
|
+
"""Reads the contents of the file `file_name` and returns lines from start_line to end_line.
|
|
79
|
+
|
|
80
|
+
:param file_name: The name of the file to read.
|
|
81
|
+
:param start_line: Number of first line in the returned chunk
|
|
82
|
+
:param end_line: Number of the last line in the returned chunk
|
|
83
|
+
:param encoding: Encoding to use, default - utf-8
|
|
84
|
+
|
|
85
|
+
:return: The contents of the selected chunk
|
|
86
|
+
"""
|
|
87
|
+
try:
|
|
88
|
+
log_debug(f"Reading file: {file_name}")
|
|
89
|
+
safe, file_path = self.check_escape(file_name)
|
|
90
|
+
if not (safe):
|
|
91
|
+
log_error(f"Attempted to read file: {file_name}")
|
|
92
|
+
return "Error reading file"
|
|
93
|
+
contents = file_path.read_text(encoding=encoding)
|
|
94
|
+
lines = contents.split(self.line_separator)
|
|
95
|
+
return self.line_separator.join(lines[start_line : end_line + 1])
|
|
96
|
+
except Exception as e:
|
|
97
|
+
log_error(f"Error reading file: {e}")
|
|
98
|
+
return f"Error reading file: {e}"
|
|
99
|
+
|
|
100
|
+
def replace_file_chunk(
|
|
101
|
+
self, file_name: str, start_line: int, end_line: int, chunk: str, encoding: str = "utf-8"
|
|
102
|
+
) -> str:
|
|
103
|
+
"""Reads the contents of the file, replaces lines
|
|
104
|
+
between start_line and end_line with chunk and writes the file
|
|
105
|
+
|
|
106
|
+
:param file_name: The name of the file to process.
|
|
107
|
+
:param start_line: Number of first line in the replaced chunk
|
|
108
|
+
:param end_line: Number of the last line in the replaced chunk
|
|
109
|
+
:param chunk: String to be inserted instead of lines from start_line to end_line. Can have multiple lines.
|
|
110
|
+
:param encoding: Encoding to use, default - utf-8
|
|
111
|
+
|
|
112
|
+
:return: file name if successfull, error message otherwise
|
|
113
|
+
"""
|
|
114
|
+
try:
|
|
115
|
+
log_debug(f"Patching file: {file_name}")
|
|
116
|
+
safe, file_path = self.check_escape(file_name)
|
|
117
|
+
if not (safe):
|
|
118
|
+
log_error(f"Attempted to read file: {file_name}")
|
|
119
|
+
return "Error reading file"
|
|
120
|
+
contents = file_path.read_text(encoding=encoding)
|
|
121
|
+
lines = contents.split(self.line_separator)
|
|
122
|
+
start = lines[0:start_line]
|
|
123
|
+
end = lines[end_line + 1 :]
|
|
124
|
+
return self.save_file(
|
|
125
|
+
file_name=file_name, contents=self.line_separator.join(start + [chunk] + end), encoding=encoding
|
|
126
|
+
)
|
|
127
|
+
except Exception as e:
|
|
128
|
+
log_error(f"Error patching file: {e}")
|
|
129
|
+
return f"Error patching file: {e}"
|
|
130
|
+
|
|
131
|
+
def read_file(self, file_name: str, encoding: str = "utf-8") -> str:
|
|
57
132
|
"""Reads the contents of the file `file_name` and returns the contents if successful.
|
|
58
133
|
|
|
59
134
|
:param file_name: The name of the file to read.
|
|
135
|
+
:param encoding: Encoding to use, default - utf-8
|
|
60
136
|
:return: The contents of the file if successful, otherwise returns an error message.
|
|
61
137
|
"""
|
|
62
138
|
try:
|
|
63
|
-
|
|
64
|
-
file_path = self.
|
|
65
|
-
|
|
139
|
+
log_debug(f"Reading file: {file_name}")
|
|
140
|
+
safe, file_path = self.check_escape(file_name)
|
|
141
|
+
if not (safe):
|
|
142
|
+
log_error(f"Attempted to read file: {file_name}")
|
|
143
|
+
return "Error reading file"
|
|
144
|
+
contents = file_path.read_text(encoding=encoding)
|
|
145
|
+
if len(contents) > self.max_file_length:
|
|
146
|
+
return "Error reading file: file too long. Use read_file_chunk instead"
|
|
147
|
+
if len(contents.split(self.line_separator)) > self.max_file_lines:
|
|
148
|
+
return "Error reading file: file too long. Use read_file_chunk instead"
|
|
149
|
+
|
|
66
150
|
return str(contents)
|
|
67
151
|
except Exception as e:
|
|
68
152
|
log_error(f"Error reading file: {e}")
|
|
69
153
|
return f"Error reading file: {e}"
|
|
70
154
|
|
|
71
|
-
def
|
|
72
|
-
"""
|
|
155
|
+
def delete_file(self, file_name: str) -> str:
|
|
156
|
+
"""Deletes a file
|
|
157
|
+
:param file_name: Name of the file to delete
|
|
158
|
+
|
|
159
|
+
:return: Empty string, if operation succeeded, otherwise returns an error message
|
|
160
|
+
"""
|
|
161
|
+
safe, path = self.check_escape(file_name)
|
|
162
|
+
try:
|
|
163
|
+
if safe:
|
|
164
|
+
if path.is_dir():
|
|
165
|
+
path.rmdir()
|
|
166
|
+
return ""
|
|
167
|
+
path.unlink()
|
|
168
|
+
return ""
|
|
169
|
+
else:
|
|
170
|
+
log_error(f"Attempt to delete file outside {self.base_dir}: {file_name}")
|
|
171
|
+
return "Incorrect file_name"
|
|
172
|
+
except Exception as e:
|
|
173
|
+
log_error(f"Error removing {file_name}: {e}")
|
|
174
|
+
return f"Error removing file: {e}"
|
|
175
|
+
|
|
176
|
+
def check_escape(self, relative_path: str) -> Tuple[bool, Path]:
|
|
177
|
+
d = self.base_dir.joinpath(Path(relative_path)).resolve()
|
|
178
|
+
if self.base_dir == d:
|
|
179
|
+
return True, d
|
|
180
|
+
try:
|
|
181
|
+
d.relative_to(self.base_dir)
|
|
182
|
+
except ValueError:
|
|
183
|
+
log_error("Attempted to escape base_dir")
|
|
184
|
+
return False, self.base_dir
|
|
185
|
+
return True, d
|
|
186
|
+
|
|
187
|
+
def list_files(self, **kwargs) -> str:
|
|
188
|
+
"""Returns a list of files in directory
|
|
189
|
+
:param directory: (Optional) name of directory to list.
|
|
73
190
|
|
|
74
191
|
:return: The contents of the file if successful, otherwise returns an error message.
|
|
75
192
|
"""
|
|
193
|
+
directory = kwargs.get("directory", ".")
|
|
76
194
|
try:
|
|
77
|
-
|
|
78
|
-
|
|
195
|
+
log_debug(f"Reading files in : {self.base_dir}/{directory}")
|
|
196
|
+
safe, d = self.check_escape(directory)
|
|
197
|
+
if safe:
|
|
198
|
+
return json.dumps([str(file_path.relative_to(self.base_dir)) for file_path in d.iterdir()], indent=4)
|
|
199
|
+
else:
|
|
200
|
+
return "{}"
|
|
79
201
|
except Exception as e:
|
|
80
202
|
log_error(f"Error reading files: {e}")
|
|
81
203
|
return f"Error reading files: {e}"
|
|
@@ -92,15 +214,23 @@ class FileTools(Toolkit):
|
|
|
92
214
|
|
|
93
215
|
log_debug(f"Searching files in {self.base_dir} with pattern {pattern}")
|
|
94
216
|
matching_files = list(self.base_dir.glob(pattern))
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
217
|
+
result = None
|
|
218
|
+
if self.expose_base_directory:
|
|
219
|
+
file_paths = [str(file_path) for file_path in matching_files]
|
|
220
|
+
result = {
|
|
221
|
+
"pattern": pattern,
|
|
222
|
+
"matches_found": len(file_paths),
|
|
223
|
+
"base_directory": str(self.base_dir),
|
|
224
|
+
"files": file_paths,
|
|
225
|
+
}
|
|
226
|
+
else:
|
|
227
|
+
file_paths = [str(file_path.relative_to(self.base_dir)) for file_path in matching_files]
|
|
228
|
+
|
|
229
|
+
result = {
|
|
230
|
+
"pattern": pattern,
|
|
231
|
+
"matches_found": len(file_paths),
|
|
232
|
+
"files": file_paths,
|
|
233
|
+
}
|
|
104
234
|
log_debug(f"Found {len(file_paths)} files matching pattern {pattern}")
|
|
105
235
|
return json.dumps(result, indent=2)
|
|
106
236
|
|
agno/tools/file_generation.py
CHANGED
|
@@ -108,15 +108,17 @@ class FileGenerationTools(Toolkit):
|
|
|
108
108
|
# Save file to disk (if output_directory is set)
|
|
109
109
|
file_path = self._save_file_to_disk(json_content, filename)
|
|
110
110
|
|
|
111
|
+
content_bytes = json_content.encode("utf-8")
|
|
112
|
+
|
|
111
113
|
# Create FileArtifact
|
|
112
114
|
file_artifact = File(
|
|
113
115
|
id=str(uuid4()),
|
|
114
|
-
content=
|
|
116
|
+
content=content_bytes,
|
|
115
117
|
mime_type="application/json",
|
|
116
118
|
file_type="json",
|
|
117
119
|
filename=filename,
|
|
118
|
-
size=len(
|
|
119
|
-
|
|
120
|
+
size=len(content_bytes),
|
|
121
|
+
filepath=file_path if file_path else None,
|
|
120
122
|
)
|
|
121
123
|
|
|
122
124
|
log_debug("JSON file generated successfully")
|
|
@@ -195,15 +197,17 @@ class FileGenerationTools(Toolkit):
|
|
|
195
197
|
# Save file to disk (if output_directory is set)
|
|
196
198
|
file_path = self._save_file_to_disk(csv_content, filename)
|
|
197
199
|
|
|
200
|
+
content_bytes = csv_content.encode("utf-8")
|
|
201
|
+
|
|
198
202
|
# Create FileArtifact
|
|
199
203
|
file_artifact = File(
|
|
200
204
|
id=str(uuid4()),
|
|
201
|
-
content=
|
|
205
|
+
content=content_bytes,
|
|
202
206
|
mime_type="text/csv",
|
|
203
207
|
file_type="csv",
|
|
204
208
|
filename=filename,
|
|
205
|
-
size=len(
|
|
206
|
-
|
|
209
|
+
size=len(content_bytes),
|
|
210
|
+
filepath=file_path if file_path else None,
|
|
207
211
|
)
|
|
208
212
|
|
|
209
213
|
log_debug("CSV file generated successfully")
|
|
@@ -287,7 +291,7 @@ class FileGenerationTools(Toolkit):
|
|
|
287
291
|
file_type="pdf",
|
|
288
292
|
filename=filename,
|
|
289
293
|
size=len(pdf_content),
|
|
290
|
-
|
|
294
|
+
filepath=file_path if file_path else None,
|
|
291
295
|
)
|
|
292
296
|
|
|
293
297
|
log_debug("PDF file generated successfully")
|
|
@@ -325,15 +329,17 @@ class FileGenerationTools(Toolkit):
|
|
|
325
329
|
# Save file to disk (if output_directory is set)
|
|
326
330
|
file_path = self._save_file_to_disk(content, filename)
|
|
327
331
|
|
|
332
|
+
content_bytes = content.encode("utf-8")
|
|
333
|
+
|
|
328
334
|
# Create FileArtifact
|
|
329
335
|
file_artifact = File(
|
|
330
336
|
id=str(uuid4()),
|
|
331
|
-
content=
|
|
337
|
+
content=content_bytes,
|
|
332
338
|
mime_type="text/plain",
|
|
333
339
|
file_type="txt",
|
|
334
340
|
filename=filename,
|
|
335
|
-
size=len(
|
|
336
|
-
|
|
341
|
+
size=len(content_bytes),
|
|
342
|
+
filepath=file_path if file_path else None,
|
|
337
343
|
)
|
|
338
344
|
|
|
339
345
|
log_debug("Text file generated successfully")
|
agno/tools/firecrawl.py
CHANGED
|
@@ -101,8 +101,10 @@ class FirecrawlTools(Toolkit):
|
|
|
101
101
|
The results of the crawling.
|
|
102
102
|
"""
|
|
103
103
|
params: Dict[str, Any] = {}
|
|
104
|
-
if self.limit
|
|
105
|
-
params["limit"] = self.limit
|
|
104
|
+
if self.limit is not None:
|
|
105
|
+
params["limit"] = self.limit
|
|
106
|
+
elif limit is not None:
|
|
107
|
+
params["limit"] = limit
|
|
106
108
|
if self.formats:
|
|
107
109
|
params["scrape_options"] = ScrapeOptions(formats=self.formats) # type: ignore
|
|
108
110
|
|
|
@@ -129,15 +131,21 @@ class FirecrawlTools(Toolkit):
|
|
|
129
131
|
limit (int): The maximum number of results to return.
|
|
130
132
|
"""
|
|
131
133
|
params: Dict[str, Any] = {}
|
|
132
|
-
if self.limit
|
|
133
|
-
params["limit"] = self.limit
|
|
134
|
+
if self.limit is not None:
|
|
135
|
+
params["limit"] = self.limit
|
|
136
|
+
elif limit is not None:
|
|
137
|
+
params["limit"] = limit
|
|
134
138
|
if self.formats:
|
|
135
139
|
params["scrape_options"] = ScrapeOptions(formats=self.formats) # type: ignore
|
|
136
140
|
if self.search_params:
|
|
137
141
|
params.update(self.search_params)
|
|
138
142
|
|
|
139
143
|
search_result = self.app.search(query, **params)
|
|
140
|
-
|
|
141
|
-
|
|
144
|
+
|
|
145
|
+
if hasattr(search_result, "success"):
|
|
146
|
+
if search_result.success:
|
|
147
|
+
return json.dumps(search_result.data, cls=CustomJSONEncoder)
|
|
148
|
+
else:
|
|
149
|
+
return f"Error searching with the Firecrawl tool: {search_result.error}"
|
|
142
150
|
else:
|
|
143
|
-
return
|
|
151
|
+
return json.dumps(search_result.model_dump(), cls=CustomJSONEncoder)
|