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.
- quantum/__init__.py +1 -0
- quantum/cli/__init__.py +2 -0
- quantum/cli/admin.py +131 -0
- quantum/cli/check.py +223 -0
- quantum/cli/commands/__init__.py +15 -0
- quantum/cli/commands/build.py +414 -0
- quantum/cli/commands/dev.py +185 -0
- quantum/cli/commands/docs.py +329 -0
- quantum/cli/commands/lint.py +523 -0
- quantum/cli/commands/migrate.py +548 -0
- quantum/cli/commands/new.py +622 -0
- quantum/cli/commands/serve.py +190 -0
- quantum/cli/commands/test.py +193 -0
- quantum/cli/deploy.py +810 -0
- quantum/cli/hot_reload.py +951 -0
- quantum/cli/jobs.py +356 -0
- quantum/cli/mq.py +582 -0
- quantum/cli/pkg.py +390 -0
- quantum/cli/runner.py +565 -0
- quantum/cli/server_process.py +159 -0
- quantum/cli/utils.py +334 -0
- quantum/compiler/__init__.py +30 -0
- quantum/compiler/base_generator.py +367 -0
- quantum/compiler/cli.py +295 -0
- quantum/compiler/expression_transformer.py +444 -0
- quantum/compiler/javascript/__init__.py +10 -0
- quantum/compiler/javascript/generator.py +659 -0
- quantum/compiler/optimizer.py +270 -0
- quantum/compiler/python/__init__.py +10 -0
- quantum/compiler/python/generator.py +883 -0
- quantum/compiler/python/runtime.py +863 -0
- quantum/compiler/transpiler.py +330 -0
- quantum/core/__init__.py +3 -0
- quantum/core/ast_nodes.py +2619 -0
- quantum/core/config_env.py +54 -0
- quantum/core/docs_links.py +8 -0
- quantum/core/expression_diagnostics.py +87 -0
- quantum/core/expression_stdlib.py +208 -0
- quantum/core/expressions.py +677 -0
- quantum/core/features/agents/src/__init__.py +30 -0
- quantum/core/features/agents/src/ast_node.py +540 -0
- quantum/core/features/conditionals/src/__init__.py +8 -0
- quantum/core/features/conditionals/src/ast_node.py +68 -0
- quantum/core/features/data_import/src/__init__.py +0 -0
- quantum/core/features/data_import/src/ast_node.py +292 -0
- quantum/core/features/data_import/src/runtime.py +533 -0
- quantum/core/features/dump/src/__init__.py +12 -0
- quantum/core/features/dump/src/ast_node.py +106 -0
- quantum/core/features/dump/src/parser.py +61 -0
- quantum/core/features/dump/src/runtime.py +246 -0
- quantum/core/features/functions/src/__init__.py +8 -0
- quantum/core/features/functions/src/ast_node.py +149 -0
- quantum/core/features/game_engine_2d/src/__init__.py +19 -0
- quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
- quantum/core/features/game_engine_2d/src/parser.py +983 -0
- quantum/core/features/invocation/src/__init__.py +0 -0
- quantum/core/features/invocation/src/ast_node.py +147 -0
- quantum/core/features/invocation/src/runtime.py +332 -0
- quantum/core/features/knowledge_base/src/__init__.py +6 -0
- quantum/core/features/knowledge_base/src/ast_node.py +113 -0
- quantum/core/features/knowledge_base/src/parser.py +82 -0
- quantum/core/features/logging/src/__init__.py +12 -0
- quantum/core/features/logging/src/ast_node.py +111 -0
- quantum/core/features/logging/src/parser.py +50 -0
- quantum/core/features/logging/src/runtime.py +190 -0
- quantum/core/features/loops/src/__init__.py +8 -0
- quantum/core/features/loops/src/ast_node.py +60 -0
- quantum/core/features/query/src/__init__.py +0 -0
- quantum/core/features/query/src/query_validators.py +20 -0
- quantum/core/features/state_management/src/__init__.py +11 -0
- quantum/core/features/state_management/src/ast_node.py +121 -0
- quantum/core/features/terminal_engine/src/__init__.py +21 -0
- quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
- quantum/core/features/terminal_engine/src/parser.py +361 -0
- quantum/core/features/testing_engine/src/__init__.py +41 -0
- quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
- quantum/core/features/testing_engine/src/parser.py +604 -0
- quantum/core/features/theming/src/__init__.py +48 -0
- quantum/core/features/theming/src/ast_node.py +137 -0
- quantum/core/features/theming/src/presets.py +405 -0
- quantum/core/features/ui_engine/src/__init__.py +20 -0
- quantum/core/features/ui_engine/src/ast_nodes.py +1913 -0
- quantum/core/features/ui_engine/src/parser.py +1192 -0
- quantum/core/features/websocket/src/__init__.py +24 -0
- quantum/core/features/websocket/src/ast_node.py +247 -0
- quantum/core/guards.py +53 -0
- quantum/core/html_compat.py +299 -0
- quantum/core/parser.py +1551 -0
- quantum/core/parser_registry.py +213 -0
- quantum/core/parsers/__init__.py +76 -0
- quantum/core/parsers/ai/__init__.py +12 -0
- quantum/core/parsers/ai/agent_parser.py +106 -0
- quantum/core/parsers/ai/knowledge_parser.py +86 -0
- quantum/core/parsers/ai/llm_parser.py +78 -0
- quantum/core/parsers/ai/team_parser.py +88 -0
- quantum/core/parsers/base.py +322 -0
- quantum/core/parsers/composition/__init__.py +10 -0
- quantum/core/parsers/composition/import_parser.py +50 -0
- quantum/core/parsers/composition/slot_parser.py +48 -0
- quantum/core/parsers/control_flow/__init__.py +11 -0
- quantum/core/parsers/control_flow/if_parser.py +71 -0
- quantum/core/parsers/control_flow/loop_parser.py +105 -0
- quantum/core/parsers/control_flow/set_parser.py +93 -0
- quantum/core/parsers/data/__init__.py +12 -0
- quantum/core/parsers/data/data_parser.py +229 -0
- quantum/core/parsers/data/invoke_parser.py +133 -0
- quantum/core/parsers/data/query_parser.py +213 -0
- quantum/core/parsers/data/transaction_parser.py +86 -0
- quantum/core/parsers/events/__init__.py +9 -0
- quantum/core/parsers/events/dispatch_event_parser.py +44 -0
- quantum/core/parsers/forms/__init__.py +11 -0
- quantum/core/parsers/forms/action_parser.py +79 -0
- quantum/core/parsers/forms/flash_parser.py +34 -0
- quantum/core/parsers/forms/redirect_parser.py +33 -0
- quantum/core/parsers/functions/__init__.py +11 -0
- quantum/core/parsers/functions/function_parser.py +98 -0
- quantum/core/parsers/functions/param_parser.py +73 -0
- quantum/core/parsers/functions/return_parser.py +31 -0
- quantum/core/parsers/html/__init__.py +10 -0
- quantum/core/parsers/html/component_call_parser.py +130 -0
- quantum/core/parsers/html/html_parser.py +115 -0
- quantum/core/parsers/jobs/__init__.py +11 -0
- quantum/core/parsers/jobs/job_parser.py +71 -0
- quantum/core/parsers/jobs/schedule_parser.py +62 -0
- quantum/core/parsers/jobs/thread_parser.py +57 -0
- quantum/core/parsers/messaging/__init__.py +17 -0
- quantum/core/parsers/messaging/message_ack_parser.py +30 -0
- quantum/core/parsers/messaging/message_nack_parser.py +30 -0
- quantum/core/parsers/messaging/message_parser.py +114 -0
- quantum/core/parsers/messaging/queue_parser.py +61 -0
- quantum/core/parsers/messaging/websocket_parser.py +121 -0
- quantum/core/parsers/routing/__init__.py +9 -0
- quantum/core/parsers/routing/route_parser.py +41 -0
- quantum/core/parsers/scripting/__init__.py +12 -0
- quantum/core/parsers/scripting/pyclass_parser.py +62 -0
- quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
- quantum/core/parsers/scripting/pyimport_parser.py +52 -0
- quantum/core/parsers/scripting/python_parser.py +49 -0
- quantum/core/parsers/services/__init__.py +12 -0
- quantum/core/parsers/services/dump_parser.py +52 -0
- quantum/core/parsers/services/file_parser.py +48 -0
- quantum/core/parsers/services/log_parser.py +46 -0
- quantum/core/parsers/services/mail_parser.py +65 -0
- quantum/core/tiers.py +164 -0
- quantum/core/xml_lines.py +81 -0
- quantum/packages/__init__.py +28 -0
- quantum/packages/manager.py +413 -0
- quantum/packages/manifest.py +351 -0
- quantum/packages/registry.py +399 -0
- quantum/packages/resolver.py +336 -0
- quantum/plugins/__init__.py +33 -0
- quantum/plugins/hooks.py +329 -0
- quantum/plugins/loader.py +479 -0
- quantum/plugins/manifest.py +336 -0
- quantum/plugins/registry.py +371 -0
- quantum/runtime/__init__.py +25 -0
- quantum/runtime/action_handler.py +532 -0
- quantum/runtime/adapters/__init__.py +88 -0
- quantum/runtime/adapters/memory_adapter.py +690 -0
- quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
- quantum/runtime/adapters/redis_adapter.py +582 -0
- quantum/runtime/adapters/sqlite_adapter.py +414 -0
- quantum/runtime/agent_service.py +1133 -0
- quantum/runtime/ast_cache.py +506 -0
- quantum/runtime/auth_service.py +267 -0
- quantum/runtime/component.py +1048 -0
- quantum/runtime/component_composer.py +133 -0
- quantum/runtime/component_resolver.py +174 -0
- quantum/runtime/conditions.py +54 -0
- quantum/runtime/database_service.py +595 -0
- quantum/runtime/db_schema.py +100 -0
- quantum/runtime/dev_panel.py +178 -0
- quantum/runtime/email_service.py +162 -0
- quantum/runtime/error_handler.py +295 -0
- quantum/runtime/execution_context.py +283 -0
- quantum/runtime/executor_registry.py +178 -0
- quantum/runtime/executors/__init__.py +71 -0
- quantum/runtime/executors/ai/__init__.py +12 -0
- quantum/runtime/executors/ai/agent_executor.py +217 -0
- quantum/runtime/executors/ai/knowledge_executor.py +114 -0
- quantum/runtime/executors/ai/llm_executor.py +153 -0
- quantum/runtime/executors/ai/team_executor.py +171 -0
- quantum/runtime/executors/base.py +264 -0
- quantum/runtime/executors/control_flow/__init__.py +12 -0
- quantum/runtime/executors/control_flow/if_executor.py +98 -0
- quantum/runtime/executors/control_flow/loop_executor.py +308 -0
- quantum/runtime/executors/control_flow/redirect_executor.py +43 -0
- quantum/runtime/executors/control_flow/set_executor.py +464 -0
- quantum/runtime/executors/data/__init__.py +12 -0
- quantum/runtime/executors/data/data_executor.py +156 -0
- quantum/runtime/executors/data/invoke_executor.py +228 -0
- quantum/runtime/executors/data/query_executor.py +290 -0
- quantum/runtime/executors/data/transaction_executor.py +91 -0
- quantum/runtime/executors/jobs/__init__.py +11 -0
- quantum/runtime/executors/jobs/job_executor.py +190 -0
- quantum/runtime/executors/jobs/schedule_executor.py +132 -0
- quantum/runtime/executors/jobs/thread_executor.py +127 -0
- quantum/runtime/executors/messaging/__init__.py +17 -0
- quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
- quantum/runtime/executors/messaging/message_executor.py +174 -0
- quantum/runtime/executors/messaging/queue_executor.py +103 -0
- quantum/runtime/executors/messaging/websocket_executor.py +197 -0
- quantum/runtime/executors/scripting/__init__.py +11 -0
- quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
- quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
- quantum/runtime/executors/scripting/python_executor.py +249 -0
- quantum/runtime/executors/services/__init__.py +12 -0
- quantum/runtime/executors/services/dump_executor.py +72 -0
- quantum/runtime/executors/services/file_executor.py +89 -0
- quantum/runtime/executors/services/log_executor.py +77 -0
- quantum/runtime/executors/services/mail_executor.py +81 -0
- quantum/runtime/expression_cache.py +498 -0
- quantum/runtime/file_upload_service.py +326 -0
- quantum/runtime/function_registry.py +118 -0
- quantum/runtime/game_builder.py +166 -0
- quantum/runtime/game_code_generator.py +2371 -0
- quantum/runtime/game_templates.py +2006 -0
- quantum/runtime/godot_code_generator.py +4681 -0
- quantum/runtime/godot_templates.py +1449 -0
- quantum/runtime/job_executor.py +1599 -0
- quantum/runtime/knowledge_service.py +546 -0
- quantum/runtime/llm_cache.py +100 -0
- quantum/runtime/llm_providers.py +704 -0
- quantum/runtime/llm_service.py +287 -0
- quantum/runtime/local_server.py +28 -0
- quantum/runtime/logging_setup.py +141 -0
- quantum/runtime/message_broker.py +364 -0
- quantum/runtime/message_queue_service.py +571 -0
- quantum/runtime/param_validation.py +218 -0
- quantum/runtime/pypy_compat.py +317 -0
- quantum/runtime/python_bridge.py +698 -0
- quantum/runtime/query_validators.py +304 -0
- quantum/runtime/renderer.py +734 -0
- quantum/runtime/service_container.py +470 -0
- quantum/runtime/table_params.py +163 -0
- quantum/runtime/terminal_builder.py +76 -0
- quantum/runtime/terminal_code_generator.py +607 -0
- quantum/runtime/terminal_templates.py +243 -0
- quantum/runtime/testing_builder.py +77 -0
- quantum/runtime/testing_code_generator.py +833 -0
- quantum/runtime/testing_templates.py +85 -0
- quantum/runtime/ui_builder.py +222 -0
- quantum/runtime/ui_console.py +405 -0
- quantum/runtime/ui_desktop.py +56 -0
- quantum/runtime/ui_form_rules.py +193 -0
- quantum/runtime/ui_html_adapter.py +2567 -0
- quantum/runtime/ui_html_templates.py +2106 -0
- quantum/runtime/ui_mobile_adapter.py +1832 -0
- quantum/runtime/ui_mobile_templates.py +1003 -0
- quantum/runtime/ui_page_renderer.py +392 -0
- quantum/runtime/ui_pager.py +73 -0
- quantum/runtime/ui_table_edit.py +139 -0
- quantum/runtime/ui_textual_adapter.py +1866 -0
- quantum/runtime/ui_textual_templates.py +45 -0
- quantum/runtime/ui_tokens.py +465 -0
- quantum/runtime/ui_validator.py +365 -0
- quantum/runtime/ui_view.py +221 -0
- quantum/runtime/validators.py +256 -0
- quantum/runtime/web_server.py +2131 -0
- quantum/runtime/websocket_adapter.py +501 -0
- quantum/runtime/websocket_service.py +585 -0
- quantum/runtime/websocket_transport.py +289 -0
- quantum/runtime/wsgi.py +101 -0
- quantum/services.py +109 -0
- quantum/utils/__init__.py +1 -0
- quantum_admin/__init__.py +4 -0
- quantum_admin/assets/quantum-admin-connectors.js +123 -0
- quantum_admin/assets/quantum-admin.css +2058 -0
- quantum_admin/assets/quantum-source-highlight.js +106 -0
- quantum_admin/backend/__init__.py +4 -0
- quantum_admin/backend/audit_service.py +346 -0
- quantum_admin/backend/auth_service.py +394 -0
- quantum_admin/backend/component_discovery.py +766 -0
- quantum_admin/backend/config_generator.py +586 -0
- quantum_admin/backend/connector_service.py +1016 -0
- quantum_admin/backend/crud.py +557 -0
- quantum_admin/backend/database.py +226 -0
- quantum_admin/backend/db_setup_service.py +193 -0
- quantum_admin/backend/deploy_service.py +698 -0
- quantum_admin/backend/docker_service.py +422 -0
- quantum_admin/backend/environment_service.py +418 -0
- quantum_admin/backend/git_service.py +370 -0
- quantum_admin/backend/health_service.py +390 -0
- quantum_admin/backend/job_service.py +347 -0
- quantum_admin/backend/log_service.py +331 -0
- quantum_admin/backend/main.py +14426 -0
- quantum_admin/backend/models.py +986 -0
- quantum_admin/backend/pipeline_service.py +812 -0
- quantum_admin/backend/resource_manager.py +1118 -0
- quantum_admin/backend/schemas.py +419 -0
- quantum_admin/backend/secret_manager.py +277 -0
- quantum_admin/backend/settings_service.py +488 -0
- quantum_admin/backend/template_service.py +710 -0
- quantum_admin/backend/test_execution_service.py +364 -0
- quantum_admin/backend/test_generator.py +782 -0
- quantum_admin/backend/webhook_service.py +454 -0
- quantum_admin/backend/websocket_manager.py +360 -0
- quantum_admin/components/admin/_layout/AdminShell.q +121 -0
- quantum_admin/components/admin/agents.q +107 -0
- quantum_admin/components/admin/app/[name].q +558 -0
- quantum_admin/components/admin/applications.q +160 -0
- quantum_admin/components/admin/component/[...path].q +205 -0
- quantum_admin/components/admin/components.q +59 -0
- quantum_admin/components/admin/connectors.q +283 -0
- quantum_admin/components/admin/dashboard.q +86 -0
- quantum_admin/components/admin/database.q +95 -0
- quantum_admin/components/admin/features.q +69 -0
- quantum_admin/components/admin/index.q +12 -0
- quantum_admin/components/admin/jobs.q +75 -0
- quantum_admin/components/admin/login.q +65 -0
- quantum_admin/components/admin/logout.q +11 -0
- quantum_admin/components/admin/settings.q +174 -0
- quantum_admin/components/admin/source.q +45 -0
- quantum_admin/components/admin/tests.q +73 -0
- quantum_admin/run.py +13 -0
- quantum_admin/services/__init__.py +16 -0
- quantum_admin/services/_base.py +84 -0
- quantum_admin/services/_component_test_generator.py +205 -0
- quantum_admin/services/_yaml_editor.py +82 -0
- quantum_admin/services/apps.py +330 -0
- quantum_admin/services/auth.py +61 -0
- quantum_admin/services/components.py +210 -0
- quantum_admin/services/connectors.py +164 -0
- quantum_admin/services/projects.py +189 -0
- quantum_admin/services/repository.py +223 -0
- quantum_admin/services/settings.py +150 -0
- quantum_admin/services/yaml_import.py +118 -0
- quantum_framework-0.18.0.dist-info/METADATA +281 -0
- quantum_framework-0.18.0.dist-info/RECORD +333 -0
- quantum_framework-0.18.0.dist-info/WHEEL +5 -0
- quantum_framework-0.18.0.dist-info/entry_points.txt +2 -0
- quantum_framework-0.18.0.dist-info/licenses/LICENSE +21 -0
- 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'
|