ai-parrot 0.17.2__cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.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 (535) hide show
  1. agentui/.prettierrc +15 -0
  2. agentui/QUICKSTART.md +272 -0
  3. agentui/README.md +59 -0
  4. agentui/env.example +16 -0
  5. agentui/jsconfig.json +14 -0
  6. agentui/package-lock.json +4242 -0
  7. agentui/package.json +34 -0
  8. agentui/scripts/postinstall/apply-patches.mjs +260 -0
  9. agentui/src/app.css +61 -0
  10. agentui/src/app.d.ts +13 -0
  11. agentui/src/app.html +12 -0
  12. agentui/src/components/LoadingSpinner.svelte +64 -0
  13. agentui/src/components/ThemeSwitcher.svelte +159 -0
  14. agentui/src/components/index.js +4 -0
  15. agentui/src/lib/api/bots.ts +60 -0
  16. agentui/src/lib/api/chat.ts +22 -0
  17. agentui/src/lib/api/http.ts +25 -0
  18. agentui/src/lib/components/BotCard.svelte +33 -0
  19. agentui/src/lib/components/ChatBubble.svelte +63 -0
  20. agentui/src/lib/components/Toast.svelte +21 -0
  21. agentui/src/lib/config.ts +20 -0
  22. agentui/src/lib/stores/auth.svelte.ts +73 -0
  23. agentui/src/lib/stores/theme.svelte.js +64 -0
  24. agentui/src/lib/stores/toast.svelte.ts +31 -0
  25. agentui/src/lib/utils/conversation.ts +39 -0
  26. agentui/src/routes/+layout.svelte +20 -0
  27. agentui/src/routes/+page.svelte +232 -0
  28. agentui/src/routes/login/+page.svelte +200 -0
  29. agentui/src/routes/talk/[agentId]/+page.svelte +297 -0
  30. agentui/src/routes/talk/[agentId]/+page.ts +7 -0
  31. agentui/static/README.md +1 -0
  32. agentui/svelte.config.js +11 -0
  33. agentui/tailwind.config.ts +53 -0
  34. agentui/tsconfig.json +3 -0
  35. agentui/vite.config.ts +10 -0
  36. ai_parrot-0.17.2.dist-info/METADATA +472 -0
  37. ai_parrot-0.17.2.dist-info/RECORD +535 -0
  38. ai_parrot-0.17.2.dist-info/WHEEL +6 -0
  39. ai_parrot-0.17.2.dist-info/entry_points.txt +2 -0
  40. ai_parrot-0.17.2.dist-info/licenses/LICENSE +21 -0
  41. ai_parrot-0.17.2.dist-info/top_level.txt +6 -0
  42. crew-builder/.prettierrc +15 -0
  43. crew-builder/QUICKSTART.md +259 -0
  44. crew-builder/README.md +113 -0
  45. crew-builder/env.example +17 -0
  46. crew-builder/jsconfig.json +14 -0
  47. crew-builder/package-lock.json +4182 -0
  48. crew-builder/package.json +37 -0
  49. crew-builder/scripts/postinstall/apply-patches.mjs +260 -0
  50. crew-builder/src/app.css +62 -0
  51. crew-builder/src/app.d.ts +13 -0
  52. crew-builder/src/app.html +12 -0
  53. crew-builder/src/components/LoadingSpinner.svelte +64 -0
  54. crew-builder/src/components/ThemeSwitcher.svelte +149 -0
  55. crew-builder/src/components/index.js +9 -0
  56. crew-builder/src/lib/api/bots.ts +60 -0
  57. crew-builder/src/lib/api/chat.ts +80 -0
  58. crew-builder/src/lib/api/client.ts +56 -0
  59. crew-builder/src/lib/api/crew/crew.ts +136 -0
  60. crew-builder/src/lib/api/index.ts +5 -0
  61. crew-builder/src/lib/api/o365/auth.ts +65 -0
  62. crew-builder/src/lib/auth/auth.ts +54 -0
  63. crew-builder/src/lib/components/AgentNode.svelte +43 -0
  64. crew-builder/src/lib/components/BotCard.svelte +33 -0
  65. crew-builder/src/lib/components/ChatBubble.svelte +67 -0
  66. crew-builder/src/lib/components/ConfigPanel.svelte +278 -0
  67. crew-builder/src/lib/components/JsonTreeNode.svelte +76 -0
  68. crew-builder/src/lib/components/JsonViewer.svelte +24 -0
  69. crew-builder/src/lib/components/MarkdownEditor.svelte +48 -0
  70. crew-builder/src/lib/components/ThemeToggle.svelte +36 -0
  71. crew-builder/src/lib/components/Toast.svelte +67 -0
  72. crew-builder/src/lib/components/Toolbar.svelte +157 -0
  73. crew-builder/src/lib/components/index.ts +10 -0
  74. crew-builder/src/lib/config.ts +8 -0
  75. crew-builder/src/lib/stores/auth.svelte.ts +228 -0
  76. crew-builder/src/lib/stores/crewStore.ts +369 -0
  77. crew-builder/src/lib/stores/theme.svelte.js +145 -0
  78. crew-builder/src/lib/stores/toast.svelte.ts +69 -0
  79. crew-builder/src/lib/utils/conversation.ts +39 -0
  80. crew-builder/src/lib/utils/markdown.ts +122 -0
  81. crew-builder/src/lib/utils/talkHistory.ts +47 -0
  82. crew-builder/src/routes/+layout.svelte +20 -0
  83. crew-builder/src/routes/+page.svelte +539 -0
  84. crew-builder/src/routes/agents/+page.svelte +247 -0
  85. crew-builder/src/routes/agents/[agentId]/+page.svelte +288 -0
  86. crew-builder/src/routes/agents/[agentId]/+page.ts +7 -0
  87. crew-builder/src/routes/builder/+page.svelte +204 -0
  88. crew-builder/src/routes/crew/ask/+page.svelte +1052 -0
  89. crew-builder/src/routes/crew/ask/+page.ts +1 -0
  90. crew-builder/src/routes/integrations/o365/+page.svelte +304 -0
  91. crew-builder/src/routes/login/+page.svelte +197 -0
  92. crew-builder/src/routes/talk/[agentId]/+page.svelte +487 -0
  93. crew-builder/src/routes/talk/[agentId]/+page.ts +7 -0
  94. crew-builder/static/README.md +1 -0
  95. crew-builder/svelte.config.js +11 -0
  96. crew-builder/tailwind.config.ts +53 -0
  97. crew-builder/tsconfig.json +3 -0
  98. crew-builder/vite.config.ts +10 -0
  99. mcp_servers/calculator_server.py +309 -0
  100. parrot/__init__.py +27 -0
  101. parrot/__pycache__/__init__.cpython-310.pyc +0 -0
  102. parrot/__pycache__/version.cpython-310.pyc +0 -0
  103. parrot/_version.py +34 -0
  104. parrot/a2a/__init__.py +48 -0
  105. parrot/a2a/client.py +658 -0
  106. parrot/a2a/discovery.py +89 -0
  107. parrot/a2a/mixin.py +257 -0
  108. parrot/a2a/models.py +376 -0
  109. parrot/a2a/server.py +770 -0
  110. parrot/agents/__init__.py +29 -0
  111. parrot/bots/__init__.py +12 -0
  112. parrot/bots/a2a_agent.py +19 -0
  113. parrot/bots/abstract.py +3139 -0
  114. parrot/bots/agent.py +1129 -0
  115. parrot/bots/basic.py +9 -0
  116. parrot/bots/chatbot.py +669 -0
  117. parrot/bots/data.py +1618 -0
  118. parrot/bots/database/__init__.py +5 -0
  119. parrot/bots/database/abstract.py +3071 -0
  120. parrot/bots/database/cache.py +286 -0
  121. parrot/bots/database/models.py +468 -0
  122. parrot/bots/database/prompts.py +154 -0
  123. parrot/bots/database/retries.py +98 -0
  124. parrot/bots/database/router.py +269 -0
  125. parrot/bots/database/sql.py +41 -0
  126. parrot/bots/db/__init__.py +6 -0
  127. parrot/bots/db/abstract.py +556 -0
  128. parrot/bots/db/bigquery.py +602 -0
  129. parrot/bots/db/cache.py +85 -0
  130. parrot/bots/db/documentdb.py +668 -0
  131. parrot/bots/db/elastic.py +1014 -0
  132. parrot/bots/db/influx.py +898 -0
  133. parrot/bots/db/mock.py +96 -0
  134. parrot/bots/db/multi.py +783 -0
  135. parrot/bots/db/prompts.py +185 -0
  136. parrot/bots/db/sql.py +1255 -0
  137. parrot/bots/db/tools.py +212 -0
  138. parrot/bots/document.py +680 -0
  139. parrot/bots/hrbot.py +15 -0
  140. parrot/bots/kb.py +170 -0
  141. parrot/bots/mcp.py +36 -0
  142. parrot/bots/orchestration/README.md +463 -0
  143. parrot/bots/orchestration/__init__.py +1 -0
  144. parrot/bots/orchestration/agent.py +155 -0
  145. parrot/bots/orchestration/crew.py +3330 -0
  146. parrot/bots/orchestration/fsm.py +1179 -0
  147. parrot/bots/orchestration/hr.py +434 -0
  148. parrot/bots/orchestration/storage/__init__.py +4 -0
  149. parrot/bots/orchestration/storage/memory.py +100 -0
  150. parrot/bots/orchestration/storage/mixin.py +119 -0
  151. parrot/bots/orchestration/verify.py +202 -0
  152. parrot/bots/product.py +204 -0
  153. parrot/bots/prompts/__init__.py +96 -0
  154. parrot/bots/prompts/agents.py +155 -0
  155. parrot/bots/prompts/data.py +216 -0
  156. parrot/bots/prompts/output_generation.py +8 -0
  157. parrot/bots/scraper/__init__.py +3 -0
  158. parrot/bots/scraper/models.py +122 -0
  159. parrot/bots/scraper/scraper.py +1173 -0
  160. parrot/bots/scraper/templates.py +115 -0
  161. parrot/bots/stores/__init__.py +5 -0
  162. parrot/bots/stores/local.py +172 -0
  163. parrot/bots/webdev.py +81 -0
  164. parrot/cli.py +17 -0
  165. parrot/clients/__init__.py +16 -0
  166. parrot/clients/base.py +1491 -0
  167. parrot/clients/claude.py +1191 -0
  168. parrot/clients/factory.py +129 -0
  169. parrot/clients/google.py +4567 -0
  170. parrot/clients/gpt.py +1975 -0
  171. parrot/clients/grok.py +432 -0
  172. parrot/clients/groq.py +986 -0
  173. parrot/clients/hf.py +582 -0
  174. parrot/clients/models.py +18 -0
  175. parrot/conf.py +395 -0
  176. parrot/embeddings/__init__.py +9 -0
  177. parrot/embeddings/base.py +157 -0
  178. parrot/embeddings/google.py +98 -0
  179. parrot/embeddings/huggingface.py +74 -0
  180. parrot/embeddings/openai.py +84 -0
  181. parrot/embeddings/processor.py +88 -0
  182. parrot/exceptions.c +13868 -0
  183. parrot/exceptions.cpython-310-x86_64-linux-gnu.so +0 -0
  184. parrot/exceptions.pxd +22 -0
  185. parrot/exceptions.pxi +15 -0
  186. parrot/exceptions.pyx +44 -0
  187. parrot/generators/__init__.py +29 -0
  188. parrot/generators/base.py +200 -0
  189. parrot/generators/html.py +293 -0
  190. parrot/generators/react.py +205 -0
  191. parrot/generators/streamlit.py +203 -0
  192. parrot/generators/template.py +105 -0
  193. parrot/handlers/__init__.py +4 -0
  194. parrot/handlers/agent.py +861 -0
  195. parrot/handlers/agents/__init__.py +1 -0
  196. parrot/handlers/agents/abstract.py +900 -0
  197. parrot/handlers/bots.py +338 -0
  198. parrot/handlers/chat.py +915 -0
  199. parrot/handlers/creation.sql +192 -0
  200. parrot/handlers/crew/ARCHITECTURE.md +362 -0
  201. parrot/handlers/crew/README_BOTMANAGER_PERSISTENCE.md +303 -0
  202. parrot/handlers/crew/README_REDIS_PERSISTENCE.md +366 -0
  203. parrot/handlers/crew/__init__.py +0 -0
  204. parrot/handlers/crew/handler.py +801 -0
  205. parrot/handlers/crew/models.py +229 -0
  206. parrot/handlers/crew/redis_persistence.py +523 -0
  207. parrot/handlers/jobs/__init__.py +10 -0
  208. parrot/handlers/jobs/job.py +384 -0
  209. parrot/handlers/jobs/mixin.py +627 -0
  210. parrot/handlers/jobs/models.py +115 -0
  211. parrot/handlers/jobs/worker.py +31 -0
  212. parrot/handlers/models.py +596 -0
  213. parrot/handlers/o365_auth.py +105 -0
  214. parrot/handlers/stream.py +337 -0
  215. parrot/interfaces/__init__.py +6 -0
  216. parrot/interfaces/aws.py +143 -0
  217. parrot/interfaces/credentials.py +113 -0
  218. parrot/interfaces/database.py +27 -0
  219. parrot/interfaces/google.py +1123 -0
  220. parrot/interfaces/hierarchy.py +1227 -0
  221. parrot/interfaces/http.py +651 -0
  222. parrot/interfaces/images/__init__.py +0 -0
  223. parrot/interfaces/images/plugins/__init__.py +24 -0
  224. parrot/interfaces/images/plugins/abstract.py +58 -0
  225. parrot/interfaces/images/plugins/analisys.py +148 -0
  226. parrot/interfaces/images/plugins/classify.py +150 -0
  227. parrot/interfaces/images/plugins/classifybase.py +182 -0
  228. parrot/interfaces/images/plugins/detect.py +150 -0
  229. parrot/interfaces/images/plugins/exif.py +1103 -0
  230. parrot/interfaces/images/plugins/hash.py +52 -0
  231. parrot/interfaces/images/plugins/vision.py +104 -0
  232. parrot/interfaces/images/plugins/yolo.py +66 -0
  233. parrot/interfaces/images/plugins/zerodetect.py +197 -0
  234. parrot/interfaces/o365.py +978 -0
  235. parrot/interfaces/onedrive.py +822 -0
  236. parrot/interfaces/sharepoint.py +1435 -0
  237. parrot/interfaces/soap.py +257 -0
  238. parrot/loaders/__init__.py +8 -0
  239. parrot/loaders/abstract.py +1131 -0
  240. parrot/loaders/audio.py +199 -0
  241. parrot/loaders/basepdf.py +53 -0
  242. parrot/loaders/basevideo.py +1568 -0
  243. parrot/loaders/csv.py +409 -0
  244. parrot/loaders/docx.py +116 -0
  245. parrot/loaders/epubloader.py +316 -0
  246. parrot/loaders/excel.py +199 -0
  247. parrot/loaders/factory.py +55 -0
  248. parrot/loaders/files/__init__.py +0 -0
  249. parrot/loaders/files/abstract.py +39 -0
  250. parrot/loaders/files/html.py +26 -0
  251. parrot/loaders/files/text.py +63 -0
  252. parrot/loaders/html.py +152 -0
  253. parrot/loaders/markdown.py +442 -0
  254. parrot/loaders/pdf.py +373 -0
  255. parrot/loaders/pdfmark.py +320 -0
  256. parrot/loaders/pdftables.py +506 -0
  257. parrot/loaders/ppt.py +476 -0
  258. parrot/loaders/qa.py +63 -0
  259. parrot/loaders/splitters/__init__.py +10 -0
  260. parrot/loaders/splitters/base.py +138 -0
  261. parrot/loaders/splitters/md.py +228 -0
  262. parrot/loaders/splitters/token.py +143 -0
  263. parrot/loaders/txt.py +26 -0
  264. parrot/loaders/video.py +89 -0
  265. parrot/loaders/videolocal.py +218 -0
  266. parrot/loaders/videounderstanding.py +377 -0
  267. parrot/loaders/vimeo.py +167 -0
  268. parrot/loaders/web.py +599 -0
  269. parrot/loaders/youtube.py +504 -0
  270. parrot/manager/__init__.py +5 -0
  271. parrot/manager/manager.py +1030 -0
  272. parrot/mcp/__init__.py +28 -0
  273. parrot/mcp/adapter.py +105 -0
  274. parrot/mcp/cli.py +174 -0
  275. parrot/mcp/client.py +119 -0
  276. parrot/mcp/config.py +75 -0
  277. parrot/mcp/integration.py +842 -0
  278. parrot/mcp/oauth.py +933 -0
  279. parrot/mcp/server.py +225 -0
  280. parrot/mcp/transports/__init__.py +3 -0
  281. parrot/mcp/transports/base.py +279 -0
  282. parrot/mcp/transports/grpc_session.py +163 -0
  283. parrot/mcp/transports/http.py +312 -0
  284. parrot/mcp/transports/mcp.proto +108 -0
  285. parrot/mcp/transports/quic.py +1082 -0
  286. parrot/mcp/transports/sse.py +330 -0
  287. parrot/mcp/transports/stdio.py +309 -0
  288. parrot/mcp/transports/unix.py +395 -0
  289. parrot/mcp/transports/websocket.py +547 -0
  290. parrot/memory/__init__.py +16 -0
  291. parrot/memory/abstract.py +209 -0
  292. parrot/memory/agent.py +32 -0
  293. parrot/memory/cache.py +175 -0
  294. parrot/memory/core.py +555 -0
  295. parrot/memory/file.py +153 -0
  296. parrot/memory/mem.py +131 -0
  297. parrot/memory/redis.py +613 -0
  298. parrot/models/__init__.py +46 -0
  299. parrot/models/basic.py +118 -0
  300. parrot/models/compliance.py +208 -0
  301. parrot/models/crew.py +395 -0
  302. parrot/models/detections.py +654 -0
  303. parrot/models/generation.py +85 -0
  304. parrot/models/google.py +223 -0
  305. parrot/models/groq.py +23 -0
  306. parrot/models/openai.py +30 -0
  307. parrot/models/outputs.py +285 -0
  308. parrot/models/responses.py +938 -0
  309. parrot/notifications/__init__.py +743 -0
  310. parrot/openapi/__init__.py +3 -0
  311. parrot/openapi/components.yaml +641 -0
  312. parrot/openapi/config.py +322 -0
  313. parrot/outputs/__init__.py +32 -0
  314. parrot/outputs/formats/__init__.py +108 -0
  315. parrot/outputs/formats/altair.py +359 -0
  316. parrot/outputs/formats/application.py +122 -0
  317. parrot/outputs/formats/base.py +351 -0
  318. parrot/outputs/formats/bokeh.py +356 -0
  319. parrot/outputs/formats/card.py +424 -0
  320. parrot/outputs/formats/chart.py +436 -0
  321. parrot/outputs/formats/d3.py +255 -0
  322. parrot/outputs/formats/echarts.py +310 -0
  323. parrot/outputs/formats/generators/__init__.py +0 -0
  324. parrot/outputs/formats/generators/abstract.py +61 -0
  325. parrot/outputs/formats/generators/panel.py +145 -0
  326. parrot/outputs/formats/generators/streamlit.py +86 -0
  327. parrot/outputs/formats/generators/terminal.py +63 -0
  328. parrot/outputs/formats/holoviews.py +310 -0
  329. parrot/outputs/formats/html.py +147 -0
  330. parrot/outputs/formats/jinja2.py +46 -0
  331. parrot/outputs/formats/json.py +87 -0
  332. parrot/outputs/formats/map.py +933 -0
  333. parrot/outputs/formats/markdown.py +172 -0
  334. parrot/outputs/formats/matplotlib.py +237 -0
  335. parrot/outputs/formats/mixins/__init__.py +0 -0
  336. parrot/outputs/formats/mixins/emaps.py +855 -0
  337. parrot/outputs/formats/plotly.py +341 -0
  338. parrot/outputs/formats/seaborn.py +310 -0
  339. parrot/outputs/formats/table.py +397 -0
  340. parrot/outputs/formats/template_report.py +138 -0
  341. parrot/outputs/formats/yaml.py +125 -0
  342. parrot/outputs/formatter.py +152 -0
  343. parrot/outputs/templates/__init__.py +95 -0
  344. parrot/pipelines/__init__.py +0 -0
  345. parrot/pipelines/abstract.py +210 -0
  346. parrot/pipelines/detector.py +124 -0
  347. parrot/pipelines/models.py +90 -0
  348. parrot/pipelines/planogram.py +3002 -0
  349. parrot/pipelines/table.sql +97 -0
  350. parrot/plugins/__init__.py +106 -0
  351. parrot/plugins/importer.py +80 -0
  352. parrot/py.typed +0 -0
  353. parrot/registry/__init__.py +18 -0
  354. parrot/registry/registry.py +594 -0
  355. parrot/scheduler/__init__.py +1189 -0
  356. parrot/scheduler/models.py +60 -0
  357. parrot/security/__init__.py +16 -0
  358. parrot/security/prompt_injection.py +268 -0
  359. parrot/security/security_events.sql +25 -0
  360. parrot/services/__init__.py +1 -0
  361. parrot/services/mcp/__init__.py +8 -0
  362. parrot/services/mcp/config.py +13 -0
  363. parrot/services/mcp/server.py +295 -0
  364. parrot/services/o365_remote_auth.py +235 -0
  365. parrot/stores/__init__.py +7 -0
  366. parrot/stores/abstract.py +352 -0
  367. parrot/stores/arango.py +1090 -0
  368. parrot/stores/bigquery.py +1377 -0
  369. parrot/stores/cache.py +106 -0
  370. parrot/stores/empty.py +10 -0
  371. parrot/stores/faiss_store.py +1157 -0
  372. parrot/stores/kb/__init__.py +9 -0
  373. parrot/stores/kb/abstract.py +68 -0
  374. parrot/stores/kb/cache.py +165 -0
  375. parrot/stores/kb/doc.py +325 -0
  376. parrot/stores/kb/hierarchy.py +346 -0
  377. parrot/stores/kb/local.py +457 -0
  378. parrot/stores/kb/prompt.py +28 -0
  379. parrot/stores/kb/redis.py +659 -0
  380. parrot/stores/kb/store.py +115 -0
  381. parrot/stores/kb/user.py +374 -0
  382. parrot/stores/models.py +59 -0
  383. parrot/stores/pgvector.py +3 -0
  384. parrot/stores/postgres.py +2853 -0
  385. parrot/stores/utils/__init__.py +0 -0
  386. parrot/stores/utils/chunking.py +197 -0
  387. parrot/telemetry/__init__.py +3 -0
  388. parrot/telemetry/mixin.py +111 -0
  389. parrot/template/__init__.py +3 -0
  390. parrot/template/engine.py +259 -0
  391. parrot/tools/__init__.py +23 -0
  392. parrot/tools/abstract.py +644 -0
  393. parrot/tools/agent.py +363 -0
  394. parrot/tools/arangodbsearch.py +537 -0
  395. parrot/tools/arxiv_tool.py +188 -0
  396. parrot/tools/calculator/__init__.py +3 -0
  397. parrot/tools/calculator/operations/__init__.py +38 -0
  398. parrot/tools/calculator/operations/calculus.py +80 -0
  399. parrot/tools/calculator/operations/statistics.py +76 -0
  400. parrot/tools/calculator/tool.py +150 -0
  401. parrot/tools/cloudwatch.py +988 -0
  402. parrot/tools/codeinterpreter/__init__.py +127 -0
  403. parrot/tools/codeinterpreter/executor.py +371 -0
  404. parrot/tools/codeinterpreter/internals.py +473 -0
  405. parrot/tools/codeinterpreter/models.py +643 -0
  406. parrot/tools/codeinterpreter/prompts.py +224 -0
  407. parrot/tools/codeinterpreter/tool.py +664 -0
  408. parrot/tools/company_info/__init__.py +6 -0
  409. parrot/tools/company_info/tool.py +1138 -0
  410. parrot/tools/correlationanalysis.py +437 -0
  411. parrot/tools/database/abstract.py +286 -0
  412. parrot/tools/database/bq.py +115 -0
  413. parrot/tools/database/cache.py +284 -0
  414. parrot/tools/database/models.py +95 -0
  415. parrot/tools/database/pg.py +343 -0
  416. parrot/tools/databasequery.py +1159 -0
  417. parrot/tools/db.py +1800 -0
  418. parrot/tools/ddgo.py +370 -0
  419. parrot/tools/decorators.py +271 -0
  420. parrot/tools/dftohtml.py +282 -0
  421. parrot/tools/document.py +549 -0
  422. parrot/tools/ecs.py +819 -0
  423. parrot/tools/edareport.py +368 -0
  424. parrot/tools/elasticsearch.py +1049 -0
  425. parrot/tools/employees.py +462 -0
  426. parrot/tools/epson/__init__.py +96 -0
  427. parrot/tools/excel.py +683 -0
  428. parrot/tools/file/__init__.py +13 -0
  429. parrot/tools/file/abstract.py +76 -0
  430. parrot/tools/file/gcs.py +378 -0
  431. parrot/tools/file/local.py +284 -0
  432. parrot/tools/file/s3.py +511 -0
  433. parrot/tools/file/tmp.py +309 -0
  434. parrot/tools/file/tool.py +501 -0
  435. parrot/tools/file_reader.py +129 -0
  436. parrot/tools/flowtask/__init__.py +19 -0
  437. parrot/tools/flowtask/tool.py +761 -0
  438. parrot/tools/gittoolkit.py +508 -0
  439. parrot/tools/google/__init__.py +18 -0
  440. parrot/tools/google/base.py +169 -0
  441. parrot/tools/google/tools.py +1251 -0
  442. parrot/tools/googlelocation.py +5 -0
  443. parrot/tools/googleroutes.py +5 -0
  444. parrot/tools/googlesearch.py +5 -0
  445. parrot/tools/googlesitesearch.py +5 -0
  446. parrot/tools/googlevoice.py +2 -0
  447. parrot/tools/gvoice.py +695 -0
  448. parrot/tools/ibisworld/README.md +225 -0
  449. parrot/tools/ibisworld/__init__.py +11 -0
  450. parrot/tools/ibisworld/tool.py +366 -0
  451. parrot/tools/jiratoolkit.py +1718 -0
  452. parrot/tools/manager.py +1098 -0
  453. parrot/tools/math.py +152 -0
  454. parrot/tools/metadata.py +476 -0
  455. parrot/tools/msteams.py +1621 -0
  456. parrot/tools/msword.py +635 -0
  457. parrot/tools/multidb.py +580 -0
  458. parrot/tools/multistoresearch.py +369 -0
  459. parrot/tools/networkninja.py +167 -0
  460. parrot/tools/nextstop/__init__.py +4 -0
  461. parrot/tools/nextstop/base.py +286 -0
  462. parrot/tools/nextstop/employee.py +733 -0
  463. parrot/tools/nextstop/store.py +462 -0
  464. parrot/tools/notification.py +435 -0
  465. parrot/tools/o365/__init__.py +42 -0
  466. parrot/tools/o365/base.py +295 -0
  467. parrot/tools/o365/bundle.py +522 -0
  468. parrot/tools/o365/events.py +554 -0
  469. parrot/tools/o365/mail.py +992 -0
  470. parrot/tools/o365/onedrive.py +497 -0
  471. parrot/tools/o365/sharepoint.py +641 -0
  472. parrot/tools/openapi_toolkit.py +904 -0
  473. parrot/tools/openweather.py +527 -0
  474. parrot/tools/pdfprint.py +1001 -0
  475. parrot/tools/powerbi.py +518 -0
  476. parrot/tools/powerpoint.py +1113 -0
  477. parrot/tools/pricestool.py +146 -0
  478. parrot/tools/products/__init__.py +246 -0
  479. parrot/tools/prophet_tool.py +171 -0
  480. parrot/tools/pythonpandas.py +630 -0
  481. parrot/tools/pythonrepl.py +910 -0
  482. parrot/tools/qsource.py +436 -0
  483. parrot/tools/querytoolkit.py +395 -0
  484. parrot/tools/quickeda.py +827 -0
  485. parrot/tools/resttool.py +553 -0
  486. parrot/tools/retail/__init__.py +0 -0
  487. parrot/tools/retail/bby.py +528 -0
  488. parrot/tools/sandboxtool.py +703 -0
  489. parrot/tools/sassie/__init__.py +352 -0
  490. parrot/tools/scraping/__init__.py +7 -0
  491. parrot/tools/scraping/docs/select.md +466 -0
  492. parrot/tools/scraping/documentation.md +1278 -0
  493. parrot/tools/scraping/driver.py +436 -0
  494. parrot/tools/scraping/models.py +576 -0
  495. parrot/tools/scraping/options.py +85 -0
  496. parrot/tools/scraping/orchestrator.py +517 -0
  497. parrot/tools/scraping/readme.md +740 -0
  498. parrot/tools/scraping/tool.py +3115 -0
  499. parrot/tools/seasonaldetection.py +642 -0
  500. parrot/tools/shell_tool/__init__.py +5 -0
  501. parrot/tools/shell_tool/actions.py +408 -0
  502. parrot/tools/shell_tool/engine.py +155 -0
  503. parrot/tools/shell_tool/models.py +322 -0
  504. parrot/tools/shell_tool/tool.py +442 -0
  505. parrot/tools/site_search.py +214 -0
  506. parrot/tools/textfile.py +418 -0
  507. parrot/tools/think.py +378 -0
  508. parrot/tools/toolkit.py +298 -0
  509. parrot/tools/webapp_tool.py +187 -0
  510. parrot/tools/whatif.py +1279 -0
  511. parrot/tools/workday/MULTI_WSDL_EXAMPLE.md +249 -0
  512. parrot/tools/workday/__init__.py +6 -0
  513. parrot/tools/workday/models.py +1389 -0
  514. parrot/tools/workday/tool.py +1293 -0
  515. parrot/tools/yfinance_tool.py +306 -0
  516. parrot/tools/zipcode.py +217 -0
  517. parrot/utils/__init__.py +2 -0
  518. parrot/utils/helpers.py +73 -0
  519. parrot/utils/parsers/__init__.py +5 -0
  520. parrot/utils/parsers/toml.c +12078 -0
  521. parrot/utils/parsers/toml.cpython-310-x86_64-linux-gnu.so +0 -0
  522. parrot/utils/parsers/toml.pyx +21 -0
  523. parrot/utils/toml.py +11 -0
  524. parrot/utils/types.cpp +20936 -0
  525. parrot/utils/types.cpython-310-x86_64-linux-gnu.so +0 -0
  526. parrot/utils/types.pyx +213 -0
  527. parrot/utils/uv.py +11 -0
  528. parrot/version.py +10 -0
  529. parrot/yaml-rs/Cargo.lock +350 -0
  530. parrot/yaml-rs/Cargo.toml +19 -0
  531. parrot/yaml-rs/pyproject.toml +19 -0
  532. parrot/yaml-rs/python/yaml_rs/__init__.py +81 -0
  533. parrot/yaml-rs/src/lib.rs +222 -0
  534. requirements/docker-compose.yml +24 -0
  535. requirements/requirements-dev.txt +21 -0
@@ -0,0 +1,58 @@
1
+ from typing import Any
2
+ from abc import ABC, abstractmethod
3
+ from PIL import Image
4
+ from navconfig.logging import logging
5
+
6
+
7
+ class ImagePlugin(ABC):
8
+ """
9
+ ImagePlugin is a base class for image processing plugins.
10
+ It provides a common interface for image processing tasks.
11
+ Subclasses should implement the `analyze` method to define
12
+ the specific image processing logic.
13
+ """
14
+ column_name: str = "image_info"
15
+
16
+ def __init__(self, *args, **kwargs):
17
+ """
18
+ Initialize the ImagePlugin with an optional image path.
19
+
20
+ :param image: Path to the image file.
21
+ """
22
+ self.column_name = kwargs.get("column_name", self.column_name)
23
+ self.logger = logging.getLogger(__name__)
24
+
25
+ @abstractmethod
26
+ async def analyze(self, image: Image.Image, **kwargs) -> Any:
27
+ """
28
+ Analyze the image and perform the desired processing.
29
+
30
+ :param image: Image Bytes opened with PIL Image.open
31
+ """
32
+ raise NotImplementedError(
33
+ "Image Plugin must implement analyze() method."
34
+ )
35
+
36
+ async def __aenter__(self):
37
+ if hasattr(self, "open"):
38
+ await self.open()
39
+ return self
40
+
41
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
42
+ if hasattr(self, "close"):
43
+ await self.close()
44
+ return True
45
+
46
+ async def start(self):
47
+ """
48
+ Start the plugin. This method can be overridden by subclasses
49
+ to perform any initialization or setup tasks.
50
+ """
51
+ pass
52
+ return self
53
+
54
+ async def dispose(self):
55
+ """
56
+ Dispose of the plugin resources.
57
+ """
58
+ return self
@@ -0,0 +1,148 @@
1
+ from pathlib import Path
2
+ from typing import Union, Dict, List
3
+ import yaml
4
+ from pydantic import BaseModel
5
+ from PIL import Image
6
+ from .abstract import ImagePlugin
7
+ from ....clients.google import GoogleModel, GoogleGenAIClient
8
+
9
+
10
+ class AnalysisPlugin(ImagePlugin):
11
+ """Plugin for analyzing images."""
12
+ column_name: str = "image_features"
13
+
14
+ def __init__(self, *args, **kwargs):
15
+ super().__init__(*args, **kwargs)
16
+ self._model_name: str = kwargs.get(
17
+ "model_name", GoogleModel.GEMINI_2_5_FLASH.value
18
+ )
19
+ self.prompt_path = kwargs.get("prompt_path", Path.cwd() / "prompts")
20
+ self.prompt_file: Union[str, Path] = kwargs.get(
21
+ "prompt_file", "default_analysis_prompt.json"
22
+ )
23
+ self._structured_outputs: Dict[str, BaseModel] = kwargs.get(
24
+ "structured_outputs", {}
25
+ )
26
+ self.filter_by: List[str] = kwargs.get(
27
+ "filter_by", ["Boxes on Floor"]
28
+ )
29
+ self.filter_column: str = kwargs.get(
30
+ "filter_column", "category"
31
+ )
32
+
33
+ def _load_model(self, model_name: str) -> BaseModel:
34
+ """ Load the classification or categorization model based on the provided model name.
35
+ This method uses importlib to dynamically import the model class.
36
+ """
37
+ try:
38
+ module_path, class_name = model_name.rsplit('.', 1)
39
+ module = __import__(module_path, fromlist=[class_name])
40
+ return getattr(module, class_name)
41
+ except (ImportError, AttributeError) as e:
42
+ raise ValueError(
43
+ f"Failed to load categorization model: {model_name}. Error: {e}"
44
+ )
45
+
46
+ async def start(self, **kwargs):
47
+ """Initialize the plugin and load the prompt."""
48
+ if isinstance(self.prompt_file, str):
49
+ self.prompt_file = Path(self.prompt_file)
50
+ if not self.prompt_file.is_absolute():
51
+ self.prompt_file = self.prompt_path.joinpath(self.prompt_file)
52
+ if not self.prompt_file.exists():
53
+ raise FileNotFoundError(
54
+ f"Prompt file {self.prompt_file} does not exist."
55
+ )
56
+ # From the prompt File, load the prompt content:
57
+ content = self.prompt_file.read_text()
58
+ # open from YAML file:
59
+ try:
60
+ self.prompt = yaml.safe_load(content).get('analysis_configs', {})
61
+ except Exception as e:
62
+ raise ValueError(
63
+ f"Failed to decode YAML from prompt file {self.prompt_file}. Error: {e}"
64
+ )
65
+ # Iterate over all prompts and load the models:
66
+ for key, value in self.prompt.items():
67
+ structured_output = value.get("structured_output", None)
68
+ if structured_output is None:
69
+ self.logger.warning(
70
+ f"No structured output defined for {key} in prompt file."
71
+ )
72
+ continue
73
+ try:
74
+ self._structured_outputs[key] = self._load_model(structured_output)
75
+ except Exception as e:
76
+ raise ValueError(
77
+ f"Failed to load structured output model for {key}. Error: {e}"
78
+ )
79
+
80
+ def _extract_analysis_results(self, result) -> dict:
81
+ """
82
+ Extract analysis results from AIMessage object and return as dictionary.
83
+
84
+ Args:
85
+ result: AIMessage object with structured_output containing InkWallAnalysis
86
+
87
+ Returns:
88
+ dict: Dictionary with analysis data
89
+ """
90
+ if hasattr(result, 'output') and result.output:
91
+ analysis_result = result.output
92
+ # Convert Pydantic model to dictionary
93
+ return analysis_result.dict()
94
+ else:
95
+ raise ValueError("No output found in the result object.")
96
+
97
+ async def analyze(self, image: Union[Path, Image.Image], **kwargs) -> dict:
98
+ """
99
+ Analyze the ink wall image and perform structured analysis.
100
+
101
+ :param image: Image Bytes opened with PIL Image.open
102
+ :return: A dictionary containing the updated detections column.
103
+ """
104
+ row = kwargs.get("row", None)
105
+ # Check filter condition
106
+ if hasattr(self, 'filter_column') and hasattr(self, 'filter_by'):
107
+ if row[self.filter_column] not in self.filter_by:
108
+ self.logger.info(
109
+ f"Skipping for analysis for row {row.name} with category {row[self.filter_column]}"
110
+ )
111
+ return None
112
+ image_classification = row[self.filter_column]
113
+ if not image_classification:
114
+ self.logger.warning(
115
+ f"Row {row.name} has no valid category for analysis."
116
+ )
117
+ return None
118
+ structured_output = self._structured_outputs.get(image_classification, None)
119
+ if structured_output is None:
120
+ self.logger.error(
121
+ f"No structured output defined for image classification: {image_classification}"
122
+ )
123
+ return None
124
+ # Perform analysis based on the image classification
125
+ try:
126
+ async with GoogleGenAIClient() as client:
127
+ _result = await client.ask_to_image(
128
+ image=image,
129
+ prompt=self.prompt[image_classification]['prompt'],
130
+ structured_output=structured_output,
131
+ model=self._model_name
132
+ )
133
+ if _result and isinstance(_result.output, structured_output):
134
+ content = self._extract_analysis_results(_result)
135
+ return {
136
+ "analysis:": image_classification,
137
+ **content
138
+ }
139
+ else:
140
+ self.logger.error(
141
+ f"Unexpected output format for {image_classification} analysis."
142
+ )
143
+ return None
144
+ except Exception as e:
145
+ self.logger.error(
146
+ f"Error during analysis for {image_classification}: {e}"
147
+ )
148
+ return None
@@ -0,0 +1,150 @@
1
+ from pathlib import Path
2
+ from typing import List, Union, Optional
3
+ from enum import Enum, EnumMeta
4
+ from pydantic import BaseModel, Field
5
+ from PIL import Image
6
+ from .abstract import ImagePlugin
7
+ from ....clients.google import GoogleModel, GoogleGenAIClient
8
+
9
+ DEFAULT_PROMPT = """
10
+ You are an expert in retail image analysis. Your task is to classify the provided image into one of the following categories.
11
+ Please read the definitions carefully and choose the single best fit.
12
+ """
13
+
14
+ def is_model_class(cls) -> bool:
15
+ return isinstance(cls, type) and issubclass(cls, BaseModel)
16
+
17
+
18
+ def is_enum_class(cls) -> bool:
19
+ return isinstance(cls, type) and issubclass(cls, Enum)
20
+
21
+ class ImageCategory(str, Enum):
22
+ """Enumeration for retail image categories."""
23
+ INK_WALL = "Ink Wall"
24
+ FRONT_OF_STORE = "Front of Store"
25
+ SHELVES_WITH_PRODUCTS = "Shelves with Products"
26
+ BOXES_ON_FLOOR = "Boxes on Floor"
27
+ MERCHANDISING_ENDCAP = "Merchandising Endcap"
28
+ OTHER = "Other"
29
+
30
+
31
+ class ImageClassification(BaseModel):
32
+ """Schema for classifying a retail image."""
33
+ category: ImageCategory = Field(
34
+ ...,
35
+ description="The best-fitting category for the image based on the provided definitions."
36
+ )
37
+ confidence_score: float = Field(
38
+ ..., ge=0.0, le=1.0,
39
+ description="The model's confidence in its classification, from 0.0 to 1.0."
40
+ )
41
+ reasoning: str = Field(
42
+ ...,
43
+ description="A brief explanation for why the image was assigned to this category."
44
+ )
45
+
46
+
47
+ class ClassificationPlugin(ImagePlugin):
48
+ """
49
+ ClassificationPlugin is a plugin for performing image classification.
50
+ Uses Gemini 2.5 multimodal model for image classification tasks.
51
+ """
52
+ column_name: str = "image_classifications"
53
+
54
+ def __init__(self, *args, **kwargs):
55
+ super().__init__(*args, **kwargs)
56
+ self._model_name: str = kwargs.get(
57
+ "model_name", GoogleModel.GEMINI_2_5_FLASH.value
58
+ )
59
+ self.prompt: List[str] = kwargs.get("prompt", DEFAULT_PROMPT)
60
+ self.confidence: float = kwargs.get("confidence", 0.5)
61
+ self._classification_model = kwargs.get(
62
+ "classification_model", self._load_classification_model(
63
+ ImageClassification
64
+ )
65
+ )
66
+ self._category_model = kwargs.get(
67
+ "category_model", self._load_category_model(
68
+ ImageCategory
69
+ )
70
+ )
71
+
72
+ def _load_model(self, model_name: str) -> BaseModel:
73
+ """ Load the classification or categorization model based on the provided model name.
74
+ This method uses importlib to dynamically import the model class.
75
+ """
76
+ try:
77
+ module_path, class_name = model_name.rsplit('.', 1)
78
+ module = __import__(module_path, fromlist=[class_name])
79
+ return getattr(module, class_name)
80
+ except (ImportError, AttributeError) as e:
81
+ raise ValueError(
82
+ f"Failed to load categorization model: {model_name}. Error: {e}"
83
+ )
84
+
85
+ def _load_category_model(self, model_name: Union[str, Enum]) -> Enum:
86
+ """
87
+ Load the categorization model based on the provided model name.
88
+
89
+ Category Model is a BaseModel that defines the structure of the categorization result.
90
+ model_name will be the python path where the model is stored.
91
+ for example, resources.models.categorization_models.ImageCategory
92
+ uses importlib to dynamically import the model class.
93
+ """
94
+ if is_enum_class(model_name):
95
+ # Already a Enum instance, return it directly
96
+ return model_name
97
+ elif isinstance(model_name, str):
98
+ # Attempt to import the model class dynamically
99
+ return self._load_model(model_name)
100
+ else:
101
+ raise ValueError(
102
+ "Category model_name must be a string or a Enum instance."
103
+ )
104
+
105
+ def _load_classification_model(self, model_name: Union[str, BaseModel]) -> BaseModel:
106
+ """
107
+ Load the classification model based on the provided model name.
108
+ """
109
+ if is_model_class(model_name):
110
+ # Already a BaseModel instance, return it directly
111
+ return model_name
112
+ elif isinstance(model_name, str):
113
+ # Attempt to import the model class dynamically
114
+ return self._load_model(model_name)
115
+ else:
116
+ raise ValueError(
117
+ "Classification model_name must be a string or a BaseModel instance."
118
+ )
119
+
120
+ async def analyze(self, image: Union[Path, Image.Image], **kwargs) -> dict:
121
+ """
122
+ Analyze the image and classify it into a retail category.
123
+
124
+ :param image: Image Bytes opened with PIL Image.open
125
+ :return: A dictionary containing the classification result.
126
+ """
127
+ async with GoogleGenAIClient() as client:
128
+ _result = await client.ask_to_image(
129
+ image=image,
130
+ prompt=self.prompt,
131
+ structured_output=self._classification_model,
132
+ model=self._model_name
133
+ )
134
+ if _result and isinstance(_result.output, self._classification_model):
135
+ result = _result.output
136
+ # evaluate if confidence_score is above the threshold
137
+ if result.confidence_score < self.confidence:
138
+ self.logger.warning(
139
+ f"Classification confidence {result.confidence_score} "
140
+ f"is below the threshold {self.confidence}. "
141
+ "Returning None."
142
+ )
143
+ return None
144
+ # If the model returns a valid classification result
145
+ return result.dict()
146
+ else:
147
+ self.logger.error(
148
+ "The model did not return a valid classification result."
149
+ )
150
+ return None
@@ -0,0 +1,182 @@
1
+ from pathlib import Path
2
+ from typing import List, Optional, Union
3
+ from pydantic import BaseModel
4
+ import pandas as pd
5
+ from .abstract import ImagePlugin
6
+ from ....clients.google import GoogleModel
7
+ from ....models import ObjectDetectionResult
8
+
9
+
10
+ def is_model_class(cls) -> bool:
11
+ return isinstance(cls, type) and issubclass(cls, BaseModel)
12
+
13
+
14
+ DEFAULT_PROMPT = ''
15
+
16
+
17
+ class ClassifyBase(ImagePlugin):
18
+ """
19
+ ClassifyBase is an Abstract base class for performing image classification.
20
+ Uses Gemini 2.5 multimodal model for image classification tasks.
21
+ """
22
+ column_name: str = "detections"
23
+
24
+ def __init__(self, *args, **kwargs):
25
+ super().__init__(*args, **kwargs)
26
+ self._model_name: str = kwargs.get(
27
+ "model_name", GoogleModel.GEMINI_2_5_FLASH.value
28
+ )
29
+ model = kwargs.get(
30
+ "detection_model",
31
+ ObjectDetectionResult
32
+ )
33
+ self.reference_image: Optional[Path] = kwargs.get("reference_image", None)
34
+ self._detection_model: Optional[BaseModel] = self._load_model(model)
35
+ self.prompt: List[str] = kwargs.get("prompt", DEFAULT_PROMPT)
36
+ self.filter_by: List[str] = kwargs.get(
37
+ "filter_by", ["Boxes on Floor"]
38
+ )
39
+ # Modified: filter_column can be None to disable filtering
40
+ self.filter_column: Optional[str] = kwargs.get("filter_column", None)
41
+
42
+ async def start(self, **kwargs):
43
+ if isinstance(self.reference_image, str):
44
+ self.reference_image = Path(self.reference_image)
45
+ if self.reference_image and not self.reference_image.is_absolute():
46
+ self.reference_image = Path.cwd() / self.reference_image
47
+ if self.reference_image and not self.reference_image.exists():
48
+ self.logger.warning(
49
+ f"Reference image {self.reference_image} does not exist. "
50
+ "Classification may not work as expected."
51
+ )
52
+ self.reference_image = None
53
+
54
+ def _load_model(self, model_name: str) -> BaseModel:
55
+ """ Load the classification or categorization model based on the provided model name.
56
+ This method uses importlib to dynamically import the model class.
57
+ """
58
+ if is_model_class(model_name):
59
+ # Already a BaseModel instance, return it directly
60
+ return model_name
61
+ try:
62
+ module_path, class_name = model_name.rsplit('.', 1)
63
+ module = __import__(module_path, fromlist=[class_name])
64
+ return getattr(module, class_name)
65
+ except (ImportError, AttributeError) as e:
66
+ raise ValueError(
67
+ f"Failed to load categorization model: {model_name}. Error: {e}"
68
+ )
69
+
70
+ def _is_valid_filter_value(self, value):
71
+ """Check if a filter value is valid (not NA/NaN/None)."""
72
+ if pd.isna(value):
73
+ return False
74
+ if value is None:
75
+ return False
76
+ return True
77
+
78
+ def _should_apply_filter(self) -> bool:
79
+ """
80
+ Determine if filtering should be applied based on filter_column.
81
+ Returns False if filter_column is None, True otherwise.
82
+ """
83
+ return self.filter_column is not None
84
+
85
+ def _filter_dataset(self, dataset: pd.DataFrame) -> pd.DataFrame:
86
+ """
87
+ Apply filtering to the dataset if filter_column is specified.
88
+ If filter_column is None, return the entire dataset unchanged.
89
+
90
+ Args:
91
+ dataset: Input DataFrame to potentially filter
92
+
93
+ Returns:
94
+ Filtered DataFrame or original dataset if no filtering should be applied
95
+ """
96
+ if not self._should_apply_filter():
97
+ self.logger.debug(
98
+ "Filter column is None - processing entire dataset without filtering"
99
+ )
100
+ return dataset
101
+
102
+ if self.filter_column not in dataset.columns:
103
+ self.logger.warning(
104
+ f"Filter column '{self.filter_column}' not found in dataset. "
105
+ "Processing entire dataset."
106
+ )
107
+ return dataset
108
+
109
+ # Apply filtering logic
110
+ if not self.filter_by:
111
+ self.logger.warning("filter_by is empty - processing entire dataset")
112
+ return dataset
113
+
114
+ # Filter the dataset based on filter_by values in filter_column
115
+ mask = dataset[self.filter_column].apply(
116
+ lambda x: self._is_valid_filter_value(x) and x in self.filter_by
117
+ )
118
+
119
+ filtered_dataset = dataset[mask].copy()
120
+
121
+ self.logger.info(
122
+ f"Filtered dataset from {len(dataset)} to {len(filtered_dataset)} rows "
123
+ f"using filter_column='{self.filter_column}' with values {self.filter_by}"
124
+ )
125
+
126
+ return filtered_dataset
127
+
128
+ async def process_dataset(self, dataset: pd.DataFrame) -> pd.DataFrame:
129
+ """
130
+ Process the dataset with optional filtering.
131
+ This method should be implemented by subclasses to handle the actual classification.
132
+
133
+ Args:
134
+ dataset: Input DataFrame
135
+
136
+ Returns:
137
+ Processed DataFrame with classification results
138
+ """
139
+ # Apply filtering (or not, depending on filter_column)
140
+ filtered_dataset = self._filter_dataset(dataset)
141
+
142
+ # Placeholder for actual classification logic
143
+ # Subclasses should override this method
144
+ processed_dataset = await self._classify_images(filtered_dataset)
145
+
146
+ return processed_dataset
147
+
148
+ async def _classify_images(self, dataset: pd.DataFrame) -> pd.DataFrame:
149
+ """
150
+ Perform the actual image classification.
151
+ This method should be implemented by subclasses.
152
+
153
+ Args:
154
+ dataset: Filtered (or unfiltered) DataFrame to classify
155
+
156
+ Returns:
157
+ DataFrame with classification results
158
+ """
159
+ raise NotImplementedError("Subclasses must implement _classify_images method")
160
+
161
+ def configure_filtering(
162
+ self,
163
+ filter_column: Optional[str] = None,
164
+ filter_by: Optional[List[str]] = None
165
+ ) -> None:
166
+ """
167
+ Dynamically configure filtering parameters.
168
+
169
+ Args:
170
+ filter_column: Column to filter by. Set to None to disable filtering.
171
+ filter_by: Values to filter by. Only used if filter_column is not None.
172
+ """
173
+ if filter_column is not None:
174
+ self.filter_column = filter_column
175
+
176
+ if filter_by is not None:
177
+ self.filter_by = filter_by
178
+
179
+ self.logger.info(
180
+ f"Filtering configured: filter_column='{self.filter_column}', "
181
+ f"filter_by={self.filter_by if self.filter_column else 'N/A (filtering disabled)'}"
182
+ )
@@ -0,0 +1,150 @@
1
+ from pathlib import Path
2
+ from typing import Union
3
+ from pydantic import BaseModel
4
+ from PIL import Image
5
+ from ....clients.google import GoogleGenAIClient
6
+ from .classifybase import ClassifyBase
7
+
8
+
9
+ def is_model_class(cls) -> bool:
10
+ return isinstance(cls, type) and issubclass(cls, BaseModel)
11
+
12
+
13
+ DEFAULT_PROMPT = """
14
+ Analyze this retail image to identify Epson and competitor products.
15
+
16
+ TARGET BRANDS: Epson, HP, Canon, Brother, Lexmark, Xerox, Ricoh, Kyocera, Sharp
17
+
18
+ TASK:
19
+ 1. Count ALL visible products (boxed and unboxed)
20
+ 2. Identify each product's brand, type, and model (if visible)
21
+ 3. Provide approximate location as normalized coordinates (0.0 to 1.0)
22
+
23
+ IMPORTANT:
24
+ - Focus on printers, scanners, ink cartridges, toner, and related products
25
+ - Include products that are partially visible
26
+ - Use confidence scores (0.0 to 1.0) for each detection
27
+ - Provide bounding boxes as [x1, y1, x2, y2] where (0,0) is top-left, (1,1) is bottom-right
28
+
29
+ Respond with a complete JSON object following the specified schema.
30
+ """ # noqa
31
+
32
+
33
+ class DetectionPlugin(ClassifyBase):
34
+ """
35
+ DetectionPlugin is a plugin for performing image detection.
36
+ Uses Gemini 2.5 multimodal model for image detection tasks.
37
+ """
38
+ column_name: str = "detections"
39
+ section_name: str = "detections"
40
+
41
+ def __init__(self, *args, **kwargs):
42
+ super().__init__(*args, **kwargs)
43
+ self.section_name = kwargs.get("section_name", self.section_name)
44
+
45
+ def _extract_detection_results(self, result) -> dict:
46
+ """
47
+ Extract detection results from AIMessage object and return as dictionary.
48
+
49
+ Args:
50
+ result: AIMessage object with structured_output containing ObjectDetectionResult
51
+
52
+ Returns:
53
+ dict: Dictionary with 'analysis' and 'detections' keys
54
+ """
55
+
56
+ # Check if we have structured output
57
+ print(hasattr(result, 'structured_output'), hasattr(result, 'output'))
58
+ if hasattr(result, 'output') and result.output:
59
+ detection_result = result.output
60
+
61
+ # Convert BoundingBox objects to dictionaries
62
+ detections = []
63
+ for bbox in detection_result.detections:
64
+ detection_dict = {
65
+ "object_id": bbox.object_id,
66
+ "brand": bbox.brand,
67
+ "model": bbox.model,
68
+ "product_type": bbox.product_type,
69
+ "description": bbox.description,
70
+ "confidence": bbox.confidence,
71
+ "bbox": bbox.bbox # Already a list [x1, y1, x2, y2]
72
+ }
73
+ detections.append(detection_dict)
74
+
75
+ # Create the final result dictionary
76
+ detected = False
77
+ if detection_result.total_count > 0:
78
+ detected = True
79
+ r = {
80
+ "analysis": detection_result.analysis,
81
+ "total_count": detection_result.total_count, # Include this for completeness
82
+ "detected": detected,
83
+ "detections": detections
84
+ }
85
+
86
+ return r
87
+
88
+ else:
89
+ # Fallback if no structured output
90
+ return {
91
+ "analysis": "No structured output available",
92
+ "total_count": 0,
93
+ "detections": []
94
+ }
95
+
96
+ async def analyze(self, image: Union[Path, Image.Image], **kwargs) -> dict:
97
+ """
98
+ Analyze the image and classify it into a retail category.
99
+
100
+ :param image: Image Bytes opened with PIL Image.open
101
+ :return: A dictionary containing the classification result.
102
+ """
103
+ row = kwargs.get("row", None)
104
+ detections_column = kwargs.get(self.column_name, None)
105
+ if detections_column is None:
106
+ detections_column = {}
107
+ else:
108
+ # Make a copy to avoid modifying original
109
+ detections_column = detections_column.copy()
110
+ if self.section_name in detections_column:
111
+ del detections_column[self.section_name]
112
+
113
+ if getattr(self, 'filter_column', None) and getattr(self, 'filter_by', None):
114
+ filter_value = row[self.filter_column] if self.filter_column in row else None
115
+ # Check if filter value is valid and not NA
116
+ if not self._is_valid_filter_value(filter_value):
117
+ self.logger.info(
118
+ f"Skipping detection for row {row.name} - filter column '{self.filter_column}' has NA/invalid value"
119
+ )
120
+ return detections_column
121
+ # Now safe to do the comparison
122
+ if filter_value not in self.filter_by:
123
+ self.logger.info(
124
+ f"Skipping detection for row {row.name} with category {filter_value}"
125
+ )
126
+ return detections_column
127
+ # Open the image
128
+ async with GoogleGenAIClient() as client:
129
+ _result = await client.ask_to_image(
130
+ image=image,
131
+ reference_images=[self.reference_image] if self.reference_image else None,
132
+ prompt=self.prompt,
133
+ structured_output=self._detection_model,
134
+ model=self._model_name
135
+ )
136
+ if _result:
137
+ detections = self._extract_detection_results(_result)
138
+ self.logger.info(f"Successfully detected {detections['total_count']} products")
139
+ self.logger.debug(
140
+ f"Analysis: {detections['analysis'][:100]}..."
141
+ )
142
+ detections_column[self.section_name] = detections
143
+ row[self.column_name] = detections_column
144
+ # Return the updated detections column
145
+ return detections_column
146
+ else:
147
+ self.logger.error(
148
+ "The model did not return a valid Detection result."
149
+ )
150
+ return detections_column