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.
Files changed (383) hide show
  1. agno/agent/__init__.py +6 -0
  2. agno/agent/agent.py +5252 -3145
  3. agno/agent/remote.py +525 -0
  4. agno/api/api.py +2 -0
  5. agno/client/__init__.py +3 -0
  6. agno/client/a2a/__init__.py +10 -0
  7. agno/client/a2a/client.py +554 -0
  8. agno/client/a2a/schemas.py +112 -0
  9. agno/client/a2a/utils.py +369 -0
  10. agno/client/os.py +2669 -0
  11. agno/compression/__init__.py +3 -0
  12. agno/compression/manager.py +247 -0
  13. agno/culture/manager.py +2 -2
  14. agno/db/base.py +927 -6
  15. agno/db/dynamo/dynamo.py +788 -2
  16. agno/db/dynamo/schemas.py +128 -0
  17. agno/db/dynamo/utils.py +26 -3
  18. agno/db/firestore/firestore.py +674 -50
  19. agno/db/firestore/schemas.py +41 -0
  20. agno/db/firestore/utils.py +25 -10
  21. agno/db/gcs_json/gcs_json_db.py +506 -3
  22. agno/db/gcs_json/utils.py +14 -2
  23. agno/db/in_memory/in_memory_db.py +203 -4
  24. agno/db/in_memory/utils.py +14 -2
  25. agno/db/json/json_db.py +498 -2
  26. agno/db/json/utils.py +14 -2
  27. agno/db/migrations/manager.py +199 -0
  28. agno/db/migrations/utils.py +19 -0
  29. agno/db/migrations/v1_to_v2.py +54 -16
  30. agno/db/migrations/versions/__init__.py +0 -0
  31. agno/db/migrations/versions/v2_3_0.py +977 -0
  32. agno/db/mongo/async_mongo.py +1013 -39
  33. agno/db/mongo/mongo.py +684 -4
  34. agno/db/mongo/schemas.py +48 -0
  35. agno/db/mongo/utils.py +17 -0
  36. agno/db/mysql/__init__.py +2 -1
  37. agno/db/mysql/async_mysql.py +2958 -0
  38. agno/db/mysql/mysql.py +722 -53
  39. agno/db/mysql/schemas.py +77 -11
  40. agno/db/mysql/utils.py +151 -8
  41. agno/db/postgres/async_postgres.py +1254 -137
  42. agno/db/postgres/postgres.py +2316 -93
  43. agno/db/postgres/schemas.py +153 -21
  44. agno/db/postgres/utils.py +22 -7
  45. agno/db/redis/redis.py +531 -3
  46. agno/db/redis/schemas.py +36 -0
  47. agno/db/redis/utils.py +31 -15
  48. agno/db/schemas/evals.py +1 -0
  49. agno/db/schemas/memory.py +20 -9
  50. agno/db/singlestore/schemas.py +70 -1
  51. agno/db/singlestore/singlestore.py +737 -74
  52. agno/db/singlestore/utils.py +13 -3
  53. agno/db/sqlite/async_sqlite.py +1069 -89
  54. agno/db/sqlite/schemas.py +133 -1
  55. agno/db/sqlite/sqlite.py +2203 -165
  56. agno/db/sqlite/utils.py +21 -11
  57. agno/db/surrealdb/models.py +25 -0
  58. agno/db/surrealdb/surrealdb.py +603 -1
  59. agno/db/utils.py +60 -0
  60. agno/eval/__init__.py +26 -3
  61. agno/eval/accuracy.py +25 -12
  62. agno/eval/agent_as_judge.py +871 -0
  63. agno/eval/base.py +29 -0
  64. agno/eval/performance.py +10 -4
  65. agno/eval/reliability.py +22 -13
  66. agno/eval/utils.py +2 -1
  67. agno/exceptions.py +42 -0
  68. agno/hooks/__init__.py +3 -0
  69. agno/hooks/decorator.py +164 -0
  70. agno/integrations/discord/client.py +13 -2
  71. agno/knowledge/__init__.py +4 -0
  72. agno/knowledge/chunking/code.py +90 -0
  73. agno/knowledge/chunking/document.py +65 -4
  74. agno/knowledge/chunking/fixed.py +4 -1
  75. agno/knowledge/chunking/markdown.py +102 -11
  76. agno/knowledge/chunking/recursive.py +2 -2
  77. agno/knowledge/chunking/semantic.py +130 -48
  78. agno/knowledge/chunking/strategy.py +18 -0
  79. agno/knowledge/embedder/azure_openai.py +0 -1
  80. agno/knowledge/embedder/google.py +1 -1
  81. agno/knowledge/embedder/mistral.py +1 -1
  82. agno/knowledge/embedder/nebius.py +1 -1
  83. agno/knowledge/embedder/openai.py +16 -12
  84. agno/knowledge/filesystem.py +412 -0
  85. agno/knowledge/knowledge.py +4261 -1199
  86. agno/knowledge/protocol.py +134 -0
  87. agno/knowledge/reader/arxiv_reader.py +3 -2
  88. agno/knowledge/reader/base.py +9 -7
  89. agno/knowledge/reader/csv_reader.py +91 -42
  90. agno/knowledge/reader/docx_reader.py +9 -10
  91. agno/knowledge/reader/excel_reader.py +225 -0
  92. agno/knowledge/reader/field_labeled_csv_reader.py +38 -48
  93. agno/knowledge/reader/firecrawl_reader.py +3 -2
  94. agno/knowledge/reader/json_reader.py +16 -22
  95. agno/knowledge/reader/markdown_reader.py +15 -14
  96. agno/knowledge/reader/pdf_reader.py +33 -28
  97. agno/knowledge/reader/pptx_reader.py +9 -10
  98. agno/knowledge/reader/reader_factory.py +135 -1
  99. agno/knowledge/reader/s3_reader.py +8 -16
  100. agno/knowledge/reader/tavily_reader.py +3 -3
  101. agno/knowledge/reader/text_reader.py +15 -14
  102. agno/knowledge/reader/utils/__init__.py +17 -0
  103. agno/knowledge/reader/utils/spreadsheet.py +114 -0
  104. agno/knowledge/reader/web_search_reader.py +8 -65
  105. agno/knowledge/reader/website_reader.py +16 -13
  106. agno/knowledge/reader/wikipedia_reader.py +36 -3
  107. agno/knowledge/reader/youtube_reader.py +3 -2
  108. agno/knowledge/remote_content/__init__.py +33 -0
  109. agno/knowledge/remote_content/config.py +266 -0
  110. agno/knowledge/remote_content/remote_content.py +105 -17
  111. agno/knowledge/utils.py +76 -22
  112. agno/learn/__init__.py +71 -0
  113. agno/learn/config.py +463 -0
  114. agno/learn/curate.py +185 -0
  115. agno/learn/machine.py +725 -0
  116. agno/learn/schemas.py +1114 -0
  117. agno/learn/stores/__init__.py +38 -0
  118. agno/learn/stores/decision_log.py +1156 -0
  119. agno/learn/stores/entity_memory.py +3275 -0
  120. agno/learn/stores/learned_knowledge.py +1583 -0
  121. agno/learn/stores/protocol.py +117 -0
  122. agno/learn/stores/session_context.py +1217 -0
  123. agno/learn/stores/user_memory.py +1495 -0
  124. agno/learn/stores/user_profile.py +1220 -0
  125. agno/learn/utils.py +209 -0
  126. agno/media.py +22 -6
  127. agno/memory/__init__.py +14 -1
  128. agno/memory/manager.py +223 -8
  129. agno/memory/strategies/__init__.py +15 -0
  130. agno/memory/strategies/base.py +66 -0
  131. agno/memory/strategies/summarize.py +196 -0
  132. agno/memory/strategies/types.py +37 -0
  133. agno/models/aimlapi/aimlapi.py +17 -0
  134. agno/models/anthropic/claude.py +434 -59
  135. agno/models/aws/bedrock.py +121 -20
  136. agno/models/aws/claude.py +131 -274
  137. agno/models/azure/ai_foundry.py +10 -6
  138. agno/models/azure/openai_chat.py +33 -10
  139. agno/models/base.py +1162 -561
  140. agno/models/cerebras/cerebras.py +120 -24
  141. agno/models/cerebras/cerebras_openai.py +21 -2
  142. agno/models/cohere/chat.py +65 -6
  143. agno/models/cometapi/cometapi.py +18 -1
  144. agno/models/dashscope/dashscope.py +2 -3
  145. agno/models/deepinfra/deepinfra.py +18 -1
  146. agno/models/deepseek/deepseek.py +69 -3
  147. agno/models/fireworks/fireworks.py +18 -1
  148. agno/models/google/gemini.py +959 -89
  149. agno/models/google/utils.py +22 -0
  150. agno/models/groq/groq.py +48 -18
  151. agno/models/huggingface/huggingface.py +17 -6
  152. agno/models/ibm/watsonx.py +16 -6
  153. agno/models/internlm/internlm.py +18 -1
  154. agno/models/langdb/langdb.py +13 -1
  155. agno/models/litellm/chat.py +88 -9
  156. agno/models/litellm/litellm_openai.py +18 -1
  157. agno/models/message.py +24 -5
  158. agno/models/meta/llama.py +40 -13
  159. agno/models/meta/llama_openai.py +22 -21
  160. agno/models/metrics.py +12 -0
  161. agno/models/mistral/mistral.py +8 -4
  162. agno/models/n1n/__init__.py +3 -0
  163. agno/models/n1n/n1n.py +57 -0
  164. agno/models/nebius/nebius.py +6 -7
  165. agno/models/nvidia/nvidia.py +20 -3
  166. agno/models/ollama/__init__.py +2 -0
  167. agno/models/ollama/chat.py +17 -6
  168. agno/models/ollama/responses.py +100 -0
  169. agno/models/openai/__init__.py +2 -0
  170. agno/models/openai/chat.py +117 -26
  171. agno/models/openai/open_responses.py +46 -0
  172. agno/models/openai/responses.py +110 -32
  173. agno/models/openrouter/__init__.py +2 -0
  174. agno/models/openrouter/openrouter.py +67 -2
  175. agno/models/openrouter/responses.py +146 -0
  176. agno/models/perplexity/perplexity.py +19 -1
  177. agno/models/portkey/portkey.py +7 -6
  178. agno/models/requesty/requesty.py +19 -2
  179. agno/models/response.py +20 -2
  180. agno/models/sambanova/sambanova.py +20 -3
  181. agno/models/siliconflow/siliconflow.py +19 -2
  182. agno/models/together/together.py +20 -3
  183. agno/models/vercel/v0.py +20 -3
  184. agno/models/vertexai/claude.py +124 -4
  185. agno/models/vllm/vllm.py +19 -14
  186. agno/models/xai/xai.py +19 -2
  187. agno/os/app.py +467 -137
  188. agno/os/auth.py +253 -5
  189. agno/os/config.py +22 -0
  190. agno/os/interfaces/a2a/a2a.py +7 -6
  191. agno/os/interfaces/a2a/router.py +635 -26
  192. agno/os/interfaces/a2a/utils.py +32 -33
  193. agno/os/interfaces/agui/agui.py +5 -3
  194. agno/os/interfaces/agui/router.py +26 -16
  195. agno/os/interfaces/agui/utils.py +97 -57
  196. agno/os/interfaces/base.py +7 -7
  197. agno/os/interfaces/slack/router.py +16 -7
  198. agno/os/interfaces/slack/slack.py +7 -7
  199. agno/os/interfaces/whatsapp/router.py +35 -7
  200. agno/os/interfaces/whatsapp/security.py +3 -1
  201. agno/os/interfaces/whatsapp/whatsapp.py +11 -8
  202. agno/os/managers.py +326 -0
  203. agno/os/mcp.py +652 -79
  204. agno/os/middleware/__init__.py +4 -0
  205. agno/os/middleware/jwt.py +718 -115
  206. agno/os/middleware/trailing_slash.py +27 -0
  207. agno/os/router.py +105 -1558
  208. agno/os/routers/agents/__init__.py +3 -0
  209. agno/os/routers/agents/router.py +655 -0
  210. agno/os/routers/agents/schema.py +288 -0
  211. agno/os/routers/components/__init__.py +3 -0
  212. agno/os/routers/components/components.py +475 -0
  213. agno/os/routers/database.py +155 -0
  214. agno/os/routers/evals/evals.py +111 -18
  215. agno/os/routers/evals/schemas.py +38 -5
  216. agno/os/routers/evals/utils.py +80 -11
  217. agno/os/routers/health.py +3 -3
  218. agno/os/routers/knowledge/knowledge.py +284 -35
  219. agno/os/routers/knowledge/schemas.py +14 -2
  220. agno/os/routers/memory/memory.py +274 -11
  221. agno/os/routers/memory/schemas.py +44 -3
  222. agno/os/routers/metrics/metrics.py +30 -15
  223. agno/os/routers/metrics/schemas.py +10 -6
  224. agno/os/routers/registry/__init__.py +3 -0
  225. agno/os/routers/registry/registry.py +337 -0
  226. agno/os/routers/session/session.py +143 -14
  227. agno/os/routers/teams/__init__.py +3 -0
  228. agno/os/routers/teams/router.py +550 -0
  229. agno/os/routers/teams/schema.py +280 -0
  230. agno/os/routers/traces/__init__.py +3 -0
  231. agno/os/routers/traces/schemas.py +414 -0
  232. agno/os/routers/traces/traces.py +549 -0
  233. agno/os/routers/workflows/__init__.py +3 -0
  234. agno/os/routers/workflows/router.py +757 -0
  235. agno/os/routers/workflows/schema.py +139 -0
  236. agno/os/schema.py +157 -584
  237. agno/os/scopes.py +469 -0
  238. agno/os/settings.py +3 -0
  239. agno/os/utils.py +574 -185
  240. agno/reasoning/anthropic.py +85 -1
  241. agno/reasoning/azure_ai_foundry.py +93 -1
  242. agno/reasoning/deepseek.py +102 -2
  243. agno/reasoning/default.py +6 -7
  244. agno/reasoning/gemini.py +87 -3
  245. agno/reasoning/groq.py +109 -2
  246. agno/reasoning/helpers.py +6 -7
  247. agno/reasoning/manager.py +1238 -0
  248. agno/reasoning/ollama.py +93 -1
  249. agno/reasoning/openai.py +115 -1
  250. agno/reasoning/vertexai.py +85 -1
  251. agno/registry/__init__.py +3 -0
  252. agno/registry/registry.py +68 -0
  253. agno/remote/__init__.py +3 -0
  254. agno/remote/base.py +581 -0
  255. agno/run/__init__.py +2 -4
  256. agno/run/agent.py +134 -19
  257. agno/run/base.py +49 -1
  258. agno/run/cancel.py +65 -52
  259. agno/run/cancellation_management/__init__.py +9 -0
  260. agno/run/cancellation_management/base.py +78 -0
  261. agno/run/cancellation_management/in_memory_cancellation_manager.py +100 -0
  262. agno/run/cancellation_management/redis_cancellation_manager.py +236 -0
  263. agno/run/requirement.py +181 -0
  264. agno/run/team.py +111 -19
  265. agno/run/workflow.py +2 -1
  266. agno/session/agent.py +57 -92
  267. agno/session/summary.py +1 -1
  268. agno/session/team.py +62 -115
  269. agno/session/workflow.py +353 -57
  270. agno/skills/__init__.py +17 -0
  271. agno/skills/agent_skills.py +377 -0
  272. agno/skills/errors.py +32 -0
  273. agno/skills/loaders/__init__.py +4 -0
  274. agno/skills/loaders/base.py +27 -0
  275. agno/skills/loaders/local.py +216 -0
  276. agno/skills/skill.py +65 -0
  277. agno/skills/utils.py +107 -0
  278. agno/skills/validator.py +277 -0
  279. agno/table.py +10 -0
  280. agno/team/__init__.py +5 -1
  281. agno/team/remote.py +447 -0
  282. agno/team/team.py +3769 -2202
  283. agno/tools/brandfetch.py +27 -18
  284. agno/tools/browserbase.py +225 -16
  285. agno/tools/crawl4ai.py +3 -0
  286. agno/tools/duckduckgo.py +25 -71
  287. agno/tools/exa.py +0 -21
  288. agno/tools/file.py +14 -13
  289. agno/tools/file_generation.py +12 -6
  290. agno/tools/firecrawl.py +15 -7
  291. agno/tools/function.py +94 -113
  292. agno/tools/google_bigquery.py +11 -2
  293. agno/tools/google_drive.py +4 -3
  294. agno/tools/knowledge.py +9 -4
  295. agno/tools/mcp/mcp.py +301 -18
  296. agno/tools/mcp/multi_mcp.py +269 -14
  297. agno/tools/mem0.py +11 -10
  298. agno/tools/memory.py +47 -46
  299. agno/tools/mlx_transcribe.py +10 -7
  300. agno/tools/models/nebius.py +5 -5
  301. agno/tools/models_labs.py +20 -10
  302. agno/tools/nano_banana.py +151 -0
  303. agno/tools/parallel.py +0 -7
  304. agno/tools/postgres.py +76 -36
  305. agno/tools/python.py +14 -6
  306. agno/tools/reasoning.py +30 -23
  307. agno/tools/redshift.py +406 -0
  308. agno/tools/shopify.py +1519 -0
  309. agno/tools/spotify.py +919 -0
  310. agno/tools/tavily.py +4 -1
  311. agno/tools/toolkit.py +253 -18
  312. agno/tools/websearch.py +93 -0
  313. agno/tools/website.py +1 -1
  314. agno/tools/wikipedia.py +1 -1
  315. agno/tools/workflow.py +56 -48
  316. agno/tools/yfinance.py +12 -11
  317. agno/tracing/__init__.py +12 -0
  318. agno/tracing/exporter.py +161 -0
  319. agno/tracing/schemas.py +276 -0
  320. agno/tracing/setup.py +112 -0
  321. agno/utils/agent.py +251 -10
  322. agno/utils/cryptography.py +22 -0
  323. agno/utils/dttm.py +33 -0
  324. agno/utils/events.py +264 -7
  325. agno/utils/hooks.py +111 -3
  326. agno/utils/http.py +161 -2
  327. agno/utils/mcp.py +49 -8
  328. agno/utils/media.py +22 -1
  329. agno/utils/models/ai_foundry.py +9 -2
  330. agno/utils/models/claude.py +20 -5
  331. agno/utils/models/cohere.py +9 -2
  332. agno/utils/models/llama.py +9 -2
  333. agno/utils/models/mistral.py +4 -2
  334. agno/utils/os.py +0 -0
  335. agno/utils/print_response/agent.py +99 -16
  336. agno/utils/print_response/team.py +223 -24
  337. agno/utils/print_response/workflow.py +0 -2
  338. agno/utils/prompts.py +8 -6
  339. agno/utils/remote.py +23 -0
  340. agno/utils/response.py +1 -13
  341. agno/utils/string.py +91 -2
  342. agno/utils/team.py +62 -12
  343. agno/utils/tokens.py +657 -0
  344. agno/vectordb/base.py +15 -2
  345. agno/vectordb/cassandra/cassandra.py +1 -1
  346. agno/vectordb/chroma/__init__.py +2 -1
  347. agno/vectordb/chroma/chromadb.py +468 -23
  348. agno/vectordb/clickhouse/clickhousedb.py +1 -1
  349. agno/vectordb/couchbase/couchbase.py +6 -2
  350. agno/vectordb/lancedb/lance_db.py +7 -38
  351. agno/vectordb/lightrag/lightrag.py +7 -6
  352. agno/vectordb/milvus/milvus.py +118 -84
  353. agno/vectordb/mongodb/__init__.py +2 -1
  354. agno/vectordb/mongodb/mongodb.py +14 -31
  355. agno/vectordb/pgvector/pgvector.py +120 -66
  356. agno/vectordb/pineconedb/pineconedb.py +2 -19
  357. agno/vectordb/qdrant/__init__.py +2 -1
  358. agno/vectordb/qdrant/qdrant.py +33 -56
  359. agno/vectordb/redis/__init__.py +2 -1
  360. agno/vectordb/redis/redisdb.py +19 -31
  361. agno/vectordb/singlestore/singlestore.py +17 -9
  362. agno/vectordb/surrealdb/surrealdb.py +2 -38
  363. agno/vectordb/weaviate/__init__.py +2 -1
  364. agno/vectordb/weaviate/weaviate.py +7 -3
  365. agno/workflow/__init__.py +5 -1
  366. agno/workflow/agent.py +2 -2
  367. agno/workflow/condition.py +12 -10
  368. agno/workflow/loop.py +28 -9
  369. agno/workflow/parallel.py +21 -13
  370. agno/workflow/remote.py +362 -0
  371. agno/workflow/router.py +12 -9
  372. agno/workflow/step.py +261 -36
  373. agno/workflow/steps.py +12 -8
  374. agno/workflow/types.py +40 -77
  375. agno/workflow/workflow.py +939 -213
  376. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/METADATA +134 -181
  377. agno-2.4.3.dist-info/RECORD +677 -0
  378. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/WHEEL +1 -1
  379. agno/tools/googlesearch.py +0 -98
  380. agno/tools/memori.py +0 -339
  381. agno-2.2.13.dist-info/RECORD +0 -575
  382. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/licenses/LICENSE +0 -0
  383. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,377 @@
1
+ import json
2
+ import subprocess
3
+ from pathlib import Path
4
+ from typing import Dict, List, Optional
5
+
6
+ from agno.skills.errors import SkillValidationError
7
+ from agno.skills.loaders.base import SkillLoader
8
+ from agno.skills.skill import Skill
9
+ from agno.skills.utils import is_safe_path, read_file_safe, run_script
10
+ from agno.tools.function import Function
11
+ from agno.utils.log import log_debug, log_warning
12
+
13
+
14
+ class Skills:
15
+ """Orchestrates skill loading and provides tools for agents to access skills.
16
+
17
+ The Skills class is responsible for:
18
+ 1. Loading skills from various sources (loaders)
19
+ 2. Providing methods to access loaded skills
20
+ 3. Generating tools for agents to use skills
21
+ 4. Creating system prompt snippets with available skills metadata
22
+
23
+ Args:
24
+ loaders: List of SkillLoader instances to load skills from.
25
+ """
26
+
27
+ def __init__(self, loaders: List[SkillLoader]):
28
+ self.loaders = loaders
29
+ self._skills: Dict[str, Skill] = {}
30
+ self._load_skills()
31
+
32
+ def _load_skills(self) -> None:
33
+ """Load skills from all loaders.
34
+
35
+ Raises:
36
+ SkillValidationError: If any skill fails validation.
37
+ """
38
+ for loader in self.loaders:
39
+ try:
40
+ skills = loader.load()
41
+ for skill in skills:
42
+ if skill.name in self._skills:
43
+ log_warning(f"Duplicate skill name '{skill.name}', overwriting with newer version")
44
+ self._skills[skill.name] = skill
45
+ except SkillValidationError:
46
+ raise # Re-raise validation errors as hard failures
47
+ except Exception as e:
48
+ log_warning(f"Error loading skills from {loader}: {e}")
49
+
50
+ log_debug(f"Loaded {len(self._skills)} total skills")
51
+
52
+ def reload(self) -> None:
53
+ """Reload skills from all loaders, clearing existing skills.
54
+
55
+ Raises:
56
+ SkillValidationError: If any skill fails validation.
57
+ """
58
+ self._skills.clear()
59
+ self._load_skills()
60
+
61
+ def get_skill(self, name: str) -> Optional[Skill]:
62
+ """Get a skill by name.
63
+
64
+ Args:
65
+ name: The name of the skill to retrieve.
66
+
67
+ Returns:
68
+ The Skill object if found, None otherwise.
69
+ """
70
+ return self._skills.get(name)
71
+
72
+ def get_all_skills(self) -> List[Skill]:
73
+ """Get all loaded skills.
74
+
75
+ Returns:
76
+ A list of all loaded Skill objects.
77
+ """
78
+ return list(self._skills.values())
79
+
80
+ def get_skill_names(self) -> List[str]:
81
+ """Get the names of all loaded skills.
82
+
83
+ Returns:
84
+ A list of skill names.
85
+ """
86
+ return list(self._skills.keys())
87
+
88
+ def get_system_prompt_snippet(self) -> str:
89
+ """Generate a system prompt snippet with available skills metadata.
90
+
91
+ This creates an XML-formatted snippet that provides the agent with
92
+ information about available skills without including the full instructions.
93
+
94
+ Returns:
95
+ An XML-formatted string with skills metadata.
96
+ """
97
+ if not self._skills:
98
+ return ""
99
+
100
+ lines = [
101
+ "<skills_system>",
102
+ "",
103
+ "## What are Skills?",
104
+ "Skills are packages of domain expertise that extend your capabilities. Each skill contains:",
105
+ "- **Instructions**: Detailed guidance on when and how to apply the skill",
106
+ "- **Scripts**: Executable code templates you can use or adapt",
107
+ "- **References**: Supporting documentation (guides, cheatsheets, examples)",
108
+ "",
109
+ "## IMPORTANT: How to Use Skills",
110
+ "**Skill names are NOT callable functions.** You cannot call a skill directly by its name.",
111
+ "Instead, you MUST use the provided skill access tools:",
112
+ "",
113
+ "1. `get_skill_instructions(skill_name)` - Load the full instructions for a skill",
114
+ "2. `get_skill_reference(skill_name, reference_path)` - Access specific documentation",
115
+ "3. `get_skill_script(skill_name, script_path, execute=False)` - Read or run scripts",
116
+ "",
117
+ "## Progressive Discovery Workflow",
118
+ "1. **Browse**: Review the skill summaries below to understand what's available",
119
+ "2. **Load**: When a task matches a skill, call `get_skill_instructions(skill_name)` first",
120
+ "3. **Reference**: Use `get_skill_reference` to access specific documentation as needed",
121
+ "4. **Scripts**: Use `get_skill_script` to read or execute scripts from a skill",
122
+ "",
123
+ "This approach ensures you only load detailed instructions when actually needed.",
124
+ "",
125
+ "## Available Skills",
126
+ ]
127
+ for skill in self._skills.values():
128
+ lines.append("<skill>")
129
+ lines.append(f" <name>{skill.name}</name>")
130
+ lines.append(f" <description>{skill.description}</description>")
131
+ if skill.scripts:
132
+ script_names = [s["name"] if isinstance(s, dict) else s for s in skill.scripts]
133
+ lines.append(f" <scripts>{', '.join(script_names)}</scripts>")
134
+ if skill.references:
135
+ ref_names = [r["name"] if isinstance(r, dict) else r for r in skill.references]
136
+ lines.append(f" <references>{', '.join(ref_names)}</references>")
137
+ lines.append("</skill>")
138
+ lines.append("")
139
+ lines.append("</skills_system>")
140
+
141
+ return "\n".join(lines)
142
+
143
+ def get_tools(self) -> List[Function]:
144
+ """Get the tools for accessing skills.
145
+
146
+ Returns:
147
+ A list of Function objects that agents can use to access skills.
148
+ """
149
+ tools: List[Function] = []
150
+
151
+ # Tool: get_skill_instructions
152
+ tools.append(
153
+ Function(
154
+ name="get_skill_instructions",
155
+ description="Load the full instructions for a skill. Use this when you need to follow a skill's guidance.",
156
+ entrypoint=self._get_skill_instructions,
157
+ )
158
+ )
159
+
160
+ # Tool: get_skill_reference
161
+ tools.append(
162
+ Function(
163
+ name="get_skill_reference",
164
+ description="Load a reference document from a skill's references. Use this to access detailed documentation.",
165
+ entrypoint=self._get_skill_reference,
166
+ )
167
+ )
168
+
169
+ # Tool: get_skill_script
170
+ tools.append(
171
+ Function(
172
+ name="get_skill_script",
173
+ description="Read or execute a script from a skill. Set execute=True to run the script and get output, or execute=False (default) to read the script content.",
174
+ entrypoint=self._get_skill_script,
175
+ )
176
+ )
177
+
178
+ return tools
179
+
180
+ def _get_skill_instructions(self, skill_name: str) -> str:
181
+ """Load the full instructions for a skill.
182
+
183
+ Args:
184
+ skill_name: The name of the skill to get instructions for.
185
+
186
+ Returns:
187
+ A JSON string with the skill's instructions and metadata.
188
+ """
189
+ skill = self.get_skill(skill_name)
190
+ if skill is None:
191
+ available = ", ".join(self.get_skill_names())
192
+ return json.dumps(
193
+ {
194
+ "error": f"Skill '{skill_name}' not found",
195
+ "available_skills": available,
196
+ }
197
+ )
198
+
199
+ return json.dumps(
200
+ {
201
+ "skill_name": skill.name,
202
+ "description": skill.description,
203
+ "instructions": skill.instructions,
204
+ "available_scripts": skill.scripts,
205
+ "available_references": skill.references,
206
+ }
207
+ )
208
+
209
+ def _get_skill_reference(self, skill_name: str, reference_path: str) -> str:
210
+ """Load a reference document from a skill.
211
+
212
+ Args:
213
+ skill_name: The name of the skill.
214
+ reference_path: The filename of the reference document.
215
+
216
+ Returns:
217
+ A JSON string with the reference content.
218
+ """
219
+ skill = self.get_skill(skill_name)
220
+ if skill is None:
221
+ available = ", ".join(self.get_skill_names())
222
+ return json.dumps(
223
+ {
224
+ "error": f"Skill '{skill_name}' not found",
225
+ "available_skills": available,
226
+ }
227
+ )
228
+
229
+ if reference_path not in skill.references:
230
+ return json.dumps(
231
+ {
232
+ "error": f"Reference '{reference_path}' not found in skill '{skill_name}'",
233
+ "available_references": skill.references,
234
+ }
235
+ )
236
+
237
+ # Validate path to prevent path traversal attacks
238
+ refs_dir = Path(skill.source_path) / "references"
239
+ if not is_safe_path(refs_dir, reference_path):
240
+ return json.dumps(
241
+ {
242
+ "error": f"Invalid reference path: '{reference_path}'",
243
+ "skill_name": skill_name,
244
+ }
245
+ )
246
+
247
+ # Load the reference file
248
+ ref_file = refs_dir / reference_path
249
+ try:
250
+ content = read_file_safe(ref_file)
251
+ return json.dumps(
252
+ {
253
+ "skill_name": skill_name,
254
+ "reference_path": reference_path,
255
+ "content": content,
256
+ }
257
+ )
258
+ except Exception as e:
259
+ return json.dumps(
260
+ {
261
+ "error": f"Error reading reference file: {e}",
262
+ "skill_name": skill_name,
263
+ "reference_path": reference_path,
264
+ }
265
+ )
266
+
267
+ def _get_skill_script(
268
+ self,
269
+ skill_name: str,
270
+ script_path: str,
271
+ execute: bool = False,
272
+ args: Optional[List[str]] = None,
273
+ timeout: int = 30,
274
+ ) -> str:
275
+ """Read or execute a script from a skill.
276
+
277
+ Args:
278
+ skill_name: The name of the skill.
279
+ script_path: The filename of the script.
280
+ execute: If True, execute the script. If False (default), return content.
281
+ args: Optional list of arguments to pass to the script (only used if execute=True).
282
+ timeout: Maximum execution time in seconds (default: 30, only used if execute=True).
283
+
284
+ Returns:
285
+ A JSON string with either the script content or execution results.
286
+ """
287
+ skill = self.get_skill(skill_name)
288
+ if skill is None:
289
+ available = ", ".join(self.get_skill_names())
290
+ return json.dumps(
291
+ {
292
+ "error": f"Skill '{skill_name}' not found",
293
+ "available_skills": available,
294
+ }
295
+ )
296
+
297
+ if script_path not in skill.scripts:
298
+ return json.dumps(
299
+ {
300
+ "error": f"Script '{script_path}' not found in skill '{skill_name}'",
301
+ "available_scripts": skill.scripts,
302
+ }
303
+ )
304
+
305
+ # Validate path to prevent path traversal attacks
306
+ scripts_dir = Path(skill.source_path) / "scripts"
307
+ if not is_safe_path(scripts_dir, script_path):
308
+ return json.dumps(
309
+ {
310
+ "error": f"Invalid script path: '{script_path}'",
311
+ "skill_name": skill_name,
312
+ }
313
+ )
314
+
315
+ script_file = scripts_dir / script_path
316
+
317
+ if not execute:
318
+ # Read mode: return script content
319
+ try:
320
+ content = read_file_safe(script_file)
321
+ return json.dumps(
322
+ {
323
+ "skill_name": skill_name,
324
+ "script_path": script_path,
325
+ "content": content,
326
+ }
327
+ )
328
+ except Exception as e:
329
+ return json.dumps(
330
+ {
331
+ "error": f"Error reading script file: {e}",
332
+ "skill_name": skill_name,
333
+ "script_path": script_path,
334
+ }
335
+ )
336
+
337
+ # Execute mode: run the script
338
+ try:
339
+ result = run_script(
340
+ script_path=script_file,
341
+ args=args,
342
+ timeout=timeout,
343
+ cwd=Path(skill.source_path),
344
+ )
345
+ return json.dumps(
346
+ {
347
+ "skill_name": skill_name,
348
+ "script_path": script_path,
349
+ "stdout": result.stdout,
350
+ "stderr": result.stderr,
351
+ "returncode": result.returncode,
352
+ }
353
+ )
354
+ except subprocess.TimeoutExpired:
355
+ return json.dumps(
356
+ {
357
+ "error": f"Script execution timed out after {timeout} seconds",
358
+ "skill_name": skill_name,
359
+ "script_path": script_path,
360
+ }
361
+ )
362
+ except FileNotFoundError as e:
363
+ return json.dumps(
364
+ {
365
+ "error": f"Interpreter or script not found: {e}",
366
+ "skill_name": skill_name,
367
+ "script_path": script_path,
368
+ }
369
+ )
370
+ except Exception as e:
371
+ return json.dumps(
372
+ {
373
+ "error": f"Error executing script: {e}",
374
+ "skill_name": skill_name,
375
+ "script_path": script_path,
376
+ }
377
+ )
agno/skills/errors.py ADDED
@@ -0,0 +1,32 @@
1
+ """Skill-related exceptions."""
2
+
3
+ from typing import List, Optional
4
+
5
+
6
+ class SkillError(Exception):
7
+ """Base exception for all skill-related errors."""
8
+
9
+ pass
10
+
11
+
12
+ class SkillParseError(SkillError):
13
+ """Raised when SKILL.md parsing fails."""
14
+
15
+ pass
16
+
17
+
18
+ class SkillValidationError(SkillError):
19
+ """Raised when skill validation fails.
20
+
21
+ Attributes:
22
+ errors: List of validation error messages.
23
+ """
24
+
25
+ def __init__(self, message: str, errors: Optional[List[str]] = None):
26
+ super().__init__(message)
27
+ self.errors = errors if errors is not None else [message]
28
+
29
+ def __str__(self) -> str:
30
+ if len(self.errors) == 1:
31
+ return self.errors[0]
32
+ return f"{len(self.errors)} validation errors: {'; '.join(self.errors)}"
@@ -0,0 +1,4 @@
1
+ from agno.skills.loaders.base import SkillLoader
2
+ from agno.skills.loaders.local import LocalSkills
3
+
4
+ __all__ = ["SkillLoader", "LocalSkills"]
@@ -0,0 +1,27 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import List
3
+
4
+ from agno.skills.skill import Skill
5
+
6
+
7
+ class SkillLoader(ABC):
8
+ """Abstract base class for skill loaders.
9
+
10
+ Skill loaders are responsible for loading skills from various sources
11
+ (local filesystem, GitHub, URLs, etc.) and returning them as Skill objects.
12
+
13
+ Subclasses must implement the `load()` method to define how skills
14
+ are loaded from their specific source.
15
+ """
16
+
17
+ @abstractmethod
18
+ def load(self) -> List[Skill]:
19
+ """Load skills from the source.
20
+
21
+ Returns:
22
+ A list of Skill objects loaded from the source.
23
+
24
+ Raises:
25
+ SkillLoadError: If there's an error loading skills from the source.
26
+ """
27
+ pass
@@ -0,0 +1,216 @@
1
+ import re
2
+ from pathlib import Path
3
+ from typing import Any, Dict, List, Optional, Tuple
4
+
5
+ from agno.skills.errors import SkillValidationError
6
+ from agno.skills.loaders.base import SkillLoader
7
+ from agno.skills.skill import Skill
8
+ from agno.skills.validator import validate_skill_directory
9
+ from agno.utils.log import log_debug, log_warning
10
+
11
+
12
+ class LocalSkills(SkillLoader):
13
+ """Loads skills from the local filesystem.
14
+
15
+ This loader can handle both:
16
+ 1. A single skill folder (contains SKILL.md)
17
+ 2. A directory containing multiple skill folders
18
+
19
+ Args:
20
+ path: Path to a skill folder or directory containing skill folders.
21
+ validate: Whether to validate skills against the Agent Skills spec.
22
+ If True (default), invalid skills will raise SkillValidationError.
23
+ """
24
+
25
+ def __init__(self, path: str, validate: bool = True):
26
+ self.path = Path(path).resolve()
27
+ self.validate = validate
28
+
29
+ def load(self) -> List[Skill]:
30
+ """Load skills from the local filesystem.
31
+
32
+ Returns:
33
+ A list of Skill objects loaded from the filesystem.
34
+
35
+ Raises:
36
+ FileNotFoundError: If the path doesn't exist.
37
+ """
38
+ if not self.path.exists():
39
+ raise FileNotFoundError(f"Skills path does not exist: {self.path}")
40
+
41
+ skills: List[Skill] = []
42
+
43
+ # Check if this is a single skill folder or a directory of skills
44
+ skill_md_path = self.path / "SKILL.md"
45
+ if skill_md_path.exists():
46
+ # Single skill folder
47
+ skill = self._load_skill_from_folder(self.path)
48
+ if skill:
49
+ skills.append(skill)
50
+ else:
51
+ # Directory of skill folders
52
+ for item in self.path.iterdir():
53
+ if item.is_dir() and not item.name.startswith("."):
54
+ skill_md = item / "SKILL.md"
55
+ if skill_md.exists():
56
+ skill = self._load_skill_from_folder(item)
57
+ if skill:
58
+ skills.append(skill)
59
+ else:
60
+ log_debug(f"Skipping directory without SKILL.md: {item}")
61
+
62
+ log_debug(f"Loaded {len(skills)} skills from {self.path}")
63
+ return skills
64
+
65
+ def _load_skill_from_folder(self, folder: Path) -> Optional[Skill]:
66
+ """Load a single skill from a folder.
67
+
68
+ Args:
69
+ folder: Path to the skill folder.
70
+
71
+ Returns:
72
+ A Skill object if successful, None if there's an error.
73
+
74
+ Raises:
75
+ SkillValidationError: If validation is enabled and the skill is invalid.
76
+ """
77
+ # Validate skill directory structure and content if validation is enabled
78
+ if self.validate:
79
+ errors = validate_skill_directory(folder)
80
+ if errors:
81
+ raise SkillValidationError(
82
+ f"Skill validation failed for '{folder.name}'",
83
+ errors=errors,
84
+ )
85
+
86
+ skill_md_path = folder / "SKILL.md"
87
+
88
+ try:
89
+ content = skill_md_path.read_text(encoding="utf-8")
90
+ frontmatter, instructions = self._parse_skill_md(content)
91
+
92
+ # Get skill name from the frontmatter or folder name
93
+ name = frontmatter.get("name", folder.name)
94
+ description = frontmatter.get("description", "")
95
+
96
+ # Get optional fields
97
+ license_info = frontmatter.get("license")
98
+ metadata = frontmatter.get("metadata")
99
+ compatibility = frontmatter.get("compatibility")
100
+ allowed_tools = frontmatter.get("allowed-tools")
101
+
102
+ # Discover scripts
103
+ scripts = self._discover_scripts(folder)
104
+
105
+ # Discover references
106
+ references = self._discover_references(folder)
107
+
108
+ return Skill(
109
+ name=name,
110
+ description=description,
111
+ instructions=instructions,
112
+ source_path=str(folder),
113
+ scripts=scripts,
114
+ references=references,
115
+ metadata=metadata,
116
+ license=license_info,
117
+ compatibility=compatibility,
118
+ allowed_tools=allowed_tools,
119
+ )
120
+
121
+ except SkillValidationError:
122
+ raise # Re-raise validation errors
123
+ except Exception as e:
124
+ log_warning(f"Error loading skill from {folder}: {e}")
125
+ return None
126
+
127
+ def _parse_skill_md(self, content: str) -> Tuple[Dict[str, Any], str]:
128
+ """Parse SKILL.md content into frontmatter and instructions.
129
+
130
+ Args:
131
+ content: The raw SKILL.md content.
132
+
133
+ Returns:
134
+ A tuple of (frontmatter_dict, instructions_body).
135
+ """
136
+ frontmatter: Dict[str, Any] = {}
137
+ instructions = content
138
+
139
+ # Check for YAML frontmatter (between --- delimiters)
140
+ frontmatter_match = re.match(r"^---\s*\n(.*?)\n---\s*\n?(.*)$", content, re.DOTALL)
141
+
142
+ if frontmatter_match:
143
+ frontmatter_text = frontmatter_match.group(1)
144
+ instructions = frontmatter_match.group(2).strip()
145
+
146
+ # Parse YAML frontmatter
147
+ try:
148
+ import yaml
149
+
150
+ frontmatter = yaml.safe_load(frontmatter_text) or {}
151
+ except ImportError:
152
+ # Fallback: simple key-value parsing if yaml not available
153
+ frontmatter = self._parse_simple_frontmatter(frontmatter_text)
154
+ except Exception as e:
155
+ log_warning(f"Error parsing YAML frontmatter: {e}")
156
+ frontmatter = self._parse_simple_frontmatter(frontmatter_text)
157
+
158
+ return frontmatter, instructions
159
+
160
+ def _parse_simple_frontmatter(self, text: str) -> Dict[str, Any]:
161
+ """Simple fallback frontmatter parser for basic key: value pairs.
162
+
163
+ Args:
164
+ text: The frontmatter text.
165
+
166
+ Returns:
167
+ A dictionary of parsed key-value pairs.
168
+ """
169
+ result: Dict[str, Any] = {}
170
+ for line in text.strip().split("\n"):
171
+ if ":" in line:
172
+ key, value = line.split(":", 1)
173
+ key = key.strip()
174
+ value = value.strip().strip('"').strip("'")
175
+ result[key] = value
176
+ return result
177
+
178
+ def _discover_scripts(self, folder: Path) -> List[str]:
179
+ """Discover script files in the scripts/ subdirectory.
180
+
181
+ Args:
182
+ folder: Path to the skill folder.
183
+
184
+ Returns:
185
+ A list of script filenames.
186
+ """
187
+ scripts_dir = folder / "scripts"
188
+ if not scripts_dir.exists() or not scripts_dir.is_dir():
189
+ return []
190
+
191
+ scripts: List[str] = []
192
+ for item in scripts_dir.iterdir():
193
+ if item.is_file() and not item.name.startswith("."):
194
+ scripts.append(item.name)
195
+
196
+ return sorted(scripts)
197
+
198
+ def _discover_references(self, folder: Path) -> List[str]:
199
+ """Discover reference files in the references/ subdirectory.
200
+
201
+ Args:
202
+ folder: Path to the skill folder.
203
+
204
+ Returns:
205
+ A list of reference filenames.
206
+ """
207
+ refs_dir = folder / "references"
208
+ if not refs_dir.exists() or not refs_dir.is_dir():
209
+ return []
210
+
211
+ references: List[str] = []
212
+ for item in refs_dir.iterdir():
213
+ if item.is_file() and not item.name.startswith("."):
214
+ references.append(item.name)
215
+
216
+ return sorted(references)