agno 2.2.13__py3-none-any.whl → 2.4.3__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/__init__.py +6 -0
- agno/agent/agent.py +5252 -3145
- agno/agent/remote.py +525 -0
- agno/api/api.py +2 -0
- agno/client/__init__.py +3 -0
- agno/client/a2a/__init__.py +10 -0
- agno/client/a2a/client.py +554 -0
- agno/client/a2a/schemas.py +112 -0
- agno/client/a2a/utils.py +369 -0
- agno/client/os.py +2669 -0
- agno/compression/__init__.py +3 -0
- agno/compression/manager.py +247 -0
- agno/culture/manager.py +2 -2
- agno/db/base.py +927 -6
- agno/db/dynamo/dynamo.py +788 -2
- agno/db/dynamo/schemas.py +128 -0
- agno/db/dynamo/utils.py +26 -3
- agno/db/firestore/firestore.py +674 -50
- agno/db/firestore/schemas.py +41 -0
- agno/db/firestore/utils.py +25 -10
- agno/db/gcs_json/gcs_json_db.py +506 -3
- agno/db/gcs_json/utils.py +14 -2
- agno/db/in_memory/in_memory_db.py +203 -4
- agno/db/in_memory/utils.py +14 -2
- agno/db/json/json_db.py +498 -2
- agno/db/json/utils.py +14 -2
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/utils.py +19 -0
- agno/db/migrations/v1_to_v2.py +54 -16
- agno/db/migrations/versions/__init__.py +0 -0
- agno/db/migrations/versions/v2_3_0.py +977 -0
- agno/db/mongo/async_mongo.py +1013 -39
- agno/db/mongo/mongo.py +684 -4
- agno/db/mongo/schemas.py +48 -0
- agno/db/mongo/utils.py +17 -0
- agno/db/mysql/__init__.py +2 -1
- agno/db/mysql/async_mysql.py +2958 -0
- agno/db/mysql/mysql.py +722 -53
- agno/db/mysql/schemas.py +77 -11
- agno/db/mysql/utils.py +151 -8
- agno/db/postgres/async_postgres.py +1254 -137
- agno/db/postgres/postgres.py +2316 -93
- agno/db/postgres/schemas.py +153 -21
- agno/db/postgres/utils.py +22 -7
- agno/db/redis/redis.py +531 -3
- agno/db/redis/schemas.py +36 -0
- agno/db/redis/utils.py +31 -15
- agno/db/schemas/evals.py +1 -0
- agno/db/schemas/memory.py +20 -9
- agno/db/singlestore/schemas.py +70 -1
- agno/db/singlestore/singlestore.py +737 -74
- agno/db/singlestore/utils.py +13 -3
- agno/db/sqlite/async_sqlite.py +1069 -89
- agno/db/sqlite/schemas.py +133 -1
- agno/db/sqlite/sqlite.py +2203 -165
- agno/db/sqlite/utils.py +21 -11
- agno/db/surrealdb/models.py +25 -0
- agno/db/surrealdb/surrealdb.py +603 -1
- agno/db/utils.py +60 -0
- agno/eval/__init__.py +26 -3
- agno/eval/accuracy.py +25 -12
- agno/eval/agent_as_judge.py +871 -0
- agno/eval/base.py +29 -0
- agno/eval/performance.py +10 -4
- agno/eval/reliability.py +22 -13
- agno/eval/utils.py +2 -1
- agno/exceptions.py +42 -0
- agno/hooks/__init__.py +3 -0
- agno/hooks/decorator.py +164 -0
- agno/integrations/discord/client.py +13 -2
- agno/knowledge/__init__.py +4 -0
- agno/knowledge/chunking/code.py +90 -0
- agno/knowledge/chunking/document.py +65 -4
- agno/knowledge/chunking/fixed.py +4 -1
- agno/knowledge/chunking/markdown.py +102 -11
- agno/knowledge/chunking/recursive.py +2 -2
- agno/knowledge/chunking/semantic.py +130 -48
- agno/knowledge/chunking/strategy.py +18 -0
- agno/knowledge/embedder/azure_openai.py +0 -1
- agno/knowledge/embedder/google.py +1 -1
- agno/knowledge/embedder/mistral.py +1 -1
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/embedder/openai.py +16 -12
- agno/knowledge/filesystem.py +412 -0
- agno/knowledge/knowledge.py +4261 -1199
- agno/knowledge/protocol.py +134 -0
- agno/knowledge/reader/arxiv_reader.py +3 -2
- agno/knowledge/reader/base.py +9 -7
- agno/knowledge/reader/csv_reader.py +91 -42
- agno/knowledge/reader/docx_reader.py +9 -10
- agno/knowledge/reader/excel_reader.py +225 -0
- agno/knowledge/reader/field_labeled_csv_reader.py +38 -48
- agno/knowledge/reader/firecrawl_reader.py +3 -2
- agno/knowledge/reader/json_reader.py +16 -22
- agno/knowledge/reader/markdown_reader.py +15 -14
- agno/knowledge/reader/pdf_reader.py +33 -28
- agno/knowledge/reader/pptx_reader.py +9 -10
- agno/knowledge/reader/reader_factory.py +135 -1
- agno/knowledge/reader/s3_reader.py +8 -16
- agno/knowledge/reader/tavily_reader.py +3 -3
- agno/knowledge/reader/text_reader.py +15 -14
- agno/knowledge/reader/utils/__init__.py +17 -0
- agno/knowledge/reader/utils/spreadsheet.py +114 -0
- agno/knowledge/reader/web_search_reader.py +8 -65
- agno/knowledge/reader/website_reader.py +16 -13
- agno/knowledge/reader/wikipedia_reader.py +36 -3
- agno/knowledge/reader/youtube_reader.py +3 -2
- agno/knowledge/remote_content/__init__.py +33 -0
- agno/knowledge/remote_content/config.py +266 -0
- agno/knowledge/remote_content/remote_content.py +105 -17
- agno/knowledge/utils.py +76 -22
- agno/learn/__init__.py +71 -0
- agno/learn/config.py +463 -0
- agno/learn/curate.py +185 -0
- agno/learn/machine.py +725 -0
- agno/learn/schemas.py +1114 -0
- agno/learn/stores/__init__.py +38 -0
- agno/learn/stores/decision_log.py +1156 -0
- agno/learn/stores/entity_memory.py +3275 -0
- agno/learn/stores/learned_knowledge.py +1583 -0
- agno/learn/stores/protocol.py +117 -0
- agno/learn/stores/session_context.py +1217 -0
- agno/learn/stores/user_memory.py +1495 -0
- agno/learn/stores/user_profile.py +1220 -0
- agno/learn/utils.py +209 -0
- agno/media.py +22 -6
- agno/memory/__init__.py +14 -1
- agno/memory/manager.py +223 -8
- 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 +434 -59
- agno/models/aws/bedrock.py +121 -20
- agno/models/aws/claude.py +131 -274
- agno/models/azure/ai_foundry.py +10 -6
- agno/models/azure/openai_chat.py +33 -10
- agno/models/base.py +1162 -561
- agno/models/cerebras/cerebras.py +120 -24
- agno/models/cerebras/cerebras_openai.py +21 -2
- agno/models/cohere/chat.py +65 -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 +959 -89
- agno/models/google/utils.py +22 -0
- agno/models/groq/groq.py +48 -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 +88 -9
- agno/models/litellm/litellm_openai.py +18 -1
- agno/models/message.py +24 -5
- agno/models/meta/llama.py +40 -13
- agno/models/meta/llama_openai.py +22 -21
- agno/models/metrics.py +12 -0
- agno/models/mistral/mistral.py +8 -4
- agno/models/n1n/__init__.py +3 -0
- agno/models/n1n/n1n.py +57 -0
- agno/models/nebius/nebius.py +6 -7
- agno/models/nvidia/nvidia.py +20 -3
- agno/models/ollama/__init__.py +2 -0
- agno/models/ollama/chat.py +17 -6
- agno/models/ollama/responses.py +100 -0
- agno/models/openai/__init__.py +2 -0
- agno/models/openai/chat.py +117 -26
- agno/models/openai/open_responses.py +46 -0
- agno/models/openai/responses.py +110 -32
- agno/models/openrouter/__init__.py +2 -0
- agno/models/openrouter/openrouter.py +67 -2
- agno/models/openrouter/responses.py +146 -0
- agno/models/perplexity/perplexity.py +19 -1
- agno/models/portkey/portkey.py +7 -6
- agno/models/requesty/requesty.py +19 -2
- agno/models/response.py +20 -2
- agno/models/sambanova/sambanova.py +20 -3
- agno/models/siliconflow/siliconflow.py +19 -2
- agno/models/together/together.py +20 -3
- agno/models/vercel/v0.py +20 -3
- agno/models/vertexai/claude.py +124 -4
- agno/models/vllm/vllm.py +19 -14
- agno/models/xai/xai.py +19 -2
- agno/os/app.py +467 -137
- agno/os/auth.py +253 -5
- agno/os/config.py +22 -0
- agno/os/interfaces/a2a/a2a.py +7 -6
- agno/os/interfaces/a2a/router.py +635 -26
- agno/os/interfaces/a2a/utils.py +32 -33
- agno/os/interfaces/agui/agui.py +5 -3
- agno/os/interfaces/agui/router.py +26 -16
- agno/os/interfaces/agui/utils.py +97 -57
- agno/os/interfaces/base.py +7 -7
- agno/os/interfaces/slack/router.py +16 -7
- agno/os/interfaces/slack/slack.py +7 -7
- agno/os/interfaces/whatsapp/router.py +35 -7
- agno/os/interfaces/whatsapp/security.py +3 -1
- agno/os/interfaces/whatsapp/whatsapp.py +11 -8
- agno/os/managers.py +326 -0
- agno/os/mcp.py +652 -79
- agno/os/middleware/__init__.py +4 -0
- agno/os/middleware/jwt.py +718 -115
- agno/os/middleware/trailing_slash.py +27 -0
- agno/os/router.py +105 -1558
- agno/os/routers/agents/__init__.py +3 -0
- agno/os/routers/agents/router.py +655 -0
- agno/os/routers/agents/schema.py +288 -0
- agno/os/routers/components/__init__.py +3 -0
- agno/os/routers/components/components.py +475 -0
- agno/os/routers/database.py +155 -0
- agno/os/routers/evals/evals.py +111 -18
- agno/os/routers/evals/schemas.py +38 -5
- agno/os/routers/evals/utils.py +80 -11
- agno/os/routers/health.py +3 -3
- agno/os/routers/knowledge/knowledge.py +284 -35
- agno/os/routers/knowledge/schemas.py +14 -2
- agno/os/routers/memory/memory.py +274 -11
- agno/os/routers/memory/schemas.py +44 -3
- agno/os/routers/metrics/metrics.py +30 -15
- agno/os/routers/metrics/schemas.py +10 -6
- agno/os/routers/registry/__init__.py +3 -0
- agno/os/routers/registry/registry.py +337 -0
- agno/os/routers/session/session.py +143 -14
- agno/os/routers/teams/__init__.py +3 -0
- agno/os/routers/teams/router.py +550 -0
- agno/os/routers/teams/schema.py +280 -0
- agno/os/routers/traces/__init__.py +3 -0
- agno/os/routers/traces/schemas.py +414 -0
- agno/os/routers/traces/traces.py +549 -0
- agno/os/routers/workflows/__init__.py +3 -0
- agno/os/routers/workflows/router.py +757 -0
- agno/os/routers/workflows/schema.py +139 -0
- agno/os/schema.py +157 -584
- agno/os/scopes.py +469 -0
- agno/os/settings.py +3 -0
- agno/os/utils.py +574 -185
- agno/reasoning/anthropic.py +85 -1
- agno/reasoning/azure_ai_foundry.py +93 -1
- agno/reasoning/deepseek.py +102 -2
- agno/reasoning/default.py +6 -7
- agno/reasoning/gemini.py +87 -3
- agno/reasoning/groq.py +109 -2
- agno/reasoning/helpers.py +6 -7
- agno/reasoning/manager.py +1238 -0
- agno/reasoning/ollama.py +93 -1
- agno/reasoning/openai.py +115 -1
- agno/reasoning/vertexai.py +85 -1
- agno/registry/__init__.py +3 -0
- agno/registry/registry.py +68 -0
- agno/remote/__init__.py +3 -0
- agno/remote/base.py +581 -0
- agno/run/__init__.py +2 -4
- agno/run/agent.py +134 -19
- agno/run/base.py +49 -1
- agno/run/cancel.py +65 -52
- agno/run/cancellation_management/__init__.py +9 -0
- agno/run/cancellation_management/base.py +78 -0
- agno/run/cancellation_management/in_memory_cancellation_manager.py +100 -0
- agno/run/cancellation_management/redis_cancellation_manager.py +236 -0
- agno/run/requirement.py +181 -0
- agno/run/team.py +111 -19
- agno/run/workflow.py +2 -1
- agno/session/agent.py +57 -92
- agno/session/summary.py +1 -1
- agno/session/team.py +62 -115
- agno/session/workflow.py +353 -57
- agno/skills/__init__.py +17 -0
- agno/skills/agent_skills.py +377 -0
- agno/skills/errors.py +32 -0
- agno/skills/loaders/__init__.py +4 -0
- agno/skills/loaders/base.py +27 -0
- agno/skills/loaders/local.py +216 -0
- agno/skills/skill.py +65 -0
- agno/skills/utils.py +107 -0
- agno/skills/validator.py +277 -0
- agno/table.py +10 -0
- agno/team/__init__.py +5 -1
- agno/team/remote.py +447 -0
- agno/team/team.py +3769 -2202
- agno/tools/brandfetch.py +27 -18
- agno/tools/browserbase.py +225 -16
- agno/tools/crawl4ai.py +3 -0
- agno/tools/duckduckgo.py +25 -71
- agno/tools/exa.py +0 -21
- agno/tools/file.py +14 -13
- agno/tools/file_generation.py +12 -6
- agno/tools/firecrawl.py +15 -7
- agno/tools/function.py +94 -113
- agno/tools/google_bigquery.py +11 -2
- agno/tools/google_drive.py +4 -3
- agno/tools/knowledge.py +9 -4
- agno/tools/mcp/mcp.py +301 -18
- agno/tools/mcp/multi_mcp.py +269 -14
- agno/tools/mem0.py +11 -10
- agno/tools/memory.py +47 -46
- agno/tools/mlx_transcribe.py +10 -7
- agno/tools/models/nebius.py +5 -5
- agno/tools/models_labs.py +20 -10
- agno/tools/nano_banana.py +151 -0
- agno/tools/parallel.py +0 -7
- agno/tools/postgres.py +76 -36
- agno/tools/python.py +14 -6
- agno/tools/reasoning.py +30 -23
- agno/tools/redshift.py +406 -0
- agno/tools/shopify.py +1519 -0
- agno/tools/spotify.py +919 -0
- agno/tools/tavily.py +4 -1
- agno/tools/toolkit.py +253 -18
- agno/tools/websearch.py +93 -0
- agno/tools/website.py +1 -1
- agno/tools/wikipedia.py +1 -1
- agno/tools/workflow.py +56 -48
- agno/tools/yfinance.py +12 -11
- agno/tracing/__init__.py +12 -0
- agno/tracing/exporter.py +161 -0
- agno/tracing/schemas.py +276 -0
- agno/tracing/setup.py +112 -0
- agno/utils/agent.py +251 -10
- agno/utils/cryptography.py +22 -0
- agno/utils/dttm.py +33 -0
- agno/utils/events.py +264 -7
- agno/utils/hooks.py +111 -3
- agno/utils/http.py +161 -2
- agno/utils/mcp.py +49 -8
- agno/utils/media.py +22 -1
- agno/utils/models/ai_foundry.py +9 -2
- agno/utils/models/claude.py +20 -5
- agno/utils/models/cohere.py +9 -2
- agno/utils/models/llama.py +9 -2
- agno/utils/models/mistral.py +4 -2
- agno/utils/os.py +0 -0
- agno/utils/print_response/agent.py +99 -16
- agno/utils/print_response/team.py +223 -24
- agno/utils/print_response/workflow.py +0 -2
- agno/utils/prompts.py +8 -6
- agno/utils/remote.py +23 -0
- agno/utils/response.py +1 -13
- agno/utils/string.py +91 -2
- agno/utils/team.py +62 -12
- agno/utils/tokens.py +657 -0
- agno/vectordb/base.py +15 -2
- agno/vectordb/cassandra/cassandra.py +1 -1
- agno/vectordb/chroma/__init__.py +2 -1
- agno/vectordb/chroma/chromadb.py +468 -23
- agno/vectordb/clickhouse/clickhousedb.py +1 -1
- agno/vectordb/couchbase/couchbase.py +6 -2
- agno/vectordb/lancedb/lance_db.py +7 -38
- agno/vectordb/lightrag/lightrag.py +7 -6
- agno/vectordb/milvus/milvus.py +118 -84
- agno/vectordb/mongodb/__init__.py +2 -1
- agno/vectordb/mongodb/mongodb.py +14 -31
- agno/vectordb/pgvector/pgvector.py +120 -66
- agno/vectordb/pineconedb/pineconedb.py +2 -19
- agno/vectordb/qdrant/__init__.py +2 -1
- agno/vectordb/qdrant/qdrant.py +33 -56
- agno/vectordb/redis/__init__.py +2 -1
- agno/vectordb/redis/redisdb.py +19 -31
- agno/vectordb/singlestore/singlestore.py +17 -9
- agno/vectordb/surrealdb/surrealdb.py +2 -38
- agno/vectordb/weaviate/__init__.py +2 -1
- agno/vectordb/weaviate/weaviate.py +7 -3
- agno/workflow/__init__.py +5 -1
- agno/workflow/agent.py +2 -2
- agno/workflow/condition.py +12 -10
- agno/workflow/loop.py +28 -9
- agno/workflow/parallel.py +21 -13
- agno/workflow/remote.py +362 -0
- agno/workflow/router.py +12 -9
- agno/workflow/step.py +261 -36
- agno/workflow/steps.py +12 -8
- agno/workflow/types.py +40 -77
- agno/workflow/workflow.py +939 -213
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/METADATA +134 -181
- agno-2.4.3.dist-info/RECORD +677 -0
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/WHEEL +1 -1
- agno/tools/googlesearch.py +0 -98
- agno/tools/memori.py +0 -339
- agno-2.2.13.dist-info/RECORD +0 -575
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/licenses/LICENSE +0 -0
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/top_level.txt +0 -0
agno/tools/workflow.py
CHANGED
|
@@ -4,6 +4,7 @@ from typing import Any, Dict, Optional
|
|
|
4
4
|
|
|
5
5
|
from pydantic import BaseModel, Field
|
|
6
6
|
|
|
7
|
+
from agno.run import RunContext
|
|
7
8
|
from agno.tools import Toolkit
|
|
8
9
|
from agno.utils.log import log_debug, log_error
|
|
9
10
|
from agno.workflow.workflow import Workflow, WorkflowRunOutput
|
|
@@ -65,7 +66,7 @@ class WorkflowTools(Toolkit):
|
|
|
65
66
|
else:
|
|
66
67
|
self.register(self.analyze, name="analyze")
|
|
67
68
|
|
|
68
|
-
def think(self,
|
|
69
|
+
def think(self, run_context: RunContext, thought: str) -> str:
|
|
69
70
|
"""Use this tool as a scratchpad to reason about the workflow execution, refine your approach, brainstorm workflow inputs, or revise your plan.
|
|
70
71
|
Call `Think` whenever you need to figure out what to do next, analyze the user's requirements, plan workflow inputs, or decide on execution strategy.
|
|
71
72
|
You should use this tool as frequently as needed.
|
|
@@ -76,14 +77,14 @@ class WorkflowTools(Toolkit):
|
|
|
76
77
|
log_debug(f"Workflow Thought: {thought}")
|
|
77
78
|
|
|
78
79
|
# Add the thought to the session state
|
|
79
|
-
if session_state is None:
|
|
80
|
-
session_state = {}
|
|
81
|
-
if "workflow_thoughts" not in session_state:
|
|
82
|
-
session_state["workflow_thoughts"] = []
|
|
83
|
-
session_state["workflow_thoughts"].append(thought)
|
|
80
|
+
if run_context.session_state is None:
|
|
81
|
+
run_context.session_state = {}
|
|
82
|
+
if "workflow_thoughts" not in run_context.session_state:
|
|
83
|
+
run_context.session_state["workflow_thoughts"] = []
|
|
84
|
+
run_context.session_state["workflow_thoughts"].append(thought)
|
|
84
85
|
|
|
85
86
|
# Return the full log of thoughts and the new thought
|
|
86
|
-
thoughts = "\n".join([f"- {t}" for t in session_state["workflow_thoughts"]])
|
|
87
|
+
thoughts = "\n".join([f"- {t}" for t in run_context.session_state["workflow_thoughts"]])
|
|
87
88
|
formatted_thoughts = dedent(
|
|
88
89
|
f"""Workflow Thoughts:
|
|
89
90
|
{thoughts}
|
|
@@ -94,7 +95,7 @@ class WorkflowTools(Toolkit):
|
|
|
94
95
|
log_error(f"Error recording workflow thought: {e}")
|
|
95
96
|
return f"Error recording workflow thought: {e}"
|
|
96
97
|
|
|
97
|
-
async def async_think(self,
|
|
98
|
+
async def async_think(self, run_context: RunContext, thought: str) -> str:
|
|
98
99
|
"""Use this tool as a scratchpad to reason about the workflow execution, refine your approach, brainstorm workflow inputs, or revise your plan.
|
|
99
100
|
Call `Think` whenever you need to figure out what to do next, analyze the user's requirements, plan workflow inputs, or decide on execution strategy.
|
|
100
101
|
You should use this tool as frequently as needed.
|
|
@@ -105,14 +106,14 @@ class WorkflowTools(Toolkit):
|
|
|
105
106
|
log_debug(f"Workflow Thought: {thought}")
|
|
106
107
|
|
|
107
108
|
# Add the thought to the session state
|
|
108
|
-
if session_state is None:
|
|
109
|
-
session_state = {}
|
|
110
|
-
if "workflow_thoughts" not in session_state:
|
|
111
|
-
session_state["workflow_thoughts"] = []
|
|
112
|
-
session_state["workflow_thoughts"].append(thought)
|
|
109
|
+
if run_context.session_state is None:
|
|
110
|
+
run_context.session_state = {}
|
|
111
|
+
if "workflow_thoughts" not in run_context.session_state:
|
|
112
|
+
run_context.session_state["workflow_thoughts"] = []
|
|
113
|
+
run_context.session_state["workflow_thoughts"].append(thought)
|
|
113
114
|
|
|
114
115
|
# Return the full log of thoughts and the new thought
|
|
115
|
-
thoughts = "\n".join([f"- {t}" for t in session_state["workflow_thoughts"]])
|
|
116
|
+
thoughts = "\n".join([f"- {t}" for t in run_context.session_state["workflow_thoughts"]])
|
|
116
117
|
formatted_thoughts = dedent(
|
|
117
118
|
f"""Workflow Thoughts:
|
|
118
119
|
{thoughts}
|
|
@@ -125,33 +126,37 @@ class WorkflowTools(Toolkit):
|
|
|
125
126
|
|
|
126
127
|
def run_workflow(
|
|
127
128
|
self,
|
|
128
|
-
|
|
129
|
+
run_context: RunContext,
|
|
129
130
|
input: RunWorkflowInput,
|
|
130
131
|
) -> str:
|
|
131
132
|
"""Use this tool to execute the workflow with the specified inputs and parameters.
|
|
132
133
|
After thinking through the requirements, use this tool to run the workflow with appropriate inputs.
|
|
134
|
+
|
|
133
135
|
Args:
|
|
134
|
-
|
|
136
|
+
input: The input data for the workflow.
|
|
135
137
|
"""
|
|
138
|
+
if isinstance(input, dict):
|
|
139
|
+
input = RunWorkflowInput.model_validate(input)
|
|
140
|
+
|
|
136
141
|
try:
|
|
137
142
|
log_debug(f"Running workflow with input: {input.input_data}")
|
|
138
143
|
|
|
139
|
-
|
|
140
|
-
|
|
144
|
+
if run_context.session_state is None:
|
|
145
|
+
run_context.session_state = {}
|
|
141
146
|
|
|
142
147
|
# Execute the workflow
|
|
143
148
|
result: WorkflowRunOutput = self.workflow.run(
|
|
144
149
|
input=input.input_data,
|
|
145
|
-
user_id=user_id,
|
|
146
|
-
session_id=session_id,
|
|
147
|
-
session_state=session_state,
|
|
150
|
+
user_id=run_context.user_id,
|
|
151
|
+
session_id=run_context.session_id,
|
|
152
|
+
session_state=run_context.session_state,
|
|
148
153
|
additional_data=input.additional_data,
|
|
149
154
|
)
|
|
150
155
|
|
|
151
|
-
if "workflow_results" not in session_state:
|
|
152
|
-
session_state["workflow_results"] = []
|
|
156
|
+
if "workflow_results" not in run_context.session_state:
|
|
157
|
+
run_context.session_state["workflow_results"] = []
|
|
153
158
|
|
|
154
|
-
session_state["workflow_results"].append(result.to_dict())
|
|
159
|
+
run_context.session_state["workflow_results"].append(result.to_dict())
|
|
155
160
|
|
|
156
161
|
return json.dumps(result.to_dict(), indent=2)
|
|
157
162
|
|
|
@@ -161,7 +166,7 @@ class WorkflowTools(Toolkit):
|
|
|
161
166
|
|
|
162
167
|
async def async_run_workflow(
|
|
163
168
|
self,
|
|
164
|
-
|
|
169
|
+
run_context: RunContext,
|
|
165
170
|
input: RunWorkflowInput,
|
|
166
171
|
) -> str:
|
|
167
172
|
"""Use this tool to execute the workflow with the specified inputs and parameters.
|
|
@@ -170,25 +175,28 @@ class WorkflowTools(Toolkit):
|
|
|
170
175
|
input_data: The input data for the workflow (use a `str` for a simple input)
|
|
171
176
|
additional_data: The additional data for the workflow. This is a dictionary of key-value pairs that will be passed to the workflow. E.g. {"topic": "food", "style": "Humour"}
|
|
172
177
|
"""
|
|
178
|
+
if isinstance(input, dict):
|
|
179
|
+
input = RunWorkflowInput.model_validate(input)
|
|
180
|
+
|
|
173
181
|
try:
|
|
174
182
|
log_debug(f"Running workflow with input: {input.input_data}")
|
|
175
183
|
|
|
176
|
-
|
|
177
|
-
|
|
184
|
+
if run_context.session_state is None:
|
|
185
|
+
run_context.session_state = {}
|
|
178
186
|
|
|
179
187
|
# Execute the workflow
|
|
180
188
|
result: WorkflowRunOutput = await self.workflow.arun(
|
|
181
189
|
input=input.input_data,
|
|
182
|
-
user_id=user_id,
|
|
183
|
-
session_id=session_id,
|
|
184
|
-
session_state=session_state,
|
|
190
|
+
user_id=run_context.user_id,
|
|
191
|
+
session_id=run_context.session_id,
|
|
192
|
+
session_state=run_context.session_state,
|
|
185
193
|
additional_data=input.additional_data,
|
|
186
194
|
)
|
|
187
195
|
|
|
188
|
-
if "workflow_results" not in session_state:
|
|
189
|
-
session_state["workflow_results"] = []
|
|
196
|
+
if "workflow_results" not in run_context.session_state:
|
|
197
|
+
run_context.session_state["workflow_results"] = []
|
|
190
198
|
|
|
191
|
-
session_state["workflow_results"].append(result.to_dict())
|
|
199
|
+
run_context.session_state["workflow_results"].append(result.to_dict())
|
|
192
200
|
|
|
193
201
|
return json.dumps(result.to_dict(), indent=2)
|
|
194
202
|
|
|
@@ -196,7 +204,7 @@ class WorkflowTools(Toolkit):
|
|
|
196
204
|
log_error(f"Error running workflow: {e}")
|
|
197
205
|
return f"Error running workflow: {e}"
|
|
198
206
|
|
|
199
|
-
def analyze(self,
|
|
207
|
+
def analyze(self, run_context: RunContext, analysis: str) -> str:
|
|
200
208
|
"""Use this tool to evaluate whether the workflow execution results are correct and sufficient.
|
|
201
209
|
If not, go back to "Think" or "Run" with refined inputs or parameters.
|
|
202
210
|
Args:
|
|
@@ -206,14 +214,14 @@ class WorkflowTools(Toolkit):
|
|
|
206
214
|
log_debug(f"Workflow Analysis: {analysis}")
|
|
207
215
|
|
|
208
216
|
# Add the analysis to the session state
|
|
209
|
-
if session_state is None:
|
|
210
|
-
session_state = {}
|
|
211
|
-
if "workflow_analysis" not in session_state:
|
|
212
|
-
session_state["workflow_analysis"] = []
|
|
213
|
-
session_state["workflow_analysis"].append(analysis)
|
|
217
|
+
if run_context.session_state is None:
|
|
218
|
+
run_context.session_state = {}
|
|
219
|
+
if "workflow_analysis" not in run_context.session_state:
|
|
220
|
+
run_context.session_state["workflow_analysis"] = []
|
|
221
|
+
run_context.session_state["workflow_analysis"].append(analysis)
|
|
214
222
|
|
|
215
223
|
# Return the full log of analysis and the new analysis
|
|
216
|
-
analysis_log = "\n".join([f"- {a}" for a in session_state["workflow_analysis"]])
|
|
224
|
+
analysis_log = "\n".join([f"- {a}" for a in run_context.session_state["workflow_analysis"]])
|
|
217
225
|
formatted_analysis = dedent(
|
|
218
226
|
f"""Workflow Analysis:
|
|
219
227
|
{analysis_log}
|
|
@@ -224,7 +232,7 @@ class WorkflowTools(Toolkit):
|
|
|
224
232
|
log_error(f"Error recording workflow analysis: {e}")
|
|
225
233
|
return f"Error recording workflow analysis: {e}"
|
|
226
234
|
|
|
227
|
-
async def async_analyze(self,
|
|
235
|
+
async def async_analyze(self, run_context: RunContext, analysis: str) -> str:
|
|
228
236
|
"""Use this tool to evaluate whether the workflow execution results are correct and sufficient.
|
|
229
237
|
If not, go back to "Think" or "Run" with refined inputs or parameters.
|
|
230
238
|
Args:
|
|
@@ -234,14 +242,14 @@ class WorkflowTools(Toolkit):
|
|
|
234
242
|
log_debug(f"Workflow Analysis: {analysis}")
|
|
235
243
|
|
|
236
244
|
# Add the analysis to the session state
|
|
237
|
-
if session_state is None:
|
|
238
|
-
session_state = {}
|
|
239
|
-
if "workflow_analysis" not in session_state:
|
|
240
|
-
session_state["workflow_analysis"] = []
|
|
241
|
-
session_state["workflow_analysis"].append(analysis)
|
|
245
|
+
if run_context.session_state is None:
|
|
246
|
+
run_context.session_state = {}
|
|
247
|
+
if "workflow_analysis" not in run_context.session_state:
|
|
248
|
+
run_context.session_state["workflow_analysis"] = []
|
|
249
|
+
run_context.session_state["workflow_analysis"].append(analysis)
|
|
242
250
|
|
|
243
251
|
# Return the full log of analysis and the new analysis
|
|
244
|
-
analysis_log = "\n".join([f"- {a}" for a in session_state["workflow_analysis"]])
|
|
252
|
+
analysis_log = "\n".join([f"- {a}" for a in run_context.session_state["workflow_analysis"]])
|
|
245
253
|
formatted_analysis = dedent(
|
|
246
254
|
f"""Workflow Analysis:
|
|
247
255
|
{analysis_log}
|
|
@@ -255,7 +263,7 @@ class WorkflowTools(Toolkit):
|
|
|
255
263
|
DEFAULT_INSTRUCTIONS = dedent("""\
|
|
256
264
|
You have access to the Think, Run Workflow, and Analyze tools that will help you execute workflows and analyze their results. Use these tools as frequently as needed to successfully complete workflow-based tasks.
|
|
257
265
|
## How to use the Think, Run Workflow, and Analyze tools:
|
|
258
|
-
|
|
266
|
+
|
|
259
267
|
1. **Think**
|
|
260
268
|
- Purpose: A scratchpad for planning workflow execution, brainstorming inputs, and refining your approach. You never reveal your "Think" content to the user.
|
|
261
269
|
- Usage: Call `think` whenever you need to figure out what workflow inputs to use, analyze requirements, or decide on execution strategy before (or after) you run the workflow.
|
agno/tools/yfinance.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
-
from typing import Any, List
|
|
2
|
+
from typing import Any, List, Optional
|
|
3
3
|
|
|
4
4
|
from agno.tools import Toolkit
|
|
5
5
|
from agno.utils.log import log_debug
|
|
@@ -18,6 +18,7 @@ class YFinanceTools(Toolkit):
|
|
|
18
18
|
|
|
19
19
|
def __init__(
|
|
20
20
|
self,
|
|
21
|
+
session: Optional[Any] = None,
|
|
21
22
|
**kwargs,
|
|
22
23
|
):
|
|
23
24
|
tools: List[Any] = [
|
|
@@ -31,7 +32,7 @@ class YFinanceTools(Toolkit):
|
|
|
31
32
|
self.get_technical_indicators,
|
|
32
33
|
self.get_historical_stock_prices,
|
|
33
34
|
]
|
|
34
|
-
|
|
35
|
+
self.session = session
|
|
35
36
|
super().__init__(name="yfinance_tools", tools=tools, **kwargs)
|
|
36
37
|
|
|
37
38
|
def get_current_stock_price(self, symbol: str) -> str:
|
|
@@ -46,7 +47,7 @@ class YFinanceTools(Toolkit):
|
|
|
46
47
|
"""
|
|
47
48
|
try:
|
|
48
49
|
log_debug(f"Fetching current price for {symbol}")
|
|
49
|
-
stock = yf.Ticker(symbol)
|
|
50
|
+
stock = yf.Ticker(symbol, session=self.session)
|
|
50
51
|
# Use "regularMarketPrice" for regular market hours, or "currentPrice" for pre/post market
|
|
51
52
|
current_price = stock.info.get("regularMarketPrice", stock.info.get("currentPrice"))
|
|
52
53
|
return f"{current_price:.4f}" if current_price else f"Could not fetch current price for {symbol}"
|
|
@@ -63,7 +64,7 @@ class YFinanceTools(Toolkit):
|
|
|
63
64
|
str: JSON containing company profile and overview.
|
|
64
65
|
"""
|
|
65
66
|
try:
|
|
66
|
-
company_info_full = yf.Ticker(symbol).info
|
|
67
|
+
company_info_full = yf.Ticker(symbol, session=self.session).info
|
|
67
68
|
if company_info_full is None:
|
|
68
69
|
return f"Could not fetch company info for {symbol}"
|
|
69
70
|
|
|
@@ -120,7 +121,7 @@ class YFinanceTools(Toolkit):
|
|
|
120
121
|
"""
|
|
121
122
|
try:
|
|
122
123
|
log_debug(f"Fetching historical prices for {symbol}")
|
|
123
|
-
stock = yf.Ticker(symbol)
|
|
124
|
+
stock = yf.Ticker(symbol, session=self.session)
|
|
124
125
|
historical_price = stock.history(period=period, interval=interval)
|
|
125
126
|
return historical_price.to_json(orient="index")
|
|
126
127
|
except Exception as e:
|
|
@@ -150,7 +151,7 @@ class YFinanceTools(Toolkit):
|
|
|
150
151
|
"""
|
|
151
152
|
try:
|
|
152
153
|
log_debug(f"Fetching fundamentals for {symbol}")
|
|
153
|
-
stock = yf.Ticker(symbol)
|
|
154
|
+
stock = yf.Ticker(symbol, session=self.session)
|
|
154
155
|
info = stock.info
|
|
155
156
|
fundamentals = {
|
|
156
157
|
"symbol": symbol,
|
|
@@ -181,7 +182,7 @@ class YFinanceTools(Toolkit):
|
|
|
181
182
|
"""
|
|
182
183
|
try:
|
|
183
184
|
log_debug(f"Fetching income statements for {symbol}")
|
|
184
|
-
stock = yf.Ticker(symbol)
|
|
185
|
+
stock = yf.Ticker(symbol, session=self.session)
|
|
185
186
|
financials = stock.financials
|
|
186
187
|
return financials.to_json(orient="index")
|
|
187
188
|
except Exception as e:
|
|
@@ -198,7 +199,7 @@ class YFinanceTools(Toolkit):
|
|
|
198
199
|
"""
|
|
199
200
|
try:
|
|
200
201
|
log_debug(f"Fetching key financial ratios for {symbol}")
|
|
201
|
-
stock = yf.Ticker(symbol)
|
|
202
|
+
stock = yf.Ticker(symbol, session=self.session)
|
|
202
203
|
key_ratios = stock.info
|
|
203
204
|
return json.dumps(key_ratios, indent=2)
|
|
204
205
|
except Exception as e:
|
|
@@ -215,7 +216,7 @@ class YFinanceTools(Toolkit):
|
|
|
215
216
|
"""
|
|
216
217
|
try:
|
|
217
218
|
log_debug(f"Fetching analyst recommendations for {symbol}")
|
|
218
|
-
stock = yf.Ticker(symbol)
|
|
219
|
+
stock = yf.Ticker(symbol, session=self.session)
|
|
219
220
|
recommendations = stock.recommendations
|
|
220
221
|
return recommendations.to_json(orient="index")
|
|
221
222
|
except Exception as e:
|
|
@@ -233,7 +234,7 @@ class YFinanceTools(Toolkit):
|
|
|
233
234
|
"""
|
|
234
235
|
try:
|
|
235
236
|
log_debug(f"Fetching company news for {symbol}")
|
|
236
|
-
news = yf.Ticker(symbol).news
|
|
237
|
+
news = yf.Ticker(symbol, session=self.session).news
|
|
237
238
|
return json.dumps(news[:num_stories], indent=2)
|
|
238
239
|
except Exception as e:
|
|
239
240
|
return f"Error fetching company news for {symbol}: {e}"
|
|
@@ -251,7 +252,7 @@ class YFinanceTools(Toolkit):
|
|
|
251
252
|
"""
|
|
252
253
|
try:
|
|
253
254
|
log_debug(f"Fetching technical indicators for {symbol}")
|
|
254
|
-
indicators = yf.Ticker(symbol).history(period=period)
|
|
255
|
+
indicators = yf.Ticker(symbol, session=self.session).history(period=period)
|
|
255
256
|
return indicators.to_json(orient="index")
|
|
256
257
|
except Exception as e:
|
|
257
258
|
return f"Error fetching technical indicators for {symbol}: {e}"
|
agno/tracing/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agno Tracing Module
|
|
3
|
+
|
|
4
|
+
This module provides OpenTelemetry-based tracing capabilities for Agno agents.
|
|
5
|
+
It uses the openinference-instrumentation-agno package for automatic instrumentation
|
|
6
|
+
and provides a custom DatabaseSpanExporter to store traces in the Agno database.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from agno.tracing.exporter import DatabaseSpanExporter
|
|
10
|
+
from agno.tracing.setup import setup_tracing
|
|
11
|
+
|
|
12
|
+
__all__ = ["DatabaseSpanExporter", "setup_tracing"]
|
agno/tracing/exporter.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom OpenTelemetry SpanExporter that writes traces to Agno database.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from collections import defaultdict
|
|
7
|
+
from typing import Dict, List, Sequence, Union
|
|
8
|
+
|
|
9
|
+
from opentelemetry.sdk.trace import ReadableSpan # type: ignore
|
|
10
|
+
from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult # type: ignore
|
|
11
|
+
|
|
12
|
+
from agno.db.base import AsyncBaseDb, BaseDb
|
|
13
|
+
from agno.remote.base import RemoteDb
|
|
14
|
+
from agno.tracing.schemas import Span, create_trace_from_spans
|
|
15
|
+
from agno.utils.log import logger
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DatabaseSpanExporter(SpanExporter):
|
|
19
|
+
"""Custom OpenTelemetry SpanExporter that writes to Agno database"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, db: Union[BaseDb, AsyncBaseDb, RemoteDb]):
|
|
22
|
+
"""
|
|
23
|
+
Initialize the DatabaseSpanExporter.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
db: Database instance (sync or async) to store traces
|
|
27
|
+
"""
|
|
28
|
+
self.db = db
|
|
29
|
+
self._shutdown = False
|
|
30
|
+
|
|
31
|
+
def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
|
|
32
|
+
"""
|
|
33
|
+
Export spans to the database.
|
|
34
|
+
|
|
35
|
+
This method:
|
|
36
|
+
1. Converts OpenTelemetry spans to Span objects
|
|
37
|
+
2. Groups spans by trace_id
|
|
38
|
+
3. Creates Trace records (one per trace_id)
|
|
39
|
+
4. Creates Span records (multiple per trace_id)
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
spans: Sequence of OpenTelemetry ReadableSpan objects
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
SpanExportResult indicating success or failure
|
|
46
|
+
"""
|
|
47
|
+
if self._shutdown:
|
|
48
|
+
logger.warning("DatabaseSpanExporter is shutdown, cannot export spans")
|
|
49
|
+
return SpanExportResult.FAILURE
|
|
50
|
+
|
|
51
|
+
if not spans:
|
|
52
|
+
return SpanExportResult.SUCCESS
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
# Convert OpenTelemetry spans to Span objects
|
|
56
|
+
converted_spans: List[Span] = []
|
|
57
|
+
for span in spans:
|
|
58
|
+
try:
|
|
59
|
+
converted_span = Span.from_otel_span(span)
|
|
60
|
+
converted_spans.append(converted_span)
|
|
61
|
+
except Exception as e:
|
|
62
|
+
logger.error(f"Failed to convert span {span.name}: {e}")
|
|
63
|
+
# Continue processing other spans
|
|
64
|
+
continue
|
|
65
|
+
|
|
66
|
+
if not converted_spans:
|
|
67
|
+
return SpanExportResult.SUCCESS
|
|
68
|
+
|
|
69
|
+
# Group spans by trace_id
|
|
70
|
+
spans_by_trace: Dict[str, List[Span]] = defaultdict(list)
|
|
71
|
+
for converted_span in converted_spans:
|
|
72
|
+
spans_by_trace[converted_span.trace_id].append(converted_span)
|
|
73
|
+
|
|
74
|
+
# Handle async DB
|
|
75
|
+
if isinstance(self.db, RemoteDb):
|
|
76
|
+
# Skipping remote database because it handles its own tracing
|
|
77
|
+
pass
|
|
78
|
+
elif isinstance(self.db, AsyncBaseDb):
|
|
79
|
+
self._export_async(spans_by_trace)
|
|
80
|
+
else:
|
|
81
|
+
# Synchronous database
|
|
82
|
+
self._export_sync(spans_by_trace)
|
|
83
|
+
|
|
84
|
+
return SpanExportResult.SUCCESS
|
|
85
|
+
except Exception as e:
|
|
86
|
+
logger.error(f"Failed to export spans to database: {e}", exc_info=True)
|
|
87
|
+
return SpanExportResult.FAILURE
|
|
88
|
+
|
|
89
|
+
def _export_sync(self, spans_by_trace: Dict[str, List[Span]]) -> None:
|
|
90
|
+
"""Export traces and spans to synchronous database"""
|
|
91
|
+
try:
|
|
92
|
+
# Create trace and span records for each trace
|
|
93
|
+
for trace_id, spans in spans_by_trace.items():
|
|
94
|
+
# Create trace record (aggregate of all spans)
|
|
95
|
+
trace = create_trace_from_spans(spans)
|
|
96
|
+
if trace:
|
|
97
|
+
self.db.upsert_trace(trace) # type: ignore
|
|
98
|
+
|
|
99
|
+
# Create span records
|
|
100
|
+
self.db.create_spans(spans) # type: ignore
|
|
101
|
+
|
|
102
|
+
except Exception as e:
|
|
103
|
+
logger.error(f"Failed to export sync traces: {e}", exc_info=True)
|
|
104
|
+
raise
|
|
105
|
+
|
|
106
|
+
def _export_async(self, spans_by_trace: Dict[str, List[Span]]) -> None:
|
|
107
|
+
"""Handle async database export"""
|
|
108
|
+
try:
|
|
109
|
+
loop = asyncio.get_event_loop()
|
|
110
|
+
if loop.is_running():
|
|
111
|
+
# We're in an async context, schedule the coroutine
|
|
112
|
+
asyncio.create_task(self._do_async_export(spans_by_trace))
|
|
113
|
+
else:
|
|
114
|
+
# No running loop, run in new loop
|
|
115
|
+
loop.run_until_complete(self._do_async_export(spans_by_trace))
|
|
116
|
+
except RuntimeError:
|
|
117
|
+
# No event loop, create new one
|
|
118
|
+
try:
|
|
119
|
+
asyncio.run(self._do_async_export(spans_by_trace))
|
|
120
|
+
except Exception as e:
|
|
121
|
+
logger.error(f"Failed to export async traces: {e}", exc_info=True)
|
|
122
|
+
|
|
123
|
+
async def _do_async_export(self, spans_by_trace: Dict[str, List[Span]]) -> None:
|
|
124
|
+
"""Actually perform the async export"""
|
|
125
|
+
try:
|
|
126
|
+
# Create trace and span records for each trace
|
|
127
|
+
for trace_id, spans in spans_by_trace.items():
|
|
128
|
+
# Create trace record (aggregate of all spans)
|
|
129
|
+
trace = create_trace_from_spans(spans)
|
|
130
|
+
if trace:
|
|
131
|
+
create_trace_result = self.db.upsert_trace(trace) # type: ignore
|
|
132
|
+
if create_trace_result is not None:
|
|
133
|
+
await create_trace_result
|
|
134
|
+
|
|
135
|
+
# Create span records
|
|
136
|
+
create_spans_result = self.db.create_spans(spans) # type: ignore
|
|
137
|
+
if create_spans_result is not None:
|
|
138
|
+
await create_spans_result
|
|
139
|
+
|
|
140
|
+
except Exception as e:
|
|
141
|
+
logger.error(f"Failed to do async export: {e}", exc_info=True)
|
|
142
|
+
raise
|
|
143
|
+
|
|
144
|
+
def shutdown(self) -> None:
|
|
145
|
+
"""Shutdown the exporter"""
|
|
146
|
+
self._shutdown = True
|
|
147
|
+
logger.debug("DatabaseSpanExporter shutdown")
|
|
148
|
+
|
|
149
|
+
def force_flush(self, timeout_millis: int = 30000) -> bool:
|
|
150
|
+
"""
|
|
151
|
+
Force flush any pending spans.
|
|
152
|
+
|
|
153
|
+
Since we write immediately to the database, this is a no-op.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
timeout_millis: Timeout in milliseconds
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
True if flush was successful
|
|
160
|
+
"""
|
|
161
|
+
return True
|