agno 1.7.2__py3-none-any.whl → 1.7.4__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 +264 -155
- agno/api/schemas/agent.py +1 -0
- agno/api/schemas/team.py +1 -0
- agno/app/base.py +0 -22
- agno/app/discord/client.py +134 -56
- agno/app/fastapi/app.py +0 -11
- agno/app/playground/app.py +3 -24
- agno/app/playground/async_router.py +97 -28
- agno/app/playground/operator.py +25 -19
- agno/app/playground/schemas.py +1 -0
- agno/app/playground/sync_router.py +93 -26
- agno/document/reader/gcs/__init__.py +0 -0
- agno/document/reader/gcs/pdf_reader.py +44 -0
- agno/embedder/langdb.py +9 -5
- agno/knowledge/document.py +199 -8
- agno/knowledge/gcs/__init__.py +0 -0
- agno/knowledge/gcs/base.py +39 -0
- agno/knowledge/gcs/pdf.py +21 -0
- agno/models/langdb/langdb.py +8 -5
- agno/run/base.py +2 -0
- agno/run/response.py +4 -4
- agno/run/team.py +6 -6
- agno/run/v2/__init__.py +0 -0
- agno/run/v2/workflow.py +563 -0
- agno/storage/base.py +4 -4
- agno/storage/dynamodb.py +74 -10
- agno/storage/firestore.py +6 -1
- agno/storage/gcs_json.py +8 -2
- agno/storage/json.py +20 -5
- agno/storage/mongodb.py +14 -5
- agno/storage/mysql.py +56 -17
- agno/storage/postgres.py +55 -13
- agno/storage/redis.py +25 -5
- agno/storage/session/__init__.py +3 -1
- agno/storage/session/agent.py +3 -0
- agno/storage/session/team.py +3 -0
- agno/storage/session/v2/__init__.py +5 -0
- agno/storage/session/v2/workflow.py +89 -0
- agno/storage/singlestore.py +74 -12
- agno/storage/sqlite.py +64 -18
- agno/storage/yaml.py +26 -6
- agno/team/team.py +198 -243
- agno/tools/scrapegraph.py +8 -10
- agno/utils/log.py +12 -0
- agno/utils/message.py +5 -1
- agno/utils/openai.py +20 -5
- agno/utils/pprint.py +32 -8
- agno/workflow/v2/__init__.py +21 -0
- agno/workflow/v2/condition.py +554 -0
- agno/workflow/v2/loop.py +602 -0
- agno/workflow/v2/parallel.py +659 -0
- agno/workflow/v2/router.py +521 -0
- agno/workflow/v2/step.py +861 -0
- agno/workflow/v2/steps.py +465 -0
- agno/workflow/v2/types.py +347 -0
- agno/workflow/v2/workflow.py +3134 -0
- agno/workflow/workflow.py +15 -147
- {agno-1.7.2.dist-info → agno-1.7.4.dist-info}/METADATA +1 -1
- {agno-1.7.2.dist-info → agno-1.7.4.dist-info}/RECORD +63 -45
- {agno-1.7.2.dist-info → agno-1.7.4.dist-info}/WHEEL +0 -0
- {agno-1.7.2.dist-info → agno-1.7.4.dist-info}/entry_points.txt +0 -0
- {agno-1.7.2.dist-info → agno-1.7.4.dist-info}/licenses/LICENSE +0 -0
- {agno-1.7.2.dist-info → agno-1.7.4.dist-info}/top_level.txt +0 -0
agno/tools/scrapegraph.py
CHANGED
|
@@ -45,7 +45,7 @@ class ScrapeGraphTools(Toolkit):
|
|
|
45
45
|
super().__init__(name="scrapegraph_tools", tools=tools, **kwargs)
|
|
46
46
|
|
|
47
47
|
def smartscraper(self, url: str, prompt: str) -> str:
|
|
48
|
-
"""
|
|
48
|
+
"""Extract structured data from a webpage using LLM.
|
|
49
49
|
Args:
|
|
50
50
|
url (str): The URL to scrape
|
|
51
51
|
prompt (str): Natural language prompt describing what to extract
|
|
@@ -59,7 +59,7 @@ class ScrapeGraphTools(Toolkit):
|
|
|
59
59
|
return json.dumps({"error": str(e)})
|
|
60
60
|
|
|
61
61
|
def markdownify(self, url: str) -> str:
|
|
62
|
-
"""
|
|
62
|
+
"""Convert a webpage to markdown format.
|
|
63
63
|
Args:
|
|
64
64
|
url (str): The URL to convert
|
|
65
65
|
Returns:
|
|
@@ -82,7 +82,7 @@ class ScrapeGraphTools(Toolkit):
|
|
|
82
82
|
same_domain_only: bool = True,
|
|
83
83
|
batch_size: int = 1,
|
|
84
84
|
) -> str:
|
|
85
|
-
"""
|
|
85
|
+
"""Crawl a website and extract structured data
|
|
86
86
|
Args:
|
|
87
87
|
url (str): The URL to crawl
|
|
88
88
|
prompt (str): Natural language prompt describing what to extract
|
|
@@ -110,17 +110,15 @@ class ScrapeGraphTools(Toolkit):
|
|
|
110
110
|
except Exception as e:
|
|
111
111
|
return json.dumps({"error": str(e)})
|
|
112
112
|
|
|
113
|
-
def searchscraper(self,
|
|
114
|
-
"""
|
|
113
|
+
def searchscraper(self, prompt: str) -> str:
|
|
114
|
+
"""Search the web and extract information from the web.
|
|
115
115
|
Args:
|
|
116
|
-
|
|
117
|
-
prompt (str): Natural language prompt describing what to search for
|
|
116
|
+
prompt (str): Search query
|
|
118
117
|
Returns:
|
|
119
|
-
|
|
118
|
+
JSON of the search results
|
|
120
119
|
"""
|
|
121
120
|
try:
|
|
122
|
-
response = self.client.searchscraper(
|
|
123
|
-
# If response has a 'result' attribute, return it, else return the whole response
|
|
121
|
+
response = self.client.searchscraper(user_prompt=prompt)
|
|
124
122
|
if hasattr(response, "result"):
|
|
125
123
|
return json.dumps(response.result)
|
|
126
124
|
elif isinstance(response, dict) and "result" in response:
|
agno/utils/log.py
CHANGED
|
@@ -7,6 +7,7 @@ from rich.text import Text
|
|
|
7
7
|
|
|
8
8
|
LOGGER_NAME = "agno"
|
|
9
9
|
TEAM_LOGGER_NAME = f"{LOGGER_NAME}-team"
|
|
10
|
+
WORKFLOW_LOGGER_NAME = f"{LOGGER_NAME}-workflow"
|
|
10
11
|
|
|
11
12
|
# Define custom styles for different log sources
|
|
12
13
|
LOG_STYLES = {
|
|
@@ -18,6 +19,10 @@ LOG_STYLES = {
|
|
|
18
19
|
"debug": "magenta",
|
|
19
20
|
"info": "steel_blue1",
|
|
20
21
|
},
|
|
22
|
+
"workflow": {
|
|
23
|
+
"debug": "sandy_brown",
|
|
24
|
+
"info": "orange3",
|
|
25
|
+
},
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
|
|
@@ -92,6 +97,7 @@ def build_logger(logger_name: str, source_type: Optional[str] = None) -> Any:
|
|
|
92
97
|
|
|
93
98
|
agent_logger: AgnoLogger = build_logger(LOGGER_NAME, source_type="agent")
|
|
94
99
|
team_logger: AgnoLogger = build_logger(TEAM_LOGGER_NAME, source_type="team")
|
|
100
|
+
workflow_logger: AgnoLogger = build_logger(WORKFLOW_LOGGER_NAME, source_type="workflow")
|
|
95
101
|
|
|
96
102
|
# Set the default logger to the agent logger
|
|
97
103
|
logger: AgnoLogger = agent_logger
|
|
@@ -146,6 +152,12 @@ def use_agent_logger():
|
|
|
146
152
|
logger = agent_logger
|
|
147
153
|
|
|
148
154
|
|
|
155
|
+
def use_workflow_logger():
|
|
156
|
+
"""Switch the default logger to use workflow_logger"""
|
|
157
|
+
global logger
|
|
158
|
+
logger = workflow_logger
|
|
159
|
+
|
|
160
|
+
|
|
149
161
|
def log_debug(msg, center: bool = False, symbol: str = "*", log_level: Literal[1, 2] = 1, *args, **kwargs):
|
|
150
162
|
global logger
|
|
151
163
|
global debug_on
|
agno/utils/message.py
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
from typing import Dict, List, Union
|
|
2
2
|
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
3
5
|
from agno.models.message import Message
|
|
4
6
|
|
|
5
7
|
|
|
6
|
-
def get_text_from_message(message: Union[List, Dict, str, Message]) -> str:
|
|
8
|
+
def get_text_from_message(message: Union[List, Dict, str, Message, BaseModel]) -> str:
|
|
7
9
|
"""Return the user texts from the message"""
|
|
8
10
|
|
|
9
11
|
if isinstance(message, str):
|
|
10
12
|
return message
|
|
13
|
+
if isinstance(message, BaseModel):
|
|
14
|
+
return message.model_dump_json(indent=2, exclude_none=True)
|
|
11
15
|
if isinstance(message, list):
|
|
12
16
|
text_messages = []
|
|
13
17
|
if len(message) == 0:
|
agno/utils/openai.py
CHANGED
|
@@ -93,11 +93,24 @@ def audio_to_message(audio: Sequence[Audio]) -> List[Dict[str, Any]]:
|
|
|
93
93
|
return audio_messages
|
|
94
94
|
|
|
95
95
|
|
|
96
|
-
def _process_bytes_image(image: bytes) -> Dict[str, Any]:
|
|
96
|
+
def _process_bytes_image(image: bytes, image_format: Optional[str] = None) -> Dict[str, Any]:
|
|
97
97
|
"""Process bytes image data."""
|
|
98
98
|
base64_image = base64.b64encode(image).decode("utf-8")
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
|
|
100
|
+
# Use provided format or attempt detection, defaulting to JPEG
|
|
101
|
+
if image_format:
|
|
102
|
+
mime_type = f"image/{image_format.lower()}"
|
|
103
|
+
else:
|
|
104
|
+
# Try to detect the image format from the bytes
|
|
105
|
+
try:
|
|
106
|
+
import imghdr
|
|
107
|
+
|
|
108
|
+
detected_format = imghdr.what(None, h=image)
|
|
109
|
+
mime_type = f"image/{detected_format}" if detected_format else "image/jpeg"
|
|
110
|
+
except Exception:
|
|
111
|
+
mime_type = "image/jpeg"
|
|
112
|
+
|
|
113
|
+
image_url = f"data:{mime_type};base64,{base64_image}"
|
|
101
114
|
return {"type": "image_url", "image_url": {"url": image_url}}
|
|
102
115
|
|
|
103
116
|
|
|
@@ -141,7 +154,8 @@ def process_image(image: Image) -> Optional[Dict[str, Any]]:
|
|
|
141
154
|
image_payload = _process_image_path(image.filepath)
|
|
142
155
|
|
|
143
156
|
elif image.content is not None:
|
|
144
|
-
|
|
157
|
+
# Pass the format from the Image object
|
|
158
|
+
image_payload = _process_bytes_image(image.content, image.format)
|
|
145
159
|
|
|
146
160
|
else:
|
|
147
161
|
log_warning(f"Unsupported image format or no data provided: {image}")
|
|
@@ -150,7 +164,8 @@ def process_image(image: Image) -> Optional[Dict[str, Any]]:
|
|
|
150
164
|
if image_payload and image.detail: # Check if payload was created before adding detail
|
|
151
165
|
# Ensure image_url key exists before trying to access its sub-dictionary
|
|
152
166
|
if "image_url" not in image_payload:
|
|
153
|
-
|
|
167
|
+
# Initialize if missing (though unlikely based on helper funcs)
|
|
168
|
+
image_payload["image_url"] = {}
|
|
154
169
|
image_payload["image_url"]["detail"] = image.detail
|
|
155
170
|
|
|
156
171
|
return image_payload
|
agno/utils/pprint.py
CHANGED
|
@@ -5,13 +5,20 @@ from pydantic import BaseModel
|
|
|
5
5
|
|
|
6
6
|
from agno.run.response import RunResponse, RunResponseEvent
|
|
7
7
|
from agno.run.team import TeamRunResponse, TeamRunResponseEvent
|
|
8
|
-
from agno.run.workflow import WorkflowRunResponseEvent
|
|
8
|
+
from agno.run.v2.workflow import WorkflowRunResponse, WorkflowRunResponseEvent
|
|
9
9
|
from agno.utils.log import logger
|
|
10
10
|
from agno.utils.timer import Timer
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def pprint_run_response(
|
|
14
|
-
run_response: Union[
|
|
14
|
+
run_response: Union[
|
|
15
|
+
RunResponse,
|
|
16
|
+
Iterable[RunResponseEvent],
|
|
17
|
+
TeamRunResponse,
|
|
18
|
+
Iterable[TeamRunResponseEvent],
|
|
19
|
+
WorkflowRunResponse,
|
|
20
|
+
Iterable[WorkflowRunResponseEvent],
|
|
21
|
+
],
|
|
15
22
|
markdown: bool = False,
|
|
16
23
|
show_time: bool = False,
|
|
17
24
|
) -> None:
|
|
@@ -25,7 +32,11 @@ def pprint_run_response(
|
|
|
25
32
|
from agno.cli.console import console
|
|
26
33
|
|
|
27
34
|
# If run_response is a single RunResponse, wrap it in a list to make it iterable
|
|
28
|
-
if
|
|
35
|
+
if (
|
|
36
|
+
isinstance(run_response, RunResponse)
|
|
37
|
+
or isinstance(run_response, TeamRunResponse)
|
|
38
|
+
or isinstance(run_response, WorkflowRunResponse)
|
|
39
|
+
):
|
|
29
40
|
single_response_content: Union[str, JSON, Markdown] = ""
|
|
30
41
|
if isinstance(run_response.content, str):
|
|
31
42
|
single_response_content = (
|
|
@@ -64,11 +75,13 @@ def pprint_run_response(
|
|
|
64
75
|
):
|
|
65
76
|
if isinstance(resp.content, BaseModel):
|
|
66
77
|
try:
|
|
67
|
-
|
|
78
|
+
JSON(resp.content.model_dump_json(exclude_none=True), indent=2) # type: ignore
|
|
68
79
|
except Exception as e:
|
|
69
80
|
logger.warning(f"Failed to convert response to Markdown: {e}")
|
|
70
81
|
else:
|
|
71
|
-
streaming_response_content
|
|
82
|
+
if isinstance(streaming_response_content, JSON):
|
|
83
|
+
streaming_response_content = streaming_response_content.text + "\n" # type: ignore
|
|
84
|
+
streaming_response_content += resp.content # type: ignore
|
|
72
85
|
|
|
73
86
|
formatted_response = Markdown(streaming_response_content) if markdown else streaming_response_content # type: ignore
|
|
74
87
|
table = Table(box=ROUNDED, border_style="blue", show_header=False)
|
|
@@ -81,7 +94,14 @@ def pprint_run_response(
|
|
|
81
94
|
|
|
82
95
|
|
|
83
96
|
async def apprint_run_response(
|
|
84
|
-
run_response: Union[
|
|
97
|
+
run_response: Union[
|
|
98
|
+
RunResponse,
|
|
99
|
+
AsyncIterable[RunResponse],
|
|
100
|
+
TeamRunResponse,
|
|
101
|
+
AsyncIterable[TeamRunResponse],
|
|
102
|
+
WorkflowRunResponse,
|
|
103
|
+
AsyncIterable[WorkflowRunResponseEvent],
|
|
104
|
+
],
|
|
85
105
|
markdown: bool = False,
|
|
86
106
|
show_time: bool = False,
|
|
87
107
|
) -> None:
|
|
@@ -95,7 +115,11 @@ async def apprint_run_response(
|
|
|
95
115
|
from agno.cli.console import console
|
|
96
116
|
|
|
97
117
|
# If run_response is a single RunResponse, wrap it in a list to make it iterable
|
|
98
|
-
if
|
|
118
|
+
if (
|
|
119
|
+
isinstance(run_response, RunResponse)
|
|
120
|
+
or isinstance(run_response, TeamRunResponse)
|
|
121
|
+
or isinstance(run_response, WorkflowRunResponse)
|
|
122
|
+
):
|
|
99
123
|
single_response_content: Union[str, JSON, Markdown] = ""
|
|
100
124
|
if isinstance(run_response.content, str):
|
|
101
125
|
single_response_content = (
|
|
@@ -139,7 +163,7 @@ async def apprint_run_response(
|
|
|
139
163
|
except Exception as e:
|
|
140
164
|
logger.warning(f"Failed to convert response to Markdown: {e}")
|
|
141
165
|
else:
|
|
142
|
-
streaming_response_content += resp.content
|
|
166
|
+
streaming_response_content += resp.content # type: ignore
|
|
143
167
|
|
|
144
168
|
formatted_response = Markdown(streaming_response_content) if markdown else streaming_response_content # type: ignore
|
|
145
169
|
table = Table(box=ROUNDED, border_style="blue", show_header=False)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from agno.workflow.v2.condition import Condition
|
|
2
|
+
from agno.workflow.v2.loop import Loop
|
|
3
|
+
from agno.workflow.v2.parallel import Parallel
|
|
4
|
+
from agno.workflow.v2.router import Router
|
|
5
|
+
from agno.workflow.v2.step import Step
|
|
6
|
+
from agno.workflow.v2.steps import Steps
|
|
7
|
+
from agno.workflow.v2.types import StepInput, StepOutput, WorkflowExecutionInput
|
|
8
|
+
from agno.workflow.v2.workflow import Workflow
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"Workflow",
|
|
12
|
+
"Steps",
|
|
13
|
+
"Step",
|
|
14
|
+
"Loop",
|
|
15
|
+
"Parallel",
|
|
16
|
+
"Condition",
|
|
17
|
+
"Router",
|
|
18
|
+
"WorkflowExecutionInput",
|
|
19
|
+
"StepInput",
|
|
20
|
+
"StepOutput",
|
|
21
|
+
]
|