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
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from cryptography.hazmat.primitives import serialization
|
|
2
|
+
from cryptography.hazmat.primitives.asymmetric import rsa
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def generate_rsa_keys():
|
|
6
|
+
"""Generate RSA key pair for RS256 JWT signing/verification."""
|
|
7
|
+
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
|
8
|
+
|
|
9
|
+
# Private key PEM (used by auth server to sign tokens)
|
|
10
|
+
private_pem = private_key.private_bytes(
|
|
11
|
+
encoding=serialization.Encoding.PEM,
|
|
12
|
+
format=serialization.PrivateFormat.PKCS8,
|
|
13
|
+
encryption_algorithm=serialization.NoEncryption(),
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
# Public key PEM (used by AgentOS to verify tokens)
|
|
17
|
+
public_pem = private_key.public_key().public_bytes(
|
|
18
|
+
encoding=serialization.Encoding.PEM,
|
|
19
|
+
format=serialization.PublicFormat.SubjectPublicKeyInfo,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
return private_pem.decode("utf-8"), public_pem.decode("utf-8")
|
agno/utils/dttm.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from datetime import datetime, timezone
|
|
2
|
+
from typing import Union
|
|
2
3
|
|
|
3
4
|
|
|
4
5
|
def current_datetime() -> datetime:
|
|
@@ -11,3 +12,35 @@ def current_datetime_utc() -> datetime:
|
|
|
11
12
|
|
|
12
13
|
def current_datetime_utc_str() -> str:
|
|
13
14
|
return current_datetime_utc().strftime("%Y-%m-%dT%H:%M:%S")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def now_epoch_s() -> int:
|
|
18
|
+
return int(datetime.now(timezone.utc).timestamp())
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def to_epoch_s(value: Union[int, float, str, datetime]) -> int:
|
|
22
|
+
"""Normalize various datetime representations to epoch seconds (UTC)."""
|
|
23
|
+
|
|
24
|
+
if isinstance(value, (int, float)):
|
|
25
|
+
# assume value is already in seconds
|
|
26
|
+
return int(value)
|
|
27
|
+
|
|
28
|
+
if isinstance(value, datetime):
|
|
29
|
+
dt = value
|
|
30
|
+
if dt.tzinfo is None:
|
|
31
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
32
|
+
return int(dt.timestamp())
|
|
33
|
+
|
|
34
|
+
if isinstance(value, str):
|
|
35
|
+
s = value.strip()
|
|
36
|
+
if s.endswith("Z"):
|
|
37
|
+
s = s[:-1] + "+00:00"
|
|
38
|
+
try:
|
|
39
|
+
dt = datetime.fromisoformat(s)
|
|
40
|
+
except ValueError as e:
|
|
41
|
+
raise ValueError(f"Unsupported datetime string: {value!r}") from e
|
|
42
|
+
if dt.tzinfo is None:
|
|
43
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
44
|
+
return int(dt.timestamp())
|
|
45
|
+
|
|
46
|
+
raise TypeError(f"Unsupported datetime value: {type(value)}")
|
agno/utils/events.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any, Dict, List, Optional
|
|
1
|
+
from typing import Any, Dict, List, Optional, Union
|
|
2
2
|
|
|
3
3
|
from agno.media import Audio, Image
|
|
4
4
|
from agno.models.message import Citations
|
|
@@ -11,6 +11,8 @@ from agno.run.agent import (
|
|
|
11
11
|
OutputModelResponseStartedEvent,
|
|
12
12
|
ParserModelResponseCompletedEvent,
|
|
13
13
|
ParserModelResponseStartedEvent,
|
|
14
|
+
PostHookCompletedEvent,
|
|
15
|
+
PostHookStartedEvent,
|
|
14
16
|
PreHookCompletedEvent,
|
|
15
17
|
PreHookStartedEvent,
|
|
16
18
|
ReasoningCompletedEvent,
|
|
@@ -18,22 +20,30 @@ from agno.run.agent import (
|
|
|
18
20
|
ReasoningStepEvent,
|
|
19
21
|
RunCancelledEvent,
|
|
20
22
|
RunCompletedEvent,
|
|
23
|
+
RunContentCompletedEvent,
|
|
21
24
|
RunContentEvent,
|
|
22
25
|
RunContinuedEvent,
|
|
23
26
|
RunErrorEvent,
|
|
27
|
+
RunEvent,
|
|
24
28
|
RunInput,
|
|
25
29
|
RunOutput,
|
|
30
|
+
RunOutputEvent,
|
|
26
31
|
RunPausedEvent,
|
|
27
32
|
RunStartedEvent,
|
|
33
|
+
SessionSummaryCompletedEvent,
|
|
34
|
+
SessionSummaryStartedEvent,
|
|
28
35
|
ToolCallCompletedEvent,
|
|
29
36
|
ToolCallStartedEvent,
|
|
30
37
|
)
|
|
38
|
+
from agno.run.requirement import RunRequirement
|
|
31
39
|
from agno.run.team import MemoryUpdateCompletedEvent as TeamMemoryUpdateCompletedEvent
|
|
32
40
|
from agno.run.team import MemoryUpdateStartedEvent as TeamMemoryUpdateStartedEvent
|
|
33
41
|
from agno.run.team import OutputModelResponseCompletedEvent as TeamOutputModelResponseCompletedEvent
|
|
34
42
|
from agno.run.team import OutputModelResponseStartedEvent as TeamOutputModelResponseStartedEvent
|
|
35
43
|
from agno.run.team import ParserModelResponseCompletedEvent as TeamParserModelResponseCompletedEvent
|
|
36
44
|
from agno.run.team import ParserModelResponseStartedEvent as TeamParserModelResponseStartedEvent
|
|
45
|
+
from agno.run.team import PostHookCompletedEvent as TeamPostHookCompletedEvent
|
|
46
|
+
from agno.run.team import PostHookStartedEvent as TeamPostHookStartedEvent
|
|
37
47
|
from agno.run.team import PreHookCompletedEvent as TeamPreHookCompletedEvent
|
|
38
48
|
from agno.run.team import PreHookStartedEvent as TeamPreHookStartedEvent
|
|
39
49
|
from agno.run.team import ReasoningCompletedEvent as TeamReasoningCompletedEvent
|
|
@@ -41,12 +51,16 @@ from agno.run.team import ReasoningStartedEvent as TeamReasoningStartedEvent
|
|
|
41
51
|
from agno.run.team import ReasoningStepEvent as TeamReasoningStepEvent
|
|
42
52
|
from agno.run.team import RunCancelledEvent as TeamRunCancelledEvent
|
|
43
53
|
from agno.run.team import RunCompletedEvent as TeamRunCompletedEvent
|
|
54
|
+
from agno.run.team import RunContentCompletedEvent as TeamRunContentCompletedEvent
|
|
44
55
|
from agno.run.team import RunContentEvent as TeamRunContentEvent
|
|
45
56
|
from agno.run.team import RunErrorEvent as TeamRunErrorEvent
|
|
46
57
|
from agno.run.team import RunStartedEvent as TeamRunStartedEvent
|
|
47
|
-
from agno.run.team import
|
|
58
|
+
from agno.run.team import SessionSummaryCompletedEvent as TeamSessionSummaryCompletedEvent
|
|
59
|
+
from agno.run.team import SessionSummaryStartedEvent as TeamSessionSummaryStartedEvent
|
|
60
|
+
from agno.run.team import TeamRunEvent, TeamRunInput, TeamRunOutput, TeamRunOutputEvent
|
|
48
61
|
from agno.run.team import ToolCallCompletedEvent as TeamToolCallCompletedEvent
|
|
49
62
|
from agno.run.team import ToolCallStartedEvent as TeamToolCallStartedEvent
|
|
63
|
+
from agno.session.summary import SessionSummary
|
|
50
64
|
|
|
51
65
|
|
|
52
66
|
def create_team_run_started_event(from_run_response: TeamRunOutput) -> TeamRunStartedEvent:
|
|
@@ -93,6 +107,7 @@ def create_team_run_completed_event(from_run_response: TeamRunOutput) -> TeamRun
|
|
|
93
107
|
member_responses=from_run_response.member_responses, # type: ignore
|
|
94
108
|
metadata=from_run_response.metadata, # type: ignore
|
|
95
109
|
metrics=from_run_response.metrics, # type: ignore
|
|
110
|
+
session_state=from_run_response.session_state, # type: ignore
|
|
96
111
|
)
|
|
97
112
|
|
|
98
113
|
|
|
@@ -117,11 +132,14 @@ def create_run_completed_event(from_run_response: RunOutput) -> RunCompletedEven
|
|
|
117
132
|
reasoning_messages=from_run_response.reasoning_messages, # type: ignore
|
|
118
133
|
metadata=from_run_response.metadata, # type: ignore
|
|
119
134
|
metrics=from_run_response.metrics, # type: ignore
|
|
135
|
+
session_state=from_run_response.session_state, # type: ignore
|
|
120
136
|
)
|
|
121
137
|
|
|
122
138
|
|
|
123
139
|
def create_run_paused_event(
|
|
124
|
-
from_run_response: RunOutput,
|
|
140
|
+
from_run_response: RunOutput,
|
|
141
|
+
tools: Optional[List[ToolExecution]] = None,
|
|
142
|
+
requirements: Optional[List[RunRequirement]] = None,
|
|
125
143
|
) -> RunPausedEvent:
|
|
126
144
|
return RunPausedEvent(
|
|
127
145
|
session_id=from_run_response.session_id,
|
|
@@ -129,6 +147,7 @@ def create_run_paused_event(
|
|
|
129
147
|
agent_name=from_run_response.agent_name, # type: ignore
|
|
130
148
|
run_id=from_run_response.run_id,
|
|
131
149
|
tools=tools,
|
|
150
|
+
requirements=requirements,
|
|
132
151
|
content=from_run_response.content,
|
|
133
152
|
)
|
|
134
153
|
|
|
@@ -242,6 +261,54 @@ def create_team_pre_hook_completed_event(
|
|
|
242
261
|
)
|
|
243
262
|
|
|
244
263
|
|
|
264
|
+
def create_post_hook_started_event(
|
|
265
|
+
from_run_response: RunOutput, post_hook_name: Optional[str] = None
|
|
266
|
+
) -> PostHookStartedEvent:
|
|
267
|
+
return PostHookStartedEvent(
|
|
268
|
+
session_id=from_run_response.session_id,
|
|
269
|
+
agent_id=from_run_response.agent_id, # type: ignore
|
|
270
|
+
agent_name=from_run_response.agent_name, # type: ignore
|
|
271
|
+
run_id=from_run_response.run_id,
|
|
272
|
+
post_hook_name=post_hook_name,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def create_team_post_hook_started_event(
|
|
277
|
+
from_run_response: TeamRunOutput, post_hook_name: Optional[str] = None
|
|
278
|
+
) -> TeamPostHookStartedEvent:
|
|
279
|
+
return TeamPostHookStartedEvent(
|
|
280
|
+
session_id=from_run_response.session_id,
|
|
281
|
+
team_id=from_run_response.team_id, # type: ignore
|
|
282
|
+
team_name=from_run_response.team_name, # type: ignore
|
|
283
|
+
run_id=from_run_response.run_id,
|
|
284
|
+
post_hook_name=post_hook_name,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def create_post_hook_completed_event(
|
|
289
|
+
from_run_response: RunOutput, post_hook_name: Optional[str] = None
|
|
290
|
+
) -> PostHookCompletedEvent:
|
|
291
|
+
return PostHookCompletedEvent(
|
|
292
|
+
session_id=from_run_response.session_id,
|
|
293
|
+
agent_id=from_run_response.agent_id, # type: ignore
|
|
294
|
+
agent_name=from_run_response.agent_name, # type: ignore
|
|
295
|
+
run_id=from_run_response.run_id,
|
|
296
|
+
post_hook_name=post_hook_name,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def create_team_post_hook_completed_event(
|
|
301
|
+
from_run_response: TeamRunOutput, post_hook_name: Optional[str] = None
|
|
302
|
+
) -> TeamPostHookCompletedEvent:
|
|
303
|
+
return TeamPostHookCompletedEvent(
|
|
304
|
+
session_id=from_run_response.session_id,
|
|
305
|
+
team_id=from_run_response.team_id, # type: ignore
|
|
306
|
+
team_name=from_run_response.team_name, # type: ignore
|
|
307
|
+
run_id=from_run_response.run_id,
|
|
308
|
+
post_hook_name=post_hook_name,
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
|
|
245
312
|
def create_memory_update_started_event(from_run_response: RunOutput) -> MemoryUpdateStartedEvent:
|
|
246
313
|
return MemoryUpdateStartedEvent(
|
|
247
314
|
session_id=from_run_response.session_id,
|
|
@@ -278,6 +345,50 @@ def create_team_memory_update_completed_event(from_run_response: TeamRunOutput)
|
|
|
278
345
|
)
|
|
279
346
|
|
|
280
347
|
|
|
348
|
+
def create_team_session_summary_started_event(
|
|
349
|
+
from_run_response: TeamRunOutput,
|
|
350
|
+
) -> TeamSessionSummaryStartedEvent:
|
|
351
|
+
return TeamSessionSummaryStartedEvent(
|
|
352
|
+
session_id=from_run_response.session_id,
|
|
353
|
+
team_id=from_run_response.team_id, # type: ignore
|
|
354
|
+
team_name=from_run_response.team_name, # type: ignore
|
|
355
|
+
run_id=from_run_response.run_id,
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def create_team_session_summary_completed_event(
|
|
360
|
+
from_run_response: TeamRunOutput, session_summary: Optional[SessionSummary] = None
|
|
361
|
+
) -> TeamSessionSummaryCompletedEvent:
|
|
362
|
+
return TeamSessionSummaryCompletedEvent(
|
|
363
|
+
session_id=from_run_response.session_id,
|
|
364
|
+
team_id=from_run_response.team_id, # type: ignore
|
|
365
|
+
team_name=from_run_response.team_name, # type: ignore
|
|
366
|
+
run_id=from_run_response.run_id,
|
|
367
|
+
session_summary=session_summary,
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def create_session_summary_started_event(from_run_response: RunOutput) -> SessionSummaryStartedEvent:
|
|
372
|
+
return SessionSummaryStartedEvent(
|
|
373
|
+
session_id=from_run_response.session_id,
|
|
374
|
+
agent_id=from_run_response.agent_id, # type: ignore
|
|
375
|
+
agent_name=from_run_response.agent_name, # type: ignore
|
|
376
|
+
run_id=from_run_response.run_id,
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def create_session_summary_completed_event(
|
|
381
|
+
from_run_response: RunOutput, session_summary: Optional[SessionSummary] = None
|
|
382
|
+
) -> SessionSummaryCompletedEvent:
|
|
383
|
+
return SessionSummaryCompletedEvent(
|
|
384
|
+
session_id=from_run_response.session_id,
|
|
385
|
+
agent_id=from_run_response.agent_id, # type: ignore
|
|
386
|
+
agent_name=from_run_response.agent_name, # type: ignore
|
|
387
|
+
run_id=from_run_response.run_id,
|
|
388
|
+
session_summary=session_summary,
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
|
|
281
392
|
def create_reasoning_started_event(from_run_response: RunOutput) -> ReasoningStartedEvent:
|
|
282
393
|
return ReasoningStartedEvent(
|
|
283
394
|
session_id=from_run_response.session_id,
|
|
@@ -468,6 +579,28 @@ def create_team_run_output_content_event(
|
|
|
468
579
|
)
|
|
469
580
|
|
|
470
581
|
|
|
582
|
+
def create_run_content_completed_event(
|
|
583
|
+
from_run_response: RunOutput,
|
|
584
|
+
) -> RunContentCompletedEvent:
|
|
585
|
+
return RunContentCompletedEvent(
|
|
586
|
+
session_id=from_run_response.session_id,
|
|
587
|
+
agent_id=from_run_response.agent_id, # type: ignore
|
|
588
|
+
agent_name=from_run_response.agent_name, # type: ignore
|
|
589
|
+
run_id=from_run_response.run_id,
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
def create_team_run_content_completed_event(
|
|
594
|
+
from_run_response: TeamRunOutput,
|
|
595
|
+
) -> TeamRunContentCompletedEvent:
|
|
596
|
+
return TeamRunContentCompletedEvent(
|
|
597
|
+
session_id=from_run_response.session_id,
|
|
598
|
+
team_id=from_run_response.team_id, # type: ignore
|
|
599
|
+
team_name=from_run_response.team_name, # type: ignore
|
|
600
|
+
run_id=from_run_response.run_id,
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
|
|
471
604
|
def create_parser_model_response_started_event(
|
|
472
605
|
from_run_response: RunOutput,
|
|
473
606
|
) -> ParserModelResponseStartedEvent:
|
|
@@ -550,3 +683,18 @@ def create_team_output_model_response_completed_event(
|
|
|
550
683
|
team_name=from_run_response.team_name, # type: ignore
|
|
551
684
|
run_id=from_run_response.run_id,
|
|
552
685
|
)
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
def handle_event(
|
|
689
|
+
event: Union[RunOutputEvent, TeamRunOutputEvent],
|
|
690
|
+
run_response: Union[RunOutput, TeamRunOutput],
|
|
691
|
+
events_to_skip: Optional[List[Union[RunEvent, TeamRunEvent]]] = None,
|
|
692
|
+
store_events: bool = False,
|
|
693
|
+
) -> Union[RunOutputEvent, TeamRunOutputEvent]:
|
|
694
|
+
# We only store events that are not run_response_content events
|
|
695
|
+
events_to_skip = [event.value for event in events_to_skip] if events_to_skip else []
|
|
696
|
+
if store_events and event.event not in events_to_skip:
|
|
697
|
+
if run_response.events is None:
|
|
698
|
+
run_response.events = []
|
|
699
|
+
run_response.events.append(event) # type: ignore
|
|
700
|
+
return event
|
agno/utils/gemini.py
CHANGED
|
@@ -225,12 +225,13 @@ def convert_schema(
|
|
|
225
225
|
if schema_type is None or schema_type == "null":
|
|
226
226
|
return None
|
|
227
227
|
description = schema_dict.get("description", None)
|
|
228
|
+
title = schema_dict.get("title", None)
|
|
228
229
|
default = schema_dict.get("default", None)
|
|
229
230
|
|
|
230
231
|
# Handle enum types
|
|
231
232
|
if "enum" in schema_dict:
|
|
232
233
|
enum_values = schema_dict["enum"]
|
|
233
|
-
return Schema(type=GeminiType.STRING, enum=enum_values, description=description, default=default)
|
|
234
|
+
return Schema(type=GeminiType.STRING, enum=enum_values, description=description, default=default, title=title)
|
|
234
235
|
|
|
235
236
|
if schema_type == "object":
|
|
236
237
|
# Handle regular objects with properties
|
|
@@ -250,6 +251,10 @@ def convert_schema(
|
|
|
250
251
|
if is_nullable:
|
|
251
252
|
converted_schema.nullable = True
|
|
252
253
|
properties[key] = converted_schema
|
|
254
|
+
else:
|
|
255
|
+
properties[key] = Schema(
|
|
256
|
+
title=prop_def.get("title", None), description=prop_def.get("description", None)
|
|
257
|
+
)
|
|
253
258
|
|
|
254
259
|
required = schema_dict.get("required", [])
|
|
255
260
|
|
|
@@ -260,9 +265,10 @@ def convert_schema(
|
|
|
260
265
|
required=required,
|
|
261
266
|
description=description,
|
|
262
267
|
default=default,
|
|
268
|
+
title=title,
|
|
263
269
|
)
|
|
264
270
|
else:
|
|
265
|
-
return Schema(type=GeminiType.OBJECT, description=description, default=default)
|
|
271
|
+
return Schema(type=GeminiType.OBJECT, description=description, default=default, title=title)
|
|
266
272
|
|
|
267
273
|
# Handle Dict types (objects with additionalProperties but no properties)
|
|
268
274
|
elif "additionalProperties" in schema_dict:
|
|
@@ -305,11 +311,11 @@ def convert_schema(
|
|
|
305
311
|
)
|
|
306
312
|
else:
|
|
307
313
|
# additionalProperties is false or true
|
|
308
|
-
return Schema(type=GeminiType.OBJECT, description=description, default=default)
|
|
314
|
+
return Schema(type=GeminiType.OBJECT, description=description, default=default, title=title)
|
|
309
315
|
|
|
310
316
|
# Handle empty objects
|
|
311
317
|
else:
|
|
312
|
-
return Schema(type=GeminiType.OBJECT, description=description, default=default)
|
|
318
|
+
return Schema(type=GeminiType.OBJECT, description=description, default=default, title=title)
|
|
313
319
|
|
|
314
320
|
elif schema_type == "array" and "items" in schema_dict:
|
|
315
321
|
if not schema_dict["items"]: # Handle empty {}
|
|
@@ -325,6 +331,7 @@ def convert_schema(
|
|
|
325
331
|
items=items,
|
|
326
332
|
min_items=min_items,
|
|
327
333
|
max_items=max_items,
|
|
334
|
+
title=title,
|
|
328
335
|
)
|
|
329
336
|
|
|
330
337
|
elif schema_type == "string":
|
|
@@ -332,6 +339,7 @@ def convert_schema(
|
|
|
332
339
|
"type": GeminiType.STRING,
|
|
333
340
|
"description": description,
|
|
334
341
|
"default": default,
|
|
342
|
+
"title": title,
|
|
335
343
|
}
|
|
336
344
|
if "format" in schema_dict:
|
|
337
345
|
schema_kwargs["format"] = schema_dict["format"]
|
|
@@ -342,6 +350,7 @@ def convert_schema(
|
|
|
342
350
|
"type": schema_type.upper(),
|
|
343
351
|
"description": description,
|
|
344
352
|
"default": default,
|
|
353
|
+
"title": title,
|
|
345
354
|
}
|
|
346
355
|
if "maximum" in schema_dict:
|
|
347
356
|
schema_kwargs["maximum"] = schema_dict["maximum"]
|
|
@@ -373,6 +382,7 @@ def convert_schema(
|
|
|
373
382
|
any_of=any_of,
|
|
374
383
|
description=description,
|
|
375
384
|
default=default,
|
|
385
|
+
title=title,
|
|
376
386
|
)
|
|
377
387
|
else:
|
|
378
388
|
if isinstance(schema_type, list):
|
|
@@ -384,7 +394,7 @@ def convert_schema(
|
|
|
384
394
|
# Only convert to uppercase if schema_type is not empty
|
|
385
395
|
if schema_type:
|
|
386
396
|
schema_type = schema_type.upper()
|
|
387
|
-
return Schema(type=schema_type, description=description, default=default)
|
|
397
|
+
return Schema(type=schema_type, description=description, default=default, title=title)
|
|
388
398
|
else:
|
|
389
399
|
# If we get here with an empty type and no other handlers matched,
|
|
390
400
|
# something is wrong with the schema
|
agno/utils/hooks.py
CHANGED
|
@@ -1,14 +1,73 @@
|
|
|
1
|
-
from
|
|
1
|
+
from copy import deepcopy
|
|
2
|
+
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union
|
|
3
|
+
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from agno.eval.base import BaseEval
|
|
2
6
|
|
|
3
7
|
from agno.guardrails.base import BaseGuardrail
|
|
8
|
+
from agno.hooks.decorator import HOOK_RUN_IN_BACKGROUND_ATTR
|
|
4
9
|
from agno.utils.log import log_warning
|
|
5
10
|
|
|
11
|
+
# Keys that should be deep copied for background hooks to prevent race conditions
|
|
12
|
+
BACKGROUND_HOOK_COPY_KEYS = frozenset(
|
|
13
|
+
{"run_input", "run_context", "run_output", "session_state", "dependencies", "metadata"}
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def copy_args_for_background(args: Dict[str, Any]) -> Dict[str, Any]:
|
|
18
|
+
"""
|
|
19
|
+
Create a copy of hook arguments for background execution.
|
|
20
|
+
|
|
21
|
+
This deep copies run_input, run_context, run_output, session_state, dependencies,
|
|
22
|
+
and metadata to prevent race conditions when hooks run in the background.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
args: The original arguments dictionary
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
A new dictionary with copied values for sensitive keys
|
|
29
|
+
"""
|
|
30
|
+
copied_args = {}
|
|
31
|
+
for key, value in args.items():
|
|
32
|
+
if key in BACKGROUND_HOOK_COPY_KEYS and value is not None:
|
|
33
|
+
try:
|
|
34
|
+
copied_args[key] = deepcopy(value)
|
|
35
|
+
except Exception:
|
|
36
|
+
# If deepcopy fails (e.g., for non-copyable objects), use the original
|
|
37
|
+
log_warning(f"Could not deepcopy {key} for background hook, using original reference")
|
|
38
|
+
copied_args[key] = value
|
|
39
|
+
else:
|
|
40
|
+
copied_args[key] = value
|
|
41
|
+
return copied_args
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def should_run_hook_in_background(hook: Callable[..., Any]) -> bool:
|
|
45
|
+
"""
|
|
46
|
+
Check if a hook function should run in background.
|
|
47
|
+
|
|
48
|
+
This checks for the _agno_run_in_background attribute set by the @hook decorator.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
hook: The hook function to check
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
True if the hook is decorated with @hook(run_in_background=True)
|
|
55
|
+
"""
|
|
56
|
+
return getattr(hook, HOOK_RUN_IN_BACKGROUND_ATTR, False)
|
|
57
|
+
|
|
6
58
|
|
|
7
|
-
def
|
|
8
|
-
hooks: Optional[Union[
|
|
59
|
+
def normalize_pre_hooks(
|
|
60
|
+
hooks: Optional[List[Union[Callable[..., Any], BaseGuardrail, "BaseEval"]]],
|
|
9
61
|
async_mode: bool = False,
|
|
10
62
|
) -> Optional[List[Callable[..., Any]]]:
|
|
11
|
-
"""Normalize hooks to a list format
|
|
63
|
+
"""Normalize pre-hooks to a list format.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
hooks: List of hook functions, guardrails, or eval instances
|
|
67
|
+
async_mode: Whether to use async versions of methods
|
|
68
|
+
"""
|
|
69
|
+
from agno.eval.base import BaseEval
|
|
70
|
+
|
|
12
71
|
result_hooks: List[Callable[..., Any]] = []
|
|
13
72
|
|
|
14
73
|
if hooks is not None:
|
|
@@ -18,6 +77,61 @@ def normalize_hooks(
|
|
|
18
77
|
result_hooks.append(hook.async_check)
|
|
19
78
|
else:
|
|
20
79
|
result_hooks.append(hook.check)
|
|
80
|
+
elif isinstance(hook, BaseEval):
|
|
81
|
+
# Extract pre_check method
|
|
82
|
+
method = hook.async_pre_check if async_mode else hook.pre_check
|
|
83
|
+
|
|
84
|
+
from functools import partial
|
|
85
|
+
|
|
86
|
+
wrapped = partial(method)
|
|
87
|
+
wrapped.__name__ = method.__name__ # type: ignore
|
|
88
|
+
setattr(wrapped, HOOK_RUN_IN_BACKGROUND_ATTR, getattr(hook, "run_in_background", False))
|
|
89
|
+
result_hooks.append(wrapped)
|
|
90
|
+
else:
|
|
91
|
+
# Check if the hook is async and used within sync methods
|
|
92
|
+
if not async_mode:
|
|
93
|
+
import asyncio
|
|
94
|
+
|
|
95
|
+
if asyncio.iscoroutinefunction(hook):
|
|
96
|
+
raise ValueError(
|
|
97
|
+
f"Cannot use {hook.__name__} (an async hook) with `run()`. Use `arun()` instead."
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
result_hooks.append(hook)
|
|
101
|
+
return result_hooks if result_hooks else None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def normalize_post_hooks(
|
|
105
|
+
hooks: Optional[List[Union[Callable[..., Any], BaseGuardrail, "BaseEval"]]],
|
|
106
|
+
async_mode: bool = False,
|
|
107
|
+
) -> Optional[List[Callable[..., Any]]]:
|
|
108
|
+
"""Normalize post-hooks to a list format.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
hooks: List of hook functions, guardrails, or eval instances
|
|
112
|
+
async_mode: Whether to use async versions of methods
|
|
113
|
+
"""
|
|
114
|
+
from agno.eval.base import BaseEval
|
|
115
|
+
|
|
116
|
+
result_hooks: List[Callable[..., Any]] = []
|
|
117
|
+
|
|
118
|
+
if hooks is not None:
|
|
119
|
+
for hook in hooks:
|
|
120
|
+
if isinstance(hook, BaseGuardrail):
|
|
121
|
+
if async_mode:
|
|
122
|
+
result_hooks.append(hook.async_check)
|
|
123
|
+
else:
|
|
124
|
+
result_hooks.append(hook.check)
|
|
125
|
+
elif isinstance(hook, BaseEval):
|
|
126
|
+
# Extract post_check method
|
|
127
|
+
method = hook.async_post_check if async_mode else hook.post_check # type: ignore[assignment]
|
|
128
|
+
|
|
129
|
+
from functools import partial
|
|
130
|
+
|
|
131
|
+
wrapped = partial(method)
|
|
132
|
+
wrapped.__name__ = method.__name__ # type: ignore
|
|
133
|
+
setattr(wrapped, HOOK_RUN_IN_BACKGROUND_ATTR, getattr(hook, "run_in_background", False))
|
|
134
|
+
result_hooks.append(wrapped)
|
|
21
135
|
else:
|
|
22
136
|
# Check if the hook is async and used within sync methods
|
|
23
137
|
if not async_mode:
|
agno/utils/http.py
CHANGED
|
@@ -10,6 +10,117 @@ logger = logging.getLogger(__name__)
|
|
|
10
10
|
DEFAULT_MAX_RETRIES = 3
|
|
11
11
|
DEFAULT_BACKOFF_FACTOR = 2 # Exponential backoff: 1, 2, 4, 8...
|
|
12
12
|
|
|
13
|
+
# Global httpx clients for resource efficiency
|
|
14
|
+
# These are shared across all models to reuse connection pools and avoid resource leaks.
|
|
15
|
+
# Consumers can override these at application startup using set_default_sync_client()
|
|
16
|
+
# and set_default_async_client() to customize limits, timeouts, proxies, etc.
|
|
17
|
+
_global_sync_client: Optional[httpx.Client] = None
|
|
18
|
+
_global_async_client: Optional[httpx.AsyncClient] = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_default_sync_client() -> httpx.Client:
|
|
22
|
+
"""Get or create the global synchronous httpx client.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
A singleton httpx.Client instance with default limits.
|
|
26
|
+
"""
|
|
27
|
+
global _global_sync_client
|
|
28
|
+
if _global_sync_client is None or _global_sync_client.is_closed:
|
|
29
|
+
_global_sync_client = httpx.Client(
|
|
30
|
+
limits=httpx.Limits(max_connections=1000, max_keepalive_connections=200), http2=True, follow_redirects=True
|
|
31
|
+
)
|
|
32
|
+
return _global_sync_client
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_default_async_client() -> httpx.AsyncClient:
|
|
36
|
+
"""Get or create the global asynchronous httpx client.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
A singleton httpx.AsyncClient instance with default limits.
|
|
40
|
+
"""
|
|
41
|
+
global _global_async_client
|
|
42
|
+
if _global_async_client is None or _global_async_client.is_closed:
|
|
43
|
+
_global_async_client = httpx.AsyncClient(
|
|
44
|
+
limits=httpx.Limits(max_connections=1000, max_keepalive_connections=200), http2=True, follow_redirects=True
|
|
45
|
+
)
|
|
46
|
+
return _global_async_client
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def close_sync_client() -> None:
|
|
50
|
+
"""Closes the global sync httpx client.
|
|
51
|
+
|
|
52
|
+
Should be called during application shutdown.
|
|
53
|
+
"""
|
|
54
|
+
global _global_sync_client
|
|
55
|
+
if _global_sync_client is not None and not _global_sync_client.is_closed:
|
|
56
|
+
_global_sync_client.close()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
async def aclose_default_clients() -> None:
|
|
60
|
+
"""Asynchronously close the global httpx clients.
|
|
61
|
+
|
|
62
|
+
Should be called during application shutdown in async contexts.
|
|
63
|
+
"""
|
|
64
|
+
global _global_sync_client, _global_async_client
|
|
65
|
+
if _global_sync_client is not None and not _global_sync_client.is_closed:
|
|
66
|
+
_global_sync_client.close()
|
|
67
|
+
if _global_async_client is not None and not _global_async_client.is_closed:
|
|
68
|
+
await _global_async_client.aclose()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def set_default_sync_client(client: httpx.Client) -> None:
|
|
72
|
+
"""Set the global synchronous httpx client.
|
|
73
|
+
|
|
74
|
+
IMPORTANT: Call before creating any model instances. Models cache clients on first use.
|
|
75
|
+
|
|
76
|
+
Allows consumers to override the default httpx client with custom configuration
|
|
77
|
+
(e.g., custom limits, timeouts, proxies, SSL verification, etc.).
|
|
78
|
+
This is useful at application startup to customize how all models connect.
|
|
79
|
+
|
|
80
|
+
Example:
|
|
81
|
+
>>> import httpx
|
|
82
|
+
>>> from agno.utils.http import set_default_sync_client
|
|
83
|
+
>>> custom_client = httpx.Client(
|
|
84
|
+
... limits=httpx.Limits(max_connections=500),
|
|
85
|
+
... timeout=httpx.Timeout(30.0),
|
|
86
|
+
... verify=False # for dev environments
|
|
87
|
+
... )
|
|
88
|
+
>>> set_default_sync_client(custom_client)
|
|
89
|
+
>>> # All models will now use this custom client
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
client: An httpx.Client instance to use as the global sync client.
|
|
93
|
+
"""
|
|
94
|
+
global _global_sync_client
|
|
95
|
+
_global_sync_client = client
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def set_default_async_client(client: httpx.AsyncClient) -> None:
|
|
99
|
+
"""Set the global asynchronous httpx client.
|
|
100
|
+
|
|
101
|
+
IMPORTANT: Call before creating any model instances. Models cache clients on first use.
|
|
102
|
+
|
|
103
|
+
Allows consumers to override the default async httpx client with custom configuration
|
|
104
|
+
(e.g., custom limits, timeouts, proxies, SSL verification, etc.).
|
|
105
|
+
This is useful at application startup to customize how all models connect.
|
|
106
|
+
|
|
107
|
+
Example:
|
|
108
|
+
>>> import httpx
|
|
109
|
+
>>> from agno.utils.http import set_default_async_client
|
|
110
|
+
>>> custom_client = httpx.AsyncClient(
|
|
111
|
+
... limits=httpx.Limits(max_connections=500),
|
|
112
|
+
... timeout=httpx.Timeout(30.0),
|
|
113
|
+
... verify=False # for dev environments
|
|
114
|
+
... )
|
|
115
|
+
>>> set_default_async_client(custom_client)
|
|
116
|
+
>>> # All models will now use this custom client
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
client: An httpx.AsyncClient instance to use as the global async client.
|
|
120
|
+
"""
|
|
121
|
+
global _global_async_client
|
|
122
|
+
_global_async_client = client
|
|
123
|
+
|
|
13
124
|
|
|
14
125
|
def fetch_with_retry(
|
|
15
126
|
url: str,
|
|
@@ -29,7 +140,7 @@ def fetch_with_retry(
|
|
|
29
140
|
logger.error(f"Failed to fetch {url} after {max_retries} attempts: {e}")
|
|
30
141
|
raise
|
|
31
142
|
wait_time = backoff_factor**attempt
|
|
32
|
-
logger.warning(
|
|
143
|
+
logger.warning("Connection error.")
|
|
33
144
|
sleep(wait_time)
|
|
34
145
|
except httpx.HTTPStatusError as e:
|
|
35
146
|
logger.error(f"HTTP error for {url}: {e.response.status_code} - {e.response.text}")
|
|
@@ -65,7 +176,7 @@ async def async_fetch_with_retry(
|
|
|
65
176
|
logger.error(f"Failed to fetch {url} after {max_retries} attempts: {e}")
|
|
66
177
|
raise
|
|
67
178
|
wait_time = backoff_factor**attempt
|
|
68
|
-
logger.warning(
|
|
179
|
+
logger.warning("Connection error.")
|
|
69
180
|
await asyncio.sleep(wait_time)
|
|
70
181
|
except httpx.HTTPStatusError as e:
|
|
71
182
|
logger.error(f"HTTP error for {url}: {e.response.status_code} - {e.response.text}")
|