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
|
File without changes
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Invocation Feature - AST Nodes
|
|
3
|
+
Defines InvokeNode and InvokeHeaderNode for q:invoke
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import List, Dict, Any
|
|
7
|
+
from quantum.core.ast_nodes import QuantumNode, QuantumParam
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class InvokeNode(QuantumNode):
|
|
11
|
+
"""Represents a <q:invoke> - Universal invocation for functions, components, HTTP, etc."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, name: str):
|
|
14
|
+
self.name = name # Variable name for result
|
|
15
|
+
|
|
16
|
+
# Invocation target (determines type)
|
|
17
|
+
self.function = None # Local function name
|
|
18
|
+
self.component = None # Local component name
|
|
19
|
+
self.url = None # HTTP/REST endpoint
|
|
20
|
+
self.endpoint = None # GraphQL/SOAP endpoint
|
|
21
|
+
self.service = None # Service name (for service discovery)
|
|
22
|
+
|
|
23
|
+
# HTTP-specific
|
|
24
|
+
self.method = "GET" # GET, POST, PUT, DELETE, PATCH
|
|
25
|
+
self.headers: List['InvokeHeaderNode'] = []
|
|
26
|
+
self.body = None # Request body (for POST/PUT/PATCH)
|
|
27
|
+
self.content_type = "application/json"
|
|
28
|
+
|
|
29
|
+
# Authentication
|
|
30
|
+
self.auth_type = None # bearer, apikey, basic, oauth2
|
|
31
|
+
self.auth_token = None # Bearer token or API key
|
|
32
|
+
self.auth_header = None # Header name for API key (default: Authorization)
|
|
33
|
+
self.auth_username = None # Basic auth username
|
|
34
|
+
self.auth_password = None # Basic auth password
|
|
35
|
+
|
|
36
|
+
# Parameters (query params for HTTP, args for functions/components)
|
|
37
|
+
self.params: List[QuantumParam] = []
|
|
38
|
+
|
|
39
|
+
# Timeouts and retries
|
|
40
|
+
self.timeout = 30000 # Default 30s
|
|
41
|
+
self.retry = 0 # Number of retries
|
|
42
|
+
self.retry_delay = 1000 # Delay between retries in ms
|
|
43
|
+
|
|
44
|
+
# Response handling
|
|
45
|
+
self.response_format = "auto" # auto, json, text, xml, binary
|
|
46
|
+
self.transform = None # Transformation expression
|
|
47
|
+
self.cache = False
|
|
48
|
+
self.ttl = None
|
|
49
|
+
self.result = None # Variable name for metadata
|
|
50
|
+
self.on_error = 'fail' # INV-2: 'fail' or 'continue'
|
|
51
|
+
|
|
52
|
+
def add_header(self, header: 'InvokeHeaderNode'):
|
|
53
|
+
"""Add HTTP header"""
|
|
54
|
+
self.headers.append(header)
|
|
55
|
+
|
|
56
|
+
def add_param(self, param: QuantumParam):
|
|
57
|
+
"""Add parameter"""
|
|
58
|
+
self.params.append(param)
|
|
59
|
+
|
|
60
|
+
def get_invocation_type(self) -> str:
|
|
61
|
+
"""Determine invocation type based on attributes"""
|
|
62
|
+
if self.function:
|
|
63
|
+
return "function"
|
|
64
|
+
elif self.component:
|
|
65
|
+
return "component"
|
|
66
|
+
elif self.url:
|
|
67
|
+
return "http"
|
|
68
|
+
elif self.endpoint:
|
|
69
|
+
# Could be GraphQL, SOAP, etc. (Phase 2)
|
|
70
|
+
return "endpoint"
|
|
71
|
+
elif self.service:
|
|
72
|
+
# Service discovery (Phase 2)
|
|
73
|
+
return "service"
|
|
74
|
+
else:
|
|
75
|
+
return "unknown"
|
|
76
|
+
|
|
77
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
78
|
+
return {
|
|
79
|
+
"type": "invoke",
|
|
80
|
+
"name": self.name,
|
|
81
|
+
"invocation_type": self.get_invocation_type(),
|
|
82
|
+
"function": self.function,
|
|
83
|
+
"component": self.component,
|
|
84
|
+
"url": self.url,
|
|
85
|
+
"method": self.method,
|
|
86
|
+
"headers": [h.to_dict() for h in self.headers],
|
|
87
|
+
"params": [p.__dict__ for p in self.params],
|
|
88
|
+
"auth_type": self.auth_type,
|
|
89
|
+
"timeout": self.timeout,
|
|
90
|
+
"retry": self.retry,
|
|
91
|
+
"cache": self.cache
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
def validate(self) -> List[str]:
|
|
95
|
+
errors = []
|
|
96
|
+
# name is optional: fire-and-forget invocation has no result to bind.
|
|
97
|
+
# What actually matters is that there is something to invoke.
|
|
98
|
+
|
|
99
|
+
# Validate that at least one target is specified
|
|
100
|
+
targets = [self.function, self.component, self.url, self.endpoint, self.service]
|
|
101
|
+
if not any(targets):
|
|
102
|
+
errors.append("Invoke requires one of: function, component, url, endpoint, or service")
|
|
103
|
+
|
|
104
|
+
# Validate that only one target is specified
|
|
105
|
+
if sum(1 for t in targets if t is not None) > 1:
|
|
106
|
+
errors.append("Invoke can only specify one target (function, component, url, endpoint, or service)")
|
|
107
|
+
|
|
108
|
+
# Validate HTTP method
|
|
109
|
+
if self.method not in ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']:
|
|
110
|
+
errors.append(f"Invalid HTTP method: {self.method}")
|
|
111
|
+
|
|
112
|
+
# Validate auth_type
|
|
113
|
+
if self.auth_type and self.auth_type not in ['bearer', 'apikey', 'basic', 'oauth2']:
|
|
114
|
+
errors.append(f"Invalid auth_type: {self.auth_type}. Must be bearer, apikey, basic, or oauth2")
|
|
115
|
+
|
|
116
|
+
# Validate headers
|
|
117
|
+
for header in self.headers:
|
|
118
|
+
errors.extend(header.validate())
|
|
119
|
+
|
|
120
|
+
# Validate params
|
|
121
|
+
for param in self.params:
|
|
122
|
+
errors.extend(param.validate())
|
|
123
|
+
|
|
124
|
+
return errors
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class InvokeHeaderNode(QuantumNode):
|
|
128
|
+
"""Represents a header in <q:invoke>"""
|
|
129
|
+
|
|
130
|
+
def __init__(self, name: str, value: str):
|
|
131
|
+
self.name = name
|
|
132
|
+
self.value = value
|
|
133
|
+
|
|
134
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
135
|
+
return {
|
|
136
|
+
"type": "invokeHeader",
|
|
137
|
+
"name": self.name,
|
|
138
|
+
"value": self.value[:50] + "..." if len(str(self.value)) > 50 else self.value
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
def validate(self) -> List[str]:
|
|
142
|
+
errors = []
|
|
143
|
+
if not self.name:
|
|
144
|
+
errors.append("Header name is required")
|
|
145
|
+
if self.value is None:
|
|
146
|
+
errors.append(f"Header '{self.name}' value is required")
|
|
147
|
+
return errors
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Invocation Feature - Runtime
|
|
3
|
+
Handles execution of all invocation types (functions, components, HTTP, etc.)
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import time
|
|
7
|
+
import json
|
|
8
|
+
import requests
|
|
9
|
+
from typing import Dict, Any, Optional, List
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
import base64
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class InvocationResult:
|
|
16
|
+
"""Result object for all invocations"""
|
|
17
|
+
success: bool
|
|
18
|
+
data: Any = None
|
|
19
|
+
error: Optional[Dict[str, Any]] = None
|
|
20
|
+
execution_time: float = 0.0
|
|
21
|
+
invocation_type: str = ""
|
|
22
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class InvocationService:
|
|
26
|
+
"""Service to handle all types of invocations"""
|
|
27
|
+
|
|
28
|
+
def __init__(self):
|
|
29
|
+
self.cache: Dict[str, Any] = {} # Simple in-memory cache
|
|
30
|
+
|
|
31
|
+
def invoke(
|
|
32
|
+
self,
|
|
33
|
+
invocation_type: str,
|
|
34
|
+
params: Dict[str, Any],
|
|
35
|
+
context: Any = None
|
|
36
|
+
) -> InvocationResult:
|
|
37
|
+
"""Main invocation dispatcher"""
|
|
38
|
+
start_time = time.time()
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
if invocation_type == "function":
|
|
42
|
+
result = self._invoke_function(params, context)
|
|
43
|
+
elif invocation_type == "component":
|
|
44
|
+
result = self._invoke_component(params, context)
|
|
45
|
+
elif invocation_type == "http":
|
|
46
|
+
result = self._invoke_http(params)
|
|
47
|
+
else:
|
|
48
|
+
return InvocationResult(
|
|
49
|
+
success=False,
|
|
50
|
+
error={"message": f"Unknown invocation type: {invocation_type}"},
|
|
51
|
+
invocation_type=invocation_type
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
execution_time = (time.time() - start_time) * 1000 # Convert to ms
|
|
55
|
+
result.execution_time = execution_time
|
|
56
|
+
result.invocation_type = invocation_type
|
|
57
|
+
return result
|
|
58
|
+
|
|
59
|
+
except Exception as e:
|
|
60
|
+
execution_time = (time.time() - start_time) * 1000
|
|
61
|
+
return InvocationResult(
|
|
62
|
+
success=False,
|
|
63
|
+
error={
|
|
64
|
+
"message": str(e),
|
|
65
|
+
"type": type(e).__name__
|
|
66
|
+
},
|
|
67
|
+
execution_time=execution_time,
|
|
68
|
+
invocation_type=invocation_type
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def _invoke_function(self, params: Dict[str, Any], context: Any) -> InvocationResult:
|
|
72
|
+
"""Invoke local function"""
|
|
73
|
+
function_name = params.get('function')
|
|
74
|
+
function_args = params.get('args', {})
|
|
75
|
+
|
|
76
|
+
if not context:
|
|
77
|
+
return InvocationResult(
|
|
78
|
+
success=False,
|
|
79
|
+
error={"message": "Context required for function invocation"}
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Get function from context (ComponentRuntime will handle this)
|
|
83
|
+
if not hasattr(context, 'get_function'):
|
|
84
|
+
return InvocationResult(
|
|
85
|
+
success=False,
|
|
86
|
+
error={"message": "Context does not support function invocation"}
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
func = context.get_function(function_name)
|
|
90
|
+
if not func:
|
|
91
|
+
return InvocationResult(
|
|
92
|
+
success=False,
|
|
93
|
+
error={"message": f"Function '{function_name}' not found"}
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Execute function
|
|
97
|
+
result = context.execute_function(function_name, function_args)
|
|
98
|
+
|
|
99
|
+
return InvocationResult(
|
|
100
|
+
success=result.get('success', False),
|
|
101
|
+
data=result.get('data'),
|
|
102
|
+
error=result.get('error'),
|
|
103
|
+
metadata=result
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def _invoke_component(self, params: Dict[str, Any], context: Any) -> InvocationResult:
|
|
107
|
+
"""Invoke local component"""
|
|
108
|
+
component_name = params.get('component')
|
|
109
|
+
component_args = params.get('args', {})
|
|
110
|
+
|
|
111
|
+
if not context:
|
|
112
|
+
return InvocationResult(
|
|
113
|
+
success=False,
|
|
114
|
+
error={"message": "Context required for component invocation"}
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Get component from context (ComponentRuntime will handle this)
|
|
118
|
+
if not hasattr(context, 'execute_component'):
|
|
119
|
+
return InvocationResult(
|
|
120
|
+
success=False,
|
|
121
|
+
error={"message": "Context does not support component invocation"}
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
result = context.execute_component(component_name, component_args)
|
|
125
|
+
|
|
126
|
+
return InvocationResult(
|
|
127
|
+
success=result.get('success', False),
|
|
128
|
+
data=result.get('data'),
|
|
129
|
+
error=result.get('error'),
|
|
130
|
+
metadata=result
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def _invoke_http(self, params: Dict[str, Any]) -> InvocationResult:
|
|
134
|
+
"""Invoke HTTP/REST endpoint"""
|
|
135
|
+
url = params.get('url')
|
|
136
|
+
method = params.get('method', 'GET').upper()
|
|
137
|
+
headers = params.get('headers', {})
|
|
138
|
+
query_params = params.get('params', {})
|
|
139
|
+
body = params.get('body')
|
|
140
|
+
auth_type = params.get('auth_type')
|
|
141
|
+
# `or`, not a get() default: the executor always passes these keys,
|
|
142
|
+
# with None when the attribute is absent — so get('timeout', 30000)
|
|
143
|
+
# returned None and `None / 1000` raised TypeError before any request
|
|
144
|
+
# was made. Every q:invoke url= without an explicit timeout failed
|
|
145
|
+
# with "unsupported operand type(s) for /: 'NoneType' and 'int'".
|
|
146
|
+
timeout_ms = params.get('timeout') or 30000
|
|
147
|
+
retry = params.get('retry') or 0
|
|
148
|
+
retry_delay = params.get('retry_delay') or 1000
|
|
149
|
+
response_format = params.get('response_format', 'auto')
|
|
150
|
+
|
|
151
|
+
if not url:
|
|
152
|
+
return InvocationResult(
|
|
153
|
+
success=False,
|
|
154
|
+
error={"message": "URL required for HTTP invocation"}
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Build authentication
|
|
158
|
+
headers = self._build_auth_headers(params, headers)
|
|
159
|
+
|
|
160
|
+
# Convert timeout to seconds for requests library
|
|
161
|
+
timeout_sec = timeout_ms / 1000
|
|
162
|
+
|
|
163
|
+
# Retry logic
|
|
164
|
+
last_error = None
|
|
165
|
+
for attempt in range(retry + 1):
|
|
166
|
+
try:
|
|
167
|
+
# Make HTTP request
|
|
168
|
+
response = requests.request(
|
|
169
|
+
method=method,
|
|
170
|
+
url=url,
|
|
171
|
+
headers=headers,
|
|
172
|
+
params=query_params,
|
|
173
|
+
json=body if method in ['POST', 'PUT', 'PATCH'] and isinstance(body, dict) else None,
|
|
174
|
+
data=body if method in ['POST', 'PUT', 'PATCH'] and isinstance(body, str) else None,
|
|
175
|
+
timeout=timeout_sec
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# Parse response based on format
|
|
179
|
+
data = self._parse_http_response(response, response_format)
|
|
180
|
+
|
|
181
|
+
# Build metadata
|
|
182
|
+
metadata = {
|
|
183
|
+
"status_code": response.status_code,
|
|
184
|
+
"status_text": response.reason,
|
|
185
|
+
"headers": dict(response.headers),
|
|
186
|
+
"url": response.url,
|
|
187
|
+
"attempts": attempt + 1
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
# Check if response is successful
|
|
191
|
+
success = 200 <= response.status_code < 300
|
|
192
|
+
|
|
193
|
+
if not success:
|
|
194
|
+
return InvocationResult(
|
|
195
|
+
success=False,
|
|
196
|
+
data=data,
|
|
197
|
+
error={
|
|
198
|
+
"message": f"HTTP {response.status_code}: {response.reason}",
|
|
199
|
+
"status_code": response.status_code
|
|
200
|
+
},
|
|
201
|
+
metadata=metadata
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
return InvocationResult(
|
|
205
|
+
success=True,
|
|
206
|
+
data=data,
|
|
207
|
+
metadata=metadata
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
except requests.exceptions.Timeout as e:
|
|
211
|
+
last_error = InvocationResult(
|
|
212
|
+
success=False,
|
|
213
|
+
error={
|
|
214
|
+
"message": f"Request timeout after {timeout_ms}ms",
|
|
215
|
+
"type": "TimeoutError"
|
|
216
|
+
},
|
|
217
|
+
metadata={"attempts": attempt + 1}
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
except requests.exceptions.ConnectionError as e:
|
|
221
|
+
last_error = InvocationResult(
|
|
222
|
+
success=False,
|
|
223
|
+
error={
|
|
224
|
+
"message": f"Connection error: {str(e)}",
|
|
225
|
+
"type": "ConnectionError"
|
|
226
|
+
},
|
|
227
|
+
metadata={"attempts": attempt + 1}
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
except Exception as e:
|
|
231
|
+
last_error = InvocationResult(
|
|
232
|
+
success=False,
|
|
233
|
+
error={
|
|
234
|
+
"message": str(e),
|
|
235
|
+
"type": type(e).__name__
|
|
236
|
+
},
|
|
237
|
+
metadata={"attempts": attempt + 1}
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
# If we have more retries, wait before retrying
|
|
241
|
+
if attempt < retry:
|
|
242
|
+
time.sleep(retry_delay / 1000) # Convert ms to seconds
|
|
243
|
+
|
|
244
|
+
# All retries exhausted, return last error
|
|
245
|
+
return last_error
|
|
246
|
+
|
|
247
|
+
def _build_auth_headers(self, params: Dict[str, Any], headers: Dict[str, str]) -> Dict[str, str]:
|
|
248
|
+
"""Build authentication headers"""
|
|
249
|
+
auth_type = params.get('auth_type')
|
|
250
|
+
headers = headers.copy() # Don't mutate original
|
|
251
|
+
|
|
252
|
+
if auth_type == 'bearer':
|
|
253
|
+
token = params.get('auth_token')
|
|
254
|
+
if token:
|
|
255
|
+
headers['Authorization'] = f'Bearer {token}'
|
|
256
|
+
|
|
257
|
+
elif auth_type == 'apikey':
|
|
258
|
+
token = params.get('auth_token')
|
|
259
|
+
header_name = params.get('auth_header', 'Authorization')
|
|
260
|
+
if token:
|
|
261
|
+
headers[header_name] = token
|
|
262
|
+
|
|
263
|
+
elif auth_type == 'basic':
|
|
264
|
+
username = params.get('auth_username')
|
|
265
|
+
password = params.get('auth_password')
|
|
266
|
+
if username and password:
|
|
267
|
+
credentials = f"{username}:{password}".encode('utf-8')
|
|
268
|
+
encoded = base64.b64encode(credentials).decode('utf-8')
|
|
269
|
+
headers['Authorization'] = f'Basic {encoded}'
|
|
270
|
+
|
|
271
|
+
return headers
|
|
272
|
+
|
|
273
|
+
def _parse_http_response(self, response: requests.Response, response_format: str) -> Any:
|
|
274
|
+
"""Parse HTTP response based on format"""
|
|
275
|
+
if response_format == 'auto':
|
|
276
|
+
# Try to detect format from Content-Type header
|
|
277
|
+
content_type = response.headers.get('Content-Type', '').lower()
|
|
278
|
+
|
|
279
|
+
if 'application/json' in content_type:
|
|
280
|
+
try:
|
|
281
|
+
return response.json()
|
|
282
|
+
except:
|
|
283
|
+
return response.text
|
|
284
|
+
|
|
285
|
+
elif 'text/' in content_type or 'application/xml' in content_type:
|
|
286
|
+
return response.text
|
|
287
|
+
|
|
288
|
+
elif 'application/octet-stream' in content_type or 'image/' in content_type:
|
|
289
|
+
return {
|
|
290
|
+
"binary": True,
|
|
291
|
+
"content_type": content_type,
|
|
292
|
+
"size": len(response.content)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
else:
|
|
296
|
+
# Default to text
|
|
297
|
+
return response.text
|
|
298
|
+
|
|
299
|
+
elif response_format == 'json':
|
|
300
|
+
try:
|
|
301
|
+
return response.json()
|
|
302
|
+
except:
|
|
303
|
+
return {"error": "Failed to parse JSON response", "text": response.text}
|
|
304
|
+
|
|
305
|
+
elif response_format == 'text':
|
|
306
|
+
return response.text
|
|
307
|
+
|
|
308
|
+
elif response_format == 'xml':
|
|
309
|
+
return response.text
|
|
310
|
+
|
|
311
|
+
elif response_format == 'binary':
|
|
312
|
+
return {
|
|
313
|
+
"binary": True,
|
|
314
|
+
"content_type": response.headers.get('Content-Type'),
|
|
315
|
+
"size": len(response.content)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
else:
|
|
319
|
+
return response.text
|
|
320
|
+
|
|
321
|
+
def get_from_cache(self, cache_key: str) -> Optional[Any]:
|
|
322
|
+
"""Get value from cache"""
|
|
323
|
+
return self.cache.get(cache_key)
|
|
324
|
+
|
|
325
|
+
def put_in_cache(self, cache_key: str, value: Any, ttl: Optional[int] = None):
|
|
326
|
+
"""Put value in cache"""
|
|
327
|
+
# Simple implementation - in production you'd use TTL properly
|
|
328
|
+
self.cache[cache_key] = value
|
|
329
|
+
|
|
330
|
+
def clear_cache(self):
|
|
331
|
+
"""Clear all cache"""
|
|
332
|
+
self.cache.clear()
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""AST nodes for q:knowledge and q:source."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import List, Dict, Any, Optional
|
|
5
|
+
from quantum.core.ast_nodes import QuantumNode
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class KnowledgeSourceNode(QuantumNode):
|
|
10
|
+
"""
|
|
11
|
+
Represents <q:source> inside <q:knowledge>.
|
|
12
|
+
|
|
13
|
+
Examples:
|
|
14
|
+
<q:source type="text">Quantum Framework allows...</q:source>
|
|
15
|
+
<q:source type="file" path="./docs/guide.md" />
|
|
16
|
+
<q:source type="directory" path="./docs/" pattern="*.md" />
|
|
17
|
+
<q:source type="query" datasource="mydb">
|
|
18
|
+
SELECT title, content FROM articles
|
|
19
|
+
</q:source>
|
|
20
|
+
"""
|
|
21
|
+
source_type: str = "text" # text, file, directory, url, query
|
|
22
|
+
content: Optional[str] = None # Inline text content
|
|
23
|
+
path: Optional[str] = None # File or directory path
|
|
24
|
+
pattern: Optional[str] = None # Glob pattern for directory type
|
|
25
|
+
url: Optional[str] = None # URL for url type
|
|
26
|
+
datasource: Optional[str] = None # Datasource for query type
|
|
27
|
+
sql: Optional[str] = None # SQL for query type
|
|
28
|
+
chunk_size: Optional[int] = None # Override parent chunk_size
|
|
29
|
+
chunk_overlap: Optional[int] = None # Override parent chunk_overlap
|
|
30
|
+
|
|
31
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
32
|
+
return {
|
|
33
|
+
"type": "knowledge_source",
|
|
34
|
+
"source_type": self.source_type,
|
|
35
|
+
"content": self.content[:50] + "..." if self.content and len(self.content) > 50 else self.content,
|
|
36
|
+
"path": self.path,
|
|
37
|
+
"pattern": self.pattern,
|
|
38
|
+
"url": self.url,
|
|
39
|
+
"datasource": self.datasource,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
def validate(self) -> List[str]:
|
|
43
|
+
errors = []
|
|
44
|
+
valid_types = ['text', 'file', 'directory', 'url', 'query']
|
|
45
|
+
if self.source_type not in valid_types:
|
|
46
|
+
errors.append(f"Invalid source type: {self.source_type}. Must be one of {valid_types}")
|
|
47
|
+
|
|
48
|
+
if self.source_type == 'text' and not self.content:
|
|
49
|
+
errors.append("Text source requires content")
|
|
50
|
+
if self.source_type == 'file' and not self.path:
|
|
51
|
+
errors.append("File source requires 'path' attribute")
|
|
52
|
+
if self.source_type == 'directory' and not self.path:
|
|
53
|
+
errors.append("Directory source requires 'path' attribute")
|
|
54
|
+
if self.source_type == 'url' and not self.url:
|
|
55
|
+
errors.append("URL source requires 'url' attribute")
|
|
56
|
+
if self.source_type == 'query' and not self.datasource:
|
|
57
|
+
errors.append("Query source requires 'datasource' attribute")
|
|
58
|
+
|
|
59
|
+
return errors
|
|
60
|
+
|
|
61
|
+
def __repr__(self):
|
|
62
|
+
return f'<KnowledgeSourceNode type={self.source_type}>'
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class KnowledgeNode(QuantumNode):
|
|
67
|
+
"""
|
|
68
|
+
Represents <q:knowledge> - defines a virtual knowledge base.
|
|
69
|
+
|
|
70
|
+
Examples:
|
|
71
|
+
<q:knowledge name="docs" model="phi3" embedModel="nomic-embed-text">
|
|
72
|
+
<q:source type="text">Quantum Framework allows...</q:source>
|
|
73
|
+
<q:source type="file" path="./docs/guide.md" />
|
|
74
|
+
</q:knowledge>
|
|
75
|
+
"""
|
|
76
|
+
name: str = ""
|
|
77
|
+
model: Optional[str] = None # LLM model for RAG
|
|
78
|
+
embed_model: str = "nomic-embed-text" # Embedding model
|
|
79
|
+
chunk_size: int = 500 # Characters per chunk
|
|
80
|
+
chunk_overlap: int = 50 # Overlap between chunks
|
|
81
|
+
sources: List[KnowledgeSourceNode] = field(default_factory=list)
|
|
82
|
+
persist: bool = False # Persist ChromaDB to disk
|
|
83
|
+
persist_path: Optional[str] = None # ChromaDB persistence path
|
|
84
|
+
rebuild: bool = False # Force reindex
|
|
85
|
+
|
|
86
|
+
def add_source(self, source: KnowledgeSourceNode):
|
|
87
|
+
self.sources.append(source)
|
|
88
|
+
|
|
89
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
90
|
+
return {
|
|
91
|
+
"type": "knowledge",
|
|
92
|
+
"name": self.name,
|
|
93
|
+
"model": self.model,
|
|
94
|
+
"embed_model": self.embed_model,
|
|
95
|
+
"chunk_size": self.chunk_size,
|
|
96
|
+
"chunk_overlap": self.chunk_overlap,
|
|
97
|
+
"sources": [s.to_dict() for s in self.sources],
|
|
98
|
+
"persist": self.persist,
|
|
99
|
+
"rebuild": self.rebuild,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
def validate(self) -> List[str]:
|
|
103
|
+
errors = []
|
|
104
|
+
if not self.name:
|
|
105
|
+
errors.append("Knowledge base name is required")
|
|
106
|
+
if not self.sources:
|
|
107
|
+
errors.append("Knowledge base requires at least one <q:source>")
|
|
108
|
+
for source in self.sources:
|
|
109
|
+
errors.extend(source.validate())
|
|
110
|
+
return errors
|
|
111
|
+
|
|
112
|
+
def __repr__(self):
|
|
113
|
+
return f'<KnowledgeNode name={self.name} sources={len(self.sources)}>'
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Parser for q:knowledge and q:source elements."""
|
|
2
|
+
|
|
3
|
+
from xml.etree import ElementTree as ET
|
|
4
|
+
from .ast_node import KnowledgeNode, KnowledgeSourceNode
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def parse_knowledge(element: ET.Element) -> KnowledgeNode:
|
|
8
|
+
"""
|
|
9
|
+
Parse <q:knowledge> element into KnowledgeNode.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
element: XML element representing <q:knowledge>
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
KnowledgeNode with parsed sources
|
|
16
|
+
"""
|
|
17
|
+
name = element.get('name', '')
|
|
18
|
+
node = KnowledgeNode(name=name)
|
|
19
|
+
|
|
20
|
+
# Parse attributes
|
|
21
|
+
node.model = element.get('model')
|
|
22
|
+
node.embed_model = element.get('embedModel', 'nomic-embed-text')
|
|
23
|
+
|
|
24
|
+
chunk_size = element.get('chunkSize')
|
|
25
|
+
if chunk_size:
|
|
26
|
+
try:
|
|
27
|
+
node.chunk_size = int(chunk_size)
|
|
28
|
+
except ValueError:
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
chunk_overlap = element.get('chunkOverlap')
|
|
32
|
+
if chunk_overlap:
|
|
33
|
+
try:
|
|
34
|
+
node.chunk_overlap = int(chunk_overlap)
|
|
35
|
+
except ValueError:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
node.persist = element.get('persist', 'false').lower() in ('true', '1', 'yes')
|
|
39
|
+
node.persist_path = element.get('persistPath')
|
|
40
|
+
node.rebuild = element.get('rebuild', 'false').lower() in ('true', '1', 'yes')
|
|
41
|
+
|
|
42
|
+
# Parse <q:source> children
|
|
43
|
+
for child in element:
|
|
44
|
+
local_name = child.tag.split('}')[-1] if '}' in child.tag else child.tag.split(':')[-1]
|
|
45
|
+
if local_name == 'source':
|
|
46
|
+
source = _parse_source(child)
|
|
47
|
+
node.add_source(source)
|
|
48
|
+
|
|
49
|
+
return node
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _parse_source(element: ET.Element) -> KnowledgeSourceNode:
|
|
53
|
+
"""Parse <q:source> element into KnowledgeSourceNode."""
|
|
54
|
+
source = KnowledgeSourceNode()
|
|
55
|
+
source.source_type = element.get('type', 'text')
|
|
56
|
+
source.path = element.get('path')
|
|
57
|
+
source.pattern = element.get('pattern', '*.md')
|
|
58
|
+
source.url = element.get('url')
|
|
59
|
+
source.datasource = element.get('datasource')
|
|
60
|
+
|
|
61
|
+
chunk_size = element.get('chunkSize')
|
|
62
|
+
if chunk_size:
|
|
63
|
+
try:
|
|
64
|
+
source.chunk_size = int(chunk_size)
|
|
65
|
+
except ValueError:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
chunk_overlap = element.get('chunkOverlap')
|
|
69
|
+
if chunk_overlap:
|
|
70
|
+
try:
|
|
71
|
+
source.chunk_overlap = int(chunk_overlap)
|
|
72
|
+
except ValueError:
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
# Text content (inline or SQL for query type)
|
|
76
|
+
text = (element.text or '').strip()
|
|
77
|
+
if source.source_type == 'query':
|
|
78
|
+
source.sql = text
|
|
79
|
+
elif source.source_type == 'text':
|
|
80
|
+
source.content = text
|
|
81
|
+
|
|
82
|
+
return source
|