quantum-framework 0.18.0__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 (333) hide show
  1. quantum/__init__.py +1 -0
  2. quantum/cli/__init__.py +2 -0
  3. quantum/cli/admin.py +131 -0
  4. quantum/cli/check.py +223 -0
  5. quantum/cli/commands/__init__.py +15 -0
  6. quantum/cli/commands/build.py +414 -0
  7. quantum/cli/commands/dev.py +185 -0
  8. quantum/cli/commands/docs.py +329 -0
  9. quantum/cli/commands/lint.py +523 -0
  10. quantum/cli/commands/migrate.py +548 -0
  11. quantum/cli/commands/new.py +622 -0
  12. quantum/cli/commands/serve.py +190 -0
  13. quantum/cli/commands/test.py +193 -0
  14. quantum/cli/deploy.py +810 -0
  15. quantum/cli/hot_reload.py +951 -0
  16. quantum/cli/jobs.py +356 -0
  17. quantum/cli/mq.py +582 -0
  18. quantum/cli/pkg.py +390 -0
  19. quantum/cli/runner.py +565 -0
  20. quantum/cli/server_process.py +159 -0
  21. quantum/cli/utils.py +334 -0
  22. quantum/compiler/__init__.py +30 -0
  23. quantum/compiler/base_generator.py +367 -0
  24. quantum/compiler/cli.py +295 -0
  25. quantum/compiler/expression_transformer.py +444 -0
  26. quantum/compiler/javascript/__init__.py +10 -0
  27. quantum/compiler/javascript/generator.py +659 -0
  28. quantum/compiler/optimizer.py +270 -0
  29. quantum/compiler/python/__init__.py +10 -0
  30. quantum/compiler/python/generator.py +883 -0
  31. quantum/compiler/python/runtime.py +863 -0
  32. quantum/compiler/transpiler.py +330 -0
  33. quantum/core/__init__.py +3 -0
  34. quantum/core/ast_nodes.py +2619 -0
  35. quantum/core/config_env.py +54 -0
  36. quantum/core/docs_links.py +8 -0
  37. quantum/core/expression_diagnostics.py +87 -0
  38. quantum/core/expression_stdlib.py +208 -0
  39. quantum/core/expressions.py +677 -0
  40. quantum/core/features/agents/src/__init__.py +30 -0
  41. quantum/core/features/agents/src/ast_node.py +540 -0
  42. quantum/core/features/conditionals/src/__init__.py +8 -0
  43. quantum/core/features/conditionals/src/ast_node.py +68 -0
  44. quantum/core/features/data_import/src/__init__.py +0 -0
  45. quantum/core/features/data_import/src/ast_node.py +292 -0
  46. quantum/core/features/data_import/src/runtime.py +533 -0
  47. quantum/core/features/dump/src/__init__.py +12 -0
  48. quantum/core/features/dump/src/ast_node.py +106 -0
  49. quantum/core/features/dump/src/parser.py +61 -0
  50. quantum/core/features/dump/src/runtime.py +246 -0
  51. quantum/core/features/functions/src/__init__.py +8 -0
  52. quantum/core/features/functions/src/ast_node.py +149 -0
  53. quantum/core/features/game_engine_2d/src/__init__.py +19 -0
  54. quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
  55. quantum/core/features/game_engine_2d/src/parser.py +983 -0
  56. quantum/core/features/invocation/src/__init__.py +0 -0
  57. quantum/core/features/invocation/src/ast_node.py +147 -0
  58. quantum/core/features/invocation/src/runtime.py +332 -0
  59. quantum/core/features/knowledge_base/src/__init__.py +6 -0
  60. quantum/core/features/knowledge_base/src/ast_node.py +113 -0
  61. quantum/core/features/knowledge_base/src/parser.py +82 -0
  62. quantum/core/features/logging/src/__init__.py +12 -0
  63. quantum/core/features/logging/src/ast_node.py +111 -0
  64. quantum/core/features/logging/src/parser.py +50 -0
  65. quantum/core/features/logging/src/runtime.py +190 -0
  66. quantum/core/features/loops/src/__init__.py +8 -0
  67. quantum/core/features/loops/src/ast_node.py +60 -0
  68. quantum/core/features/query/src/__init__.py +0 -0
  69. quantum/core/features/query/src/query_validators.py +20 -0
  70. quantum/core/features/state_management/src/__init__.py +11 -0
  71. quantum/core/features/state_management/src/ast_node.py +121 -0
  72. quantum/core/features/terminal_engine/src/__init__.py +21 -0
  73. quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
  74. quantum/core/features/terminal_engine/src/parser.py +361 -0
  75. quantum/core/features/testing_engine/src/__init__.py +41 -0
  76. quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
  77. quantum/core/features/testing_engine/src/parser.py +604 -0
  78. quantum/core/features/theming/src/__init__.py +48 -0
  79. quantum/core/features/theming/src/ast_node.py +137 -0
  80. quantum/core/features/theming/src/presets.py +405 -0
  81. quantum/core/features/ui_engine/src/__init__.py +20 -0
  82. quantum/core/features/ui_engine/src/ast_nodes.py +1913 -0
  83. quantum/core/features/ui_engine/src/parser.py +1192 -0
  84. quantum/core/features/websocket/src/__init__.py +24 -0
  85. quantum/core/features/websocket/src/ast_node.py +247 -0
  86. quantum/core/guards.py +53 -0
  87. quantum/core/html_compat.py +299 -0
  88. quantum/core/parser.py +1551 -0
  89. quantum/core/parser_registry.py +213 -0
  90. quantum/core/parsers/__init__.py +76 -0
  91. quantum/core/parsers/ai/__init__.py +12 -0
  92. quantum/core/parsers/ai/agent_parser.py +106 -0
  93. quantum/core/parsers/ai/knowledge_parser.py +86 -0
  94. quantum/core/parsers/ai/llm_parser.py +78 -0
  95. quantum/core/parsers/ai/team_parser.py +88 -0
  96. quantum/core/parsers/base.py +322 -0
  97. quantum/core/parsers/composition/__init__.py +10 -0
  98. quantum/core/parsers/composition/import_parser.py +50 -0
  99. quantum/core/parsers/composition/slot_parser.py +48 -0
  100. quantum/core/parsers/control_flow/__init__.py +11 -0
  101. quantum/core/parsers/control_flow/if_parser.py +71 -0
  102. quantum/core/parsers/control_flow/loop_parser.py +105 -0
  103. quantum/core/parsers/control_flow/set_parser.py +93 -0
  104. quantum/core/parsers/data/__init__.py +12 -0
  105. quantum/core/parsers/data/data_parser.py +229 -0
  106. quantum/core/parsers/data/invoke_parser.py +133 -0
  107. quantum/core/parsers/data/query_parser.py +213 -0
  108. quantum/core/parsers/data/transaction_parser.py +86 -0
  109. quantum/core/parsers/events/__init__.py +9 -0
  110. quantum/core/parsers/events/dispatch_event_parser.py +44 -0
  111. quantum/core/parsers/forms/__init__.py +11 -0
  112. quantum/core/parsers/forms/action_parser.py +79 -0
  113. quantum/core/parsers/forms/flash_parser.py +34 -0
  114. quantum/core/parsers/forms/redirect_parser.py +33 -0
  115. quantum/core/parsers/functions/__init__.py +11 -0
  116. quantum/core/parsers/functions/function_parser.py +98 -0
  117. quantum/core/parsers/functions/param_parser.py +73 -0
  118. quantum/core/parsers/functions/return_parser.py +31 -0
  119. quantum/core/parsers/html/__init__.py +10 -0
  120. quantum/core/parsers/html/component_call_parser.py +130 -0
  121. quantum/core/parsers/html/html_parser.py +115 -0
  122. quantum/core/parsers/jobs/__init__.py +11 -0
  123. quantum/core/parsers/jobs/job_parser.py +71 -0
  124. quantum/core/parsers/jobs/schedule_parser.py +62 -0
  125. quantum/core/parsers/jobs/thread_parser.py +57 -0
  126. quantum/core/parsers/messaging/__init__.py +17 -0
  127. quantum/core/parsers/messaging/message_ack_parser.py +30 -0
  128. quantum/core/parsers/messaging/message_nack_parser.py +30 -0
  129. quantum/core/parsers/messaging/message_parser.py +114 -0
  130. quantum/core/parsers/messaging/queue_parser.py +61 -0
  131. quantum/core/parsers/messaging/websocket_parser.py +121 -0
  132. quantum/core/parsers/routing/__init__.py +9 -0
  133. quantum/core/parsers/routing/route_parser.py +41 -0
  134. quantum/core/parsers/scripting/__init__.py +12 -0
  135. quantum/core/parsers/scripting/pyclass_parser.py +62 -0
  136. quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
  137. quantum/core/parsers/scripting/pyimport_parser.py +52 -0
  138. quantum/core/parsers/scripting/python_parser.py +49 -0
  139. quantum/core/parsers/services/__init__.py +12 -0
  140. quantum/core/parsers/services/dump_parser.py +52 -0
  141. quantum/core/parsers/services/file_parser.py +48 -0
  142. quantum/core/parsers/services/log_parser.py +46 -0
  143. quantum/core/parsers/services/mail_parser.py +65 -0
  144. quantum/core/tiers.py +164 -0
  145. quantum/core/xml_lines.py +81 -0
  146. quantum/packages/__init__.py +28 -0
  147. quantum/packages/manager.py +413 -0
  148. quantum/packages/manifest.py +351 -0
  149. quantum/packages/registry.py +399 -0
  150. quantum/packages/resolver.py +336 -0
  151. quantum/plugins/__init__.py +33 -0
  152. quantum/plugins/hooks.py +329 -0
  153. quantum/plugins/loader.py +479 -0
  154. quantum/plugins/manifest.py +336 -0
  155. quantum/plugins/registry.py +371 -0
  156. quantum/runtime/__init__.py +25 -0
  157. quantum/runtime/action_handler.py +532 -0
  158. quantum/runtime/adapters/__init__.py +88 -0
  159. quantum/runtime/adapters/memory_adapter.py +690 -0
  160. quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
  161. quantum/runtime/adapters/redis_adapter.py +582 -0
  162. quantum/runtime/adapters/sqlite_adapter.py +414 -0
  163. quantum/runtime/agent_service.py +1133 -0
  164. quantum/runtime/ast_cache.py +506 -0
  165. quantum/runtime/auth_service.py +267 -0
  166. quantum/runtime/component.py +1048 -0
  167. quantum/runtime/component_composer.py +133 -0
  168. quantum/runtime/component_resolver.py +174 -0
  169. quantum/runtime/conditions.py +54 -0
  170. quantum/runtime/database_service.py +595 -0
  171. quantum/runtime/db_schema.py +100 -0
  172. quantum/runtime/dev_panel.py +178 -0
  173. quantum/runtime/email_service.py +162 -0
  174. quantum/runtime/error_handler.py +295 -0
  175. quantum/runtime/execution_context.py +283 -0
  176. quantum/runtime/executor_registry.py +178 -0
  177. quantum/runtime/executors/__init__.py +71 -0
  178. quantum/runtime/executors/ai/__init__.py +12 -0
  179. quantum/runtime/executors/ai/agent_executor.py +217 -0
  180. quantum/runtime/executors/ai/knowledge_executor.py +114 -0
  181. quantum/runtime/executors/ai/llm_executor.py +153 -0
  182. quantum/runtime/executors/ai/team_executor.py +171 -0
  183. quantum/runtime/executors/base.py +264 -0
  184. quantum/runtime/executors/control_flow/__init__.py +12 -0
  185. quantum/runtime/executors/control_flow/if_executor.py +98 -0
  186. quantum/runtime/executors/control_flow/loop_executor.py +308 -0
  187. quantum/runtime/executors/control_flow/redirect_executor.py +43 -0
  188. quantum/runtime/executors/control_flow/set_executor.py +464 -0
  189. quantum/runtime/executors/data/__init__.py +12 -0
  190. quantum/runtime/executors/data/data_executor.py +156 -0
  191. quantum/runtime/executors/data/invoke_executor.py +228 -0
  192. quantum/runtime/executors/data/query_executor.py +290 -0
  193. quantum/runtime/executors/data/transaction_executor.py +91 -0
  194. quantum/runtime/executors/jobs/__init__.py +11 -0
  195. quantum/runtime/executors/jobs/job_executor.py +190 -0
  196. quantum/runtime/executors/jobs/schedule_executor.py +132 -0
  197. quantum/runtime/executors/jobs/thread_executor.py +127 -0
  198. quantum/runtime/executors/messaging/__init__.py +17 -0
  199. quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
  200. quantum/runtime/executors/messaging/message_executor.py +174 -0
  201. quantum/runtime/executors/messaging/queue_executor.py +103 -0
  202. quantum/runtime/executors/messaging/websocket_executor.py +197 -0
  203. quantum/runtime/executors/scripting/__init__.py +11 -0
  204. quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
  205. quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
  206. quantum/runtime/executors/scripting/python_executor.py +249 -0
  207. quantum/runtime/executors/services/__init__.py +12 -0
  208. quantum/runtime/executors/services/dump_executor.py +72 -0
  209. quantum/runtime/executors/services/file_executor.py +89 -0
  210. quantum/runtime/executors/services/log_executor.py +77 -0
  211. quantum/runtime/executors/services/mail_executor.py +81 -0
  212. quantum/runtime/expression_cache.py +498 -0
  213. quantum/runtime/file_upload_service.py +326 -0
  214. quantum/runtime/function_registry.py +118 -0
  215. quantum/runtime/game_builder.py +166 -0
  216. quantum/runtime/game_code_generator.py +2371 -0
  217. quantum/runtime/game_templates.py +2006 -0
  218. quantum/runtime/godot_code_generator.py +4681 -0
  219. quantum/runtime/godot_templates.py +1449 -0
  220. quantum/runtime/job_executor.py +1599 -0
  221. quantum/runtime/knowledge_service.py +546 -0
  222. quantum/runtime/llm_cache.py +100 -0
  223. quantum/runtime/llm_providers.py +704 -0
  224. quantum/runtime/llm_service.py +287 -0
  225. quantum/runtime/local_server.py +28 -0
  226. quantum/runtime/logging_setup.py +141 -0
  227. quantum/runtime/message_broker.py +364 -0
  228. quantum/runtime/message_queue_service.py +571 -0
  229. quantum/runtime/param_validation.py +218 -0
  230. quantum/runtime/pypy_compat.py +317 -0
  231. quantum/runtime/python_bridge.py +698 -0
  232. quantum/runtime/query_validators.py +304 -0
  233. quantum/runtime/renderer.py +734 -0
  234. quantum/runtime/service_container.py +470 -0
  235. quantum/runtime/table_params.py +163 -0
  236. quantum/runtime/terminal_builder.py +76 -0
  237. quantum/runtime/terminal_code_generator.py +607 -0
  238. quantum/runtime/terminal_templates.py +243 -0
  239. quantum/runtime/testing_builder.py +77 -0
  240. quantum/runtime/testing_code_generator.py +833 -0
  241. quantum/runtime/testing_templates.py +85 -0
  242. quantum/runtime/ui_builder.py +222 -0
  243. quantum/runtime/ui_console.py +405 -0
  244. quantum/runtime/ui_desktop.py +56 -0
  245. quantum/runtime/ui_form_rules.py +193 -0
  246. quantum/runtime/ui_html_adapter.py +2567 -0
  247. quantum/runtime/ui_html_templates.py +2106 -0
  248. quantum/runtime/ui_mobile_adapter.py +1832 -0
  249. quantum/runtime/ui_mobile_templates.py +1003 -0
  250. quantum/runtime/ui_page_renderer.py +392 -0
  251. quantum/runtime/ui_pager.py +73 -0
  252. quantum/runtime/ui_table_edit.py +139 -0
  253. quantum/runtime/ui_textual_adapter.py +1866 -0
  254. quantum/runtime/ui_textual_templates.py +45 -0
  255. quantum/runtime/ui_tokens.py +465 -0
  256. quantum/runtime/ui_validator.py +365 -0
  257. quantum/runtime/ui_view.py +221 -0
  258. quantum/runtime/validators.py +256 -0
  259. quantum/runtime/web_server.py +2131 -0
  260. quantum/runtime/websocket_adapter.py +501 -0
  261. quantum/runtime/websocket_service.py +585 -0
  262. quantum/runtime/websocket_transport.py +289 -0
  263. quantum/runtime/wsgi.py +101 -0
  264. quantum/services.py +109 -0
  265. quantum/utils/__init__.py +1 -0
  266. quantum_admin/__init__.py +4 -0
  267. quantum_admin/assets/quantum-admin-connectors.js +123 -0
  268. quantum_admin/assets/quantum-admin.css +2058 -0
  269. quantum_admin/assets/quantum-source-highlight.js +106 -0
  270. quantum_admin/backend/__init__.py +4 -0
  271. quantum_admin/backend/audit_service.py +346 -0
  272. quantum_admin/backend/auth_service.py +394 -0
  273. quantum_admin/backend/component_discovery.py +766 -0
  274. quantum_admin/backend/config_generator.py +586 -0
  275. quantum_admin/backend/connector_service.py +1016 -0
  276. quantum_admin/backend/crud.py +557 -0
  277. quantum_admin/backend/database.py +226 -0
  278. quantum_admin/backend/db_setup_service.py +193 -0
  279. quantum_admin/backend/deploy_service.py +698 -0
  280. quantum_admin/backend/docker_service.py +422 -0
  281. quantum_admin/backend/environment_service.py +418 -0
  282. quantum_admin/backend/git_service.py +370 -0
  283. quantum_admin/backend/health_service.py +390 -0
  284. quantum_admin/backend/job_service.py +347 -0
  285. quantum_admin/backend/log_service.py +331 -0
  286. quantum_admin/backend/main.py +14426 -0
  287. quantum_admin/backend/models.py +986 -0
  288. quantum_admin/backend/pipeline_service.py +812 -0
  289. quantum_admin/backend/resource_manager.py +1118 -0
  290. quantum_admin/backend/schemas.py +419 -0
  291. quantum_admin/backend/secret_manager.py +277 -0
  292. quantum_admin/backend/settings_service.py +488 -0
  293. quantum_admin/backend/template_service.py +710 -0
  294. quantum_admin/backend/test_execution_service.py +364 -0
  295. quantum_admin/backend/test_generator.py +782 -0
  296. quantum_admin/backend/webhook_service.py +454 -0
  297. quantum_admin/backend/websocket_manager.py +360 -0
  298. quantum_admin/components/admin/_layout/AdminShell.q +121 -0
  299. quantum_admin/components/admin/agents.q +107 -0
  300. quantum_admin/components/admin/app/[name].q +558 -0
  301. quantum_admin/components/admin/applications.q +160 -0
  302. quantum_admin/components/admin/component/[...path].q +205 -0
  303. quantum_admin/components/admin/components.q +59 -0
  304. quantum_admin/components/admin/connectors.q +283 -0
  305. quantum_admin/components/admin/dashboard.q +86 -0
  306. quantum_admin/components/admin/database.q +95 -0
  307. quantum_admin/components/admin/features.q +69 -0
  308. quantum_admin/components/admin/index.q +12 -0
  309. quantum_admin/components/admin/jobs.q +75 -0
  310. quantum_admin/components/admin/login.q +65 -0
  311. quantum_admin/components/admin/logout.q +11 -0
  312. quantum_admin/components/admin/settings.q +174 -0
  313. quantum_admin/components/admin/source.q +45 -0
  314. quantum_admin/components/admin/tests.q +73 -0
  315. quantum_admin/run.py +13 -0
  316. quantum_admin/services/__init__.py +16 -0
  317. quantum_admin/services/_base.py +84 -0
  318. quantum_admin/services/_component_test_generator.py +205 -0
  319. quantum_admin/services/_yaml_editor.py +82 -0
  320. quantum_admin/services/apps.py +330 -0
  321. quantum_admin/services/auth.py +61 -0
  322. quantum_admin/services/components.py +210 -0
  323. quantum_admin/services/connectors.py +164 -0
  324. quantum_admin/services/projects.py +189 -0
  325. quantum_admin/services/repository.py +223 -0
  326. quantum_admin/services/settings.py +150 -0
  327. quantum_admin/services/yaml_import.py +118 -0
  328. quantum_framework-0.18.0.dist-info/METADATA +281 -0
  329. quantum_framework-0.18.0.dist-info/RECORD +333 -0
  330. quantum_framework-0.18.0.dist-info/WHEEL +5 -0
  331. quantum_framework-0.18.0.dist-info/entry_points.txt +2 -0
  332. quantum_framework-0.18.0.dist-info/licenses/LICENSE +21 -0
  333. quantum_framework-0.18.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,287 @@
1
+ """
2
+ Quantum LLM Service - HTTP client for Ollama-compatible LLM APIs.
3
+
4
+ Provides generate (completion) and chat endpoints for the q:llm tag.
5
+ """
6
+
7
+ import os
8
+ import json
9
+ import logging
10
+ from typing import List, Dict, Any, Optional
11
+
12
+ import requests
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class LLMError(Exception):
18
+ """Raised when LLM invocation fails"""
19
+ pass
20
+
21
+
22
+ class LLMService:
23
+ """HTTP client for Ollama-compatible LLM APIs."""
24
+
25
+ def __init__(
26
+ self,
27
+ base_url: Optional[str] = None,
28
+ default_model: Optional[str] = None,
29
+ timeout: Optional[int] = None
30
+ ):
31
+ self.base_url = (
32
+ base_url
33
+ or os.getenv('QUANTUM_LLM_BASE_URL', 'http://localhost:11434')
34
+ ).rstrip('/')
35
+ self.default_model = (
36
+ default_model
37
+ or os.getenv('QUANTUM_LLM_DEFAULT_MODEL', 'phi3')
38
+ )
39
+ self.timeout = (
40
+ timeout
41
+ or int(os.getenv('QUANTUM_LLM_TIMEOUT', '60'))
42
+ )
43
+
44
+ def generate(
45
+ self,
46
+ prompt: str,
47
+ model: Optional[str] = None,
48
+ system: Optional[str] = None,
49
+ temperature: Optional[float] = None,
50
+ max_tokens: Optional[int] = None,
51
+ response_format: Optional[str] = None,
52
+ timeout: Optional[int] = None
53
+ ) -> Dict[str, Any]:
54
+ """
55
+ Call Ollama /api/generate for single-turn completion.
56
+
57
+ Args:
58
+ prompt: The prompt text
59
+ model: Model name (falls back to default_model)
60
+ system: Optional system prompt
61
+ temperature: Sampling temperature (0.0-2.0)
62
+ max_tokens: Maximum tokens to generate
63
+ response_format: "text" or "json"
64
+ timeout: Request timeout in seconds
65
+
66
+ Returns:
67
+ Dict with keys: success, data (response text), model, error
68
+ """
69
+ model = model or self.default_model
70
+ timeout = timeout or self.timeout
71
+
72
+ payload: Dict[str, Any] = {
73
+ "model": model,
74
+ "prompt": prompt,
75
+ "stream": False,
76
+ }
77
+
78
+ if system:
79
+ payload["system"] = system
80
+
81
+ options: Dict[str, Any] = {}
82
+ if temperature is not None:
83
+ options["temperature"] = temperature
84
+ if max_tokens is not None:
85
+ options["num_predict"] = max_tokens
86
+ if options:
87
+ payload["options"] = options
88
+
89
+ if response_format == "json":
90
+ payload["format"] = "json"
91
+
92
+ try:
93
+ url = f"{self.base_url}/api/generate"
94
+ logger.debug(f"LLM generate request: model={model}, url={url}")
95
+
96
+ resp = requests.post(url, json=payload, timeout=timeout)
97
+ resp.raise_for_status()
98
+
99
+ data = resp.json()
100
+ response_text = data.get("response", "")
101
+
102
+ result = {
103
+ "success": True,
104
+ "data": response_text,
105
+ "model": data.get("model", model),
106
+ "done": data.get("done", True),
107
+ "total_duration": data.get("total_duration"),
108
+ "eval_count": data.get("eval_count"),
109
+ "error": None,
110
+ }
111
+
112
+ # If JSON format requested, try to parse
113
+ if response_format == "json":
114
+ try:
115
+ result["parsed"] = json.loads(response_text)
116
+ except json.JSONDecodeError:
117
+ result["parsed"] = None
118
+
119
+ return result
120
+
121
+ except requests.ConnectionError:
122
+ raise LLMError(
123
+ f"Cannot connect to Ollama at {self.base_url}. "
124
+ "Ensure Ollama is running (ollama serve)"
125
+ )
126
+ except requests.Timeout:
127
+ raise LLMError(
128
+ f"LLM request timed out after {timeout}s. "
129
+ "Try increasing timeout or using a smaller model"
130
+ )
131
+ except requests.HTTPError as e:
132
+ raise LLMError(f"Ollama API error: {e.response.status_code} - {e.response.text}")
133
+ except Exception as e:
134
+ raise LLMError(f"LLM generate error: {e}")
135
+
136
+ def chat(
137
+ self,
138
+ messages: List[Dict[str, str]],
139
+ model: Optional[str] = None,
140
+ temperature: Optional[float] = None,
141
+ max_tokens: Optional[int] = None,
142
+ response_format: Optional[str] = None,
143
+ timeout: Optional[int] = None
144
+ ) -> Dict[str, Any]:
145
+ """
146
+ Call Ollama /api/chat for multi-turn conversation.
147
+
148
+ Args:
149
+ messages: List of {"role": "...", "content": "..."} dicts
150
+ model: Model name (falls back to default_model)
151
+ temperature: Sampling temperature (0.0-2.0)
152
+ max_tokens: Maximum tokens to generate
153
+ response_format: "text" or "json"
154
+ timeout: Request timeout in seconds
155
+
156
+ Returns:
157
+ Dict with keys: success, data (response text), model, error
158
+ """
159
+ model = model or self.default_model
160
+ timeout = timeout or self.timeout
161
+
162
+ payload: Dict[str, Any] = {
163
+ "model": model,
164
+ "messages": messages,
165
+ "stream": False,
166
+ }
167
+
168
+ options: Dict[str, Any] = {}
169
+ if temperature is not None:
170
+ options["temperature"] = temperature
171
+ if max_tokens is not None:
172
+ options["num_predict"] = max_tokens
173
+ if options:
174
+ payload["options"] = options
175
+
176
+ if response_format == "json":
177
+ payload["format"] = "json"
178
+
179
+ try:
180
+ url = f"{self.base_url}/api/chat"
181
+ logger.debug(f"LLM chat request: model={model}, url={url}, messages={len(messages)}")
182
+
183
+ resp = requests.post(url, json=payload, timeout=timeout)
184
+ resp.raise_for_status()
185
+
186
+ data = resp.json()
187
+ message = data.get("message", {})
188
+ response_text = message.get("content", "")
189
+
190
+ result = {
191
+ "success": True,
192
+ "data": response_text,
193
+ "model": data.get("model", model),
194
+ "role": message.get("role", "assistant"),
195
+ "done": data.get("done", True),
196
+ "total_duration": data.get("total_duration"),
197
+ "eval_count": data.get("eval_count"),
198
+ "error": None,
199
+ }
200
+
201
+ if response_format == "json":
202
+ try:
203
+ result["parsed"] = json.loads(response_text)
204
+ except json.JSONDecodeError:
205
+ result["parsed"] = None
206
+
207
+ return result
208
+
209
+ except requests.ConnectionError:
210
+ raise LLMError(
211
+ f"Cannot connect to Ollama at {self.base_url}. "
212
+ "Ensure Ollama is running (ollama serve)"
213
+ )
214
+ except requests.Timeout:
215
+ raise LLMError(
216
+ f"LLM chat request timed out after {timeout}s. "
217
+ "Try increasing timeout or using a smaller model"
218
+ )
219
+ except requests.HTTPError as e:
220
+ raise LLMError(f"Ollama API error: {e.response.status_code} - {e.response.text}")
221
+ except Exception as e:
222
+ raise LLMError(f"LLM chat error: {e}")
223
+
224
+ def list_models(self) -> Dict[str, Any]:
225
+ """
226
+ List available models from Ollama /api/tags.
227
+
228
+ Returns:
229
+ Dict with "models" list
230
+ """
231
+ try:
232
+ resp = requests.get(f"{self.base_url}/api/tags", timeout=10)
233
+ resp.raise_for_status()
234
+ return resp.json()
235
+ except requests.ConnectionError:
236
+ raise LLMError(f"Cannot connect to Ollama at {self.base_url}")
237
+ except Exception as e:
238
+ raise LLMError(f"Error listing models: {e}")
239
+
240
+ def pull_model(self, model_name: str) -> Dict[str, Any]:
241
+ """
242
+ Pull a model from Ollama registry via /api/pull.
243
+
244
+ Args:
245
+ model_name: Name of the model to pull (e.g. "phi3", "mistral")
246
+
247
+ Returns:
248
+ Dict with pull status
249
+ """
250
+ try:
251
+ resp = requests.post(
252
+ f"{self.base_url}/api/pull",
253
+ json={"name": model_name, "stream": False},
254
+ timeout=600 # Model downloads can take a while
255
+ )
256
+ resp.raise_for_status()
257
+ return {"success": True, "model": model_name}
258
+ except requests.ConnectionError:
259
+ raise LLMError(f"Cannot connect to Ollama at {self.base_url}")
260
+ except Exception as e:
261
+ raise LLMError(f"Error pulling model {model_name}: {e}")
262
+
263
+ def test_connection(self) -> Dict[str, Any]:
264
+ """
265
+ Test connection to Ollama server.
266
+
267
+ Returns:
268
+ Dict with online status and available models
269
+ """
270
+ try:
271
+ resp = requests.get(f"{self.base_url}/api/tags", timeout=5)
272
+ resp.raise_for_status()
273
+ data = resp.json()
274
+ models = [m.get("name", "") for m in data.get("models", [])]
275
+ return {
276
+ "online": True,
277
+ "url": self.base_url,
278
+ "models": models,
279
+ "model_count": len(models)
280
+ }
281
+ except Exception:
282
+ return {
283
+ "online": False,
284
+ "url": self.base_url,
285
+ "models": [],
286
+ "model_count": 0
287
+ }
@@ -0,0 +1,28 @@
1
+ """The application's server on a free local port, for renderers that are not a browser.
2
+
3
+ `quantum console` (UI-3) and `quantum desktop` (UI-4) open the whole
4
+ application — pages, actions, sessions — through the same server that
5
+ `quantum start` runs. Only the drawing differs; nothing of a page's logic runs
6
+ anywhere else.
7
+ """
8
+
9
+ import logging
10
+ import threading
11
+ from contextlib import contextmanager
12
+ from typing import Iterator
13
+
14
+
15
+ @contextmanager
16
+ def local_server(config_path: str = 'quantum.config.yaml') -> Iterator[str]:
17
+ """Serve the application on 127.0.0.1 (a free port) in a thread; yield its base URL."""
18
+ from werkzeug.serving import make_server
19
+ from quantum.runtime.web_server import QuantumWebServer
20
+
21
+ logging.getLogger('werkzeug').setLevel(logging.ERROR)
22
+ app = QuantumWebServer(config_path).app
23
+ servidor = make_server('127.0.0.1', 0, app, threaded=True)
24
+ threading.Thread(target=servidor.serve_forever, daemon=True).start()
25
+ try:
26
+ yield f'http://127.0.0.1:{servidor.server_port}/'
27
+ finally:
28
+ servidor.shutdown()
@@ -0,0 +1,141 @@
1
+ """
2
+ Quantum Logging Setup - Bridges quantum.config.yaml logging section to Python logging.
3
+
4
+ Configures the 'quantum' root logger with colored console output and file logging.
5
+ All child loggers (quantum.server, quantum.parser, quantum.runtime, quantum.errors)
6
+ inherit the configured handlers.
7
+ """
8
+
9
+ import logging
10
+ import os
11
+ import sys
12
+ from pathlib import Path
13
+ from typing import Any, Dict
14
+
15
+
16
+ class ColoredFormatter(logging.Formatter):
17
+ """Formatter that adds ANSI color codes based on log level."""
18
+
19
+ COLORS = {
20
+ logging.DEBUG: '\033[36m', # cyan
21
+ logging.INFO: '\033[32m', # green
22
+ logging.WARNING: '\033[33m', # yellow
23
+ logging.ERROR: '\033[31m', # red
24
+ logging.CRITICAL: '\033[41m', # red background
25
+ }
26
+ RESET = '\033[0m'
27
+
28
+ def __init__(self, fmt=None, datefmt=None, use_color=True):
29
+ super().__init__(fmt, datefmt)
30
+ self.use_color = use_color
31
+
32
+ def format(self, record):
33
+ if self.use_color:
34
+ color = self.COLORS.get(record.levelno, '')
35
+ reset = self.RESET
36
+ record.levelname = f"{color}{record.levelname:<8}{reset}"
37
+ else:
38
+ record.levelname = f"{record.levelname:<8}"
39
+ return super().format(record)
40
+
41
+
42
+ def _supports_color() -> bool:
43
+ """Detect whether the terminal supports ANSI color codes."""
44
+ if os.environ.get('NO_COLOR'):
45
+ return False
46
+ if os.environ.get('FORCE_COLOR'):
47
+ return True
48
+ if not hasattr(sys.stderr, 'isatty'):
49
+ return False
50
+ if sys.stderr.isatty():
51
+ return True
52
+ # Windows Terminal sets WT_SESSION
53
+ if os.environ.get('WT_SESSION'):
54
+ return True
55
+ return False
56
+
57
+
58
+ def setup_logging(config: Dict[str, Any]) -> logging.Logger:
59
+ """
60
+ Configure the 'quantum' root logger from the config dictionary.
61
+
62
+ Reads config['logging'] for level, format, console, file, filename.
63
+ Reads config['paths']['logs'] for the log directory.
64
+
65
+ Args:
66
+ config: Full quantum configuration dictionary
67
+
68
+ Returns:
69
+ The configured 'quantum' root logger
70
+ """
71
+ log_config = config.get('logging', {})
72
+ paths_config = config.get('paths', {})
73
+
74
+ level_name = log_config.get('level', 'INFO').upper()
75
+ level = getattr(logging, level_name, logging.INFO)
76
+ enable_console = log_config.get('console', True)
77
+ enable_file = log_config.get('file', True)
78
+ filename = log_config.get('filename', 'quantum.log')
79
+ log_dir = paths_config.get('logs', './logs')
80
+
81
+ # Get or create the quantum root logger and clear existing handlers
82
+ logger = logging.getLogger('quantum')
83
+ logger.setLevel(level)
84
+ logger.handlers.clear()
85
+
86
+ datefmt = '%Y-%m-%d %H:%M:%S'
87
+ # CFG-3: logging.format was documented here and never read.
88
+ plain_fmt = log_config.get('format') or '%(asctime)s %(levelname)-8s %(name)s - %(message)s'
89
+
90
+ # Console handler with color support
91
+ if enable_console:
92
+ console_handler = logging.StreamHandler(sys.stderr)
93
+ console_handler.setLevel(level)
94
+ use_color = _supports_color()
95
+ colored_fmt = log_config.get('format') or '%(asctime)s %(levelname)s %(name)s - %(message)s'
96
+ formatter = ColoredFormatter(colored_fmt, datefmt=datefmt, use_color=use_color)
97
+ console_handler.setFormatter(formatter)
98
+ logger.addHandler(console_handler)
99
+
100
+ # File handler with plain formatter
101
+ if enable_file:
102
+ log_path = Path(log_dir)
103
+ log_path.mkdir(parents=True, exist_ok=True)
104
+ file_handler = logging.FileHandler(
105
+ str(log_path / filename), encoding='utf-8'
106
+ )
107
+ file_handler.setLevel(level)
108
+ file_handler.setFormatter(logging.Formatter(plain_fmt, datefmt=datefmt))
109
+ logger.addHandler(file_handler)
110
+
111
+ # Suppress werkzeug's noisy default request logging
112
+ logging.getLogger('werkzeug').setLevel(logging.WARNING)
113
+
114
+ # Inject into LoggingService singleton to prevent duplicate handlers
115
+ try:
116
+ from quantum.core.features.logging.src.runtime import LoggingService
117
+ LoggingService.configure_from_external(logger)
118
+ except Exception:
119
+ pass # LoggingService may not be available
120
+
121
+ return logger
122
+
123
+
124
+ def get_status_color(code: int) -> str:
125
+ """Return ANSI color escape for an HTTP status code."""
126
+ if not _supports_color():
127
+ return ''
128
+ if code < 300:
129
+ return '\033[32m' # green
130
+ if code < 400:
131
+ return '\033[33m' # yellow
132
+ if code < 500:
133
+ return '\033[31m' # red
134
+ return '\033[91m' # bright red
135
+
136
+
137
+ def get_reset() -> str:
138
+ """Return ANSI reset escape if color is supported."""
139
+ if not _supports_color():
140
+ return ''
141
+ return '\033[0m'