agno 0.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.
Files changed (723) hide show
  1. agno/__init__.py +8 -0
  2. agno/agent/__init__.py +44 -5
  3. agno/agent/agent.py +10531 -2975
  4. agno/api/agent.py +14 -53
  5. agno/api/api.py +7 -46
  6. agno/api/evals.py +22 -0
  7. agno/api/os.py +17 -0
  8. agno/api/routes.py +6 -25
  9. agno/api/schemas/__init__.py +9 -0
  10. agno/api/schemas/agent.py +6 -9
  11. agno/api/schemas/evals.py +16 -0
  12. agno/api/schemas/os.py +14 -0
  13. agno/api/schemas/team.py +10 -10
  14. agno/api/schemas/utils.py +21 -0
  15. agno/api/schemas/workflows.py +16 -0
  16. agno/api/settings.py +53 -0
  17. agno/api/team.py +22 -26
  18. agno/api/workflow.py +28 -0
  19. agno/cloud/aws/base.py +214 -0
  20. agno/cloud/aws/s3/__init__.py +2 -0
  21. agno/cloud/aws/s3/api_client.py +43 -0
  22. agno/cloud/aws/s3/bucket.py +195 -0
  23. agno/cloud/aws/s3/object.py +57 -0
  24. agno/compression/__init__.py +3 -0
  25. agno/compression/manager.py +247 -0
  26. agno/culture/__init__.py +3 -0
  27. agno/culture/manager.py +956 -0
  28. agno/db/__init__.py +24 -0
  29. agno/db/async_postgres/__init__.py +3 -0
  30. agno/db/base.py +946 -0
  31. agno/db/dynamo/__init__.py +3 -0
  32. agno/db/dynamo/dynamo.py +2781 -0
  33. agno/db/dynamo/schemas.py +442 -0
  34. agno/db/dynamo/utils.py +743 -0
  35. agno/db/firestore/__init__.py +3 -0
  36. agno/db/firestore/firestore.py +2379 -0
  37. agno/db/firestore/schemas.py +181 -0
  38. agno/db/firestore/utils.py +376 -0
  39. agno/db/gcs_json/__init__.py +3 -0
  40. agno/db/gcs_json/gcs_json_db.py +1791 -0
  41. agno/db/gcs_json/utils.py +228 -0
  42. agno/db/in_memory/__init__.py +3 -0
  43. agno/db/in_memory/in_memory_db.py +1312 -0
  44. agno/db/in_memory/utils.py +230 -0
  45. agno/db/json/__init__.py +3 -0
  46. agno/db/json/json_db.py +1777 -0
  47. agno/db/json/utils.py +230 -0
  48. agno/db/migrations/manager.py +199 -0
  49. agno/db/migrations/v1_to_v2.py +635 -0
  50. agno/db/migrations/versions/v2_3_0.py +938 -0
  51. agno/db/mongo/__init__.py +17 -0
  52. agno/db/mongo/async_mongo.py +2760 -0
  53. agno/db/mongo/mongo.py +2597 -0
  54. agno/db/mongo/schemas.py +119 -0
  55. agno/db/mongo/utils.py +276 -0
  56. agno/db/mysql/__init__.py +4 -0
  57. agno/db/mysql/async_mysql.py +2912 -0
  58. agno/db/mysql/mysql.py +2923 -0
  59. agno/db/mysql/schemas.py +186 -0
  60. agno/db/mysql/utils.py +488 -0
  61. agno/db/postgres/__init__.py +4 -0
  62. agno/db/postgres/async_postgres.py +2579 -0
  63. agno/db/postgres/postgres.py +2870 -0
  64. agno/db/postgres/schemas.py +187 -0
  65. agno/db/postgres/utils.py +442 -0
  66. agno/db/redis/__init__.py +3 -0
  67. agno/db/redis/redis.py +2141 -0
  68. agno/db/redis/schemas.py +159 -0
  69. agno/db/redis/utils.py +346 -0
  70. agno/db/schemas/__init__.py +4 -0
  71. agno/db/schemas/culture.py +120 -0
  72. agno/db/schemas/evals.py +34 -0
  73. agno/db/schemas/knowledge.py +40 -0
  74. agno/db/schemas/memory.py +61 -0
  75. agno/db/singlestore/__init__.py +3 -0
  76. agno/db/singlestore/schemas.py +179 -0
  77. agno/db/singlestore/singlestore.py +2877 -0
  78. agno/db/singlestore/utils.py +384 -0
  79. agno/db/sqlite/__init__.py +4 -0
  80. agno/db/sqlite/async_sqlite.py +2911 -0
  81. agno/db/sqlite/schemas.py +181 -0
  82. agno/db/sqlite/sqlite.py +2908 -0
  83. agno/db/sqlite/utils.py +429 -0
  84. agno/db/surrealdb/__init__.py +3 -0
  85. agno/db/surrealdb/metrics.py +292 -0
  86. agno/db/surrealdb/models.py +334 -0
  87. agno/db/surrealdb/queries.py +71 -0
  88. agno/db/surrealdb/surrealdb.py +1908 -0
  89. agno/db/surrealdb/utils.py +147 -0
  90. agno/db/utils.py +118 -0
  91. agno/eval/__init__.py +24 -0
  92. agno/eval/accuracy.py +666 -276
  93. agno/eval/agent_as_judge.py +861 -0
  94. agno/eval/base.py +29 -0
  95. agno/eval/performance.py +779 -0
  96. agno/eval/reliability.py +241 -62
  97. agno/eval/utils.py +120 -0
  98. agno/exceptions.py +143 -1
  99. agno/filters.py +354 -0
  100. agno/guardrails/__init__.py +6 -0
  101. agno/guardrails/base.py +19 -0
  102. agno/guardrails/openai.py +144 -0
  103. agno/guardrails/pii.py +94 -0
  104. agno/guardrails/prompt_injection.py +52 -0
  105. agno/hooks/__init__.py +3 -0
  106. agno/hooks/decorator.py +164 -0
  107. agno/integrations/discord/__init__.py +3 -0
  108. agno/integrations/discord/client.py +203 -0
  109. agno/knowledge/__init__.py +5 -1
  110. agno/{document → knowledge}/chunking/agentic.py +22 -14
  111. agno/{document → knowledge}/chunking/document.py +2 -2
  112. agno/{document → knowledge}/chunking/fixed.py +7 -6
  113. agno/knowledge/chunking/markdown.py +151 -0
  114. agno/{document → knowledge}/chunking/recursive.py +15 -3
  115. agno/knowledge/chunking/row.py +39 -0
  116. agno/knowledge/chunking/semantic.py +91 -0
  117. agno/knowledge/chunking/strategy.py +165 -0
  118. agno/knowledge/content.py +74 -0
  119. agno/knowledge/document/__init__.py +5 -0
  120. agno/{document → knowledge/document}/base.py +12 -2
  121. agno/knowledge/embedder/__init__.py +5 -0
  122. agno/knowledge/embedder/aws_bedrock.py +343 -0
  123. agno/knowledge/embedder/azure_openai.py +210 -0
  124. agno/{embedder → knowledge/embedder}/base.py +8 -0
  125. agno/knowledge/embedder/cohere.py +323 -0
  126. agno/knowledge/embedder/fastembed.py +62 -0
  127. agno/{embedder → knowledge/embedder}/fireworks.py +1 -1
  128. agno/knowledge/embedder/google.py +258 -0
  129. agno/knowledge/embedder/huggingface.py +94 -0
  130. agno/knowledge/embedder/jina.py +182 -0
  131. agno/knowledge/embedder/langdb.py +22 -0
  132. agno/knowledge/embedder/mistral.py +206 -0
  133. agno/knowledge/embedder/nebius.py +13 -0
  134. agno/knowledge/embedder/ollama.py +154 -0
  135. agno/knowledge/embedder/openai.py +195 -0
  136. agno/knowledge/embedder/sentence_transformer.py +63 -0
  137. agno/{embedder → knowledge/embedder}/together.py +1 -1
  138. agno/knowledge/embedder/vllm.py +262 -0
  139. agno/knowledge/embedder/voyageai.py +165 -0
  140. agno/knowledge/knowledge.py +3006 -0
  141. agno/knowledge/reader/__init__.py +7 -0
  142. agno/knowledge/reader/arxiv_reader.py +81 -0
  143. agno/knowledge/reader/base.py +95 -0
  144. agno/knowledge/reader/csv_reader.py +164 -0
  145. agno/knowledge/reader/docx_reader.py +82 -0
  146. agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
  147. agno/knowledge/reader/firecrawl_reader.py +201 -0
  148. agno/knowledge/reader/json_reader.py +88 -0
  149. agno/knowledge/reader/markdown_reader.py +137 -0
  150. agno/knowledge/reader/pdf_reader.py +431 -0
  151. agno/knowledge/reader/pptx_reader.py +101 -0
  152. agno/knowledge/reader/reader_factory.py +313 -0
  153. agno/knowledge/reader/s3_reader.py +89 -0
  154. agno/knowledge/reader/tavily_reader.py +193 -0
  155. agno/knowledge/reader/text_reader.py +127 -0
  156. agno/knowledge/reader/web_search_reader.py +325 -0
  157. agno/knowledge/reader/website_reader.py +455 -0
  158. agno/knowledge/reader/wikipedia_reader.py +91 -0
  159. agno/knowledge/reader/youtube_reader.py +78 -0
  160. agno/knowledge/remote_content/remote_content.py +88 -0
  161. agno/knowledge/reranker/__init__.py +3 -0
  162. agno/{reranker → knowledge/reranker}/base.py +1 -1
  163. agno/{reranker → knowledge/reranker}/cohere.py +2 -2
  164. agno/knowledge/reranker/infinity.py +195 -0
  165. agno/knowledge/reranker/sentence_transformer.py +54 -0
  166. agno/knowledge/types.py +39 -0
  167. agno/knowledge/utils.py +234 -0
  168. agno/media.py +439 -95
  169. agno/memory/__init__.py +16 -3
  170. agno/memory/manager.py +1474 -123
  171. agno/memory/strategies/__init__.py +15 -0
  172. agno/memory/strategies/base.py +66 -0
  173. agno/memory/strategies/summarize.py +196 -0
  174. agno/memory/strategies/types.py +37 -0
  175. agno/models/aimlapi/__init__.py +5 -0
  176. agno/models/aimlapi/aimlapi.py +62 -0
  177. agno/models/anthropic/__init__.py +4 -0
  178. agno/models/anthropic/claude.py +960 -496
  179. agno/models/aws/__init__.py +15 -0
  180. agno/models/aws/bedrock.py +686 -451
  181. agno/models/aws/claude.py +190 -183
  182. agno/models/azure/__init__.py +18 -1
  183. agno/models/azure/ai_foundry.py +489 -0
  184. agno/models/azure/openai_chat.py +89 -40
  185. agno/models/base.py +2477 -550
  186. agno/models/cerebras/__init__.py +12 -0
  187. agno/models/cerebras/cerebras.py +565 -0
  188. agno/models/cerebras/cerebras_openai.py +131 -0
  189. agno/models/cohere/__init__.py +4 -0
  190. agno/models/cohere/chat.py +306 -492
  191. agno/models/cometapi/__init__.py +5 -0
  192. agno/models/cometapi/cometapi.py +74 -0
  193. agno/models/dashscope/__init__.py +5 -0
  194. agno/models/dashscope/dashscope.py +90 -0
  195. agno/models/deepinfra/__init__.py +5 -0
  196. agno/models/deepinfra/deepinfra.py +45 -0
  197. agno/models/deepseek/__init__.py +4 -0
  198. agno/models/deepseek/deepseek.py +110 -9
  199. agno/models/fireworks/__init__.py +4 -0
  200. agno/models/fireworks/fireworks.py +19 -22
  201. agno/models/google/__init__.py +3 -7
  202. agno/models/google/gemini.py +1717 -662
  203. agno/models/google/utils.py +22 -0
  204. agno/models/groq/__init__.py +4 -0
  205. agno/models/groq/groq.py +391 -666
  206. agno/models/huggingface/__init__.py +4 -0
  207. agno/models/huggingface/huggingface.py +266 -538
  208. agno/models/ibm/__init__.py +5 -0
  209. agno/models/ibm/watsonx.py +432 -0
  210. agno/models/internlm/__init__.py +3 -0
  211. agno/models/internlm/internlm.py +20 -3
  212. agno/models/langdb/__init__.py +1 -0
  213. agno/models/langdb/langdb.py +60 -0
  214. agno/models/litellm/__init__.py +14 -0
  215. agno/models/litellm/chat.py +503 -0
  216. agno/models/litellm/litellm_openai.py +42 -0
  217. agno/models/llama_cpp/__init__.py +5 -0
  218. agno/models/llama_cpp/llama_cpp.py +22 -0
  219. agno/models/lmstudio/__init__.py +5 -0
  220. agno/models/lmstudio/lmstudio.py +25 -0
  221. agno/models/message.py +361 -39
  222. agno/models/meta/__init__.py +12 -0
  223. agno/models/meta/llama.py +502 -0
  224. agno/models/meta/llama_openai.py +79 -0
  225. agno/models/metrics.py +120 -0
  226. agno/models/mistral/__init__.py +4 -0
  227. agno/models/mistral/mistral.py +293 -393
  228. agno/models/nebius/__init__.py +3 -0
  229. agno/models/nebius/nebius.py +53 -0
  230. agno/models/nexus/__init__.py +3 -0
  231. agno/models/nexus/nexus.py +22 -0
  232. agno/models/nvidia/__init__.py +4 -0
  233. agno/models/nvidia/nvidia.py +22 -3
  234. agno/models/ollama/__init__.py +4 -2
  235. agno/models/ollama/chat.py +257 -492
  236. agno/models/openai/__init__.py +7 -0
  237. agno/models/openai/chat.py +725 -770
  238. agno/models/openai/like.py +16 -2
  239. agno/models/openai/responses.py +1121 -0
  240. agno/models/openrouter/__init__.py +4 -0
  241. agno/models/openrouter/openrouter.py +62 -5
  242. agno/models/perplexity/__init__.py +5 -0
  243. agno/models/perplexity/perplexity.py +203 -0
  244. agno/models/portkey/__init__.py +3 -0
  245. agno/models/portkey/portkey.py +82 -0
  246. agno/models/requesty/__init__.py +5 -0
  247. agno/models/requesty/requesty.py +69 -0
  248. agno/models/response.py +177 -7
  249. agno/models/sambanova/__init__.py +4 -0
  250. agno/models/sambanova/sambanova.py +23 -4
  251. agno/models/siliconflow/__init__.py +5 -0
  252. agno/models/siliconflow/siliconflow.py +42 -0
  253. agno/models/together/__init__.py +4 -0
  254. agno/models/together/together.py +21 -164
  255. agno/models/utils.py +266 -0
  256. agno/models/vercel/__init__.py +3 -0
  257. agno/models/vercel/v0.py +43 -0
  258. agno/models/vertexai/__init__.py +0 -1
  259. agno/models/vertexai/claude.py +190 -0
  260. agno/models/vllm/__init__.py +3 -0
  261. agno/models/vllm/vllm.py +83 -0
  262. agno/models/xai/__init__.py +2 -0
  263. agno/models/xai/xai.py +111 -7
  264. agno/os/__init__.py +3 -0
  265. agno/os/app.py +1027 -0
  266. agno/os/auth.py +244 -0
  267. agno/os/config.py +126 -0
  268. agno/os/interfaces/__init__.py +1 -0
  269. agno/os/interfaces/a2a/__init__.py +3 -0
  270. agno/os/interfaces/a2a/a2a.py +42 -0
  271. agno/os/interfaces/a2a/router.py +249 -0
  272. agno/os/interfaces/a2a/utils.py +924 -0
  273. agno/os/interfaces/agui/__init__.py +3 -0
  274. agno/os/interfaces/agui/agui.py +47 -0
  275. agno/os/interfaces/agui/router.py +147 -0
  276. agno/os/interfaces/agui/utils.py +574 -0
  277. agno/os/interfaces/base.py +25 -0
  278. agno/os/interfaces/slack/__init__.py +3 -0
  279. agno/os/interfaces/slack/router.py +148 -0
  280. agno/os/interfaces/slack/security.py +30 -0
  281. agno/os/interfaces/slack/slack.py +47 -0
  282. agno/os/interfaces/whatsapp/__init__.py +3 -0
  283. agno/os/interfaces/whatsapp/router.py +210 -0
  284. agno/os/interfaces/whatsapp/security.py +55 -0
  285. agno/os/interfaces/whatsapp/whatsapp.py +36 -0
  286. agno/os/mcp.py +293 -0
  287. agno/os/middleware/__init__.py +9 -0
  288. agno/os/middleware/jwt.py +797 -0
  289. agno/os/router.py +258 -0
  290. agno/os/routers/__init__.py +3 -0
  291. agno/os/routers/agents/__init__.py +3 -0
  292. agno/os/routers/agents/router.py +599 -0
  293. agno/os/routers/agents/schema.py +261 -0
  294. agno/os/routers/evals/__init__.py +3 -0
  295. agno/os/routers/evals/evals.py +450 -0
  296. agno/os/routers/evals/schemas.py +174 -0
  297. agno/os/routers/evals/utils.py +231 -0
  298. agno/os/routers/health.py +31 -0
  299. agno/os/routers/home.py +52 -0
  300. agno/os/routers/knowledge/__init__.py +3 -0
  301. agno/os/routers/knowledge/knowledge.py +1008 -0
  302. agno/os/routers/knowledge/schemas.py +178 -0
  303. agno/os/routers/memory/__init__.py +3 -0
  304. agno/os/routers/memory/memory.py +661 -0
  305. agno/os/routers/memory/schemas.py +88 -0
  306. agno/os/routers/metrics/__init__.py +3 -0
  307. agno/os/routers/metrics/metrics.py +190 -0
  308. agno/os/routers/metrics/schemas.py +47 -0
  309. agno/os/routers/session/__init__.py +3 -0
  310. agno/os/routers/session/session.py +997 -0
  311. agno/os/routers/teams/__init__.py +3 -0
  312. agno/os/routers/teams/router.py +512 -0
  313. agno/os/routers/teams/schema.py +257 -0
  314. agno/os/routers/traces/__init__.py +3 -0
  315. agno/os/routers/traces/schemas.py +414 -0
  316. agno/os/routers/traces/traces.py +499 -0
  317. agno/os/routers/workflows/__init__.py +3 -0
  318. agno/os/routers/workflows/router.py +624 -0
  319. agno/os/routers/workflows/schema.py +75 -0
  320. agno/os/schema.py +534 -0
  321. agno/os/scopes.py +469 -0
  322. agno/{playground → os}/settings.py +7 -15
  323. agno/os/utils.py +973 -0
  324. agno/reasoning/anthropic.py +80 -0
  325. agno/reasoning/azure_ai_foundry.py +67 -0
  326. agno/reasoning/deepseek.py +63 -0
  327. agno/reasoning/default.py +97 -0
  328. agno/reasoning/gemini.py +73 -0
  329. agno/reasoning/groq.py +71 -0
  330. agno/reasoning/helpers.py +24 -1
  331. agno/reasoning/ollama.py +67 -0
  332. agno/reasoning/openai.py +86 -0
  333. agno/reasoning/step.py +2 -1
  334. agno/reasoning/vertexai.py +76 -0
  335. agno/run/__init__.py +6 -0
  336. agno/run/agent.py +822 -0
  337. agno/run/base.py +247 -0
  338. agno/run/cancel.py +81 -0
  339. agno/run/requirement.py +181 -0
  340. agno/run/team.py +767 -0
  341. agno/run/workflow.py +708 -0
  342. agno/session/__init__.py +10 -0
  343. agno/session/agent.py +260 -0
  344. agno/session/summary.py +265 -0
  345. agno/session/team.py +342 -0
  346. agno/session/workflow.py +501 -0
  347. agno/table.py +10 -0
  348. agno/team/__init__.py +37 -0
  349. agno/team/team.py +9536 -0
  350. agno/tools/__init__.py +7 -0
  351. agno/tools/agentql.py +120 -0
  352. agno/tools/airflow.py +22 -12
  353. agno/tools/api.py +122 -0
  354. agno/tools/apify.py +276 -83
  355. agno/tools/{arxiv_toolkit.py → arxiv.py} +20 -12
  356. agno/tools/aws_lambda.py +28 -7
  357. agno/tools/aws_ses.py +66 -0
  358. agno/tools/baidusearch.py +11 -4
  359. agno/tools/bitbucket.py +292 -0
  360. agno/tools/brandfetch.py +213 -0
  361. agno/tools/bravesearch.py +106 -0
  362. agno/tools/brightdata.py +367 -0
  363. agno/tools/browserbase.py +209 -0
  364. agno/tools/calcom.py +32 -23
  365. agno/tools/calculator.py +24 -37
  366. agno/tools/cartesia.py +187 -0
  367. agno/tools/{clickup_tool.py → clickup.py} +17 -28
  368. agno/tools/confluence.py +91 -26
  369. agno/tools/crawl4ai.py +139 -43
  370. agno/tools/csv_toolkit.py +28 -22
  371. agno/tools/dalle.py +36 -22
  372. agno/tools/daytona.py +475 -0
  373. agno/tools/decorator.py +169 -14
  374. agno/tools/desi_vocal.py +23 -11
  375. agno/tools/discord.py +32 -29
  376. agno/tools/docker.py +716 -0
  377. agno/tools/duckdb.py +76 -81
  378. agno/tools/duckduckgo.py +43 -40
  379. agno/tools/e2b.py +703 -0
  380. agno/tools/eleven_labs.py +65 -54
  381. agno/tools/email.py +13 -5
  382. agno/tools/evm.py +129 -0
  383. agno/tools/exa.py +324 -42
  384. agno/tools/fal.py +39 -35
  385. agno/tools/file.py +196 -30
  386. agno/tools/file_generation.py +356 -0
  387. agno/tools/financial_datasets.py +288 -0
  388. agno/tools/firecrawl.py +108 -33
  389. agno/tools/function.py +960 -122
  390. agno/tools/giphy.py +34 -12
  391. agno/tools/github.py +1294 -97
  392. agno/tools/gmail.py +922 -0
  393. agno/tools/google_bigquery.py +117 -0
  394. agno/tools/google_drive.py +271 -0
  395. agno/tools/google_maps.py +253 -0
  396. agno/tools/googlecalendar.py +607 -107
  397. agno/tools/googlesheets.py +377 -0
  398. agno/tools/hackernews.py +20 -12
  399. agno/tools/jina.py +24 -14
  400. agno/tools/jira.py +48 -19
  401. agno/tools/knowledge.py +218 -0
  402. agno/tools/linear.py +82 -43
  403. agno/tools/linkup.py +58 -0
  404. agno/tools/local_file_system.py +15 -7
  405. agno/tools/lumalab.py +41 -26
  406. agno/tools/mcp/__init__.py +10 -0
  407. agno/tools/mcp/mcp.py +331 -0
  408. agno/tools/mcp/multi_mcp.py +347 -0
  409. agno/tools/mcp/params.py +24 -0
  410. agno/tools/mcp_toolbox.py +284 -0
  411. agno/tools/mem0.py +193 -0
  412. agno/tools/memory.py +419 -0
  413. agno/tools/mlx_transcribe.py +11 -9
  414. agno/tools/models/azure_openai.py +190 -0
  415. agno/tools/models/gemini.py +203 -0
  416. agno/tools/models/groq.py +158 -0
  417. agno/tools/models/morph.py +186 -0
  418. agno/tools/models/nebius.py +124 -0
  419. agno/tools/models_labs.py +163 -82
  420. agno/tools/moviepy_video.py +18 -13
  421. agno/tools/nano_banana.py +151 -0
  422. agno/tools/neo4j.py +134 -0
  423. agno/tools/newspaper.py +15 -4
  424. agno/tools/newspaper4k.py +19 -6
  425. agno/tools/notion.py +204 -0
  426. agno/tools/openai.py +181 -17
  427. agno/tools/openbb.py +27 -20
  428. agno/tools/opencv.py +321 -0
  429. agno/tools/openweather.py +233 -0
  430. agno/tools/oxylabs.py +385 -0
  431. agno/tools/pandas.py +25 -15
  432. agno/tools/parallel.py +314 -0
  433. agno/tools/postgres.py +238 -185
  434. agno/tools/pubmed.py +125 -13
  435. agno/tools/python.py +48 -35
  436. agno/tools/reasoning.py +283 -0
  437. agno/tools/reddit.py +207 -29
  438. agno/tools/redshift.py +406 -0
  439. agno/tools/replicate.py +69 -26
  440. agno/tools/resend.py +11 -6
  441. agno/tools/scrapegraph.py +179 -19
  442. agno/tools/searxng.py +23 -31
  443. agno/tools/serpapi.py +15 -10
  444. agno/tools/serper.py +255 -0
  445. agno/tools/shell.py +23 -12
  446. agno/tools/shopify.py +1519 -0
  447. agno/tools/slack.py +56 -14
  448. agno/tools/sleep.py +8 -6
  449. agno/tools/spider.py +35 -11
  450. agno/tools/spotify.py +919 -0
  451. agno/tools/sql.py +34 -19
  452. agno/tools/tavily.py +158 -8
  453. agno/tools/telegram.py +18 -8
  454. agno/tools/todoist.py +218 -0
  455. agno/tools/toolkit.py +134 -9
  456. agno/tools/trafilatura.py +388 -0
  457. agno/tools/trello.py +25 -28
  458. agno/tools/twilio.py +18 -9
  459. agno/tools/user_control_flow.py +78 -0
  460. agno/tools/valyu.py +228 -0
  461. agno/tools/visualization.py +467 -0
  462. agno/tools/webbrowser.py +28 -0
  463. agno/tools/webex.py +76 -0
  464. agno/tools/website.py +23 -19
  465. agno/tools/webtools.py +45 -0
  466. agno/tools/whatsapp.py +286 -0
  467. agno/tools/wikipedia.py +28 -19
  468. agno/tools/workflow.py +285 -0
  469. agno/tools/{twitter.py → x.py} +142 -46
  470. agno/tools/yfinance.py +41 -39
  471. agno/tools/youtube.py +34 -17
  472. agno/tools/zendesk.py +15 -5
  473. agno/tools/zep.py +454 -0
  474. agno/tools/zoom.py +86 -37
  475. agno/tracing/__init__.py +12 -0
  476. agno/tracing/exporter.py +157 -0
  477. agno/tracing/schemas.py +276 -0
  478. agno/tracing/setup.py +111 -0
  479. agno/utils/agent.py +938 -0
  480. agno/utils/audio.py +37 -1
  481. agno/utils/certs.py +27 -0
  482. agno/utils/code_execution.py +11 -0
  483. agno/utils/common.py +103 -20
  484. agno/utils/cryptography.py +22 -0
  485. agno/utils/dttm.py +33 -0
  486. agno/utils/events.py +700 -0
  487. agno/utils/functions.py +107 -37
  488. agno/utils/gemini.py +426 -0
  489. agno/utils/hooks.py +171 -0
  490. agno/utils/http.py +185 -0
  491. agno/utils/json_schema.py +159 -37
  492. agno/utils/knowledge.py +36 -0
  493. agno/utils/location.py +19 -0
  494. agno/utils/log.py +221 -8
  495. agno/utils/mcp.py +214 -0
  496. agno/utils/media.py +335 -14
  497. agno/utils/merge_dict.py +22 -1
  498. agno/utils/message.py +77 -2
  499. agno/utils/models/ai_foundry.py +50 -0
  500. agno/utils/models/claude.py +373 -0
  501. agno/utils/models/cohere.py +94 -0
  502. agno/utils/models/llama.py +85 -0
  503. agno/utils/models/mistral.py +100 -0
  504. agno/utils/models/openai_responses.py +140 -0
  505. agno/utils/models/schema_utils.py +153 -0
  506. agno/utils/models/watsonx.py +41 -0
  507. agno/utils/openai.py +257 -0
  508. agno/utils/pickle.py +1 -1
  509. agno/utils/pprint.py +124 -8
  510. agno/utils/print_response/agent.py +930 -0
  511. agno/utils/print_response/team.py +1914 -0
  512. agno/utils/print_response/workflow.py +1668 -0
  513. agno/utils/prompts.py +111 -0
  514. agno/utils/reasoning.py +108 -0
  515. agno/utils/response.py +163 -0
  516. agno/utils/serialize.py +32 -0
  517. agno/utils/shell.py +4 -4
  518. agno/utils/streamlit.py +487 -0
  519. agno/utils/string.py +204 -51
  520. agno/utils/team.py +139 -0
  521. agno/utils/timer.py +9 -2
  522. agno/utils/tokens.py +657 -0
  523. agno/utils/tools.py +19 -1
  524. agno/utils/whatsapp.py +305 -0
  525. agno/utils/yaml_io.py +3 -3
  526. agno/vectordb/__init__.py +2 -0
  527. agno/vectordb/base.py +87 -9
  528. agno/vectordb/cassandra/__init__.py +5 -1
  529. agno/vectordb/cassandra/cassandra.py +383 -27
  530. agno/vectordb/chroma/__init__.py +4 -0
  531. agno/vectordb/chroma/chromadb.py +748 -83
  532. agno/vectordb/clickhouse/__init__.py +7 -1
  533. agno/vectordb/clickhouse/clickhousedb.py +554 -53
  534. agno/vectordb/couchbase/__init__.py +3 -0
  535. agno/vectordb/couchbase/couchbase.py +1446 -0
  536. agno/vectordb/lancedb/__init__.py +5 -0
  537. agno/vectordb/lancedb/lance_db.py +730 -98
  538. agno/vectordb/langchaindb/__init__.py +5 -0
  539. agno/vectordb/langchaindb/langchaindb.py +163 -0
  540. agno/vectordb/lightrag/__init__.py +5 -0
  541. agno/vectordb/lightrag/lightrag.py +388 -0
  542. agno/vectordb/llamaindex/__init__.py +3 -0
  543. agno/vectordb/llamaindex/llamaindexdb.py +166 -0
  544. agno/vectordb/milvus/__init__.py +3 -0
  545. agno/vectordb/milvus/milvus.py +966 -78
  546. agno/vectordb/mongodb/__init__.py +9 -1
  547. agno/vectordb/mongodb/mongodb.py +1175 -172
  548. agno/vectordb/pgvector/__init__.py +8 -0
  549. agno/vectordb/pgvector/pgvector.py +599 -115
  550. agno/vectordb/pineconedb/__init__.py +5 -1
  551. agno/vectordb/pineconedb/pineconedb.py +406 -43
  552. agno/vectordb/qdrant/__init__.py +4 -0
  553. agno/vectordb/qdrant/qdrant.py +914 -61
  554. agno/vectordb/redis/__init__.py +9 -0
  555. agno/vectordb/redis/redisdb.py +682 -0
  556. agno/vectordb/singlestore/__init__.py +8 -1
  557. agno/vectordb/singlestore/singlestore.py +771 -0
  558. agno/vectordb/surrealdb/__init__.py +3 -0
  559. agno/vectordb/surrealdb/surrealdb.py +663 -0
  560. agno/vectordb/upstashdb/__init__.py +5 -0
  561. agno/vectordb/upstashdb/upstashdb.py +718 -0
  562. agno/vectordb/weaviate/__init__.py +8 -0
  563. agno/vectordb/weaviate/index.py +15 -0
  564. agno/vectordb/weaviate/weaviate.py +1009 -0
  565. agno/workflow/__init__.py +23 -1
  566. agno/workflow/agent.py +299 -0
  567. agno/workflow/condition.py +759 -0
  568. agno/workflow/loop.py +756 -0
  569. agno/workflow/parallel.py +853 -0
  570. agno/workflow/router.py +723 -0
  571. agno/workflow/step.py +1564 -0
  572. agno/workflow/steps.py +613 -0
  573. agno/workflow/types.py +556 -0
  574. agno/workflow/workflow.py +4327 -514
  575. agno-2.3.13.dist-info/METADATA +639 -0
  576. agno-2.3.13.dist-info/RECORD +613 -0
  577. {agno-0.1.2.dist-info → agno-2.3.13.dist-info}/WHEEL +1 -1
  578. agno-2.3.13.dist-info/licenses/LICENSE +201 -0
  579. agno/api/playground.py +0 -91
  580. agno/api/schemas/playground.py +0 -22
  581. agno/api/schemas/user.py +0 -22
  582. agno/api/schemas/workspace.py +0 -46
  583. agno/api/user.py +0 -160
  584. agno/api/workspace.py +0 -151
  585. agno/cli/auth_server.py +0 -118
  586. agno/cli/config.py +0 -275
  587. agno/cli/console.py +0 -88
  588. agno/cli/credentials.py +0 -23
  589. agno/cli/entrypoint.py +0 -571
  590. agno/cli/operator.py +0 -355
  591. agno/cli/settings.py +0 -85
  592. agno/cli/ws/ws_cli.py +0 -817
  593. agno/constants.py +0 -13
  594. agno/document/__init__.py +0 -1
  595. agno/document/chunking/semantic.py +0 -47
  596. agno/document/chunking/strategy.py +0 -31
  597. agno/document/reader/__init__.py +0 -1
  598. agno/document/reader/arxiv_reader.py +0 -41
  599. agno/document/reader/base.py +0 -22
  600. agno/document/reader/csv_reader.py +0 -84
  601. agno/document/reader/docx_reader.py +0 -46
  602. agno/document/reader/firecrawl_reader.py +0 -99
  603. agno/document/reader/json_reader.py +0 -43
  604. agno/document/reader/pdf_reader.py +0 -219
  605. agno/document/reader/s3/pdf_reader.py +0 -46
  606. agno/document/reader/s3/text_reader.py +0 -51
  607. agno/document/reader/text_reader.py +0 -41
  608. agno/document/reader/website_reader.py +0 -175
  609. agno/document/reader/youtube_reader.py +0 -50
  610. agno/embedder/__init__.py +0 -1
  611. agno/embedder/azure_openai.py +0 -86
  612. agno/embedder/cohere.py +0 -72
  613. agno/embedder/fastembed.py +0 -37
  614. agno/embedder/google.py +0 -73
  615. agno/embedder/huggingface.py +0 -54
  616. agno/embedder/mistral.py +0 -80
  617. agno/embedder/ollama.py +0 -57
  618. agno/embedder/openai.py +0 -74
  619. agno/embedder/sentence_transformer.py +0 -38
  620. agno/embedder/voyageai.py +0 -64
  621. agno/eval/perf.py +0 -201
  622. agno/file/__init__.py +0 -1
  623. agno/file/file.py +0 -16
  624. agno/file/local/csv.py +0 -32
  625. agno/file/local/txt.py +0 -19
  626. agno/infra/app.py +0 -240
  627. agno/infra/base.py +0 -144
  628. agno/infra/context.py +0 -20
  629. agno/infra/db_app.py +0 -52
  630. agno/infra/resource.py +0 -205
  631. agno/infra/resources.py +0 -55
  632. agno/knowledge/agent.py +0 -230
  633. agno/knowledge/arxiv.py +0 -22
  634. agno/knowledge/combined.py +0 -22
  635. agno/knowledge/csv.py +0 -28
  636. agno/knowledge/csv_url.py +0 -19
  637. agno/knowledge/document.py +0 -20
  638. agno/knowledge/docx.py +0 -30
  639. agno/knowledge/json.py +0 -28
  640. agno/knowledge/langchain.py +0 -71
  641. agno/knowledge/llamaindex.py +0 -66
  642. agno/knowledge/pdf.py +0 -28
  643. agno/knowledge/pdf_url.py +0 -26
  644. agno/knowledge/s3/base.py +0 -60
  645. agno/knowledge/s3/pdf.py +0 -21
  646. agno/knowledge/s3/text.py +0 -23
  647. agno/knowledge/text.py +0 -30
  648. agno/knowledge/website.py +0 -88
  649. agno/knowledge/wikipedia.py +0 -31
  650. agno/knowledge/youtube.py +0 -22
  651. agno/memory/agent.py +0 -392
  652. agno/memory/classifier.py +0 -104
  653. agno/memory/db/__init__.py +0 -1
  654. agno/memory/db/base.py +0 -42
  655. agno/memory/db/mongodb.py +0 -189
  656. agno/memory/db/postgres.py +0 -203
  657. agno/memory/db/sqlite.py +0 -193
  658. agno/memory/memory.py +0 -15
  659. agno/memory/row.py +0 -36
  660. agno/memory/summarizer.py +0 -192
  661. agno/memory/summary.py +0 -19
  662. agno/memory/workflow.py +0 -38
  663. agno/models/google/gemini_openai.py +0 -26
  664. agno/models/ollama/hermes.py +0 -221
  665. agno/models/ollama/tools.py +0 -362
  666. agno/models/vertexai/gemini.py +0 -595
  667. agno/playground/__init__.py +0 -3
  668. agno/playground/async_router.py +0 -421
  669. agno/playground/deploy.py +0 -249
  670. agno/playground/operator.py +0 -92
  671. agno/playground/playground.py +0 -91
  672. agno/playground/schemas.py +0 -76
  673. agno/playground/serve.py +0 -55
  674. agno/playground/sync_router.py +0 -405
  675. agno/reasoning/agent.py +0 -68
  676. agno/run/response.py +0 -112
  677. agno/storage/agent/__init__.py +0 -0
  678. agno/storage/agent/base.py +0 -38
  679. agno/storage/agent/dynamodb.py +0 -350
  680. agno/storage/agent/json.py +0 -92
  681. agno/storage/agent/mongodb.py +0 -228
  682. agno/storage/agent/postgres.py +0 -367
  683. agno/storage/agent/session.py +0 -79
  684. agno/storage/agent/singlestore.py +0 -303
  685. agno/storage/agent/sqlite.py +0 -357
  686. agno/storage/agent/yaml.py +0 -93
  687. agno/storage/workflow/__init__.py +0 -0
  688. agno/storage/workflow/base.py +0 -40
  689. agno/storage/workflow/mongodb.py +0 -233
  690. agno/storage/workflow/postgres.py +0 -366
  691. agno/storage/workflow/session.py +0 -60
  692. agno/storage/workflow/sqlite.py +0 -359
  693. agno/tools/googlesearch.py +0 -88
  694. agno/utils/defaults.py +0 -57
  695. agno/utils/filesystem.py +0 -39
  696. agno/utils/git.py +0 -52
  697. agno/utils/json_io.py +0 -30
  698. agno/utils/load_env.py +0 -19
  699. agno/utils/py_io.py +0 -19
  700. agno/utils/pyproject.py +0 -18
  701. agno/utils/resource_filter.py +0 -31
  702. agno/vectordb/singlestore/s2vectordb.py +0 -390
  703. agno/vectordb/singlestore/s2vectordb2.py +0 -355
  704. agno/workspace/__init__.py +0 -0
  705. agno/workspace/config.py +0 -325
  706. agno/workspace/enums.py +0 -6
  707. agno/workspace/helpers.py +0 -48
  708. agno/workspace/operator.py +0 -758
  709. agno/workspace/settings.py +0 -63
  710. agno-0.1.2.dist-info/LICENSE +0 -375
  711. agno-0.1.2.dist-info/METADATA +0 -502
  712. agno-0.1.2.dist-info/RECORD +0 -352
  713. agno-0.1.2.dist-info/entry_points.txt +0 -3
  714. /agno/{cli → db/migrations}/__init__.py +0 -0
  715. /agno/{cli/ws → db/migrations/versions}/__init__.py +0 -0
  716. /agno/{document/chunking/__init__.py → db/schemas/metrics.py} +0 -0
  717. /agno/{document/reader/s3 → integrations}/__init__.py +0 -0
  718. /agno/{file/local → knowledge/chunking}/__init__.py +0 -0
  719. /agno/{infra → knowledge/remote_content}/__init__.py +0 -0
  720. /agno/{knowledge/s3 → tools/models}/__init__.py +0 -0
  721. /agno/{reranker → utils/models}/__init__.py +0 -0
  722. /agno/{storage → utils/print_response}/__init__.py +0 -0
  723. {agno-0.1.2.dist-info → agno-2.3.13.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1008 @@
1
+ import json
2
+ import logging
3
+ import math
4
+ from typing import Any, Dict, List, Optional
5
+
6
+ from fastapi import APIRouter, BackgroundTasks, Depends, File, Form, HTTPException, Path, Query, UploadFile
7
+
8
+ from agno.knowledge.content import Content, FileData
9
+ from agno.knowledge.knowledge import Knowledge
10
+ from agno.knowledge.reader import ReaderFactory
11
+ from agno.knowledge.reader.base import Reader
12
+ from agno.knowledge.utils import get_all_chunkers_info, get_all_readers_info, get_content_types_to_readers_mapping
13
+ from agno.os.auth import get_authentication_dependency
14
+ from agno.os.routers.knowledge.schemas import (
15
+ ChunkerSchema,
16
+ ConfigResponseSchema,
17
+ ContentResponseSchema,
18
+ ContentStatus,
19
+ ContentStatusResponse,
20
+ ContentUpdateSchema,
21
+ ReaderSchema,
22
+ VectorDbSchema,
23
+ VectorSearchRequestSchema,
24
+ VectorSearchResult,
25
+ )
26
+ from agno.os.schema import (
27
+ BadRequestResponse,
28
+ InternalServerErrorResponse,
29
+ NotFoundResponse,
30
+ PaginatedResponse,
31
+ PaginationInfo,
32
+ SortOrder,
33
+ UnauthenticatedResponse,
34
+ ValidationErrorResponse,
35
+ )
36
+ from agno.os.settings import AgnoAPISettings
37
+ from agno.os.utils import get_knowledge_instance_by_db_id
38
+ from agno.utils.log import log_debug, log_info
39
+ from agno.utils.string import generate_id
40
+
41
+ logger = logging.getLogger(__name__)
42
+
43
+
44
+ def get_knowledge_router(
45
+ knowledge_instances: List[Knowledge], settings: AgnoAPISettings = AgnoAPISettings()
46
+ ) -> APIRouter:
47
+ """Create knowledge router with comprehensive OpenAPI documentation for content management endpoints."""
48
+ router = APIRouter(
49
+ dependencies=[Depends(get_authentication_dependency(settings))],
50
+ tags=["Knowledge"],
51
+ responses={
52
+ 400: {"description": "Bad Request", "model": BadRequestResponse},
53
+ 401: {"description": "Unauthorized", "model": UnauthenticatedResponse},
54
+ 404: {"description": "Not Found", "model": NotFoundResponse},
55
+ 422: {"description": "Validation Error", "model": ValidationErrorResponse},
56
+ 500: {"description": "Internal Server Error", "model": InternalServerErrorResponse},
57
+ },
58
+ )
59
+ return attach_routes(router=router, knowledge_instances=knowledge_instances)
60
+
61
+
62
+ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> APIRouter:
63
+ @router.post(
64
+ "/knowledge/content",
65
+ response_model=ContentResponseSchema,
66
+ status_code=202,
67
+ operation_id="upload_content",
68
+ summary="Upload Content",
69
+ description=(
70
+ "Upload content to the knowledge base. Supports file uploads, text content, or URLs. "
71
+ "Content is processed asynchronously in the background. Supports custom readers and chunking strategies."
72
+ ),
73
+ responses={
74
+ 202: {
75
+ "description": "Content upload accepted for processing",
76
+ "content": {
77
+ "application/json": {
78
+ "example": {
79
+ "id": "content-123",
80
+ "name": "example-document.pdf",
81
+ "description": "Sample document for processing",
82
+ "metadata": {"category": "documentation", "priority": "high"},
83
+ "status": "processing",
84
+ }
85
+ }
86
+ },
87
+ },
88
+ 400: {
89
+ "description": "Invalid request - malformed metadata or missing content",
90
+ "model": BadRequestResponse,
91
+ },
92
+ 422: {"description": "Validation error in form data", "model": ValidationErrorResponse},
93
+ },
94
+ )
95
+ async def upload_content(
96
+ background_tasks: BackgroundTasks,
97
+ name: Optional[str] = Form(None, description="Content name (auto-generated from file/URL if not provided)"),
98
+ description: Optional[str] = Form(None, description="Content description for context"),
99
+ url: Optional[str] = Form(None, description="URL to fetch content from (JSON array or single URL string)"),
100
+ metadata: Optional[str] = Form(None, description="JSON metadata object for additional content properties"),
101
+ file: Optional[UploadFile] = File(None, description="File to upload for processing"),
102
+ text_content: Optional[str] = Form(None, description="Raw text content to process"),
103
+ reader_id: Optional[str] = Form(None, description="ID of the reader to use for content processing"),
104
+ chunker: Optional[str] = Form(None, description="Chunking strategy to apply during processing"),
105
+ chunk_size: Optional[int] = Form(None, description="Chunk size to use for processing"),
106
+ chunk_overlap: Optional[int] = Form(None, description="Chunk overlap to use for processing"),
107
+ db_id: Optional[str] = Query(default=None, description="Database ID to use for content storage"),
108
+ ):
109
+ knowledge = get_knowledge_instance_by_db_id(knowledge_instances, db_id)
110
+ log_info(f"Adding content: {name}, {description}, {url}, {metadata}")
111
+
112
+ parsed_metadata = None
113
+ if metadata:
114
+ try:
115
+ parsed_metadata = json.loads(metadata)
116
+ except json.JSONDecodeError:
117
+ # If it's not valid JSON, treat as a simple key-value pair
118
+ parsed_metadata = {"value": metadata} if metadata != "string" else None
119
+ if file:
120
+ content_bytes = await file.read()
121
+ elif text_content:
122
+ content_bytes = text_content.encode("utf-8")
123
+ else:
124
+ content_bytes = None
125
+
126
+ parsed_urls = None
127
+ if url and url.strip():
128
+ try:
129
+ parsed_urls = json.loads(url)
130
+ log_debug(f"Parsed URLs: {parsed_urls}")
131
+ except json.JSONDecodeError:
132
+ # If it's not valid JSON, treat as a single URL string
133
+ parsed_urls = url
134
+
135
+ # # Parse metadata with proper error handling
136
+ parsed_metadata = None
137
+ if metadata:
138
+ try:
139
+ parsed_metadata = json.loads(metadata)
140
+ except json.JSONDecodeError:
141
+ # If it's not valid JSON, treat as a simple key-value pair
142
+ parsed_metadata = {"value": metadata}
143
+
144
+ if text_content:
145
+ file_data = FileData(
146
+ content=content_bytes,
147
+ type="manual",
148
+ )
149
+ elif file:
150
+ file_data = FileData(
151
+ content=content_bytes,
152
+ type=file.content_type if file.content_type else None,
153
+ filename=file.filename,
154
+ size=file.size,
155
+ )
156
+ else:
157
+ file_data = None
158
+
159
+ if not name:
160
+ if file and file.filename:
161
+ name = file.filename
162
+ elif url:
163
+ name = parsed_urls
164
+
165
+ content = Content(
166
+ name=name,
167
+ description=description,
168
+ url=parsed_urls,
169
+ metadata=parsed_metadata,
170
+ file_data=file_data,
171
+ size=file.size if file else None if text_content else None,
172
+ )
173
+ content_hash = knowledge._build_content_hash(content)
174
+ content.content_hash = content_hash
175
+ content.id = generate_id(content_hash)
176
+
177
+ background_tasks.add_task(process_content, knowledge, content, reader_id, chunker, chunk_size, chunk_overlap)
178
+
179
+ response = ContentResponseSchema(
180
+ id=content.id,
181
+ name=name,
182
+ description=description,
183
+ metadata=parsed_metadata,
184
+ status=ContentStatus.PROCESSING,
185
+ )
186
+ return response
187
+
188
+ @router.patch(
189
+ "/knowledge/content/{content_id}",
190
+ response_model=ContentResponseSchema,
191
+ status_code=200,
192
+ operation_id="update_content",
193
+ summary="Update Content",
194
+ description=(
195
+ "Update content properties such as name, description, metadata, or processing configuration. "
196
+ "Allows modification of existing content without re-uploading."
197
+ ),
198
+ responses={
199
+ 200: {
200
+ "description": "Content updated successfully",
201
+ "content": {
202
+ "application/json": {
203
+ "example": {
204
+ "id": "3c2fc685-d451-4d47-b0c0-b9a544c672b7",
205
+ "name": "example.pdf",
206
+ "description": "",
207
+ "type": "application/pdf",
208
+ "size": "251261",
209
+ "linked_to": None,
210
+ "metadata": {},
211
+ "access_count": 1,
212
+ "status": "completed",
213
+ "status_message": "",
214
+ "created_at": "2025-09-08T15:22:53Z",
215
+ "updated_at": "2025-09-08T15:22:54Z",
216
+ }
217
+ }
218
+ },
219
+ },
220
+ 400: {
221
+ "description": "Invalid request - malformed metadata or invalid reader_id",
222
+ "model": BadRequestResponse,
223
+ },
224
+ 404: {"description": "Content not found", "model": NotFoundResponse},
225
+ },
226
+ )
227
+ async def update_content(
228
+ content_id: str = Path(..., description="Content ID"),
229
+ name: Optional[str] = Form(None, description="Content name"),
230
+ description: Optional[str] = Form(None, description="Content description"),
231
+ metadata: Optional[str] = Form(None, description="Content metadata as JSON string"),
232
+ reader_id: Optional[str] = Form(None, description="ID of the reader to use for processing"),
233
+ db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
234
+ ) -> Optional[ContentResponseSchema]:
235
+ knowledge = get_knowledge_instance_by_db_id(knowledge_instances, db_id)
236
+
237
+ # Parse metadata JSON string if provided
238
+ parsed_metadata = None
239
+ if metadata and metadata.strip():
240
+ try:
241
+ parsed_metadata = json.loads(metadata)
242
+ except json.JSONDecodeError:
243
+ raise HTTPException(status_code=400, detail="Invalid JSON format for metadata")
244
+
245
+ # Create ContentUpdateSchema object from form data
246
+ update_data = ContentUpdateSchema(
247
+ name=name if name and name.strip() else None,
248
+ description=description if description and description.strip() else None,
249
+ metadata=parsed_metadata,
250
+ reader_id=reader_id if reader_id and reader_id.strip() else None,
251
+ )
252
+
253
+ content = Content(
254
+ id=content_id,
255
+ name=update_data.name,
256
+ description=update_data.description,
257
+ metadata=update_data.metadata,
258
+ )
259
+
260
+ if update_data.reader_id:
261
+ if knowledge.readers and update_data.reader_id in knowledge.readers:
262
+ content.reader = knowledge.readers[update_data.reader_id]
263
+ else:
264
+ raise HTTPException(status_code=400, detail=f"Invalid reader_id: {update_data.reader_id}")
265
+
266
+ updated_content_dict = knowledge.patch_content(content)
267
+ if not updated_content_dict:
268
+ raise HTTPException(status_code=404, detail=f"Content not found: {content_id}")
269
+
270
+ return ContentResponseSchema.from_dict(updated_content_dict)
271
+
272
+ @router.get(
273
+ "/knowledge/content",
274
+ response_model=PaginatedResponse[ContentResponseSchema],
275
+ status_code=200,
276
+ operation_id="get_content",
277
+ summary="List Content",
278
+ description=(
279
+ "Retrieve paginated list of all content in the knowledge base with filtering and sorting options. "
280
+ "Filter by status, content type, or metadata properties."
281
+ ),
282
+ responses={
283
+ 200: {
284
+ "description": "Content list retrieved successfully",
285
+ "content": {
286
+ "application/json": {
287
+ "example": {
288
+ "data": [
289
+ {
290
+ "id": "3c2fc685-d451-4d47-b0c0-b9a544c672b7",
291
+ "name": "example.pdf",
292
+ "description": "",
293
+ "type": "application/pdf",
294
+ "size": "251261",
295
+ "linked_to": None,
296
+ "metadata": {},
297
+ "access_count": 1,
298
+ "status": "completed",
299
+ "status_message": "",
300
+ "created_at": "2025-09-08T15:22:53Z",
301
+ "updated_at": "2025-09-08T15:22:54Z",
302
+ },
303
+ ],
304
+ "meta": {"page": 1, "limit": 20, "total_pages": 1, "total_count": 2},
305
+ }
306
+ }
307
+ },
308
+ }
309
+ },
310
+ )
311
+ async def get_content(
312
+ limit: Optional[int] = Query(default=20, description="Number of content entries to return"),
313
+ page: Optional[int] = Query(default=1, description="Page number"),
314
+ sort_by: Optional[str] = Query(default="created_at", description="Field to sort by"),
315
+ sort_order: Optional[SortOrder] = Query(default="desc", description="Sort order (asc or desc)"),
316
+ db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
317
+ ) -> PaginatedResponse[ContentResponseSchema]:
318
+ knowledge = get_knowledge_instance_by_db_id(knowledge_instances, db_id)
319
+ contents, count = await knowledge.aget_content(limit=limit, page=page, sort_by=sort_by, sort_order=sort_order)
320
+
321
+ return PaginatedResponse(
322
+ data=[
323
+ ContentResponseSchema.from_dict(
324
+ {
325
+ "id": content.id,
326
+ "name": content.name,
327
+ "description": content.description,
328
+ "file_type": content.file_type,
329
+ "size": content.size,
330
+ "metadata": content.metadata,
331
+ "status": content.status,
332
+ "status_message": content.status_message,
333
+ "created_at": content.created_at,
334
+ "updated_at": content.updated_at,
335
+ }
336
+ )
337
+ for content in contents
338
+ ],
339
+ meta=PaginationInfo(
340
+ page=page,
341
+ limit=limit,
342
+ total_count=count,
343
+ total_pages=math.ceil(count / limit) if limit is not None and limit > 0 else 0,
344
+ ),
345
+ )
346
+
347
+ @router.get(
348
+ "/knowledge/content/{content_id}",
349
+ response_model=ContentResponseSchema,
350
+ status_code=200,
351
+ operation_id="get_content_by_id",
352
+ summary="Get Content by ID",
353
+ description="Retrieve detailed information about a specific content item including processing status and metadata.",
354
+ responses={
355
+ 200: {
356
+ "description": "Content details retrieved successfully",
357
+ "content": {
358
+ "application/json": {
359
+ "example": {
360
+ "id": "3c2fc685-d451-4d47-b0c0-b9a544c672b7",
361
+ "name": "example.pdf",
362
+ "description": "",
363
+ "type": "application/pdf",
364
+ "size": "251261",
365
+ "linked_to": None,
366
+ "metadata": {},
367
+ "access_count": 1,
368
+ "status": "completed",
369
+ "status_message": "",
370
+ "created_at": "2025-09-08T15:22:53Z",
371
+ "updated_at": "2025-09-08T15:22:54Z",
372
+ }
373
+ }
374
+ },
375
+ },
376
+ 404: {"description": "Content not found", "model": NotFoundResponse},
377
+ },
378
+ )
379
+ async def get_content_by_id(
380
+ content_id: str,
381
+ db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
382
+ ) -> ContentResponseSchema:
383
+ log_info(f"Getting content by id: {content_id}")
384
+ knowledge = get_knowledge_instance_by_db_id(knowledge_instances, db_id)
385
+ content = await knowledge.aget_content_by_id(content_id=content_id)
386
+ if not content:
387
+ raise HTTPException(status_code=404, detail=f"Content not found: {content_id}")
388
+ response = ContentResponseSchema.from_dict(
389
+ {
390
+ "id": content_id,
391
+ "name": content.name,
392
+ "description": content.description,
393
+ "file_type": content.file_type,
394
+ "size": len(content.file_data.content) if content.file_data and content.file_data.content else 0,
395
+ "metadata": content.metadata,
396
+ "status": content.status,
397
+ "status_message": content.status_message,
398
+ "created_at": content.created_at,
399
+ "updated_at": content.updated_at,
400
+ }
401
+ )
402
+
403
+ return response
404
+
405
+ @router.delete(
406
+ "/knowledge/content/{content_id}",
407
+ response_model=ContentResponseSchema,
408
+ status_code=200,
409
+ response_model_exclude_none=True,
410
+ operation_id="delete_content_by_id",
411
+ summary="Delete Content by ID",
412
+ description="Permanently remove a specific content item from the knowledge base. This action cannot be undone.",
413
+ responses={
414
+ 200: {},
415
+ 404: {"description": "Content not found", "model": NotFoundResponse},
416
+ 500: {"description": "Failed to delete content", "model": InternalServerErrorResponse},
417
+ },
418
+ )
419
+ async def delete_content_by_id(
420
+ content_id: str,
421
+ db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
422
+ ) -> ContentResponseSchema:
423
+ knowledge = get_knowledge_instance_by_db_id(knowledge_instances, db_id)
424
+ await knowledge.aremove_content_by_id(content_id=content_id)
425
+ log_info(f"Deleting content by id: {content_id}")
426
+
427
+ return ContentResponseSchema(
428
+ id=content_id,
429
+ )
430
+
431
+ @router.delete(
432
+ "/knowledge/content",
433
+ status_code=200,
434
+ operation_id="delete_all_content",
435
+ summary="Delete All Content",
436
+ description=(
437
+ "Permanently remove all content from the knowledge base. This is a destructive operation that "
438
+ "cannot be undone. Use with extreme caution."
439
+ ),
440
+ responses={
441
+ 200: {},
442
+ 500: {"description": "Failed to delete all content", "model": InternalServerErrorResponse},
443
+ },
444
+ )
445
+ def delete_all_content(
446
+ db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
447
+ ):
448
+ knowledge = get_knowledge_instance_by_db_id(knowledge_instances, db_id)
449
+ log_info("Deleting all content")
450
+ knowledge.remove_all_content()
451
+ return "success"
452
+
453
+ @router.get(
454
+ "/knowledge/content/{content_id}/status",
455
+ status_code=200,
456
+ response_model=ContentStatusResponse,
457
+ operation_id="get_content_status",
458
+ summary="Get Content Status",
459
+ description=(
460
+ "Retrieve the current processing status of a content item. Useful for monitoring "
461
+ "asynchronous content processing progress and identifying any processing errors."
462
+ ),
463
+ responses={
464
+ 200: {
465
+ "description": "Content status retrieved successfully",
466
+ "content": {
467
+ "application/json": {
468
+ "examples": {
469
+ "completed": {
470
+ "summary": "Example completed content status",
471
+ "value": {
472
+ "status": "completed",
473
+ "status_message": "",
474
+ },
475
+ }
476
+ }
477
+ }
478
+ },
479
+ },
480
+ 404: {"description": "Content not found", "model": NotFoundResponse},
481
+ },
482
+ )
483
+ async def get_content_status(
484
+ content_id: str,
485
+ db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
486
+ ) -> ContentStatusResponse:
487
+ log_info(f"Getting content status: {content_id}")
488
+ knowledge = get_knowledge_instance_by_db_id(knowledge_instances, db_id)
489
+ knowledge_status, status_message = await knowledge.aget_content_status(content_id=content_id)
490
+
491
+ # Handle the case where content is not found
492
+ if knowledge_status is None:
493
+ return ContentStatusResponse(
494
+ status=ContentStatus.FAILED, status_message=status_message or "Content not found"
495
+ )
496
+
497
+ # Convert knowledge ContentStatus to schema ContentStatus (they have same values)
498
+ if hasattr(knowledge_status, "value"):
499
+ status_value = knowledge_status.value
500
+ else:
501
+ status_value = str(knowledge_status)
502
+
503
+ # Convert string status to ContentStatus enum if needed (for backward compatibility and mocks)
504
+ if isinstance(status_value, str):
505
+ try:
506
+ status = ContentStatus(status_value.lower())
507
+ except ValueError:
508
+ # Handle legacy or unknown statuses gracefully
509
+ if "failed" in status_value.lower():
510
+ status = ContentStatus.FAILED
511
+ elif "completed" in status_value.lower():
512
+ status = ContentStatus.COMPLETED
513
+ else:
514
+ status = ContentStatus.PROCESSING
515
+ else:
516
+ status = ContentStatus.PROCESSING
517
+
518
+ return ContentStatusResponse(status=status, status_message=status_message or "")
519
+
520
+ @router.post(
521
+ "/knowledge/search",
522
+ status_code=200,
523
+ operation_id="search_knowledge",
524
+ summary="Search Knowledge",
525
+ description="Search the knowledge base for relevant documents using query, filters and search type.",
526
+ response_model=PaginatedResponse[VectorSearchResult],
527
+ responses={
528
+ 200: {
529
+ "description": "Search results retrieved successfully",
530
+ "content": {
531
+ "application/json": {
532
+ "example": {
533
+ "data": [
534
+ {
535
+ "id": "doc_123",
536
+ "content": "Jordan Mitchell - Software Engineer with skills in JavaScript, React, Python",
537
+ "name": "cv_1",
538
+ "meta_data": {"page": 1, "chunk": 1},
539
+ "usage": {"total_tokens": 14},
540
+ "reranking_score": 0.95,
541
+ "content_id": "content_456",
542
+ }
543
+ ],
544
+ "meta": {"page": 1, "limit": 20, "total_pages": 2, "total_count": 35},
545
+ }
546
+ }
547
+ },
548
+ },
549
+ 400: {"description": "Invalid search parameters"},
550
+ 404: {"description": "No documents found"},
551
+ },
552
+ )
553
+ def search_knowledge(request: VectorSearchRequestSchema) -> PaginatedResponse[VectorSearchResult]:
554
+ import time
555
+
556
+ start_time = time.time()
557
+
558
+ knowledge = get_knowledge_instance_by_db_id(knowledge_instances, request.db_id)
559
+
560
+ # For now, validate the vector db ids exist in the knowledge base
561
+ # We will add more logic around this once we have multi vectordb support
562
+ # If vector db ids are provided, check if any of them match the knowledge's vector db
563
+ if request.vector_db_ids:
564
+ if knowledge.vector_db and knowledge.vector_db.id:
565
+ if knowledge.vector_db.id not in request.vector_db_ids:
566
+ raise HTTPException(
567
+ status_code=400,
568
+ detail=f"None of the provided Vector DB IDs {request.vector_db_ids} match the knowledge base Vector DB ID {knowledge.vector_db.id}",
569
+ )
570
+ else:
571
+ raise HTTPException(status_code=400, detail="Knowledge base has no vector database configured")
572
+
573
+ # Calculate pagination parameters
574
+ meta = request.meta
575
+ limit = meta.limit if meta and meta.limit is not None else 20
576
+ page = meta.page if meta and meta.page is not None else 1
577
+
578
+ # Use max_results if specified, otherwise use a higher limit for search then paginate
579
+ search_limit = request.max_results
580
+
581
+ results = knowledge.search(
582
+ query=request.query, max_results=search_limit, filters=request.filters, search_type=request.search_type
583
+ )
584
+
585
+ # Calculate pagination
586
+ total_results = len(results)
587
+ start_idx = (page - 1) * limit
588
+
589
+ # Ensure start_idx doesn't exceed the total results
590
+ if start_idx >= total_results and total_results > 0:
591
+ # If page is beyond available results, return empty results
592
+ paginated_results = []
593
+ else:
594
+ end_idx = min(start_idx + limit, total_results)
595
+ paginated_results = results[start_idx:end_idx]
596
+
597
+ search_time_ms = (time.time() - start_time) * 1000
598
+
599
+ # Convert Document objects to serializable format
600
+ document_results = [VectorSearchResult.from_document(doc) for doc in paginated_results]
601
+
602
+ # Calculate pagination info
603
+ total_pages = (total_results + limit - 1) // limit # Ceiling division
604
+
605
+ return PaginatedResponse(
606
+ data=document_results,
607
+ meta=PaginationInfo(
608
+ page=page,
609
+ limit=limit,
610
+ total_pages=total_pages,
611
+ total_count=total_results,
612
+ search_time_ms=search_time_ms,
613
+ ),
614
+ )
615
+
616
+ @router.get(
617
+ "/knowledge/config",
618
+ status_code=200,
619
+ operation_id="get_knowledge_config",
620
+ summary="Get Config",
621
+ description=(
622
+ "Retrieve available readers, chunkers, and configuration options for content processing. "
623
+ "This endpoint provides metadata about supported file types, processing strategies, and filters."
624
+ ),
625
+ responses={
626
+ 200: {
627
+ "description": "Knowledge configuration retrieved successfully",
628
+ "content": {
629
+ "application/json": {
630
+ "example": {
631
+ "readers": {
632
+ "website": {
633
+ "id": "website",
634
+ "name": "WebsiteReader",
635
+ "description": "Reads website files",
636
+ "chunkers": [
637
+ "AgenticChunker",
638
+ "DocumentChunker",
639
+ "RecursiveChunker",
640
+ "SemanticChunker",
641
+ "FixedSizeChunker",
642
+ ],
643
+ },
644
+ "firecrawl": {
645
+ "id": "firecrawl",
646
+ "name": "FirecrawlReader",
647
+ "description": "Reads firecrawl files",
648
+ "chunkers": [
649
+ "SemanticChunker",
650
+ "FixedSizeChunker",
651
+ "AgenticChunker",
652
+ "DocumentChunker",
653
+ "RecursiveChunker",
654
+ ],
655
+ },
656
+ "youtube": {
657
+ "id": "youtube",
658
+ "name": "YoutubeReader",
659
+ "description": "Reads youtube files",
660
+ "chunkers": [
661
+ "RecursiveChunker",
662
+ "AgenticChunker",
663
+ "DocumentChunker",
664
+ "SemanticChunker",
665
+ "FixedSizeChunker",
666
+ ],
667
+ },
668
+ "web_search": {
669
+ "id": "web_search",
670
+ "name": "WebSearchReader",
671
+ "description": "Reads web_search files",
672
+ "chunkers": [
673
+ "AgenticChunker",
674
+ "DocumentChunker",
675
+ "RecursiveChunker",
676
+ "SemanticChunker",
677
+ "FixedSizeChunker",
678
+ ],
679
+ },
680
+ "arxiv": {
681
+ "id": "arxiv",
682
+ "name": "ArxivReader",
683
+ "description": "Reads arxiv files",
684
+ "chunkers": [
685
+ "FixedSizeChunker",
686
+ "AgenticChunker",
687
+ "DocumentChunker",
688
+ "RecursiveChunker",
689
+ "SemanticChunker",
690
+ ],
691
+ },
692
+ "csv": {
693
+ "id": "csv",
694
+ "name": "CsvReader",
695
+ "description": "Reads csv files",
696
+ "chunkers": [
697
+ "RowChunker",
698
+ "FixedSizeChunker",
699
+ "AgenticChunker",
700
+ "DocumentChunker",
701
+ "RecursiveChunker",
702
+ ],
703
+ },
704
+ "docx": {
705
+ "id": "docx",
706
+ "name": "DocxReader",
707
+ "description": "Reads docx files",
708
+ "chunkers": [
709
+ "DocumentChunker",
710
+ "FixedSizeChunker",
711
+ "SemanticChunker",
712
+ "AgenticChunker",
713
+ "RecursiveChunker",
714
+ ],
715
+ },
716
+ "gcs": {
717
+ "id": "gcs",
718
+ "name": "GcsReader",
719
+ "description": "Reads gcs files",
720
+ "chunkers": [
721
+ "FixedSizeChunker",
722
+ "AgenticChunker",
723
+ "DocumentChunker",
724
+ "RecursiveChunker",
725
+ "SemanticChunker",
726
+ ],
727
+ },
728
+ "json": {
729
+ "id": "json",
730
+ "name": "JsonReader",
731
+ "description": "Reads json files",
732
+ "chunkers": [
733
+ "FixedSizeChunker",
734
+ "AgenticChunker",
735
+ "DocumentChunker",
736
+ "RecursiveChunker",
737
+ "SemanticChunker",
738
+ ],
739
+ },
740
+ "markdown": {
741
+ "id": "markdown",
742
+ "name": "MarkdownReader",
743
+ "description": "Reads markdown files",
744
+ "chunkers": [
745
+ "MarkdownChunker",
746
+ "DocumentChunker",
747
+ "AgenticChunker",
748
+ "RecursiveChunker",
749
+ "SemanticChunker",
750
+ "FixedSizeChunker",
751
+ ],
752
+ },
753
+ "pdf": {
754
+ "id": "pdf",
755
+ "name": "PdfReader",
756
+ "description": "Reads pdf files",
757
+ "chunkers": [
758
+ "DocumentChunker",
759
+ "FixedSizeChunker",
760
+ "AgenticChunker",
761
+ "SemanticChunker",
762
+ "RecursiveChunker",
763
+ ],
764
+ },
765
+ "text": {
766
+ "id": "text",
767
+ "name": "TextReader",
768
+ "description": "Reads text files",
769
+ "chunkers": [
770
+ "FixedSizeChunker",
771
+ "AgenticChunker",
772
+ "DocumentChunker",
773
+ "RecursiveChunker",
774
+ "SemanticChunker",
775
+ ],
776
+ },
777
+ },
778
+ "readersForType": {
779
+ "url": [
780
+ "url",
781
+ "website",
782
+ "firecrawl",
783
+ "youtube",
784
+ "web_search",
785
+ "gcs",
786
+ ],
787
+ "youtube": ["youtube"],
788
+ "text": ["web_search"],
789
+ "topic": ["arxiv"],
790
+ "file": ["csv", "gcs"],
791
+ ".csv": ["csv"],
792
+ ".xlsx": ["csv"],
793
+ ".xls": ["csv"],
794
+ ".docx": ["docx"],
795
+ ".doc": ["docx"],
796
+ ".json": ["json"],
797
+ ".md": ["markdown"],
798
+ ".pdf": ["pdf"],
799
+ ".txt": ["text"],
800
+ },
801
+ "chunkers": {
802
+ "AgenticChunker": {
803
+ "key": "AgenticChunker",
804
+ "name": "AgenticChunker",
805
+ "description": "Chunking strategy that uses an LLM to determine natural breakpoints in the text",
806
+ "metadata": {"chunk_size": 5000},
807
+ },
808
+ "DocumentChunker": {
809
+ "key": "DocumentChunker",
810
+ "name": "DocumentChunker",
811
+ "description": "A chunking strategy that splits text based on document structure like paragraphs and sections",
812
+ "metadata": {
813
+ "chunk_size": 5000,
814
+ "chunk_overlap": 0,
815
+ },
816
+ },
817
+ "FixedSizeChunker": {
818
+ "key": "FixedSizeChunker",
819
+ "name": "FixedSizeChunker",
820
+ "description": "Chunking strategy that splits text into fixed-size chunks with optional overlap",
821
+ "metadata": {
822
+ "chunk_size": 5000,
823
+ "chunk_overlap": 0,
824
+ },
825
+ },
826
+ "MarkdownChunker": {
827
+ "key": "MarkdownChunker",
828
+ "name": "MarkdownChunker",
829
+ "description": "A chunking strategy that splits markdown based on structure like headers, paragraphs and sections",
830
+ "metadata": {
831
+ "chunk_size": 5000,
832
+ "chunk_overlap": 0,
833
+ },
834
+ },
835
+ "RecursiveChunker": {
836
+ "key": "RecursiveChunker",
837
+ "name": "RecursiveChunker",
838
+ "description": "Chunking strategy that recursively splits text into chunks by finding natural break points",
839
+ "metadata": {
840
+ "chunk_size": 5000,
841
+ "chunk_overlap": 0,
842
+ },
843
+ },
844
+ "RowChunker": {
845
+ "key": "RowChunker",
846
+ "name": "RowChunker",
847
+ "description": "RowChunking chunking strategy",
848
+ "metadata": {},
849
+ },
850
+ "SemanticChunker": {
851
+ "key": "SemanticChunker",
852
+ "name": "SemanticChunker",
853
+ "description": "Chunking strategy that splits text into semantic chunks using chonkie",
854
+ "metadata": {"chunk_size": 5000},
855
+ },
856
+ },
857
+ "vector_dbs": [
858
+ {
859
+ "id": "vector_db_1",
860
+ "name": "Vector DB 1",
861
+ "description": "Vector DB 1 description",
862
+ "search_types": ["vector", "keyword", "hybrid"],
863
+ }
864
+ ],
865
+ "filters": ["filter_tag_1", "filter_tag2"],
866
+ }
867
+ }
868
+ },
869
+ }
870
+ },
871
+ )
872
+ def get_config(
873
+ db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
874
+ ) -> ConfigResponseSchema:
875
+ knowledge = get_knowledge_instance_by_db_id(knowledge_instances, db_id)
876
+
877
+ # Get factory readers info (including custom readers from this knowledge instance)
878
+ readers_info = get_all_readers_info(knowledge)
879
+ reader_schemas = {}
880
+ # Add factory readers
881
+ for reader_info in readers_info:
882
+ reader_schemas[reader_info["id"]] = ReaderSchema(
883
+ id=reader_info["id"],
884
+ name=reader_info["name"],
885
+ description=reader_info.get("description"),
886
+ chunkers=reader_info.get("chunking_strategies", []),
887
+ )
888
+
889
+ # Add custom readers from knowledge.readers
890
+ readers_result: Any = knowledge.get_readers() or {}
891
+ # Ensure readers_dict is a dictionary (defensive check)
892
+ if not isinstance(readers_result, dict):
893
+ readers_dict: Dict[str, Reader] = {}
894
+ else:
895
+ readers_dict = readers_result
896
+ if readers_dict:
897
+ for reader_id, reader in readers_dict.items():
898
+ # Get chunking strategies from the reader
899
+ chunking_strategies = []
900
+ try:
901
+ strategies = reader.get_supported_chunking_strategies()
902
+ chunking_strategies = [strategy.value for strategy in strategies]
903
+ except Exception:
904
+ chunking_strategies = []
905
+
906
+ # Check if this reader ID already exists in factory readers
907
+ if reader_id not in reader_schemas:
908
+ reader_schemas[reader_id] = ReaderSchema(
909
+ id=reader_id,
910
+ name=getattr(reader, "name", reader.__class__.__name__),
911
+ description=getattr(reader, "description", f"Custom {reader.__class__.__name__}"),
912
+ chunkers=chunking_strategies,
913
+ )
914
+
915
+ # Get content types to readers mapping (including custom readers from this knowledge instance)
916
+ types_of_readers = get_content_types_to_readers_mapping(knowledge)
917
+ chunkers_list = get_all_chunkers_info()
918
+
919
+ # Convert chunkers list to dictionary format expected by schema
920
+ chunkers_dict = {}
921
+ for chunker_info in chunkers_list:
922
+ chunker_key = chunker_info.get("key")
923
+ if chunker_key:
924
+ chunkers_dict[chunker_key] = ChunkerSchema(
925
+ key=chunker_key,
926
+ name=chunker_info.get("name"),
927
+ description=chunker_info.get("description"),
928
+ metadata=chunker_info.get("metadata", {}),
929
+ )
930
+
931
+ vector_dbs = []
932
+ if knowledge.vector_db:
933
+ search_types = knowledge.vector_db.get_supported_search_types()
934
+ name = knowledge.vector_db.name
935
+ db_id = knowledge.vector_db.id
936
+ vector_dbs.append(
937
+ VectorDbSchema(
938
+ id=db_id,
939
+ name=name,
940
+ description=knowledge.vector_db.description,
941
+ search_types=search_types,
942
+ )
943
+ )
944
+
945
+ return ConfigResponseSchema(
946
+ readers=reader_schemas,
947
+ vector_dbs=vector_dbs,
948
+ readersForType=types_of_readers,
949
+ chunkers=chunkers_dict,
950
+ filters=knowledge.get_valid_filters(),
951
+ )
952
+
953
+ return router
954
+
955
+
956
+ async def process_content(
957
+ knowledge: Knowledge,
958
+ content: Content,
959
+ reader_id: Optional[str] = None,
960
+ chunker: Optional[str] = None,
961
+ chunk_size: Optional[int] = None,
962
+ chunk_overlap: Optional[int] = None,
963
+ ):
964
+ """Background task to process the content"""
965
+
966
+ try:
967
+ if reader_id:
968
+ reader = None
969
+ # Use get_readers() to ensure we get a dict (handles list conversion)
970
+ custom_readers = knowledge.get_readers()
971
+ if custom_readers and reader_id in custom_readers:
972
+ reader = custom_readers[reader_id]
973
+ log_debug(f"Found custom reader: {reader.__class__.__name__}")
974
+ else:
975
+ # Try to resolve from factory readers
976
+ key = reader_id.lower().strip().replace("-", "_").replace(" ", "_")
977
+ candidates = [key] + ([key[:-6]] if key.endswith("reader") else [])
978
+ for cand in candidates:
979
+ try:
980
+ reader = ReaderFactory.create_reader(cand)
981
+ log_debug(f"Resolved reader from factory: {reader.__class__.__name__}")
982
+ break
983
+ except Exception:
984
+ continue
985
+ if reader:
986
+ content.reader = reader
987
+ else:
988
+ log_debug(f"Could not resolve reader with id: {reader_id}")
989
+ if chunker and content.reader:
990
+ # Set the chunker name on the reader - let the reader handle it internally
991
+ content.reader.set_chunking_strategy_from_string(chunker, chunk_size=chunk_size, overlap=chunk_overlap)
992
+ log_debug(f"Set chunking strategy: {chunker}")
993
+
994
+ log_debug(f"Using reader: {content.reader.__class__.__name__}")
995
+ await knowledge._load_content_async(content, upsert=False, skip_if_exists=True)
996
+ log_info(f"Content {content.id} processed successfully")
997
+ except Exception as e:
998
+ log_info(f"Error processing content: {e}")
999
+ # Mark content as failed in the contents DB
1000
+ try:
1001
+ from agno.knowledge.content import ContentStatus as KnowledgeContentStatus
1002
+
1003
+ content.status = KnowledgeContentStatus.FAILED
1004
+ content.status_message = str(e)
1005
+ knowledge.patch_content(content)
1006
+ except Exception:
1007
+ # Swallow any secondary errors to avoid crashing the background task
1008
+ pass