agentomatic 0.9.0__tar.gz → 1.0.0__tar.gz

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 (170) hide show
  1. {agentomatic-0.9.0 → agentomatic-1.0.0}/PKG-INFO +1 -1
  2. {agentomatic-0.9.0 → agentomatic-1.0.0}/pyproject.toml +3 -1
  3. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/_version.py +1 -1
  4. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/agents/optimizers.py +6 -3
  5. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/cli/commands.py +5 -1
  6. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/cli/templates.py +5 -5
  7. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/config/settings.py +10 -4
  8. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/core/platform.py +23 -21
  9. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/demo/__init__.py +2 -0
  10. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/optimize/__init__.py +13 -0
  11. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/optimize/failure_analysis.py +5 -2
  12. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/optimize/fitter.py +6 -3
  13. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/optimize/fitter_optimizers.py +7 -6
  14. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/optimize/judges.py +5 -2
  15. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/optimize/llm_caller.py +55 -10
  16. agentomatic-1.0.0/src/agentomatic/optimize/llm_types.py +327 -0
  17. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/optimize/loop.py +11 -4
  18. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/optimize/metrics.py +54 -65
  19. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/optimize/optimizer.py +7 -4
  20. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/optimize/strategies.py +33 -56
  21. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/optimize/synthesizer.py +15 -29
  22. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/plugins/__init__.py +2 -0
  23. agentomatic-1.0.0/src/agentomatic/protocols/__init__.py +17 -0
  24. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/providers/__init__.py +2 -0
  25. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/providers/llm.py +128 -14
  26. agentomatic-1.0.0/src/agentomatic/studio/serve.py +278 -0
  27. agentomatic-1.0.0/src/agentomatic/studio/static/asset-manifest.json +13 -0
  28. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/studio/static/index.html +1 -1
  29. agentomatic-1.0.0/src/agentomatic/studio/static/static/css/main.7a2ec864.css +4 -0
  30. agentomatic-1.0.0/src/agentomatic/studio/static/static/css/main.7a2ec864.css.map +1 -0
  31. agentomatic-1.0.0/src/agentomatic/studio/static/static/js/main.ea13ebe0.js +3 -0
  32. agentomatic-1.0.0/src/agentomatic/studio/static/static/js/main.ea13ebe0.js.map +1 -0
  33. agentomatic-1.0.0/tests/test_llm_types.py +554 -0
  34. agentomatic-0.9.0/src/agentomatic/protocols/__init__.py +0 -3
  35. agentomatic-0.9.0/src/agentomatic/studio/serve.py +0 -98
  36. agentomatic-0.9.0/src/agentomatic/studio/static/asset-manifest.json +0 -13
  37. agentomatic-0.9.0/src/agentomatic/studio/static/static/css/main.8b3c42dd.css +0 -4
  38. agentomatic-0.9.0/src/agentomatic/studio/static/static/css/main.8b3c42dd.css.map +0 -1
  39. agentomatic-0.9.0/src/agentomatic/studio/static/static/js/main.cbb2a8ee.js +0 -3
  40. agentomatic-0.9.0/src/agentomatic/studio/static/static/js/main.cbb2a8ee.js.map +0 -1
  41. {agentomatic-0.9.0 → agentomatic-1.0.0}/LICENSE +0 -0
  42. {agentomatic-0.9.0 → agentomatic-1.0.0}/README.md +0 -0
  43. {agentomatic-0.9.0 → agentomatic-1.0.0}/examples/full_agent/README.md +0 -0
  44. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/__init__.py +0 -0
  45. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/agents/__init__.py +0 -0
  46. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/agents/base.py +0 -0
  47. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/agents/builder.py +0 -0
  48. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/agents/decorators.py +0 -0
  49. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/agents/graph.py +0 -0
  50. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/agents/metrics.py +0 -0
  51. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/agents/mixins/__init__.py +0 -0
  52. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/agents/mixins/dataset.py +0 -0
  53. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/agents/mixins/evaluation.py +0 -0
  54. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/agents/mixins/graph_execution.py +0 -0
  55. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/agents/mixins/observability.py +0 -0
  56. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/agents/mixins/optimization.py +0 -0
  57. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/agents/mixins/serialization.py +0 -0
  58. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/agents/types.py +0 -0
  59. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/cli/__init__.py +0 -0
  60. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/config/__init__.py +0 -0
  61. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/config/defaults.py +0 -0
  62. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/core/__init__.py +0 -0
  63. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/core/lifespan.py +0 -0
  64. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/core/manifest.py +0 -0
  65. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/core/memory_manager.py +0 -0
  66. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/core/registry.py +0 -0
  67. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/core/router_factory.py +0 -0
  68. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/core/schemas.py +0 -0
  69. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/core/state.py +0 -0
  70. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/delegation/__init__.py +0 -0
  71. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/delegation/handoff.py +0 -0
  72. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/delegation/swarm.py +0 -0
  73. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/demo/agent.py +0 -0
  74. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/demo/server.py +0 -0
  75. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/middleware/__init__.py +0 -0
  76. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/middleware/auth.py +0 -0
  77. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/middleware/feedback.py +0 -0
  78. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/middleware/logging.py +0 -0
  79. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/middleware/metrics.py +0 -0
  80. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/middleware/rate_limit.py +0 -0
  81. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/observability/__init__.py +0 -0
  82. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/observability/concurrency.py +0 -0
  83. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/observability/metrics.py +0 -0
  84. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/observability/telemetry.py +0 -0
  85. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/optimize/config.py +0 -0
  86. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/optimize/context.py +0 -0
  87. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/optimize/dashboard.py +0 -0
  88. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/optimize/dataset.py +0 -0
  89. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/optimize/deployment.py +0 -0
  90. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/optimize/eval_contract.py +0 -0
  91. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/optimize/events.py +0 -0
  92. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/optimize/progress.py +0 -0
  93. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/optimize/report.py +0 -0
  94. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/optimize/runner.py +0 -0
  95. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/optimize/search_space.py +0 -0
  96. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/pipelines/__init__.py +0 -0
  97. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/pipelines/builder.py +0 -0
  98. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/pipelines/context.py +0 -0
  99. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/pipelines/engine.py +0 -0
  100. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/pipelines/flow.py +0 -0
  101. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/pipelines/loader.py +0 -0
  102. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/pipelines/models.py +0 -0
  103. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/pipelines/router.py +0 -0
  104. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/pipelines/steps.py +0 -0
  105. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/plugins/ml.py +0 -0
  106. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/plugins/registry.py +0 -0
  107. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/plugins/router.py +0 -0
  108. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/prompts/__init__.py +0 -0
  109. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/prompts/manager.py +0 -0
  110. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/protocols/decorators.py +0 -0
  111. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/providers/embeddings.py +0 -0
  112. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/py.typed +0 -0
  113. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/security/__init__.py +0 -0
  114. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/security/jwt_auth.py +0 -0
  115. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/security/policy.py +0 -0
  116. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/security/zero_trust.py +0 -0
  117. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/stacks/__init__.py +0 -0
  118. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/stacks/defaults.py +0 -0
  119. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/stacks/manager.py +0 -0
  120. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/storage/__init__.py +0 -0
  121. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/storage/base.py +0 -0
  122. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/storage/checkpointer.py +0 -0
  123. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/storage/memory.py +0 -0
  124. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/storage/models.py +0 -0
  125. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/storage/sqlalchemy.py +0 -0
  126. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/studio/__init__.py +0 -0
  127. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/studio/adapter.py +0 -0
  128. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/studio/adapters/__init__.py +0 -0
  129. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/studio/adapters/generic.py +0 -0
  130. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/studio/adapters/graph_agent.py +0 -0
  131. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/studio/adapters/langchain.py +0 -0
  132. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/studio/adapters/langgraph.py +0 -0
  133. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/studio/decorators.py +0 -0
  134. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/studio/graph_inspector.py +0 -0
  135. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/studio/models.py +0 -0
  136. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/studio/router.py +0 -0
  137. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/studio/run_tracker.py +0 -0
  138. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/studio/static/imgs/graph_view.png +0 -0
  139. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/studio/static/imgs/logo.png +0 -0
  140. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/studio/static/imgs/main_screen.png +0 -0
  141. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/studio/static/imgs/main_screen_ok.png +0 -0
  142. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/studio/static/manifest.json +0 -0
  143. /agentomatic-0.9.0/src/agentomatic/studio/static/static/js/main.cbb2a8ee.js.LICENSE.txt → /agentomatic-1.0.0/src/agentomatic/studio/static/static/js/main.ea13ebe0.js.LICENSE.txt +0 -0
  144. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/ui/__init__.py +0 -0
  145. {agentomatic-0.9.0 → agentomatic-1.0.0}/src/agentomatic/ui/chat.py +0 -0
  146. {agentomatic-0.9.0 → agentomatic-1.0.0}/tests/test_agent_dataset.py +0 -0
  147. {agentomatic-0.9.0 → agentomatic-1.0.0}/tests/test_agent_graph.py +0 -0
  148. {agentomatic-0.9.0 → agentomatic-1.0.0}/tests/test_agent_graph_stream.py +0 -0
  149. {agentomatic-0.9.0 → agentomatic-1.0.0}/tests/test_agent_metrics.py +0 -0
  150. {agentomatic-0.9.0 → agentomatic-1.0.0}/tests/test_agentomatic.py +0 -0
  151. {agentomatic-0.9.0 → agentomatic-1.0.0}/tests/test_base_graph_agent.py +0 -0
  152. {agentomatic-0.9.0 → agentomatic-1.0.0}/tests/test_bugfixes_041.py +0 -0
  153. {agentomatic-0.9.0 → agentomatic-1.0.0}/tests/test_cli.py +0 -0
  154. {agentomatic-0.9.0 → agentomatic-1.0.0}/tests/test_coverage_boost.py +0 -0
  155. {agentomatic-0.9.0 → agentomatic-1.0.0}/tests/test_deep_agent.py +0 -0
  156. {agentomatic-0.9.0 → agentomatic-1.0.0}/tests/test_delegation.py +0 -0
  157. {agentomatic-0.9.0 → agentomatic-1.0.0}/tests/test_fitter.py +0 -0
  158. {agentomatic-0.9.0 → agentomatic-1.0.0}/tests/test_flow.py +0 -0
  159. {agentomatic-0.9.0 → agentomatic-1.0.0}/tests/test_integration.py +0 -0
  160. {agentomatic-0.9.0 → agentomatic-1.0.0}/tests/test_optimize.py +0 -0
  161. {agentomatic-0.9.0 → agentomatic-1.0.0}/tests/test_optimizer_dashboard.py +0 -0
  162. {agentomatic-0.9.0 → agentomatic-1.0.0}/tests/test_optimizer_events.py +0 -0
  163. {agentomatic-0.9.0 → agentomatic-1.0.0}/tests/test_optimizer_progress.py +0 -0
  164. {agentomatic-0.9.0 → agentomatic-1.0.0}/tests/test_pipelines.py +0 -0
  165. {agentomatic-0.9.0 → agentomatic-1.0.0}/tests/test_platform_features.py +0 -0
  166. {agentomatic-0.9.0 → agentomatic-1.0.0}/tests/test_plugins.py +0 -0
  167. {agentomatic-0.9.0 → agentomatic-1.0.0}/tests/test_security.py +0 -0
  168. {agentomatic-0.9.0 → agentomatic-1.0.0}/tests/test_stacks.py +0 -0
  169. {agentomatic-0.9.0 → agentomatic-1.0.0}/tests/test_studio.py +0 -0
  170. {agentomatic-0.9.0 → agentomatic-1.0.0}/tests/test_v06_core.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentomatic
3
- Version: 0.9.0
3
+ Version: 1.0.0
4
4
  Summary: Drop agents, not code — zero-code multi-agent API platform framework
5
5
  Project-URL: Homepage, https://github.com/UnicoLab/agentomatic
6
6
  Project-URL: Documentation, https://unicolab.github.io/agentomatic
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agentomatic"
3
- version = "0.9.0"
3
+ version = "1.0.0"
4
4
  description = "Drop agents, not code — zero-code multi-agent API platform framework"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -92,6 +92,8 @@ Changelog = "https://github.com/UnicoLab/agentomatic/blob/main/CHANGELOG.md"
92
92
 
93
93
 
94
94
 
95
+
96
+
95
97
  [dependency-groups]
96
98
  dev = [
97
99
  "pytest>=8",
@@ -2,4 +2,4 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- __version__ = "0.9.0"
5
+ __version__ = "1.0.0"
@@ -12,12 +12,15 @@ from __future__ import annotations
12
12
 
13
13
  import itertools
14
14
  from collections.abc import Sequence
15
- from typing import Any
15
+ from typing import TYPE_CHECKING, Any
16
16
 
17
17
  from loguru import logger
18
18
 
19
19
  from .types import AgentDataset, Metric
20
20
 
21
+ if TYPE_CHECKING:
22
+ from agentomatic.optimize.llm_types import LLMSpec
23
+
21
24
  # ---------------------------------------------------------------------------
22
25
  # NoOpOptimizer
23
26
  # ---------------------------------------------------------------------------
@@ -175,8 +178,8 @@ class PromptFitterBridge:
175
178
  def __init__(
176
179
  self,
177
180
  agent_name: str = "",
178
- task_model: str = "ollama/qwen2.5:7b",
179
- rewrite_model: str = "openai/gpt-4.1",
181
+ task_model: LLMSpec = "ollama/qwen2.5:7b",
182
+ rewrite_model: LLMSpec = "openai/gpt-4.1",
180
183
  **kwargs: Any,
181
184
  ) -> None:
182
185
  self.agent_name = agent_name
@@ -364,7 +364,11 @@ def init(name: str, agents_dir: str, template: str | None, force: bool) -> None:
364
364
  @click.option("--title", default=None, help="Platform title")
365
365
  @click.option("--log-level", default="INFO", help="Log level")
366
366
  @click.option("--with-ui", "--ui", is_flag=True, help="Enable Chainlit debug UI at /chat")
367
- @click.option("--studio", is_flag=True, help="Enable Agentomatic Studio debug UI at /studio/ui")
367
+ @click.option(
368
+ "--studio/--no-studio",
369
+ default=True,
370
+ help="Enable Agentomatic Studio debug UI at /studio/ui (default: on)",
371
+ )
368
372
  def run(
369
373
  agents_dir: str,
370
374
  plugins_dir: str,
@@ -1720,14 +1720,14 @@ def get_template_files(template: str, name: str) -> dict[str, str]:
1720
1720
 
1721
1721
  if template == "basic":
1722
1722
  return {
1723
- "__init__.py": '"""Agent package."""\\n',
1723
+ "__init__.py": '"""Agent package."""\n',
1724
1724
  "agent.py": _class_agent_py(name, "basic"),
1725
1725
  **common,
1726
1726
  }
1727
1727
 
1728
1728
  elif template == "full":
1729
1729
  return {
1730
- "__init__.py": '"""Agent package."""\\n',
1730
+ "__init__.py": '"""Agent package."""\n',
1731
1731
  "agent.py": _class_agent_py(name, "full"),
1732
1732
  "config.py": _config_py(name),
1733
1733
  "schemas.py": _schemas_py(name),
@@ -1744,7 +1744,7 @@ def get_template_files(template: str, name: str) -> dict[str, str]:
1744
1744
 
1745
1745
  elif template == "coordinator":
1746
1746
  return {
1747
- "__init__.py": '"""Agent package."""\\n',
1747
+ "__init__.py": '"""Agent package."""\n',
1748
1748
  "agent.py": _coordinator_agent_py(name),
1749
1749
  "delegation.py": _coordinator_delegation_py(name),
1750
1750
  "security.py": _coordinator_security_py(name),
@@ -1765,7 +1765,7 @@ def get_template_files(template: str, name: str) -> dict[str, str]:
1765
1765
 
1766
1766
  elif template == "rag":
1767
1767
  return {
1768
- "__init__.py": '"""Agent package."""\\n',
1768
+ "__init__.py": '"""Agent package."""\n',
1769
1769
  "agent.py": _class_agent_py(name, "rag"),
1770
1770
  "config.py": _config_py(name),
1771
1771
  "tools.py": _tools_py(name),
@@ -1774,7 +1774,7 @@ def get_template_files(template: str, name: str) -> dict[str, str]:
1774
1774
 
1775
1775
  elif template == "chatbot":
1776
1776
  return {
1777
- "__init__.py": '"""Agent package."""\\n',
1777
+ "__init__.py": '"""Agent package."""\n',
1778
1778
  "agent.py": _class_agent_py(name, "chatbot"),
1779
1779
  "config.py": _config_py(name),
1780
1780
  **common,
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import threading
5
6
  from pathlib import Path
6
7
  from typing import Any
7
8
 
@@ -105,20 +106,25 @@ class PlatformSettings(BaseSettings):
105
106
 
106
107
 
107
108
  _settings: PlatformSettings | None = None
109
+ _settings_lock = threading.Lock()
108
110
 
109
111
 
110
112
  def get_settings() -> PlatformSettings:
111
113
  """Get or create the singleton settings instance."""
112
114
  global _settings
113
- if _settings is None:
114
- _settings = PlatformSettings() # type: ignore[call-arg]
115
- return _settings
115
+ if _settings is not None:
116
+ return _settings
117
+ with _settings_lock:
118
+ if _settings is None:
119
+ _settings = PlatformSettings() # type: ignore[call-arg]
120
+ return _settings
116
121
 
117
122
 
118
123
  def reset_settings() -> None:
119
124
  """Reset settings singleton (for testing)."""
120
125
  global _settings
121
- _settings = None
126
+ with _settings_lock:
127
+ _settings = None
122
128
 
123
129
 
124
130
  def load_environment(env_file: str | Path | None = None) -> None:
@@ -790,29 +790,31 @@ class AgentPlatform:
790
790
  app.include_router(studio_router)
791
791
  logger.info("🎨 Studio API mounted at /studio/")
792
792
 
793
- # Mount the built Studio UI (if available)
794
- from agentomatic.studio.serve import is_studio_available, mount_studio_ui
795
-
796
- if is_studio_available():
797
- mount_studio_ui(app)
798
-
799
- # Convenience redirects: /studio → /studio/ui/
800
- from fastapi.responses import RedirectResponse
801
-
802
- @app.get("/studio", include_in_schema=False)
803
- @app.get("/studio/", include_in_schema=False)
804
- async def _studio_redirect() -> RedirectResponse:
805
- return RedirectResponse(url="/studio/ui/")
806
-
807
- logger.info("🎨 Studio UI + redirects mounted")
808
- else:
809
- logger.info(
810
- "🎨 Studio API is running but UI assets not bundled. "
811
- "Run the frontend separately or build with: "
812
- "./scripts/build_studio.sh"
813
- )
793
+ # Mount the built Studio UI (always — shows helpful
794
+ # error page if assets are missing)
795
+ from agentomatic.studio.serve import mount_studio_ui
796
+
797
+ mount_studio_ui(app)
798
+
799
+ # Convenience redirects: /studio → /studio/ui/
800
+ from fastapi.responses import RedirectResponse
801
+
802
+ @app.get("/studio", include_in_schema=False)
803
+ @app.get("/studio/", include_in_schema=False)
804
+ async def _studio_redirect() -> RedirectResponse:
805
+ return RedirectResponse(url="/studio/ui/")
806
+
814
807
  except Exception as exc: # noqa: BLE001
815
808
  logger.warning(f"Studio setup failed: {exc}")
809
+ else:
810
+ # Studio disabled — mount informative error pages so users
811
+ # hitting /studio/ui/ get guidance instead of a bare 404.
812
+ try:
813
+ from agentomatic.studio.serve import mount_studio_disabled_page
814
+
815
+ mount_studio_disabled_page(app)
816
+ except Exception: # noqa: BLE001
817
+ pass # Non-critical — don't log noise
816
818
 
817
819
  self._app = app
818
820
  return app
@@ -1 +1,3 @@
1
1
  """Built-in demo agent for testing Agentomatic Studio end-to-end."""
2
+
3
+ from __future__ import annotations
@@ -67,6 +67,14 @@ from agentomatic.optimize.judges import (
67
67
  LocalJudgeMetric,
68
68
  MultiJudgePanel,
69
69
  )
70
+
71
+ # ── Pluggable LLM type system ────────────────────────────────────────
72
+ from agentomatic.optimize.llm_types import (
73
+ LLMCallable,
74
+ LLMSpec,
75
+ call_llm,
76
+ call_llm_json,
77
+ )
70
78
  from agentomatic.optimize.loop import (
71
79
  AVAILABLE_STRATEGIES,
72
80
  LoopResult,
@@ -112,6 +120,11 @@ from agentomatic.optimize.synthesizer import (
112
120
  )
113
121
 
114
122
  __all__ = [
123
+ # ── Pluggable LLM type system ─────────────────────────────────
124
+ "LLMSpec",
125
+ "LLMCallable",
126
+ "call_llm",
127
+ "call_llm_json",
115
128
  # Core — local-first optimization loop
116
129
  "PromptOptimizationLoop",
117
130
  "LoopResult",
@@ -13,10 +13,13 @@ Classes
13
13
  from __future__ import annotations
14
14
 
15
15
  from dataclasses import dataclass, field
16
- from typing import Any
16
+ from typing import TYPE_CHECKING, Any
17
17
 
18
18
  from loguru import logger
19
19
 
20
+ if TYPE_CHECKING:
21
+ from agentomatic.optimize.llm_types import LLMSpec
22
+
20
23
  # =====================================================================
21
24
  # Failure Clustering
22
25
  # =====================================================================
@@ -71,7 +74,7 @@ class FailureClusterer:
71
74
 
72
75
  def __init__(
73
76
  self,
74
- model: str = "ollama/qwen2.5:7b",
77
+ model: LLMSpec = "ollama/qwen2.5:7b",
75
78
  max_clusters: int = 8,
76
79
  max_examples_per_cluster: int = 3,
77
80
  ) -> None:
@@ -43,7 +43,7 @@ import json
43
43
  import time
44
44
  import uuid
45
45
  from pathlib import Path
46
- from typing import Any
46
+ from typing import TYPE_CHECKING, Any
47
47
 
48
48
  from loguru import logger
49
49
 
@@ -77,6 +77,9 @@ from agentomatic.optimize.metrics import (
77
77
  from agentomatic.optimize.runner import AgentRunner, RunResult
78
78
  from agentomatic.optimize.search_space import PromptSearchSpace
79
79
 
80
+ if TYPE_CHECKING:
81
+ from agentomatic.optimize.llm_types import LLMSpec
82
+
80
83
  # ---------------------------------------------------------------------------
81
84
  # Constants
82
85
  # ---------------------------------------------------------------------------
@@ -164,8 +167,8 @@ class PromptFitter:
164
167
  self,
165
168
  agent: str,
166
169
  base_prompt_version: str = "v1",
167
- task_model: str = "ollama/qwen2.5:7b",
168
- rewrite_model: str | None = None,
170
+ task_model: LLMSpec = "ollama/qwen2.5:7b",
171
+ rewrite_model: LLMSpec | None = None,
169
172
  local_judges: list[str] | None = None,
170
173
  search_space: PromptSearchSpace | None = None,
171
174
  optimizer: str | Any = "gepa_like",
@@ -51,6 +51,7 @@ from agentomatic.optimize.search_space import PromptSearchSpace
51
51
 
52
52
  if TYPE_CHECKING:
53
53
  from agentomatic.optimize.context import OptimizationContext
54
+ from agentomatic.optimize.llm_types import LLMSpec
54
55
 
55
56
  # =====================================================================
56
57
  # Perspectives used by MIPRO-like instruction generation
@@ -177,7 +178,7 @@ class RewriteOptimizer(BaseFitterOptimizer):
177
178
  """
178
179
 
179
180
  name: str = "rewrite"
180
- model: str = "ollama/qwen2.5:7b"
181
+ model: LLMSpec = "ollama/qwen2.5:7b"
181
182
  max_failures: int = 5
182
183
 
183
184
  async def propose(
@@ -557,7 +558,7 @@ class MIPROLikeOptimizer(BaseFitterOptimizer):
557
558
  """
558
559
 
559
560
  name: str = "mipro_like"
560
- model: str = "ollama/qwen2.5:7b"
561
+ model: LLMSpec = "ollama/qwen2.5:7b"
561
562
  n_instruction_candidates: int = 5
562
563
  n_few_shot_candidates: int = 3
563
564
  fuse_top_k: int = 2
@@ -858,8 +859,8 @@ class GEPALikeOptimizer(BaseFitterOptimizer):
858
859
  """
859
860
 
860
861
  name: str = "gepa_like"
861
- judge_model: str = "ollama/qwen2.5:7b"
862
- rewrite_model: str = "ollama/qwen2.5:7b"
862
+ judge_model: LLMSpec = "ollama/qwen2.5:7b"
863
+ rewrite_model: LLMSpec = "ollama/qwen2.5:7b"
863
864
  n_mutations: int = 3
864
865
 
865
866
  # Default mutation aspects when fewer categories are found in feedback
@@ -1444,8 +1445,8 @@ def _build_failure_summary(
1444
1445
 
1445
1446
  def resolve_fitter_optimizer(
1446
1447
  name: str | BaseFitterOptimizer,
1447
- model: str = "ollama/qwen2.5:7b",
1448
- rewrite_model: str | None = None,
1448
+ model: LLMSpec = "ollama/qwen2.5:7b",
1449
+ rewrite_model: LLMSpec | None = None,
1449
1450
  **kwargs: Any,
1450
1451
  ) -> BaseFitterOptimizer:
1451
1452
  """Resolve a fitter optimizer by name or pass through an existing instance.
@@ -15,12 +15,15 @@ from __future__ import annotations
15
15
 
16
16
  import asyncio
17
17
  from dataclasses import dataclass, field
18
- from typing import Any
18
+ from typing import TYPE_CHECKING, Any
19
19
 
20
20
  from loguru import logger
21
21
 
22
22
  from agentomatic.optimize.metrics import BaseMetric, EvalResult, MetricResult
23
23
 
24
+ if TYPE_CHECKING:
25
+ from agentomatic.optimize.llm_types import LLMSpec
26
+
24
27
  # =====================================================================
25
28
  # Local SLM Judge
26
29
  # =====================================================================
@@ -48,7 +51,7 @@ class LocalJudgeMetric(BaseMetric):
48
51
  def __init__(
49
52
  self,
50
53
  name: str = "local_judge",
51
- model: str = "ollama/qwen2.5:7b",
54
+ model: LLMSpec = "ollama/qwen2.5:7b",
52
55
  criteria: str = "Evaluate the quality and correctness of the response.",
53
56
  dimensions: list[str] | None = None,
54
57
  weight: float = 1.0,
@@ -7,6 +7,8 @@ calling Ollama directly, every call goes through
7
7
  :class:`LLMCaller` which handles:
8
8
 
9
9
  * **Provider routing** — ``ollama/``, ``openai/``, ``litellm/`` prefixes.
10
+ * **Callable dispatch** — custom async/sync callables and LangChain
11
+ models are transparently supported via :data:`LLMSpec`.
10
12
  * **Graceful degradation** — failures are logged and an empty string
11
13
  (or empty dict for JSON calls) is returned so the caller never crashes.
12
14
  * **JSON extraction** — :meth:`LLMCaller.call_with_json` strips
@@ -16,16 +18,23 @@ Example
16
18
  -------
17
19
  >>> text = await LLMCaller.call("ollama/mistral:7b", "Say hello")
18
20
  >>> data = await LLMCaller.call_with_json("openai/gpt-4o-mini", "Return {\"ok\": true}")
21
+ >>>
22
+ >>> # Custom callable also works:
23
+ >>> async def my_llm(prompt, *, system_prompt=None): return "Hello!"
24
+ >>> text = await LLMCaller.call(my_llm, "Say hello")
19
25
  """
20
26
 
21
27
  from __future__ import annotations
22
28
 
23
29
  import json
24
30
  import re
25
- from typing import Any
31
+ from typing import TYPE_CHECKING, Any
26
32
 
27
33
  from loguru import logger
28
34
 
35
+ if TYPE_CHECKING:
36
+ from agentomatic.optimize.llm_types import LLMSpec
37
+
29
38
  # =====================================================================
30
39
  # Constants
31
40
  # =====================================================================
@@ -102,6 +111,10 @@ class LLMCaller:
102
111
 
103
112
  All methods are **static / async** so that no instance state is
104
113
  required — just call ``await LLMCaller.call(model, prompt)``.
114
+
115
+ Accepts both **string model specs** (e.g. ``"ollama/mistral:7b"``) and
116
+ **custom callables** (async functions, LangChain models, etc.) via the
117
+ :data:`~agentomatic.optimize.llm_types.LLMSpec` union type.
105
118
  """
106
119
 
107
120
  # -----------------------------------------------------------------
@@ -110,7 +123,7 @@ class LLMCaller:
110
123
 
111
124
  @staticmethod
112
125
  async def call(
113
- model: str,
126
+ model: LLMSpec,
114
127
  prompt: str,
115
128
  *,
116
129
  system_prompt: str | None = None,
@@ -124,8 +137,8 @@ class LLMCaller:
124
137
  Parameters
125
138
  ----------
126
139
  model:
127
- Model specification, optionally prefixed with the provider
128
- (e.g. ``"ollama/mistral:7b"``, ``"openai/gpt-4o-mini"``).
140
+ Model specification a string like ``"ollama/mistral:7b"``
141
+ or a callable / LangChain model matching :data:`LLMSpec`.
129
142
  prompt:
130
143
  User prompt text.
131
144
  system_prompt:
@@ -145,6 +158,21 @@ class LLMCaller:
145
158
  str
146
159
  Generated text, or ``""`` on failure.
147
160
  """
161
+ # ── Non-string: delegate to unified callable dispatcher ──
162
+ if not isinstance(model, str):
163
+ from agentomatic.optimize.llm_types import call_llm
164
+
165
+ return await call_llm(
166
+ model,
167
+ prompt,
168
+ system_prompt=system_prompt,
169
+ temperature=temperature,
170
+ max_tokens=max_tokens,
171
+ json_mode=json_mode,
172
+ timeout=timeout,
173
+ )
174
+
175
+ # ── String: route to provider backend ────────────────────
148
176
  provider, model_name = parse_model_spec(model)
149
177
  try:
150
178
  if provider == "ollama":
@@ -196,7 +224,7 @@ class LLMCaller:
196
224
 
197
225
  @staticmethod
198
226
  async def call_with_json(
199
- model: str,
227
+ model: LLMSpec,
200
228
  prompt: str,
201
229
  *,
202
230
  system_prompt: str | None = None,
@@ -210,10 +238,12 @@ class LLMCaller:
210
238
  If the first attempt fails to parse, the call is retried up to
211
239
  *max_retries* times. Returns an empty ``{}`` on total failure.
212
240
 
241
+ Accepts both string model specs and callables via :data:`LLMSpec`.
242
+
213
243
  Parameters
214
244
  ----------
215
245
  model:
216
- Model specification (see :meth:`call`).
246
+ Model specification — string or callable (see :meth:`call`).
217
247
  prompt:
218
248
  User prompt — a JSON-return instruction is appended automatically.
219
249
  system_prompt:
@@ -230,9 +260,23 @@ class LLMCaller:
230
260
  dict[str, Any]
231
261
  Parsed JSON object, or ``{}`` on failure.
232
262
  """
263
+ # For non-string models, delegate to call_llm_json
264
+ if not isinstance(model, str):
265
+ from agentomatic.optimize.llm_types import call_llm_json
266
+
267
+ return await call_llm_json(
268
+ model,
269
+ prompt,
270
+ system_prompt=system_prompt,
271
+ temperature=temperature,
272
+ max_retries=max_retries,
273
+ timeout=timeout,
274
+ )
275
+
233
276
  json_instruction = (
234
277
  "\n\nIMPORTANT: Reply with ONLY a valid JSON object. "
235
- "Do not include any other text, explanation, or markdown formatting."
278
+ "Do not include any other text, explanation, or "
279
+ "markdown formatting."
236
280
  )
237
281
  augmented_prompt = prompt + json_instruction
238
282
 
@@ -329,9 +373,10 @@ async def _call_openai(
329
373
  kwargs["response_format"] = {"type": "json_object"}
330
374
 
331
375
  client = openai.AsyncOpenAI(timeout=timeout)
332
- response = await client.chat.completions.create(**kwargs)
333
- choice = response.choices[0]
334
- return (choice.message.content or "").strip()
376
+ async with client as c:
377
+ response = await c.chat.completions.create(**kwargs)
378
+ choice = response.choices[0]
379
+ return (choice.message.content or "").strip()
335
380
 
336
381
 
337
382
  async def _call_litellm(