alita-sdk 0.3.379__py3-none-any.whl → 0.3.627__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.
- alita_sdk/cli/__init__.py +10 -0
- alita_sdk/cli/__main__.py +17 -0
- alita_sdk/cli/agent/__init__.py +5 -0
- alita_sdk/cli/agent/default.py +258 -0
- alita_sdk/cli/agent_executor.py +156 -0
- alita_sdk/cli/agent_loader.py +245 -0
- alita_sdk/cli/agent_ui.py +228 -0
- alita_sdk/cli/agents.py +3113 -0
- alita_sdk/cli/callbacks.py +647 -0
- alita_sdk/cli/cli.py +168 -0
- alita_sdk/cli/config.py +306 -0
- alita_sdk/cli/context/__init__.py +30 -0
- alita_sdk/cli/context/cleanup.py +198 -0
- alita_sdk/cli/context/manager.py +731 -0
- alita_sdk/cli/context/message.py +285 -0
- alita_sdk/cli/context/strategies.py +289 -0
- alita_sdk/cli/context/token_estimation.py +127 -0
- alita_sdk/cli/formatting.py +182 -0
- alita_sdk/cli/input_handler.py +419 -0
- alita_sdk/cli/inventory.py +1073 -0
- alita_sdk/cli/mcp_loader.py +315 -0
- alita_sdk/cli/testcases/__init__.py +94 -0
- alita_sdk/cli/testcases/data_generation.py +119 -0
- alita_sdk/cli/testcases/discovery.py +96 -0
- alita_sdk/cli/testcases/executor.py +84 -0
- alita_sdk/cli/testcases/logger.py +85 -0
- alita_sdk/cli/testcases/parser.py +172 -0
- alita_sdk/cli/testcases/prompts.py +91 -0
- alita_sdk/cli/testcases/reporting.py +125 -0
- alita_sdk/cli/testcases/setup.py +108 -0
- alita_sdk/cli/testcases/test_runner.py +282 -0
- alita_sdk/cli/testcases/utils.py +39 -0
- alita_sdk/cli/testcases/validation.py +90 -0
- alita_sdk/cli/testcases/workflow.py +196 -0
- alita_sdk/cli/toolkit.py +327 -0
- alita_sdk/cli/toolkit_loader.py +85 -0
- alita_sdk/cli/tools/__init__.py +43 -0
- alita_sdk/cli/tools/approval.py +224 -0
- alita_sdk/cli/tools/filesystem.py +1751 -0
- alita_sdk/cli/tools/planning.py +389 -0
- alita_sdk/cli/tools/terminal.py +414 -0
- alita_sdk/community/__init__.py +72 -12
- alita_sdk/community/inventory/__init__.py +236 -0
- alita_sdk/community/inventory/config.py +257 -0
- alita_sdk/community/inventory/enrichment.py +2137 -0
- alita_sdk/community/inventory/extractors.py +1469 -0
- alita_sdk/community/inventory/ingestion.py +3172 -0
- alita_sdk/community/inventory/knowledge_graph.py +1457 -0
- alita_sdk/community/inventory/parsers/__init__.py +218 -0
- alita_sdk/community/inventory/parsers/base.py +295 -0
- alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
- alita_sdk/community/inventory/parsers/go_parser.py +851 -0
- alita_sdk/community/inventory/parsers/html_parser.py +389 -0
- alita_sdk/community/inventory/parsers/java_parser.py +593 -0
- alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
- alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
- alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
- alita_sdk/community/inventory/parsers/python_parser.py +604 -0
- alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
- alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
- alita_sdk/community/inventory/parsers/text_parser.py +322 -0
- alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
- alita_sdk/community/inventory/patterns/__init__.py +61 -0
- alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
- alita_sdk/community/inventory/patterns/loader.py +348 -0
- alita_sdk/community/inventory/patterns/registry.py +198 -0
- alita_sdk/community/inventory/presets.py +535 -0
- alita_sdk/community/inventory/retrieval.py +1403 -0
- alita_sdk/community/inventory/toolkit.py +173 -0
- alita_sdk/community/inventory/toolkit_utils.py +176 -0
- alita_sdk/community/inventory/visualize.py +1370 -0
- alita_sdk/configurations/__init__.py +1 -1
- alita_sdk/configurations/ado.py +141 -20
- alita_sdk/configurations/bitbucket.py +94 -2
- alita_sdk/configurations/confluence.py +130 -1
- alita_sdk/configurations/figma.py +76 -0
- alita_sdk/configurations/gitlab.py +91 -0
- alita_sdk/configurations/jira.py +103 -0
- alita_sdk/configurations/openapi.py +329 -0
- alita_sdk/configurations/qtest.py +72 -1
- alita_sdk/configurations/report_portal.py +96 -0
- alita_sdk/configurations/sharepoint.py +148 -0
- alita_sdk/configurations/testio.py +83 -0
- alita_sdk/configurations/testrail.py +88 -0
- alita_sdk/configurations/xray.py +93 -0
- alita_sdk/configurations/zephyr_enterprise.py +93 -0
- alita_sdk/configurations/zephyr_essential.py +75 -0
- alita_sdk/runtime/clients/artifact.py +3 -3
- alita_sdk/runtime/clients/client.py +388 -46
- alita_sdk/runtime/clients/mcp_discovery.py +342 -0
- alita_sdk/runtime/clients/mcp_manager.py +262 -0
- alita_sdk/runtime/clients/sandbox_client.py +8 -21
- alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
- alita_sdk/runtime/langchain/assistant.py +157 -39
- alita_sdk/runtime/langchain/constants.py +647 -1
- alita_sdk/runtime/langchain/document_loaders/AlitaDocxMammothLoader.py +315 -3
- alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +103 -60
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +10 -4
- alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +226 -7
- alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +5 -2
- alita_sdk/runtime/langchain/document_loaders/constants.py +40 -19
- alita_sdk/runtime/langchain/langraph_agent.py +405 -84
- alita_sdk/runtime/langchain/utils.py +106 -7
- alita_sdk/runtime/llms/preloaded.py +2 -6
- alita_sdk/runtime/models/mcp_models.py +61 -0
- alita_sdk/runtime/skills/__init__.py +91 -0
- alita_sdk/runtime/skills/callbacks.py +498 -0
- alita_sdk/runtime/skills/discovery.py +540 -0
- alita_sdk/runtime/skills/executor.py +610 -0
- alita_sdk/runtime/skills/input_builder.py +371 -0
- alita_sdk/runtime/skills/models.py +330 -0
- alita_sdk/runtime/skills/registry.py +355 -0
- alita_sdk/runtime/skills/skill_runner.py +330 -0
- alita_sdk/runtime/toolkits/__init__.py +31 -0
- alita_sdk/runtime/toolkits/application.py +29 -10
- alita_sdk/runtime/toolkits/artifact.py +20 -11
- alita_sdk/runtime/toolkits/datasource.py +13 -6
- alita_sdk/runtime/toolkits/mcp.py +783 -0
- alita_sdk/runtime/toolkits/mcp_config.py +1048 -0
- alita_sdk/runtime/toolkits/planning.py +178 -0
- alita_sdk/runtime/toolkits/skill_router.py +238 -0
- alita_sdk/runtime/toolkits/subgraph.py +251 -6
- alita_sdk/runtime/toolkits/tools.py +356 -69
- alita_sdk/runtime/toolkits/vectorstore.py +11 -5
- alita_sdk/runtime/tools/__init__.py +10 -3
- alita_sdk/runtime/tools/application.py +27 -6
- alita_sdk/runtime/tools/artifact.py +511 -28
- alita_sdk/runtime/tools/data_analysis.py +183 -0
- alita_sdk/runtime/tools/function.py +67 -35
- alita_sdk/runtime/tools/graph.py +10 -4
- alita_sdk/runtime/tools/image_generation.py +148 -46
- alita_sdk/runtime/tools/llm.py +1003 -128
- alita_sdk/runtime/tools/loop.py +3 -1
- alita_sdk/runtime/tools/loop_output.py +3 -1
- alita_sdk/runtime/tools/mcp_inspect_tool.py +284 -0
- alita_sdk/runtime/tools/mcp_remote_tool.py +181 -0
- alita_sdk/runtime/tools/mcp_server_tool.py +8 -5
- alita_sdk/runtime/tools/planning/__init__.py +36 -0
- alita_sdk/runtime/tools/planning/models.py +246 -0
- alita_sdk/runtime/tools/planning/wrapper.py +607 -0
- alita_sdk/runtime/tools/router.py +2 -4
- alita_sdk/runtime/tools/sandbox.py +65 -48
- alita_sdk/runtime/tools/skill_router.py +776 -0
- alita_sdk/runtime/tools/tool.py +3 -1
- alita_sdk/runtime/tools/vectorstore.py +9 -3
- alita_sdk/runtime/tools/vectorstore_base.py +70 -14
- alita_sdk/runtime/utils/AlitaCallback.py +137 -21
- alita_sdk/runtime/utils/constants.py +5 -1
- alita_sdk/runtime/utils/mcp_client.py +492 -0
- alita_sdk/runtime/utils/mcp_oauth.py +361 -0
- alita_sdk/runtime/utils/mcp_sse_client.py +434 -0
- alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
- alita_sdk/runtime/utils/serialization.py +155 -0
- alita_sdk/runtime/utils/streamlit.py +40 -13
- alita_sdk/runtime/utils/toolkit_utils.py +30 -9
- alita_sdk/runtime/utils/utils.py +36 -0
- alita_sdk/tools/__init__.py +134 -35
- alita_sdk/tools/ado/repos/__init__.py +51 -32
- alita_sdk/tools/ado/repos/repos_wrapper.py +148 -89
- alita_sdk/tools/ado/test_plan/__init__.py +25 -9
- alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +23 -1
- alita_sdk/tools/ado/utils.py +1 -18
- alita_sdk/tools/ado/wiki/__init__.py +25 -12
- alita_sdk/tools/ado/wiki/ado_wrapper.py +291 -22
- alita_sdk/tools/ado/work_item/__init__.py +26 -13
- alita_sdk/tools/ado/work_item/ado_wrapper.py +73 -11
- alita_sdk/tools/advanced_jira_mining/__init__.py +11 -8
- alita_sdk/tools/aws/delta_lake/__init__.py +13 -9
- alita_sdk/tools/aws/delta_lake/tool.py +5 -1
- alita_sdk/tools/azure_ai/search/__init__.py +11 -8
- alita_sdk/tools/azure_ai/search/api_wrapper.py +1 -1
- alita_sdk/tools/base/tool.py +5 -1
- alita_sdk/tools/base_indexer_toolkit.py +271 -84
- alita_sdk/tools/bitbucket/__init__.py +17 -11
- alita_sdk/tools/bitbucket/api_wrapper.py +59 -11
- alita_sdk/tools/bitbucket/cloud_api_wrapper.py +49 -35
- alita_sdk/tools/browser/__init__.py +5 -4
- alita_sdk/tools/carrier/__init__.py +5 -6
- alita_sdk/tools/carrier/backend_reports_tool.py +6 -6
- alita_sdk/tools/carrier/run_ui_test_tool.py +6 -6
- alita_sdk/tools/carrier/ui_reports_tool.py +5 -5
- alita_sdk/tools/chunkers/__init__.py +3 -1
- alita_sdk/tools/chunkers/code/treesitter/treesitter.py +37 -13
- alita_sdk/tools/chunkers/sematic/json_chunker.py +1 -0
- alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
- alita_sdk/tools/chunkers/sematic/proposal_chunker.py +1 -1
- alita_sdk/tools/chunkers/universal_chunker.py +270 -0
- alita_sdk/tools/cloud/aws/__init__.py +10 -7
- alita_sdk/tools/cloud/azure/__init__.py +10 -7
- alita_sdk/tools/cloud/gcp/__init__.py +10 -7
- alita_sdk/tools/cloud/k8s/__init__.py +10 -7
- alita_sdk/tools/code/linter/__init__.py +10 -8
- alita_sdk/tools/code/loaders/codesearcher.py +3 -2
- alita_sdk/tools/code/sonar/__init__.py +11 -8
- alita_sdk/tools/code_indexer_toolkit.py +82 -22
- alita_sdk/tools/confluence/__init__.py +22 -16
- alita_sdk/tools/confluence/api_wrapper.py +107 -30
- alita_sdk/tools/confluence/loader.py +14 -2
- alita_sdk/tools/custom_open_api/__init__.py +12 -5
- alita_sdk/tools/elastic/__init__.py +11 -8
- alita_sdk/tools/elitea_base.py +493 -30
- alita_sdk/tools/figma/__init__.py +58 -11
- alita_sdk/tools/figma/api_wrapper.py +1235 -143
- alita_sdk/tools/figma/figma_client.py +73 -0
- alita_sdk/tools/figma/toon_tools.py +2748 -0
- alita_sdk/tools/github/__init__.py +14 -15
- alita_sdk/tools/github/github_client.py +224 -100
- alita_sdk/tools/github/graphql_client_wrapper.py +119 -33
- alita_sdk/tools/github/schemas.py +14 -5
- alita_sdk/tools/github/tool.py +5 -1
- alita_sdk/tools/github/tool_prompts.py +9 -22
- alita_sdk/tools/gitlab/__init__.py +16 -11
- alita_sdk/tools/gitlab/api_wrapper.py +218 -48
- alita_sdk/tools/gitlab_org/__init__.py +10 -9
- alita_sdk/tools/gitlab_org/api_wrapper.py +63 -64
- alita_sdk/tools/google/bigquery/__init__.py +13 -12
- alita_sdk/tools/google/bigquery/tool.py +5 -1
- alita_sdk/tools/google_places/__init__.py +11 -8
- alita_sdk/tools/google_places/api_wrapper.py +1 -1
- alita_sdk/tools/jira/__init__.py +17 -10
- alita_sdk/tools/jira/api_wrapper.py +92 -41
- alita_sdk/tools/keycloak/__init__.py +11 -8
- alita_sdk/tools/localgit/__init__.py +9 -3
- alita_sdk/tools/localgit/local_git.py +62 -54
- alita_sdk/tools/localgit/tool.py +5 -1
- alita_sdk/tools/memory/__init__.py +12 -4
- alita_sdk/tools/non_code_indexer_toolkit.py +1 -0
- alita_sdk/tools/ocr/__init__.py +11 -8
- alita_sdk/tools/openapi/__init__.py +491 -106
- alita_sdk/tools/openapi/api_wrapper.py +1368 -0
- alita_sdk/tools/openapi/tool.py +20 -0
- alita_sdk/tools/pandas/__init__.py +20 -12
- alita_sdk/tools/pandas/api_wrapper.py +38 -25
- alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
- alita_sdk/tools/postman/__init__.py +10 -9
- alita_sdk/tools/pptx/__init__.py +11 -10
- alita_sdk/tools/pptx/pptx_wrapper.py +1 -1
- alita_sdk/tools/qtest/__init__.py +31 -11
- alita_sdk/tools/qtest/api_wrapper.py +2135 -86
- alita_sdk/tools/rally/__init__.py +10 -9
- alita_sdk/tools/rally/api_wrapper.py +1 -1
- alita_sdk/tools/report_portal/__init__.py +12 -8
- alita_sdk/tools/salesforce/__init__.py +10 -8
- alita_sdk/tools/servicenow/__init__.py +17 -15
- alita_sdk/tools/servicenow/api_wrapper.py +1 -1
- alita_sdk/tools/sharepoint/__init__.py +10 -7
- alita_sdk/tools/sharepoint/api_wrapper.py +129 -38
- alita_sdk/tools/sharepoint/authorization_helper.py +191 -1
- alita_sdk/tools/sharepoint/utils.py +8 -2
- alita_sdk/tools/slack/__init__.py +10 -7
- alita_sdk/tools/slack/api_wrapper.py +2 -2
- alita_sdk/tools/sql/__init__.py +12 -9
- alita_sdk/tools/testio/__init__.py +10 -7
- alita_sdk/tools/testrail/__init__.py +11 -10
- alita_sdk/tools/testrail/api_wrapper.py +1 -1
- alita_sdk/tools/utils/__init__.py +9 -4
- alita_sdk/tools/utils/content_parser.py +103 -18
- alita_sdk/tools/utils/text_operations.py +410 -0
- alita_sdk/tools/utils/tool_prompts.py +79 -0
- alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +30 -13
- alita_sdk/tools/xray/__init__.py +13 -9
- alita_sdk/tools/yagmail/__init__.py +9 -3
- alita_sdk/tools/zephyr/__init__.py +10 -7
- alita_sdk/tools/zephyr_enterprise/__init__.py +11 -7
- alita_sdk/tools/zephyr_essential/__init__.py +10 -7
- alita_sdk/tools/zephyr_essential/api_wrapper.py +30 -13
- alita_sdk/tools/zephyr_essential/client.py +2 -2
- alita_sdk/tools/zephyr_scale/__init__.py +11 -8
- alita_sdk/tools/zephyr_scale/api_wrapper.py +2 -2
- alita_sdk/tools/zephyr_squad/__init__.py +10 -7
- {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/METADATA +154 -8
- alita_sdk-0.3.627.dist-info/RECORD +468 -0
- alita_sdk-0.3.627.dist-info/entry_points.txt +2 -0
- alita_sdk-0.3.379.dist-info/RECORD +0 -360
- {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,776 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Skill Router Wrapper for LangChain integration.
|
|
3
|
+
|
|
4
|
+
This module provides a wrapper that exposes the skills registry system
|
|
5
|
+
through multiple focused tools for listing, describing, and executing skills.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Any, Dict, List, Literal, Optional
|
|
11
|
+
|
|
12
|
+
from pydantic import BaseModel, Field, ConfigDict
|
|
13
|
+
|
|
14
|
+
from alita_sdk.tools.elitea_base import BaseToolApiWrapper
|
|
15
|
+
from ..skills import (
|
|
16
|
+
SkillsRegistry, get_default_registry, SkillType,
|
|
17
|
+
SkillExecutionResult, SkillStatus
|
|
18
|
+
)
|
|
19
|
+
from ..skills.executor import SkillExecutor
|
|
20
|
+
from ..skills.callbacks import CallbackManager, create_default_callbacks
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Input Schemas for the three separate tools
|
|
26
|
+
class ListSkillsInput(BaseModel):
|
|
27
|
+
"""Input schema for listing skills."""
|
|
28
|
+
|
|
29
|
+
skill_type: Optional[Literal["graph", "agent"]] = Field(
|
|
30
|
+
default=None,
|
|
31
|
+
description="Filter skills by type when listing"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
capability: Optional[str] = Field(
|
|
35
|
+
default=None,
|
|
36
|
+
description="Filter skills by capability when listing"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
tag: Optional[str] = Field(
|
|
40
|
+
default=None,
|
|
41
|
+
description="Filter skills by tag when listing"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class DescribeSkillInput(BaseModel):
|
|
46
|
+
"""Input schema for describing a skill."""
|
|
47
|
+
|
|
48
|
+
skill_name: str = Field(
|
|
49
|
+
description="Name of the skill to describe (required)"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ExecuteSkillInput(BaseModel):
|
|
54
|
+
"""Input schema for executing a skill."""
|
|
55
|
+
|
|
56
|
+
skill_name: Optional[str] = Field(
|
|
57
|
+
default=None,
|
|
58
|
+
description="Name of the skill to execute (optional - if not provided, LLM will select best skill)"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
task: str = Field(
|
|
62
|
+
description="Task or question for the skill (required)"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Agent-specific inputs
|
|
66
|
+
context: Optional[Dict[str, Any]] = Field(
|
|
67
|
+
default=None,
|
|
68
|
+
description="Variables/context for agent skills (key-value pairs)"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
chat_history: Optional[List[Dict[str, str]]] = Field(
|
|
72
|
+
default=None,
|
|
73
|
+
description="Chat conversation history for agent skills (list of {role: 'user'|'assistant', content: 'message'})"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Graph-specific inputs
|
|
77
|
+
state_variables: Optional[Dict[str, Any]] = Field(
|
|
78
|
+
default=None,
|
|
79
|
+
description="State variables for graph skills (key-value pairs)"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Execution options
|
|
83
|
+
execution_mode: Optional[Literal["subprocess", "remote"]] = Field(
|
|
84
|
+
default=None,
|
|
85
|
+
description="Override execution mode (subprocess or remote)"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
enable_callbacks: bool = Field(
|
|
89
|
+
default=False,
|
|
90
|
+
description="Enable real-time callback events during execution"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class SkillRouterWrapper(BaseToolApiWrapper):
|
|
95
|
+
"""
|
|
96
|
+
Wrapper for skill registry operations.
|
|
97
|
+
|
|
98
|
+
This wrapper provides methods for listing, describing, and executing skills
|
|
99
|
+
with intelligent routing capabilities and proper input handling.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
103
|
+
|
|
104
|
+
# Declare Pydantic fields for the wrapper components
|
|
105
|
+
registry: Optional[SkillsRegistry] = Field(default=None)
|
|
106
|
+
executor: Optional[SkillExecutor] = Field(default=None)
|
|
107
|
+
enable_callbacks: bool = Field(default=True)
|
|
108
|
+
|
|
109
|
+
# Router-level configuration
|
|
110
|
+
default_timeout: Optional[int] = Field(default=None)
|
|
111
|
+
default_execution_mode: Optional[str] = Field(default=None)
|
|
112
|
+
llm: Optional[Any] = Field(default=None, description="LLM for intelligent skill selection")
|
|
113
|
+
custom_prompt: Optional[str] = Field(default=None, description="Custom prompt for skill routing")
|
|
114
|
+
|
|
115
|
+
# Private attribute for callback manager (not a Pydantic field)
|
|
116
|
+
_callback_manager: Optional[CallbackManager] = None
|
|
117
|
+
|
|
118
|
+
def __init__(
|
|
119
|
+
self,
|
|
120
|
+
registry: Optional[SkillsRegistry] = None,
|
|
121
|
+
alita_client=None,
|
|
122
|
+
llm=None,
|
|
123
|
+
enable_callbacks: bool = True,
|
|
124
|
+
default_timeout: Optional[int] = None,
|
|
125
|
+
default_execution_mode: Optional[str] = None,
|
|
126
|
+
custom_prompt: Optional[str] = None,
|
|
127
|
+
**kwargs
|
|
128
|
+
):
|
|
129
|
+
"""
|
|
130
|
+
Initialize the Skill Router wrapper.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
registry: Skills registry instance. If None, uses default registry.
|
|
134
|
+
alita_client: AlitaClient for LLM access and remote execution.
|
|
135
|
+
llm: Language model for intelligent skill selection.
|
|
136
|
+
enable_callbacks: Whether to enable callback system by default.
|
|
137
|
+
default_timeout: Default timeout for skill execution.
|
|
138
|
+
default_execution_mode: Default execution mode for skills.
|
|
139
|
+
custom_prompt: Custom prompt for skill routing.
|
|
140
|
+
**kwargs: Additional arguments.
|
|
141
|
+
"""
|
|
142
|
+
# Initialize components
|
|
143
|
+
registry_instance = registry or get_default_registry()
|
|
144
|
+
executor_instance = SkillExecutor(alita_client)
|
|
145
|
+
|
|
146
|
+
# Initialize the Pydantic model with declared fields only
|
|
147
|
+
super().__init__(
|
|
148
|
+
registry=registry_instance,
|
|
149
|
+
executor=executor_instance,
|
|
150
|
+
enable_callbacks=enable_callbacks,
|
|
151
|
+
default_timeout=default_timeout,
|
|
152
|
+
default_execution_mode=default_execution_mode,
|
|
153
|
+
llm=llm,
|
|
154
|
+
custom_prompt=custom_prompt,
|
|
155
|
+
**kwargs
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Initialize callback manager as private attribute
|
|
159
|
+
self._callback_manager = CallbackManager()
|
|
160
|
+
if enable_callbacks:
|
|
161
|
+
# Add default callbacks
|
|
162
|
+
for callback in create_default_callbacks():
|
|
163
|
+
self._callback_manager.add_callback(callback)
|
|
164
|
+
|
|
165
|
+
logger.info("SkillRouterWrapper initialized")
|
|
166
|
+
|
|
167
|
+
def get_available_tools(self):
|
|
168
|
+
"""
|
|
169
|
+
Get the list of available tools provided by this wrapper.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
List of tool definitions with name, description, args_schema, and ref.
|
|
173
|
+
"""
|
|
174
|
+
return [
|
|
175
|
+
{
|
|
176
|
+
"name": "list_skills",
|
|
177
|
+
"description": """List all available skills in the registry with optional filtering.
|
|
178
|
+
|
|
179
|
+
Use this tool to discover what skills are available for execution. You can filter by:
|
|
180
|
+
- skill_type: Filter by 'agent' or 'graph' skills
|
|
181
|
+
- capability: Filter by specific capability (e.g., 'jira', 'analysis')
|
|
182
|
+
- tag: Filter by tag
|
|
183
|
+
|
|
184
|
+
Skills come in two types:
|
|
185
|
+
- Agent skills: Conversational, use variables and chat history
|
|
186
|
+
- Graph skills: State-based workflows, use state variables
|
|
187
|
+
|
|
188
|
+
Examples:
|
|
189
|
+
- List all skills: {}
|
|
190
|
+
- List agent skills: {"skill_type": "agent"}
|
|
191
|
+
- List skills with Jira capability: {"capability": "jira"}
|
|
192
|
+
""",
|
|
193
|
+
"args_schema": ListSkillsInput,
|
|
194
|
+
"ref": self._handle_list
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"name": "describe_skill",
|
|
198
|
+
"description": """Get detailed information about a specific skill.
|
|
199
|
+
|
|
200
|
+
Use this tool to understand a skill's inputs, capabilities, and how to execute it.
|
|
201
|
+
Provides complete documentation including:
|
|
202
|
+
- Input schema and required/optional parameters
|
|
203
|
+
- Capabilities and tags
|
|
204
|
+
- Execution configuration
|
|
205
|
+
- Usage examples
|
|
206
|
+
|
|
207
|
+
Example: {"skill_name": "jira_triage"}
|
|
208
|
+
""",
|
|
209
|
+
"args_schema": DescribeSkillInput,
|
|
210
|
+
"ref": self._handle_describe
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"name": "execute_skill",
|
|
214
|
+
"description": """Execute a specialized skill to perform a complex task.
|
|
215
|
+
|
|
216
|
+
This tool provides access to specialized skills with intelligent routing capabilities.
|
|
217
|
+
When skill_name is not specified, the LLM will automatically select the best skill
|
|
218
|
+
based on the task description.
|
|
219
|
+
|
|
220
|
+
Skills come in two types:
|
|
221
|
+
- Agent skills: Use 'context' and 'chat_history' parameters
|
|
222
|
+
- Graph skills: Use 'state_variables' parameter
|
|
223
|
+
|
|
224
|
+
Execution modes:
|
|
225
|
+
- subprocess: Run skills locally in isolated processes (filesystem skills)
|
|
226
|
+
- remote: Run skills via platform APIs (platform-hosted skills)
|
|
227
|
+
|
|
228
|
+
Examples:
|
|
229
|
+
- Auto-select skill: {"task": "Analyze issue PROJ-123"}
|
|
230
|
+
- Explicit skill: {"skill_name": "jira_triage", "task": "Analyze issue", "context": {"issue_key": "PROJ-123"}}
|
|
231
|
+
- Override mode: {"skill_name": "my_skill", "task": "Process data", "execution_mode": "remote"}
|
|
232
|
+
""",
|
|
233
|
+
"args_schema": ExecuteSkillInput,
|
|
234
|
+
"ref": self._handle_execute
|
|
235
|
+
}
|
|
236
|
+
]
|
|
237
|
+
|
|
238
|
+
def _handle_list(
|
|
239
|
+
self,
|
|
240
|
+
skill_type: Optional[str] = None,
|
|
241
|
+
capability: Optional[str] = None,
|
|
242
|
+
tag: Optional[str] = None
|
|
243
|
+
) -> str:
|
|
244
|
+
"""Handle list skills operation."""
|
|
245
|
+
try:
|
|
246
|
+
# Get all skills
|
|
247
|
+
skills = self.registry.list()
|
|
248
|
+
|
|
249
|
+
# Apply filters
|
|
250
|
+
if skill_type:
|
|
251
|
+
skills = [s for s in skills if (s.skill_type.value if hasattr(s.skill_type, 'value') else str(s.skill_type)) == skill_type]
|
|
252
|
+
if capability:
|
|
253
|
+
skills = [s for s in skills if capability in s.capabilities]
|
|
254
|
+
if tag:
|
|
255
|
+
skills = [s for s in skills if tag in s.tags]
|
|
256
|
+
|
|
257
|
+
if not skills:
|
|
258
|
+
return "No skills found matching the specified criteria."
|
|
259
|
+
|
|
260
|
+
# Format results
|
|
261
|
+
result = f"Found {len(skills)} skills:\n\n"
|
|
262
|
+
|
|
263
|
+
for skill in skills:
|
|
264
|
+
skill_type_str = skill.skill_type.value if hasattr(skill.skill_type, 'value') else str(skill.skill_type)
|
|
265
|
+
result += f"**{skill.name}** ({skill_type_str})\n"
|
|
266
|
+
result += f" Description: {skill.description}\n"
|
|
267
|
+
|
|
268
|
+
if skill.capabilities:
|
|
269
|
+
result += f" Capabilities: {', '.join(skill.capabilities)}\n"
|
|
270
|
+
|
|
271
|
+
if skill.tags:
|
|
272
|
+
result += f" Tags: {', '.join(skill.tags)}\n"
|
|
273
|
+
|
|
274
|
+
result += f" Version: {skill.version}\n\n"
|
|
275
|
+
|
|
276
|
+
# Add summary stats
|
|
277
|
+
stats = self.registry.get_registry_stats()
|
|
278
|
+
result += f"\n**Registry Stats:**\n"
|
|
279
|
+
result += f"- Total skills: {stats['total_skills']}\n"
|
|
280
|
+
result += f"- Graph skills: {stats['graph_skills']}\n"
|
|
281
|
+
result += f"- Agent skills: {stats['agent_skills']}\n"
|
|
282
|
+
result += f"- Unique capabilities: {stats['unique_capabilities']}\n"
|
|
283
|
+
|
|
284
|
+
return result
|
|
285
|
+
|
|
286
|
+
except Exception as e:
|
|
287
|
+
return f"Error listing skills: {str(e)}"
|
|
288
|
+
|
|
289
|
+
def _handle_describe(self, skill_name: str) -> str:
|
|
290
|
+
"""Handle describe skill operation."""
|
|
291
|
+
try:
|
|
292
|
+
skill = self.registry.get(skill_name)
|
|
293
|
+
if not skill:
|
|
294
|
+
available_skills = [s.name for s in self.registry.list()]
|
|
295
|
+
return (f"Skill '{skill_name}' not found. "
|
|
296
|
+
f"Available skills: {', '.join(available_skills)}")
|
|
297
|
+
|
|
298
|
+
# Build detailed description
|
|
299
|
+
skill_type_str = skill.skill_type.value if hasattr(skill.skill_type, 'value') else str(skill.skill_type)
|
|
300
|
+
result = f"# {skill.name} ({skill_type_str} skill)\n\n"
|
|
301
|
+
result += f"**Description:** {skill.description}\n\n"
|
|
302
|
+
|
|
303
|
+
# Basic info
|
|
304
|
+
result += f"**Version:** {skill.version}\n"
|
|
305
|
+
execution_mode_str = skill.execution.mode.value if hasattr(skill.execution.mode, 'value') else str(skill.execution.mode)
|
|
306
|
+
result += f"**Execution Mode:** {execution_mode_str}\n"
|
|
307
|
+
result += f"**Timeout:** {skill.execution.timeout}s\n\n"
|
|
308
|
+
|
|
309
|
+
# Capabilities and tags
|
|
310
|
+
if skill.capabilities:
|
|
311
|
+
result += f"**Capabilities:** {', '.join(skill.capabilities)}\n"
|
|
312
|
+
if skill.tags:
|
|
313
|
+
result += f"**Tags:** {', '.join(skill.tags)}\n\n"
|
|
314
|
+
|
|
315
|
+
# Input schema
|
|
316
|
+
result += "**Input Schema:**\n"
|
|
317
|
+
if skill.skill_type == SkillType.AGENT:
|
|
318
|
+
result += "- **task** (required): Task or question for the skill\n"
|
|
319
|
+
result += "- **context** (optional): Variables as key-value pairs\n"
|
|
320
|
+
result += "- **chat_history** (optional): Conversation history\n"
|
|
321
|
+
|
|
322
|
+
if skill.inputs.variables:
|
|
323
|
+
result += "\n**Available Variables:**\n"
|
|
324
|
+
for var_name, var_def in skill.inputs.variables.items():
|
|
325
|
+
var_type = var_def.get('type', 'any')
|
|
326
|
+
var_desc = var_def.get('description', '')
|
|
327
|
+
required = ' (required)' if var_def.get('required') else ''
|
|
328
|
+
result += f"- **{var_name}** ({var_type}){required}: {var_desc}\n"
|
|
329
|
+
|
|
330
|
+
else: # GRAPH
|
|
331
|
+
result += "- **task** (required): Task description (becomes 'input' state variable)\n"
|
|
332
|
+
result += "- **state_variables** (optional): State variables as key-value pairs\n"
|
|
333
|
+
|
|
334
|
+
if skill.inputs.state_variables:
|
|
335
|
+
result += "\n**Available State Variables:**\n"
|
|
336
|
+
for var_name, var_def in skill.inputs.state_variables.items():
|
|
337
|
+
var_type = var_def.get('type', 'any')
|
|
338
|
+
var_desc = var_def.get('description', '')
|
|
339
|
+
required = ' (required)' if var_def.get('required') else ''
|
|
340
|
+
result += f"- **{var_name}** ({var_type}){required}: {var_desc}\n"
|
|
341
|
+
|
|
342
|
+
# LLM settings
|
|
343
|
+
if skill.model or skill.temperature or skill.max_tokens:
|
|
344
|
+
result += "\n**LLM Configuration:**\n"
|
|
345
|
+
if skill.model:
|
|
346
|
+
result += f"- Model: {skill.model}\n"
|
|
347
|
+
if skill.temperature:
|
|
348
|
+
result += f"- Temperature: {skill.temperature}\n"
|
|
349
|
+
if skill.max_tokens:
|
|
350
|
+
result += f"- Max tokens: {skill.max_tokens}\n"
|
|
351
|
+
|
|
352
|
+
# Usage example
|
|
353
|
+
result += "\n**Usage Example:**\n"
|
|
354
|
+
if skill.skill_type == SkillType.AGENT:
|
|
355
|
+
example = {
|
|
356
|
+
"skill_name": skill.name,
|
|
357
|
+
"task": f"Your task description here",
|
|
358
|
+
"context": {"key": "value"}
|
|
359
|
+
}
|
|
360
|
+
else:
|
|
361
|
+
example = {
|
|
362
|
+
"skill_name": skill.name,
|
|
363
|
+
"task": f"Your task description here",
|
|
364
|
+
"state_variables": {"key": "value"}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
result += f"```json\n{json.dumps(example, indent=2)}\n```"
|
|
368
|
+
|
|
369
|
+
return result
|
|
370
|
+
|
|
371
|
+
except Exception as e:
|
|
372
|
+
return f"Error describing skill: {str(e)}"
|
|
373
|
+
|
|
374
|
+
def _handle_execute(
|
|
375
|
+
self,
|
|
376
|
+
task: str,
|
|
377
|
+
skill_name: Optional[str] = None,
|
|
378
|
+
context: Optional[Dict[str, Any]] = None,
|
|
379
|
+
chat_history: Optional[List[Dict[str, str]]] = None,
|
|
380
|
+
state_variables: Optional[Dict[str, Any]] = None,
|
|
381
|
+
execution_mode: Optional[str] = None,
|
|
382
|
+
enable_callbacks: bool = False
|
|
383
|
+
) -> str:
|
|
384
|
+
"""Handle execute skill operation with comprehensive input validation."""
|
|
385
|
+
# If no skill_name provided, use LLM to intelligently select the best skill
|
|
386
|
+
if not skill_name:
|
|
387
|
+
skill_name = self._select_skill_with_llm(task)
|
|
388
|
+
if not skill_name:
|
|
389
|
+
return "Error: No appropriate skill found for the given task."
|
|
390
|
+
|
|
391
|
+
try:
|
|
392
|
+
# Get skill metadata
|
|
393
|
+
skill = self.registry.get(skill_name)
|
|
394
|
+
if not skill:
|
|
395
|
+
available_skills = [s.name for s in self.registry.list()]
|
|
396
|
+
return (f"Skill '{skill_name}' not found. "
|
|
397
|
+
f"Available skills: {', '.join(available_skills)}")
|
|
398
|
+
|
|
399
|
+
# Validate inputs against skill expectations
|
|
400
|
+
validation_result = self._validate_skill_inputs(skill, context, state_variables, chat_history)
|
|
401
|
+
if validation_result:
|
|
402
|
+
return validation_result # Return validation error message
|
|
403
|
+
|
|
404
|
+
# Apply router-level defaults and overrides
|
|
405
|
+
# 1. Apply router-level default timeout if not already set
|
|
406
|
+
if self.default_timeout and skill.execution.timeout == 300: # 300 is system default
|
|
407
|
+
skill.execution.timeout = self.default_timeout
|
|
408
|
+
|
|
409
|
+
# 2. Apply router-level default execution mode if not already set
|
|
410
|
+
if self.default_execution_mode and not execution_mode:
|
|
411
|
+
execution_mode = self.default_execution_mode
|
|
412
|
+
|
|
413
|
+
# 3. Override execution mode if specified in input (takes priority)
|
|
414
|
+
if execution_mode:
|
|
415
|
+
skill.execution.mode = execution_mode
|
|
416
|
+
|
|
417
|
+
# Determine input based on skill type
|
|
418
|
+
if skill.skill_type == SkillType.AGENT:
|
|
419
|
+
skill_context = context
|
|
420
|
+
skill_chat_history = chat_history
|
|
421
|
+
else: # GRAPH
|
|
422
|
+
# For graphs, merge task into state variables
|
|
423
|
+
skill_context = state_variables or {}
|
|
424
|
+
skill_chat_history = None
|
|
425
|
+
|
|
426
|
+
# Execute the skill
|
|
427
|
+
result = self.executor.execute_skill(
|
|
428
|
+
metadata=skill,
|
|
429
|
+
task=task,
|
|
430
|
+
context=skill_context,
|
|
431
|
+
chat_history=skill_chat_history
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
# Format result for LLM consumption
|
|
435
|
+
return self._format_execution_result(result)
|
|
436
|
+
|
|
437
|
+
except Exception as e:
|
|
438
|
+
logger.error(f"Skill execution failed: {e}")
|
|
439
|
+
return f"Error executing skill '{skill_name}': {str(e)}"
|
|
440
|
+
|
|
441
|
+
def _select_skill_with_llm(self, task: str) -> Optional[str]:
|
|
442
|
+
"""Use LLM to intelligently select the best skill for a given task."""
|
|
443
|
+
if not self.llm or not task:
|
|
444
|
+
logger.warning("LLM or task not available for skill selection")
|
|
445
|
+
return None
|
|
446
|
+
|
|
447
|
+
try:
|
|
448
|
+
# Get available skills for LLM to choose from
|
|
449
|
+
skills = self.registry.list()
|
|
450
|
+
if not skills:
|
|
451
|
+
logger.warning("No skills available for selection")
|
|
452
|
+
return None
|
|
453
|
+
|
|
454
|
+
# Format skills list for the prompt
|
|
455
|
+
skills_list = []
|
|
456
|
+
for skill in skills:
|
|
457
|
+
skill_info = f"- {skill.name}"
|
|
458
|
+
if skill.description:
|
|
459
|
+
skill_info += f": {skill.description}"
|
|
460
|
+
if skill.capabilities:
|
|
461
|
+
skill_info += f" (capabilities: {', '.join(skill.capabilities)})"
|
|
462
|
+
skills_list.append(skill_info)
|
|
463
|
+
|
|
464
|
+
skills_text = "\n".join(skills_list)
|
|
465
|
+
|
|
466
|
+
# Create routing prompt - use custom_prompt if provided, otherwise default
|
|
467
|
+
if self.custom_prompt:
|
|
468
|
+
# If custom prompt provided, combine it with routing instructions
|
|
469
|
+
routing_template = f"""{self.custom_prompt}
|
|
470
|
+
|
|
471
|
+
Your task is to analyze the user's request and select the most appropriate skill from the available options.
|
|
472
|
+
|
|
473
|
+
Available skills:
|
|
474
|
+
{{skills_list}}
|
|
475
|
+
|
|
476
|
+
Task: {{task}}
|
|
477
|
+
|
|
478
|
+
IMPORTANT: Respond with only the skill name that best matches the task. If no skill is appropriate, respond with "no_match"."""
|
|
479
|
+
else:
|
|
480
|
+
# Use default routing prompt
|
|
481
|
+
routing_template = """You are a skill router. Analyze the user's task and select the most appropriate skill.
|
|
482
|
+
|
|
483
|
+
Available skills:
|
|
484
|
+
{skills_list}
|
|
485
|
+
|
|
486
|
+
Task: {task}
|
|
487
|
+
|
|
488
|
+
Respond with only the skill name that best matches the task. If no skill is appropriate, respond with "no_match"."""
|
|
489
|
+
|
|
490
|
+
# Format the routing prompt
|
|
491
|
+
formatted_prompt = routing_template.format(
|
|
492
|
+
skills_list=skills_text,
|
|
493
|
+
task=task
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
logger.info(f"Using LLM to select skill for task: {task}")
|
|
497
|
+
|
|
498
|
+
# Get LLM response
|
|
499
|
+
response = self.llm.invoke(formatted_prompt)
|
|
500
|
+
if hasattr(response, 'content'):
|
|
501
|
+
selected_skill = response.content.strip()
|
|
502
|
+
else:
|
|
503
|
+
selected_skill = str(response).strip()
|
|
504
|
+
|
|
505
|
+
# Validate that the selected skill exists
|
|
506
|
+
if selected_skill == "no_match":
|
|
507
|
+
logger.info("LLM determined no skill matches the task")
|
|
508
|
+
return None
|
|
509
|
+
|
|
510
|
+
# Check if the selected skill actually exists
|
|
511
|
+
skill = self.registry.get(selected_skill)
|
|
512
|
+
if skill:
|
|
513
|
+
logger.info(f"LLM selected skill: {selected_skill}")
|
|
514
|
+
return selected_skill
|
|
515
|
+
else:
|
|
516
|
+
logger.warning(f"LLM selected non-existent skill: {selected_skill}")
|
|
517
|
+
return None
|
|
518
|
+
|
|
519
|
+
except Exception as e:
|
|
520
|
+
logger.error(f"Error in LLM skill selection: {e}")
|
|
521
|
+
return None
|
|
522
|
+
|
|
523
|
+
def _validate_skill_inputs(
|
|
524
|
+
self,
|
|
525
|
+
skill,
|
|
526
|
+
context: Optional[Dict[str, Any]],
|
|
527
|
+
state_variables: Optional[Dict[str, Any]],
|
|
528
|
+
chat_history: Optional[List[Dict[str, str]]]
|
|
529
|
+
) -> Optional[str]:
|
|
530
|
+
"""
|
|
531
|
+
Validate inputs against skill expectations and provide graceful error messages.
|
|
532
|
+
|
|
533
|
+
Returns:
|
|
534
|
+
None if validation passes, error message string if validation fails.
|
|
535
|
+
"""
|
|
536
|
+
try:
|
|
537
|
+
if skill.skill_type == SkillType.AGENT:
|
|
538
|
+
return self._validate_agent_inputs(skill, context, chat_history)
|
|
539
|
+
else: # GRAPH
|
|
540
|
+
return self._validate_graph_inputs(skill, state_variables)
|
|
541
|
+
except Exception as e:
|
|
542
|
+
logger.error(f"Input validation error: {e}")
|
|
543
|
+
return f"Error validating inputs: {str(e)}"
|
|
544
|
+
|
|
545
|
+
def _validate_agent_inputs(
|
|
546
|
+
self,
|
|
547
|
+
skill,
|
|
548
|
+
context: Optional[Dict[str, Any]],
|
|
549
|
+
chat_history: Optional[List[Dict[str, str]]]
|
|
550
|
+
) -> Optional[str]:
|
|
551
|
+
"""Validate inputs for agent skills."""
|
|
552
|
+
# Validate chat history format if provided
|
|
553
|
+
if chat_history and not isinstance(chat_history, list):
|
|
554
|
+
return (f"❌ **Invalid chat_history format for skill '{skill.name}'**\n\n"
|
|
555
|
+
f"Chat history must be a list of messages.\n"
|
|
556
|
+
f"Expected format: [{{'role': 'user', 'content': 'message'}}]\n"
|
|
557
|
+
f"Got: {type(chat_history).__name__}")
|
|
558
|
+
|
|
559
|
+
if chat_history:
|
|
560
|
+
for i, msg in enumerate(chat_history):
|
|
561
|
+
if not isinstance(msg, dict) or 'role' not in msg or 'content' not in msg:
|
|
562
|
+
return (f"❌ **Invalid chat_history format for skill '{skill.name}'**\n\n"
|
|
563
|
+
f"Message at index {i} is invalid.\n"
|
|
564
|
+
f"Each message must have 'role' and 'content' keys.\n"
|
|
565
|
+
f"Expected: {{'role': 'user'|'assistant', 'content': 'message text'}}\n"
|
|
566
|
+
f"Got: {msg}")
|
|
567
|
+
|
|
568
|
+
if not hasattr(skill.inputs, 'variables') or not skill.inputs.variables:
|
|
569
|
+
return None # No input requirements defined
|
|
570
|
+
|
|
571
|
+
provided_context = context or {}
|
|
572
|
+
missing_required = []
|
|
573
|
+
invalid_types = []
|
|
574
|
+
|
|
575
|
+
# Check each required variable
|
|
576
|
+
for var_name, var_def in skill.inputs.variables.items():
|
|
577
|
+
is_required = var_def.get('required', False)
|
|
578
|
+
expected_type = var_def.get('type', 'any')
|
|
579
|
+
|
|
580
|
+
if is_required and var_name not in provided_context:
|
|
581
|
+
missing_required.append({
|
|
582
|
+
'name': var_name,
|
|
583
|
+
'type': expected_type,
|
|
584
|
+
'description': var_def.get('description', '')
|
|
585
|
+
})
|
|
586
|
+
elif var_name in provided_context and expected_type != 'any':
|
|
587
|
+
# Validate type if specified and value is provided
|
|
588
|
+
provided_value = provided_context[var_name]
|
|
589
|
+
if not self._validate_type(provided_value, expected_type):
|
|
590
|
+
invalid_types.append({
|
|
591
|
+
'name': var_name,
|
|
592
|
+
'expected': expected_type,
|
|
593
|
+
'provided': type(provided_value).__name__,
|
|
594
|
+
'value': str(provided_value)[:50]
|
|
595
|
+
})
|
|
596
|
+
|
|
597
|
+
# Build error message if there are issues
|
|
598
|
+
if missing_required or invalid_types:
|
|
599
|
+
error_msg = f"❌ **Input validation failed for skill '{skill.name}'**\n\n"
|
|
600
|
+
|
|
601
|
+
if missing_required:
|
|
602
|
+
error_msg += "**Missing required variables:**\n"
|
|
603
|
+
for var in missing_required:
|
|
604
|
+
error_msg += f"- **{var['name']}** ({var['type']}): {var['description']}\n"
|
|
605
|
+
error_msg += "\n"
|
|
606
|
+
|
|
607
|
+
if invalid_types:
|
|
608
|
+
error_msg += "**Type mismatches:**\n"
|
|
609
|
+
for var in invalid_types:
|
|
610
|
+
error_msg += f"- **{var['name']}**: expected {var['expected']}, got {var['provided']} ('{var['value']}')\n"
|
|
611
|
+
error_msg += "\n"
|
|
612
|
+
|
|
613
|
+
# Provide correct usage example
|
|
614
|
+
error_msg += "**Correct usage:**\n"
|
|
615
|
+
example_context = {}
|
|
616
|
+
for var_name, var_def in skill.inputs.variables.items():
|
|
617
|
+
example_value = self._get_example_value(var_def.get('type', 'string'))
|
|
618
|
+
example_context[var_name] = example_value
|
|
619
|
+
|
|
620
|
+
example = {
|
|
621
|
+
"skill_name": skill.name,
|
|
622
|
+
"task": "Your task description here",
|
|
623
|
+
"context": example_context
|
|
624
|
+
}
|
|
625
|
+
error_msg += f"```json\n{json.dumps(example, indent=2)}\n```"
|
|
626
|
+
|
|
627
|
+
return error_msg
|
|
628
|
+
|
|
629
|
+
return None
|
|
630
|
+
|
|
631
|
+
def _validate_graph_inputs(
|
|
632
|
+
self,
|
|
633
|
+
skill,
|
|
634
|
+
state_variables: Optional[Dict[str, Any]]
|
|
635
|
+
) -> Optional[str]:
|
|
636
|
+
"""Validate inputs for graph skills."""
|
|
637
|
+
if not hasattr(skill.inputs, 'state_variables') or not skill.inputs.state_variables:
|
|
638
|
+
return None # No input requirements defined
|
|
639
|
+
|
|
640
|
+
provided_state = state_variables or {}
|
|
641
|
+
missing_required = []
|
|
642
|
+
invalid_types = []
|
|
643
|
+
|
|
644
|
+
# Check each required state variable
|
|
645
|
+
for var_name, var_def in skill.inputs.state_variables.items():
|
|
646
|
+
is_required = var_def.get('required', False)
|
|
647
|
+
expected_type = var_def.get('type', 'any')
|
|
648
|
+
|
|
649
|
+
if is_required and var_name not in provided_state:
|
|
650
|
+
missing_required.append({
|
|
651
|
+
'name': var_name,
|
|
652
|
+
'type': expected_type,
|
|
653
|
+
'description': var_def.get('description', '')
|
|
654
|
+
})
|
|
655
|
+
elif var_name in provided_state and expected_type != 'any':
|
|
656
|
+
# Validate type if specified and value is provided
|
|
657
|
+
provided_value = provided_state[var_name]
|
|
658
|
+
if not self._validate_type(provided_value, expected_type):
|
|
659
|
+
invalid_types.append({
|
|
660
|
+
'name': var_name,
|
|
661
|
+
'expected': expected_type,
|
|
662
|
+
'provided': type(provided_value).__name__,
|
|
663
|
+
'value': str(provided_value)[:50]
|
|
664
|
+
})
|
|
665
|
+
|
|
666
|
+
# Build error message if there are issues
|
|
667
|
+
if missing_required or invalid_types:
|
|
668
|
+
error_msg = f"❌ **Input validation failed for skill '{skill.name}'**\n\n"
|
|
669
|
+
|
|
670
|
+
if missing_required:
|
|
671
|
+
error_msg += "**Missing required state variables:**\n"
|
|
672
|
+
for var in missing_required:
|
|
673
|
+
error_msg += f"- **{var['name']}** ({var['type']}): {var['description']}\n"
|
|
674
|
+
error_msg += "\n"
|
|
675
|
+
|
|
676
|
+
if invalid_types:
|
|
677
|
+
error_msg += "**Type mismatches:**\n"
|
|
678
|
+
for var in invalid_types:
|
|
679
|
+
error_msg += f"- **{var['name']}**: expected {var['expected']}, got {var['provided']} ('{var['value']}')\n"
|
|
680
|
+
error_msg += "\n"
|
|
681
|
+
|
|
682
|
+
# Provide correct usage example
|
|
683
|
+
error_msg += "**Correct usage:**\n"
|
|
684
|
+
example_state = {}
|
|
685
|
+
for var_name, var_def in skill.inputs.state_variables.items():
|
|
686
|
+
example_value = self._get_example_value(var_def.get('type', 'string'))
|
|
687
|
+
example_state[var_name] = example_value
|
|
688
|
+
|
|
689
|
+
example = {
|
|
690
|
+
"skill_name": skill.name,
|
|
691
|
+
"task": "Your task description here",
|
|
692
|
+
"state_variables": example_state
|
|
693
|
+
}
|
|
694
|
+
error_msg += f"```json\n{json.dumps(example, indent=2)}\n```"
|
|
695
|
+
|
|
696
|
+
return error_msg
|
|
697
|
+
|
|
698
|
+
return None
|
|
699
|
+
|
|
700
|
+
def _validate_type(self, value: Any, expected_type: str) -> bool:
|
|
701
|
+
"""Validate that a value matches the expected type."""
|
|
702
|
+
type_mapping = {
|
|
703
|
+
'string': str,
|
|
704
|
+
'str': str,
|
|
705
|
+
'integer': int,
|
|
706
|
+
'int': int,
|
|
707
|
+
'number': (int, float),
|
|
708
|
+
'float': float,
|
|
709
|
+
'boolean': bool,
|
|
710
|
+
'bool': bool,
|
|
711
|
+
'list': list,
|
|
712
|
+
'array': list,
|
|
713
|
+
'dict': dict,
|
|
714
|
+
'object': dict,
|
|
715
|
+
'any': object # Accept anything
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
expected_py_type = type_mapping.get(expected_type.lower(), str)
|
|
719
|
+
return isinstance(value, expected_py_type)
|
|
720
|
+
|
|
721
|
+
def _get_example_value(self, var_type: str) -> Any:
|
|
722
|
+
"""Generate example values for different types."""
|
|
723
|
+
examples = {
|
|
724
|
+
'string': 'example_value',
|
|
725
|
+
'str': 'example_value',
|
|
726
|
+
'integer': 42,
|
|
727
|
+
'int': 42,
|
|
728
|
+
'number': 42.0,
|
|
729
|
+
'float': 42.0,
|
|
730
|
+
'boolean': True,
|
|
731
|
+
'bool': True,
|
|
732
|
+
'list': ['item1', 'item2'],
|
|
733
|
+
'array': ['item1', 'item2'],
|
|
734
|
+
'dict': {'key': 'value'},
|
|
735
|
+
'object': {'key': 'value'},
|
|
736
|
+
'any': 'example_value'
|
|
737
|
+
}
|
|
738
|
+
return examples.get(var_type.lower(), 'example_value')
|
|
739
|
+
|
|
740
|
+
def _format_execution_result(self, result: SkillExecutionResult) -> str:
|
|
741
|
+
"""
|
|
742
|
+
Format execution result for LLM consumption.
|
|
743
|
+
|
|
744
|
+
Args:
|
|
745
|
+
result: SkillExecutionResult to format.
|
|
746
|
+
|
|
747
|
+
Returns:
|
|
748
|
+
Formatted string result.
|
|
749
|
+
"""
|
|
750
|
+
if result.status == SkillStatus.ERROR:
|
|
751
|
+
formatted = f"❌ **Skill '{result.skill_name}' failed**\n\n"
|
|
752
|
+
formatted += f"Error: {result.error_details or 'Unknown error'}\n"
|
|
753
|
+
formatted += f"Duration: {result.duration:.1f}s"
|
|
754
|
+
return formatted
|
|
755
|
+
|
|
756
|
+
# Start with the main output
|
|
757
|
+
formatted = result.output_text
|
|
758
|
+
|
|
759
|
+
# Add execution metadata
|
|
760
|
+
formatted += f"\n\n---\n"
|
|
761
|
+
formatted += f"**Execution Summary:**\n"
|
|
762
|
+
skill_type_str = result.skill_type.value if hasattr(result.skill_type, 'value') else str(result.skill_type)
|
|
763
|
+
status_str = result.status.value if hasattr(result.status, 'value') else str(result.status)
|
|
764
|
+
execution_mode_str = result.execution_mode.value if hasattr(result.execution_mode, 'value') else str(result.execution_mode)
|
|
765
|
+
formatted += f"- Skill: {result.skill_name} ({skill_type_str})\n"
|
|
766
|
+
formatted += f"- Status: {status_str}\n"
|
|
767
|
+
formatted += f"- Duration: {result.duration:.1f}s\n"
|
|
768
|
+
formatted += f"- Mode: {execution_mode_str}\n"
|
|
769
|
+
|
|
770
|
+
# Add file references if any
|
|
771
|
+
if result.output_files:
|
|
772
|
+
formatted += f"\n**Generated Files:**\n"
|
|
773
|
+
for file_ref in result.output_files:
|
|
774
|
+
formatted += f"- {file_ref}\n"
|
|
775
|
+
|
|
776
|
+
return formatted
|