aiqtoolkit 1.1.0a20250516__py3-none-any.whl → 1.1.0a20251020__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.
Potentially problematic release.
This version of aiqtoolkit might be problematic. Click here for more details.
- aiqtoolkit-1.1.0a20251020.dist-info/METADATA +37 -0
- aiqtoolkit-1.1.0a20251020.dist-info/RECORD +4 -0
- {aiqtoolkit-1.1.0a20250516.dist-info → aiqtoolkit-1.1.0a20251020.dist-info}/WHEEL +1 -1
- aiqtoolkit-1.1.0a20251020.dist-info/top_level.txt +1 -0
- aiq/agent/__init__.py +0 -0
- aiq/agent/base.py +0 -76
- aiq/agent/dual_node.py +0 -67
- aiq/agent/react_agent/__init__.py +0 -0
- aiq/agent/react_agent/agent.py +0 -322
- aiq/agent/react_agent/output_parser.py +0 -104
- aiq/agent/react_agent/prompt.py +0 -46
- aiq/agent/react_agent/register.py +0 -148
- aiq/agent/reasoning_agent/__init__.py +0 -0
- aiq/agent/reasoning_agent/reasoning_agent.py +0 -224
- aiq/agent/register.py +0 -23
- aiq/agent/rewoo_agent/__init__.py +0 -0
- aiq/agent/rewoo_agent/agent.py +0 -410
- aiq/agent/rewoo_agent/prompt.py +0 -108
- aiq/agent/rewoo_agent/register.py +0 -158
- aiq/agent/tool_calling_agent/__init__.py +0 -0
- aiq/agent/tool_calling_agent/agent.py +0 -123
- aiq/agent/tool_calling_agent/register.py +0 -105
- aiq/builder/__init__.py +0 -0
- aiq/builder/builder.py +0 -223
- aiq/builder/component_utils.py +0 -303
- aiq/builder/context.py +0 -227
- aiq/builder/embedder.py +0 -24
- aiq/builder/eval_builder.py +0 -120
- aiq/builder/evaluator.py +0 -29
- aiq/builder/framework_enum.py +0 -24
- aiq/builder/front_end.py +0 -73
- aiq/builder/function.py +0 -297
- aiq/builder/function_base.py +0 -376
- aiq/builder/function_info.py +0 -627
- aiq/builder/intermediate_step_manager.py +0 -176
- aiq/builder/llm.py +0 -25
- aiq/builder/retriever.py +0 -25
- aiq/builder/user_interaction_manager.py +0 -71
- aiq/builder/workflow.py +0 -143
- aiq/builder/workflow_builder.py +0 -757
- aiq/cli/__init__.py +0 -14
- aiq/cli/cli_utils/__init__.py +0 -0
- aiq/cli/cli_utils/config_override.py +0 -231
- aiq/cli/cli_utils/validation.py +0 -37
- aiq/cli/commands/__init__.py +0 -0
- aiq/cli/commands/configure/__init__.py +0 -0
- aiq/cli/commands/configure/channel/__init__.py +0 -0
- aiq/cli/commands/configure/channel/add.py +0 -28
- aiq/cli/commands/configure/channel/channel.py +0 -36
- aiq/cli/commands/configure/channel/remove.py +0 -30
- aiq/cli/commands/configure/channel/update.py +0 -30
- aiq/cli/commands/configure/configure.py +0 -33
- aiq/cli/commands/evaluate.py +0 -139
- aiq/cli/commands/info/__init__.py +0 -14
- aiq/cli/commands/info/info.py +0 -39
- aiq/cli/commands/info/list_channels.py +0 -32
- aiq/cli/commands/info/list_components.py +0 -129
- aiq/cli/commands/info/list_mcp.py +0 -126
- aiq/cli/commands/registry/__init__.py +0 -14
- aiq/cli/commands/registry/publish.py +0 -88
- aiq/cli/commands/registry/pull.py +0 -118
- aiq/cli/commands/registry/registry.py +0 -38
- aiq/cli/commands/registry/remove.py +0 -108
- aiq/cli/commands/registry/search.py +0 -155
- aiq/cli/commands/start.py +0 -250
- aiq/cli/commands/uninstall.py +0 -83
- aiq/cli/commands/validate.py +0 -47
- aiq/cli/commands/workflow/__init__.py +0 -14
- aiq/cli/commands/workflow/templates/__init__.py.j2 +0 -0
- aiq/cli/commands/workflow/templates/config.yml.j2 +0 -16
- aiq/cli/commands/workflow/templates/pyproject.toml.j2 +0 -22
- aiq/cli/commands/workflow/templates/register.py.j2 +0 -5
- aiq/cli/commands/workflow/templates/workflow.py.j2 +0 -36
- aiq/cli/commands/workflow/workflow.py +0 -37
- aiq/cli/commands/workflow/workflow_commands.py +0 -313
- aiq/cli/entrypoint.py +0 -133
- aiq/cli/main.py +0 -44
- aiq/cli/register_workflow.py +0 -408
- aiq/cli/type_registry.py +0 -879
- aiq/data_models/__init__.py +0 -14
- aiq/data_models/api_server.py +0 -588
- aiq/data_models/common.py +0 -143
- aiq/data_models/component.py +0 -46
- aiq/data_models/component_ref.py +0 -135
- aiq/data_models/config.py +0 -349
- aiq/data_models/dataset_handler.py +0 -122
- aiq/data_models/discovery_metadata.py +0 -286
- aiq/data_models/embedder.py +0 -26
- aiq/data_models/evaluate.py +0 -104
- aiq/data_models/evaluator.py +0 -26
- aiq/data_models/front_end.py +0 -26
- aiq/data_models/function.py +0 -30
- aiq/data_models/function_dependencies.py +0 -64
- aiq/data_models/interactive.py +0 -237
- aiq/data_models/intermediate_step.py +0 -269
- aiq/data_models/invocation_node.py +0 -38
- aiq/data_models/llm.py +0 -26
- aiq/data_models/logging.py +0 -26
- aiq/data_models/memory.py +0 -26
- aiq/data_models/profiler.py +0 -53
- aiq/data_models/registry_handler.py +0 -26
- aiq/data_models/retriever.py +0 -30
- aiq/data_models/step_adaptor.py +0 -64
- aiq/data_models/streaming.py +0 -33
- aiq/data_models/swe_bench_model.py +0 -54
- aiq/data_models/telemetry_exporter.py +0 -26
- aiq/embedder/__init__.py +0 -0
- aiq/embedder/langchain_client.py +0 -41
- aiq/embedder/nim_embedder.py +0 -58
- aiq/embedder/openai_embedder.py +0 -42
- aiq/embedder/register.py +0 -24
- aiq/eval/__init__.py +0 -14
- aiq/eval/config.py +0 -42
- aiq/eval/dataset_handler/__init__.py +0 -0
- aiq/eval/dataset_handler/dataset_downloader.py +0 -106
- aiq/eval/dataset_handler/dataset_filter.py +0 -52
- aiq/eval/dataset_handler/dataset_handler.py +0 -169
- aiq/eval/evaluate.py +0 -325
- aiq/eval/evaluator/__init__.py +0 -14
- aiq/eval/evaluator/evaluator_model.py +0 -44
- aiq/eval/intermediate_step_adapter.py +0 -93
- aiq/eval/rag_evaluator/__init__.py +0 -0
- aiq/eval/rag_evaluator/evaluate.py +0 -138
- aiq/eval/rag_evaluator/register.py +0 -138
- aiq/eval/register.py +0 -23
- aiq/eval/remote_workflow.py +0 -128
- aiq/eval/runtime_event_subscriber.py +0 -52
- aiq/eval/swe_bench_evaluator/__init__.py +0 -0
- aiq/eval/swe_bench_evaluator/evaluate.py +0 -215
- aiq/eval/swe_bench_evaluator/register.py +0 -36
- aiq/eval/trajectory_evaluator/__init__.py +0 -0
- aiq/eval/trajectory_evaluator/evaluate.py +0 -118
- aiq/eval/trajectory_evaluator/register.py +0 -40
- aiq/eval/tunable_rag_evaluator/__init__.py +0 -0
- aiq/eval/tunable_rag_evaluator/evaluate.py +0 -263
- aiq/eval/tunable_rag_evaluator/register.py +0 -50
- aiq/eval/utils/__init__.py +0 -0
- aiq/eval/utils/output_uploader.py +0 -131
- aiq/eval/utils/tqdm_position_registry.py +0 -40
- aiq/front_ends/__init__.py +0 -14
- aiq/front_ends/console/__init__.py +0 -14
- aiq/front_ends/console/console_front_end_config.py +0 -32
- aiq/front_ends/console/console_front_end_plugin.py +0 -107
- aiq/front_ends/console/register.py +0 -25
- aiq/front_ends/cron/__init__.py +0 -14
- aiq/front_ends/fastapi/__init__.py +0 -14
- aiq/front_ends/fastapi/fastapi_front_end_config.py +0 -150
- aiq/front_ends/fastapi/fastapi_front_end_plugin.py +0 -103
- aiq/front_ends/fastapi/fastapi_front_end_plugin_worker.py +0 -607
- aiq/front_ends/fastapi/intermediate_steps_subscriber.py +0 -80
- aiq/front_ends/fastapi/job_store.py +0 -161
- aiq/front_ends/fastapi/main.py +0 -70
- aiq/front_ends/fastapi/message_handler.py +0 -279
- aiq/front_ends/fastapi/message_validator.py +0 -345
- aiq/front_ends/fastapi/register.py +0 -25
- aiq/front_ends/fastapi/response_helpers.py +0 -195
- aiq/front_ends/fastapi/step_adaptor.py +0 -320
- aiq/front_ends/fastapi/websocket.py +0 -148
- aiq/front_ends/mcp/__init__.py +0 -14
- aiq/front_ends/mcp/mcp_front_end_config.py +0 -32
- aiq/front_ends/mcp/mcp_front_end_plugin.py +0 -93
- aiq/front_ends/mcp/register.py +0 -27
- aiq/front_ends/mcp/tool_converter.py +0 -242
- aiq/front_ends/register.py +0 -22
- aiq/front_ends/simple_base/__init__.py +0 -14
- aiq/front_ends/simple_base/simple_front_end_plugin_base.py +0 -52
- aiq/llm/__init__.py +0 -0
- aiq/llm/nim_llm.py +0 -45
- aiq/llm/openai_llm.py +0 -45
- aiq/llm/register.py +0 -22
- aiq/llm/utils/__init__.py +0 -14
- aiq/llm/utils/env_config_value.py +0 -94
- aiq/llm/utils/error.py +0 -17
- aiq/memory/__init__.py +0 -20
- aiq/memory/interfaces.py +0 -183
- aiq/memory/models.py +0 -112
- aiq/meta/module_to_distro.json +0 -3
- aiq/meta/pypi.md +0 -58
- aiq/observability/__init__.py +0 -0
- aiq/observability/async_otel_listener.py +0 -429
- aiq/observability/register.py +0 -99
- aiq/plugins/.namespace +0 -1
- aiq/profiler/__init__.py +0 -0
- aiq/profiler/callbacks/__init__.py +0 -0
- aiq/profiler/callbacks/agno_callback_handler.py +0 -295
- aiq/profiler/callbacks/base_callback_class.py +0 -20
- aiq/profiler/callbacks/langchain_callback_handler.py +0 -278
- aiq/profiler/callbacks/llama_index_callback_handler.py +0 -205
- aiq/profiler/callbacks/semantic_kernel_callback_handler.py +0 -238
- aiq/profiler/callbacks/token_usage_base_model.py +0 -27
- aiq/profiler/data_frame_row.py +0 -51
- aiq/profiler/decorators/__init__.py +0 -0
- aiq/profiler/decorators/framework_wrapper.py +0 -131
- aiq/profiler/decorators/function_tracking.py +0 -254
- aiq/profiler/forecasting/__init__.py +0 -0
- aiq/profiler/forecasting/config.py +0 -18
- aiq/profiler/forecasting/model_trainer.py +0 -75
- aiq/profiler/forecasting/models/__init__.py +0 -22
- aiq/profiler/forecasting/models/forecasting_base_model.py +0 -40
- aiq/profiler/forecasting/models/linear_model.py +0 -196
- aiq/profiler/forecasting/models/random_forest_regressor.py +0 -268
- aiq/profiler/inference_metrics_model.py +0 -25
- aiq/profiler/inference_optimization/__init__.py +0 -0
- aiq/profiler/inference_optimization/bottleneck_analysis/__init__.py +0 -0
- aiq/profiler/inference_optimization/bottleneck_analysis/nested_stack_analysis.py +0 -452
- aiq/profiler/inference_optimization/bottleneck_analysis/simple_stack_analysis.py +0 -258
- aiq/profiler/inference_optimization/data_models.py +0 -386
- aiq/profiler/inference_optimization/experimental/__init__.py +0 -0
- aiq/profiler/inference_optimization/experimental/concurrency_spike_analysis.py +0 -468
- aiq/profiler/inference_optimization/experimental/prefix_span_analysis.py +0 -405
- aiq/profiler/inference_optimization/llm_metrics.py +0 -212
- aiq/profiler/inference_optimization/prompt_caching.py +0 -163
- aiq/profiler/inference_optimization/token_uniqueness.py +0 -107
- aiq/profiler/inference_optimization/workflow_runtimes.py +0 -72
- aiq/profiler/intermediate_property_adapter.py +0 -102
- aiq/profiler/profile_runner.py +0 -433
- aiq/profiler/utils.py +0 -184
- aiq/registry_handlers/__init__.py +0 -0
- aiq/registry_handlers/local/__init__.py +0 -0
- aiq/registry_handlers/local/local_handler.py +0 -176
- aiq/registry_handlers/local/register_local.py +0 -37
- aiq/registry_handlers/metadata_factory.py +0 -60
- aiq/registry_handlers/package_utils.py +0 -198
- aiq/registry_handlers/pypi/__init__.py +0 -0
- aiq/registry_handlers/pypi/pypi_handler.py +0 -251
- aiq/registry_handlers/pypi/register_pypi.py +0 -40
- aiq/registry_handlers/register.py +0 -21
- aiq/registry_handlers/registry_handler_base.py +0 -157
- aiq/registry_handlers/rest/__init__.py +0 -0
- aiq/registry_handlers/rest/register_rest.py +0 -56
- aiq/registry_handlers/rest/rest_handler.py +0 -237
- aiq/registry_handlers/schemas/__init__.py +0 -0
- aiq/registry_handlers/schemas/headers.py +0 -42
- aiq/registry_handlers/schemas/package.py +0 -68
- aiq/registry_handlers/schemas/publish.py +0 -63
- aiq/registry_handlers/schemas/pull.py +0 -82
- aiq/registry_handlers/schemas/remove.py +0 -36
- aiq/registry_handlers/schemas/search.py +0 -91
- aiq/registry_handlers/schemas/status.py +0 -47
- aiq/retriever/__init__.py +0 -0
- aiq/retriever/interface.py +0 -37
- aiq/retriever/milvus/__init__.py +0 -14
- aiq/retriever/milvus/register.py +0 -81
- aiq/retriever/milvus/retriever.py +0 -228
- aiq/retriever/models.py +0 -74
- aiq/retriever/nemo_retriever/__init__.py +0 -14
- aiq/retriever/nemo_retriever/register.py +0 -60
- aiq/retriever/nemo_retriever/retriever.py +0 -190
- aiq/retriever/register.py +0 -22
- aiq/runtime/__init__.py +0 -14
- aiq/runtime/loader.py +0 -188
- aiq/runtime/runner.py +0 -176
- aiq/runtime/session.py +0 -140
- aiq/runtime/user_metadata.py +0 -131
- aiq/settings/__init__.py +0 -0
- aiq/settings/global_settings.py +0 -318
- aiq/test/.namespace +0 -1
- aiq/tool/__init__.py +0 -0
- aiq/tool/code_execution/__init__.py +0 -0
- aiq/tool/code_execution/code_sandbox.py +0 -188
- aiq/tool/code_execution/local_sandbox/Dockerfile.sandbox +0 -60
- aiq/tool/code_execution/local_sandbox/__init__.py +0 -13
- aiq/tool/code_execution/local_sandbox/local_sandbox_server.py +0 -83
- aiq/tool/code_execution/local_sandbox/sandbox.requirements.txt +0 -4
- aiq/tool/code_execution/local_sandbox/start_local_sandbox.sh +0 -25
- aiq/tool/code_execution/register.py +0 -70
- aiq/tool/code_execution/utils.py +0 -100
- aiq/tool/datetime_tools.py +0 -42
- aiq/tool/document_search.py +0 -141
- aiq/tool/github_tools/__init__.py +0 -0
- aiq/tool/github_tools/create_github_commit.py +0 -133
- aiq/tool/github_tools/create_github_issue.py +0 -87
- aiq/tool/github_tools/create_github_pr.py +0 -106
- aiq/tool/github_tools/get_github_file.py +0 -106
- aiq/tool/github_tools/get_github_issue.py +0 -166
- aiq/tool/github_tools/get_github_pr.py +0 -256
- aiq/tool/github_tools/update_github_issue.py +0 -100
- aiq/tool/mcp/__init__.py +0 -14
- aiq/tool/mcp/mcp_client.py +0 -220
- aiq/tool/mcp/mcp_tool.py +0 -95
- aiq/tool/memory_tools/__init__.py +0 -0
- aiq/tool/memory_tools/add_memory_tool.py +0 -79
- aiq/tool/memory_tools/delete_memory_tool.py +0 -67
- aiq/tool/memory_tools/get_memory_tool.py +0 -72
- aiq/tool/nvidia_rag.py +0 -95
- aiq/tool/register.py +0 -37
- aiq/tool/retriever.py +0 -89
- aiq/tool/server_tools.py +0 -63
- aiq/utils/__init__.py +0 -0
- aiq/utils/data_models/__init__.py +0 -0
- aiq/utils/data_models/schema_validator.py +0 -58
- aiq/utils/debugging_utils.py +0 -43
- aiq/utils/exception_handlers/__init__.py +0 -0
- aiq/utils/exception_handlers/schemas.py +0 -114
- aiq/utils/io/__init__.py +0 -0
- aiq/utils/io/yaml_tools.py +0 -119
- aiq/utils/metadata_utils.py +0 -74
- aiq/utils/optional_imports.py +0 -142
- aiq/utils/producer_consumer_queue.py +0 -178
- aiq/utils/reactive/__init__.py +0 -0
- aiq/utils/reactive/base/__init__.py +0 -0
- aiq/utils/reactive/base/observable_base.py +0 -65
- aiq/utils/reactive/base/observer_base.py +0 -55
- aiq/utils/reactive/base/subject_base.py +0 -79
- aiq/utils/reactive/observable.py +0 -59
- aiq/utils/reactive/observer.py +0 -76
- aiq/utils/reactive/subject.py +0 -131
- aiq/utils/reactive/subscription.py +0 -49
- aiq/utils/settings/__init__.py +0 -0
- aiq/utils/settings/global_settings.py +0 -197
- aiq/utils/type_converter.py +0 -232
- aiq/utils/type_utils.py +0 -397
- aiq/utils/url_utils.py +0 -27
- aiqtoolkit-1.1.0a20250516.dist-info/METADATA +0 -331
- aiqtoolkit-1.1.0a20250516.dist-info/RECORD +0 -316
- aiqtoolkit-1.1.0a20250516.dist-info/entry_points.txt +0 -17
- aiqtoolkit-1.1.0a20250516.dist-info/licenses/LICENSE-3rd-party.txt +0 -3686
- aiqtoolkit-1.1.0a20250516.dist-info/licenses/LICENSE.md +0 -201
- aiqtoolkit-1.1.0a20250516.dist-info/top_level.txt +0 -1
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
-
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
#
|
|
4
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
# you may not use this file except in compliance with the License.
|
|
6
|
-
# You may obtain a copy of the License at
|
|
7
|
-
#
|
|
8
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
#
|
|
10
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
# See the License for the specific language governing permissions and
|
|
14
|
-
# limitations under the License.
|
|
15
|
-
|
|
16
|
-
import logging
|
|
17
|
-
import os
|
|
18
|
-
import shutil
|
|
19
|
-
from datetime import UTC
|
|
20
|
-
from datetime import datetime
|
|
21
|
-
from datetime import timedelta
|
|
22
|
-
from enum import Enum
|
|
23
|
-
from uuid import uuid4
|
|
24
|
-
|
|
25
|
-
from pydantic import BaseModel
|
|
26
|
-
|
|
27
|
-
logger = logging.getLogger(__name__)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class JobStatus(str, Enum):
|
|
31
|
-
SUBMITTED = "submitted"
|
|
32
|
-
RUNNING = "running"
|
|
33
|
-
SUCCESS = "success"
|
|
34
|
-
FAILURE = "failure"
|
|
35
|
-
INTERRUPTED = "interrupted"
|
|
36
|
-
NOT_FOUND = "not_found"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
# pydantic model for the job status
|
|
40
|
-
class JobInfo(BaseModel):
|
|
41
|
-
job_id: str
|
|
42
|
-
status: JobStatus
|
|
43
|
-
config_file: str
|
|
44
|
-
error: str | None
|
|
45
|
-
output_path: str | None
|
|
46
|
-
created_at: datetime
|
|
47
|
-
updated_at: datetime
|
|
48
|
-
expiry_seconds: int
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
class JobStore:
|
|
52
|
-
|
|
53
|
-
MIN_EXPIRY = 600 # 10 minutes
|
|
54
|
-
MAX_EXPIRY = 86400 # 24 hours
|
|
55
|
-
DEFAULT_EXPIRY = 3600 # 1 hour
|
|
56
|
-
|
|
57
|
-
# active jobs are exempt from expiry
|
|
58
|
-
ACTIVE_STATUS = {"running", "submitted"}
|
|
59
|
-
|
|
60
|
-
def __init__(self):
|
|
61
|
-
self._jobs = {}
|
|
62
|
-
|
|
63
|
-
def create_job(self, config_file: str, job_id: str | None = None, expiry_seconds: int = DEFAULT_EXPIRY) -> str:
|
|
64
|
-
if job_id is None:
|
|
65
|
-
job_id = str(uuid4())
|
|
66
|
-
|
|
67
|
-
clamped_expiry = max(self.MIN_EXPIRY, min(expiry_seconds, self.MAX_EXPIRY))
|
|
68
|
-
if expiry_seconds != clamped_expiry:
|
|
69
|
-
logger.info("Clamped expiry_seconds from %d to %d for job %s", expiry_seconds, clamped_expiry, job_id)
|
|
70
|
-
|
|
71
|
-
job = JobInfo(job_id=job_id,
|
|
72
|
-
status=JobStatus.SUBMITTED,
|
|
73
|
-
config_file=config_file,
|
|
74
|
-
created_at=datetime.now(UTC),
|
|
75
|
-
updated_at=datetime.now(UTC),
|
|
76
|
-
error=None,
|
|
77
|
-
output_path=None,
|
|
78
|
-
expiry_seconds=clamped_expiry)
|
|
79
|
-
self._jobs[job_id] = job
|
|
80
|
-
logger.info("Created new job %s with config %s", job_id, config_file)
|
|
81
|
-
return job_id
|
|
82
|
-
|
|
83
|
-
def update_status(self, job_id: str, status: str, error: str | None = None, output_path: str | None = None):
|
|
84
|
-
if job_id not in self._jobs:
|
|
85
|
-
raise ValueError(f"Job {job_id} not found")
|
|
86
|
-
|
|
87
|
-
job = self._jobs[job_id]
|
|
88
|
-
job.status = status
|
|
89
|
-
job.error = error
|
|
90
|
-
job.output_path = output_path
|
|
91
|
-
job.updated_at = datetime.now(UTC)
|
|
92
|
-
|
|
93
|
-
def get_status(self, job_id: str) -> JobInfo | None:
|
|
94
|
-
return self._jobs.get(job_id)
|
|
95
|
-
|
|
96
|
-
def list_jobs(self):
|
|
97
|
-
return self._jobs
|
|
98
|
-
|
|
99
|
-
def get_job(self, job_id: str) -> JobInfo | None:
|
|
100
|
-
"""Get a job by its ID."""
|
|
101
|
-
return self._jobs.get(job_id)
|
|
102
|
-
|
|
103
|
-
def get_last_job(self) -> JobInfo | None:
|
|
104
|
-
"""Get the last created job."""
|
|
105
|
-
if not self._jobs:
|
|
106
|
-
logger.info("No jobs found in job store")
|
|
107
|
-
return None
|
|
108
|
-
last_job = max(self._jobs.values(), key=lambda job: job.created_at)
|
|
109
|
-
logger.info("Retrieved last job %s created at %s", last_job.job_id, last_job.created_at)
|
|
110
|
-
return last_job
|
|
111
|
-
|
|
112
|
-
def get_jobs_by_status(self, status: str) -> list[JobInfo]:
|
|
113
|
-
"""Get all jobs with the specified status."""
|
|
114
|
-
return [job for job in self._jobs.values() if job.status == status]
|
|
115
|
-
|
|
116
|
-
def get_all_jobs(self) -> list[JobInfo]:
|
|
117
|
-
"""Get all jobs in the store."""
|
|
118
|
-
return list(self._jobs.values())
|
|
119
|
-
|
|
120
|
-
def get_expires_at(self, job: JobInfo) -> datetime | None:
|
|
121
|
-
"""Get the time for a job to expire."""
|
|
122
|
-
if job.status in self.ACTIVE_STATUS:
|
|
123
|
-
return None
|
|
124
|
-
return job.updated_at + timedelta(seconds=job.expiry_seconds)
|
|
125
|
-
|
|
126
|
-
def cleanup_expired_jobs(self):
|
|
127
|
-
"""
|
|
128
|
-
Cleanup expired jobs, keeping the most recent one.
|
|
129
|
-
Updated_at is used instead of created_at to determine the most recent job.
|
|
130
|
-
This is because jobs may not be processed in the order they are created.
|
|
131
|
-
"""
|
|
132
|
-
now = datetime.now(UTC)
|
|
133
|
-
|
|
134
|
-
# Filter out active jobs
|
|
135
|
-
finished_jobs = {job_id: job for job_id, job in self._jobs.items() if job.status not in self.ACTIVE_STATUS}
|
|
136
|
-
|
|
137
|
-
# Sort finished jobs by updated_at descending
|
|
138
|
-
sorted_finished = sorted(finished_jobs.items(), key=lambda item: item[1].updated_at, reverse=True)
|
|
139
|
-
|
|
140
|
-
# Always keep the most recent finished job
|
|
141
|
-
jobs_to_check = sorted_finished[1:]
|
|
142
|
-
|
|
143
|
-
expired_ids = []
|
|
144
|
-
for job_id, job in jobs_to_check:
|
|
145
|
-
expires_at = self.get_expires_at(job)
|
|
146
|
-
if expires_at and now > expires_at:
|
|
147
|
-
expired_ids.append(job_id)
|
|
148
|
-
# cleanup output dir if present
|
|
149
|
-
if job.output_path:
|
|
150
|
-
logger.info("Cleaning up output directory for job %s at %s", job_id, job.output_path)
|
|
151
|
-
# If it is a file remove it
|
|
152
|
-
if os.path.isfile(job.output_path):
|
|
153
|
-
os.remove(job.output_path)
|
|
154
|
-
# If it is a directory remove it
|
|
155
|
-
elif os.path.isdir(job.output_path):
|
|
156
|
-
shutil.rmtree(job.output_path)
|
|
157
|
-
|
|
158
|
-
for job_id in expired_ids:
|
|
159
|
-
# cleanup output dir if present
|
|
160
|
-
|
|
161
|
-
del self._jobs[job_id]
|
aiq/front_ends/fastapi/main.py
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
# SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
-
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
#
|
|
4
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
# you may not use this file except in compliance with the License.
|
|
6
|
-
# You may obtain a copy of the License at
|
|
7
|
-
#
|
|
8
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
#
|
|
10
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
# See the License for the specific language governing permissions and
|
|
14
|
-
# limitations under the License.
|
|
15
|
-
|
|
16
|
-
import importlib
|
|
17
|
-
import logging
|
|
18
|
-
import os
|
|
19
|
-
|
|
20
|
-
from aiq.front_ends.fastapi.fastapi_front_end_plugin_worker import FastApiFrontEndPluginWorkerBase
|
|
21
|
-
from aiq.runtime.loader import load_config
|
|
22
|
-
|
|
23
|
-
logger = logging.getLogger(__name__)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def get_app():
|
|
27
|
-
|
|
28
|
-
config_file_path = os.getenv("AIQ_CONFIG_FILE")
|
|
29
|
-
front_end_worker_full_name = os.getenv("AIQ_FRONT_END_WORKER")
|
|
30
|
-
|
|
31
|
-
if (not config_file_path):
|
|
32
|
-
raise ValueError("Config file not found in environment variable AIQ_CONFIG_FILE.")
|
|
33
|
-
|
|
34
|
-
if (not front_end_worker_full_name):
|
|
35
|
-
raise ValueError("Front end worker not found in environment variable AIQ_FRONT_END_WORKER.")
|
|
36
|
-
|
|
37
|
-
# Try to import the front end worker class
|
|
38
|
-
try:
|
|
39
|
-
# Split the package from the class
|
|
40
|
-
front_end_worker_parts = front_end_worker_full_name.split(".")
|
|
41
|
-
|
|
42
|
-
front_end_worker_module_name = ".".join(front_end_worker_parts[:-1])
|
|
43
|
-
front_end_worker_class_name = front_end_worker_parts[-1]
|
|
44
|
-
|
|
45
|
-
front_end_worker_module = importlib.import_module(front_end_worker_module_name)
|
|
46
|
-
|
|
47
|
-
if not hasattr(front_end_worker_module, front_end_worker_class_name):
|
|
48
|
-
raise ValueError(f"Front end worker {front_end_worker_full_name} not found.")
|
|
49
|
-
|
|
50
|
-
front_end_worker_class: type[FastApiFrontEndPluginWorkerBase] = getattr(front_end_worker_module,
|
|
51
|
-
front_end_worker_class_name)
|
|
52
|
-
|
|
53
|
-
if (not issubclass(front_end_worker_class, FastApiFrontEndPluginWorkerBase)):
|
|
54
|
-
raise ValueError(
|
|
55
|
-
f"Front end worker {front_end_worker_full_name} is not a subclass of FastApiFrontEndPluginWorker.")
|
|
56
|
-
|
|
57
|
-
# Load the config
|
|
58
|
-
abs_config_file_path = os.path.abspath(config_file_path)
|
|
59
|
-
|
|
60
|
-
config = load_config(abs_config_file_path)
|
|
61
|
-
|
|
62
|
-
# Create an instance of the front end worker class
|
|
63
|
-
front_end_worker = front_end_worker_class(config)
|
|
64
|
-
|
|
65
|
-
aiq_app = front_end_worker.build_app()
|
|
66
|
-
|
|
67
|
-
return aiq_app
|
|
68
|
-
|
|
69
|
-
except ImportError as e:
|
|
70
|
-
raise ValueError(f"Front end worker {front_end_worker_full_name} not found.") from e
|
|
@@ -1,279 +0,0 @@
|
|
|
1
|
-
# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
-
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
#
|
|
4
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
# you may not use this file except in compliance with the License.
|
|
6
|
-
# You may obtain a copy of the License at
|
|
7
|
-
#
|
|
8
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
#
|
|
10
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
# See the License for the specific language governing permissions and
|
|
14
|
-
# limitations under the License.
|
|
15
|
-
|
|
16
|
-
import asyncio
|
|
17
|
-
import logging
|
|
18
|
-
import uuid
|
|
19
|
-
from typing import Any
|
|
20
|
-
|
|
21
|
-
from fastapi import WebSocket
|
|
22
|
-
from pydantic import BaseModel
|
|
23
|
-
from pydantic import ValidationError
|
|
24
|
-
from starlette.endpoints import WebSocketEndpoint
|
|
25
|
-
|
|
26
|
-
from aiq.data_models.api_server import Error
|
|
27
|
-
from aiq.data_models.api_server import ErrorTypes
|
|
28
|
-
from aiq.data_models.api_server import SystemResponseContent
|
|
29
|
-
from aiq.data_models.api_server import TextContent
|
|
30
|
-
from aiq.data_models.api_server import WebSocketMessageStatus
|
|
31
|
-
from aiq.data_models.api_server import WebSocketMessageType
|
|
32
|
-
from aiq.data_models.api_server import WebSocketSystemInteractionMessage
|
|
33
|
-
from aiq.data_models.api_server import WebSocketSystemIntermediateStepMessage
|
|
34
|
-
from aiq.data_models.api_server import WebSocketSystemResponseTokenMessage
|
|
35
|
-
from aiq.data_models.api_server import WebSocketUserInteractionResponseMessage
|
|
36
|
-
from aiq.data_models.api_server import WebSocketUserMessage
|
|
37
|
-
from aiq.data_models.interactive import HumanPromptNotification
|
|
38
|
-
from aiq.data_models.interactive import HumanResponse
|
|
39
|
-
from aiq.data_models.interactive import HumanResponseNotification
|
|
40
|
-
from aiq.data_models.interactive import InteractionPrompt
|
|
41
|
-
from aiq.front_ends.fastapi.message_validator import MessageValidator
|
|
42
|
-
|
|
43
|
-
logger = logging.getLogger(__name__)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class MessageHandler:
|
|
47
|
-
|
|
48
|
-
def __init__(self, websocket_reference: WebSocketEndpoint):
|
|
49
|
-
self._websocket_reference: WebSocketEndpoint = websocket_reference
|
|
50
|
-
self._message_validator: MessageValidator = MessageValidator()
|
|
51
|
-
self._messages_queue: asyncio.Queue[dict[str, str]] = asyncio.Queue()
|
|
52
|
-
self._out_going_messages_queue: asyncio.Queue[dict] = asyncio.Queue()
|
|
53
|
-
self._process_messages_task: asyncio.Task | None = None
|
|
54
|
-
self._process_out_going_messages_task: asyncio.Task = None
|
|
55
|
-
self._background_task: asyncio.Task = None
|
|
56
|
-
self._message_parent_id: str = "default_id"
|
|
57
|
-
self._workflow_schema_type: str = None
|
|
58
|
-
self._user_interaction_response: asyncio.Future[TextContent] = asyncio.Future()
|
|
59
|
-
|
|
60
|
-
@property
|
|
61
|
-
def messages_queue(self) -> asyncio.Queue[dict[str, str]]:
|
|
62
|
-
return self._messages_queue
|
|
63
|
-
|
|
64
|
-
@property
|
|
65
|
-
def background_task(self) -> asyncio.Task:
|
|
66
|
-
return self._background_task
|
|
67
|
-
|
|
68
|
-
@property
|
|
69
|
-
def process_messages_task(self) -> asyncio.Task | None:
|
|
70
|
-
return self._process_messages_task
|
|
71
|
-
|
|
72
|
-
@process_messages_task.setter
|
|
73
|
-
def process_messages_task(self, process_messages_task) -> None:
|
|
74
|
-
self._process_messages_task = process_messages_task
|
|
75
|
-
|
|
76
|
-
@property
|
|
77
|
-
def process_out_going_messages_task(self) -> asyncio.Task:
|
|
78
|
-
return self._process_out_going_messages_task
|
|
79
|
-
|
|
80
|
-
@process_out_going_messages_task.setter
|
|
81
|
-
def process_out_going_messages_task(self, process_out_going_messages_task) -> None:
|
|
82
|
-
self._process_out_going_messages_task = process_out_going_messages_task
|
|
83
|
-
|
|
84
|
-
async def process_messages(self) -> None:
|
|
85
|
-
"""
|
|
86
|
-
Processes received messages from websocket and routes them appropriately.
|
|
87
|
-
"""
|
|
88
|
-
while True:
|
|
89
|
-
|
|
90
|
-
try:
|
|
91
|
-
message: dict[str, Any] = await self._messages_queue.get()
|
|
92
|
-
|
|
93
|
-
validated_message: BaseModel = await self._message_validator.validate_message(message)
|
|
94
|
-
|
|
95
|
-
if (isinstance(validated_message, WebSocketUserMessage)):
|
|
96
|
-
await self.process_user_message(validated_message)
|
|
97
|
-
|
|
98
|
-
if isinstance(
|
|
99
|
-
validated_message,
|
|
100
|
-
( # noqa: E131
|
|
101
|
-
WebSocketSystemResponseTokenMessage,
|
|
102
|
-
WebSocketSystemIntermediateStepMessage,
|
|
103
|
-
WebSocketSystemInteractionMessage)):
|
|
104
|
-
await self._out_going_messages_queue.put(validated_message.model_dump())
|
|
105
|
-
|
|
106
|
-
if (isinstance(validated_message, WebSocketUserInteractionResponseMessage)):
|
|
107
|
-
user_content = await self.process_user_message_content(validated_message)
|
|
108
|
-
self._user_interaction_response.set_result(user_content)
|
|
109
|
-
except (asyncio.CancelledError):
|
|
110
|
-
break
|
|
111
|
-
|
|
112
|
-
return None
|
|
113
|
-
|
|
114
|
-
async def process_user_message_content(
|
|
115
|
-
self, user_content: WebSocketUserMessage | WebSocketUserInteractionResponseMessage) -> BaseModel | None:
|
|
116
|
-
"""
|
|
117
|
-
Processes the contents of a user message.
|
|
118
|
-
|
|
119
|
-
:param user_content: Incoming content data model.
|
|
120
|
-
:return: A validated Pydantic user content model or None if not found.
|
|
121
|
-
"""
|
|
122
|
-
|
|
123
|
-
for user_message in user_content.content.messages[::-1]:
|
|
124
|
-
if (user_message.role == "user"):
|
|
125
|
-
|
|
126
|
-
for attachment in user_message.content:
|
|
127
|
-
|
|
128
|
-
if isinstance(attachment, TextContent):
|
|
129
|
-
return attachment
|
|
130
|
-
|
|
131
|
-
return None
|
|
132
|
-
|
|
133
|
-
async def process_user_message(self, message_as_validated_type: WebSocketUserMessage) -> None:
|
|
134
|
-
"""
|
|
135
|
-
Process user messages and routes them appropriately.
|
|
136
|
-
|
|
137
|
-
:param message_as_validated_type: A WebSocketUserMessage Data Model instance.
|
|
138
|
-
"""
|
|
139
|
-
|
|
140
|
-
try:
|
|
141
|
-
self._message_parent_id = message_as_validated_type.id
|
|
142
|
-
self._workflow_schema_type = message_as_validated_type.schema_type
|
|
143
|
-
|
|
144
|
-
content: BaseModel | None = await self.process_user_message_content(message_as_validated_type)
|
|
145
|
-
|
|
146
|
-
if content is None:
|
|
147
|
-
raise ValueError(f"User message content could not be found: {message_as_validated_type}")
|
|
148
|
-
|
|
149
|
-
if isinstance(content, TextContent) and (self._background_task is None):
|
|
150
|
-
|
|
151
|
-
await self._process_response()
|
|
152
|
-
self._background_task = asyncio.create_task(
|
|
153
|
-
self._websocket_reference.workflow_schema_type.get(self._workflow_schema_type)(
|
|
154
|
-
content.text)).add_done_callback(
|
|
155
|
-
lambda task: asyncio.create_task(self._on_process_stream_task_done(task)))
|
|
156
|
-
|
|
157
|
-
except ValueError as e:
|
|
158
|
-
logger.error("User message content not found: %s", str(e), exc_info=True)
|
|
159
|
-
await self.create_websocket_message(data_model=Error(code=ErrorTypes.INVALID_USER_MESSAGE_CONTENT,
|
|
160
|
-
message="User message content could not be found",
|
|
161
|
-
details=str(e)),
|
|
162
|
-
message_type=WebSocketMessageType.ERROR_MESSAGE,
|
|
163
|
-
status=WebSocketMessageStatus.IN_PROGRESS)
|
|
164
|
-
|
|
165
|
-
async def create_websocket_message(self,
|
|
166
|
-
data_model: BaseModel,
|
|
167
|
-
message_type: str | None = None,
|
|
168
|
-
status: str = WebSocketMessageStatus.IN_PROGRESS) -> None:
|
|
169
|
-
"""
|
|
170
|
-
Creates a websocket message that will be ready for routing based on message type or data model.
|
|
171
|
-
|
|
172
|
-
:param data_model: Message content model.
|
|
173
|
-
:param message_type: Message content model.
|
|
174
|
-
:param status: Message content model.
|
|
175
|
-
"""
|
|
176
|
-
try:
|
|
177
|
-
message: BaseModel | None = None
|
|
178
|
-
|
|
179
|
-
if message_type is None:
|
|
180
|
-
message_type = await self._message_validator.resolve_message_type_by_data(data_model)
|
|
181
|
-
|
|
182
|
-
message_schema: type[BaseModel] = await self._message_validator.get_message_schema_by_type(message_type)
|
|
183
|
-
|
|
184
|
-
if 'id' in data_model.model_fields:
|
|
185
|
-
message_id: str = data_model.id
|
|
186
|
-
else:
|
|
187
|
-
message_id = str(uuid.uuid4())
|
|
188
|
-
|
|
189
|
-
content: BaseModel = await self._message_validator.convert_data_to_message_content(data_model)
|
|
190
|
-
|
|
191
|
-
if issubclass(message_schema, WebSocketSystemResponseTokenMessage):
|
|
192
|
-
message = await self._message_validator.create_system_response_token_message(
|
|
193
|
-
message_id=message_id, parent_id=self._message_parent_id, content=content, status=status)
|
|
194
|
-
|
|
195
|
-
elif issubclass(message_schema, WebSocketSystemIntermediateStepMessage):
|
|
196
|
-
message = await self._message_validator.create_system_intermediate_step_message(
|
|
197
|
-
message_id=message_id,
|
|
198
|
-
parent_id=await self._message_validator.get_intermediate_step_parent_id(data_model),
|
|
199
|
-
content=content,
|
|
200
|
-
status=status)
|
|
201
|
-
|
|
202
|
-
elif issubclass(message_schema, WebSocketSystemInteractionMessage):
|
|
203
|
-
message = await self._message_validator.create_system_interaction_message(
|
|
204
|
-
message_id=message_id, parent_id=self._message_parent_id, content=content, status=status)
|
|
205
|
-
|
|
206
|
-
elif isinstance(content, Error):
|
|
207
|
-
raise ValidationError(f"Invalid input data creating websocket message. {data_model.model_dump_json()}")
|
|
208
|
-
|
|
209
|
-
elif issubclass(message_schema, Error):
|
|
210
|
-
raise TypeError(f"Invalid message type: {message_type}")
|
|
211
|
-
|
|
212
|
-
elif (message is None):
|
|
213
|
-
raise ValueError(
|
|
214
|
-
f"Message type could not be resolved by input data model: {data_model.model_dump_json()}")
|
|
215
|
-
|
|
216
|
-
except (ValidationError, TypeError, ValueError) as e:
|
|
217
|
-
logger.error("A data vaidation error ocurred creating websocket message: %s", str(e), exc_info=True)
|
|
218
|
-
message = await self._message_validator.create_system_response_token_message(
|
|
219
|
-
message_type=WebSocketMessageType.ERROR_MESSAGE,
|
|
220
|
-
content=Error(code=ErrorTypes.UNKNOWN_ERROR, message="default", details=str(e)))
|
|
221
|
-
|
|
222
|
-
finally:
|
|
223
|
-
await self._messages_queue.put(message.model_dump())
|
|
224
|
-
|
|
225
|
-
async def _on_process_stream_task_done(self, task: asyncio.Task) -> None:
|
|
226
|
-
await self.create_websocket_message(data_model=SystemResponseContent(),
|
|
227
|
-
message_type=WebSocketMessageType.RESPONSE_MESSAGE,
|
|
228
|
-
status=WebSocketMessageStatus.COMPLETE)
|
|
229
|
-
|
|
230
|
-
return None
|
|
231
|
-
|
|
232
|
-
async def process_out_going_messages(self, websocket: WebSocket) -> None:
|
|
233
|
-
"""
|
|
234
|
-
Spawns out going message processing task.
|
|
235
|
-
|
|
236
|
-
:param websocket: Websocket instance.
|
|
237
|
-
"""
|
|
238
|
-
while True:
|
|
239
|
-
try:
|
|
240
|
-
out_going_message = await self._out_going_messages_queue.get()
|
|
241
|
-
await self._websocket_reference.on_send(websocket, out_going_message)
|
|
242
|
-
|
|
243
|
-
except (asyncio.CancelledError, ValidationError):
|
|
244
|
-
break
|
|
245
|
-
|
|
246
|
-
return None
|
|
247
|
-
|
|
248
|
-
async def _process_response(self):
|
|
249
|
-
self._websocket_reference.process_response_event.set()
|
|
250
|
-
|
|
251
|
-
async def _pause_response(self):
|
|
252
|
-
self._websocket_reference.process_response_event.clear()
|
|
253
|
-
|
|
254
|
-
async def __reset_user_interaction_response(self):
|
|
255
|
-
self._user_interaction_response = asyncio.Future()
|
|
256
|
-
|
|
257
|
-
async def human_interaction(self, prompt: InteractionPrompt) -> HumanResponse:
|
|
258
|
-
"""
|
|
259
|
-
Registered human interaction callback that processes human interactions and returns
|
|
260
|
-
responses from websocket connection.
|
|
261
|
-
|
|
262
|
-
:param prompt: Incoming interaction content data model.
|
|
263
|
-
:return: A Text Content Base Pydantic model.
|
|
264
|
-
"""
|
|
265
|
-
await self.create_websocket_message(data_model=prompt.content,
|
|
266
|
-
message_type=WebSocketMessageType.SYSTEM_INTERACTION_MESSAGE,
|
|
267
|
-
status=WebSocketMessageStatus.IN_PROGRESS)
|
|
268
|
-
|
|
269
|
-
if (isinstance(prompt.content, HumanPromptNotification)):
|
|
270
|
-
return HumanResponseNotification()
|
|
271
|
-
|
|
272
|
-
user_message_repsonse_content: TextContent = await self._user_interaction_response
|
|
273
|
-
interaction_response: HumanResponse = await self._message_validator.convert_text_content_to_human_response(
|
|
274
|
-
user_message_repsonse_content, prompt.content)
|
|
275
|
-
|
|
276
|
-
await self.__reset_user_interaction_response()
|
|
277
|
-
await self._process_response()
|
|
278
|
-
|
|
279
|
-
return interaction_response
|