langchain-skilllite 0.1.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.
- langchain_skilllite/__init__.py +54 -0
- langchain_skilllite/_version.py +4 -0
- langchain_skilllite/callbacks.py +166 -0
- langchain_skilllite/tools.py +526 -0
- langchain_skilllite-0.1.0.dist-info/METADATA +203 -0
- langchain_skilllite-0.1.0.dist-info/RECORD +9 -0
- langchain_skilllite-0.1.0.dist-info/WHEEL +5 -0
- langchain_skilllite-0.1.0.dist-info/licenses/LICENSE +22 -0
- langchain_skilllite-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LangChain integration for SkillLite.
|
|
3
|
+
|
|
4
|
+
This package provides LangChain-compatible tools for executing SkillLite skills
|
|
5
|
+
in a sandboxed environment. It acts as a thin adapter layer on top of the
|
|
6
|
+
skilllite core package.
|
|
7
|
+
|
|
8
|
+
Key Features:
|
|
9
|
+
- SkillLiteTool: LangChain BaseTool adapter for individual skills
|
|
10
|
+
- SkillLiteToolkit: Convenient toolkit for loading multiple skills
|
|
11
|
+
- Security scanning and confirmation callbacks for sandbox level 3
|
|
12
|
+
- Full async support for LangGraph agents
|
|
13
|
+
|
|
14
|
+
Installation:
|
|
15
|
+
pip install langchain-skilllite
|
|
16
|
+
|
|
17
|
+
Quick Start:
|
|
18
|
+
```python
|
|
19
|
+
from langchain_skilllite import SkillLiteToolkit
|
|
20
|
+
from langchain_openai import ChatOpenAI
|
|
21
|
+
from langgraph.prebuilt import create_react_agent
|
|
22
|
+
|
|
23
|
+
# Load skills as LangChain tools
|
|
24
|
+
tools = SkillLiteToolkit.from_directory("./skills")
|
|
25
|
+
|
|
26
|
+
# Use with any LangChain agent
|
|
27
|
+
agent = create_react_agent(ChatOpenAI(), tools)
|
|
28
|
+
result = agent.invoke({"messages": [("user", "Run my skill")]})
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
For more information, see:
|
|
32
|
+
- SkillLite: https://github.com/EXboys/skilllite
|
|
33
|
+
- LangChain: https://python.langchain.com/
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from langchain_skilllite.tools import (
|
|
37
|
+
SkillLiteTool,
|
|
38
|
+
SkillLiteToolkit,
|
|
39
|
+
)
|
|
40
|
+
from langchain_skilllite.callbacks import (
|
|
41
|
+
SkillLiteCallbackHandler,
|
|
42
|
+
)
|
|
43
|
+
from langchain_skilllite._version import __version__
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
# Core Tools
|
|
47
|
+
"SkillLiteTool",
|
|
48
|
+
"SkillLiteToolkit",
|
|
49
|
+
# Callbacks
|
|
50
|
+
"SkillLiteCallbackHandler",
|
|
51
|
+
# Version
|
|
52
|
+
"__version__",
|
|
53
|
+
]
|
|
54
|
+
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LangChain callback handlers for SkillLite.
|
|
3
|
+
|
|
4
|
+
This module provides callback handlers for integrating SkillLite
|
|
5
|
+
with LangChain's callback system for logging, tracing, and monitoring.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from typing import Any, Dict, List, Optional, TYPE_CHECKING, Union
|
|
12
|
+
from uuid import UUID
|
|
13
|
+
|
|
14
|
+
from langchain_core.callbacks import BaseCallbackHandler
|
|
15
|
+
from langchain_core.outputs import LLMResult
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from langchain_core.agents import AgentAction, AgentFinish
|
|
19
|
+
from langchain_core.messages import BaseMessage
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SkillLiteCallbackHandler(BaseCallbackHandler):
|
|
25
|
+
"""
|
|
26
|
+
LangChain callback handler for SkillLite skill execution.
|
|
27
|
+
|
|
28
|
+
This handler logs skill execution events and can be used for
|
|
29
|
+
monitoring, debugging, and auditing SkillLite tool usage.
|
|
30
|
+
|
|
31
|
+
Usage:
|
|
32
|
+
from langchain_skilllite import SkillLiteCallbackHandler
|
|
33
|
+
|
|
34
|
+
handler = SkillLiteCallbackHandler(verbose=True)
|
|
35
|
+
|
|
36
|
+
# Use with LangChain agent
|
|
37
|
+
agent.invoke({"input": "..."}, config={"callbacks": [handler]})
|
|
38
|
+
|
|
39
|
+
Attributes:
|
|
40
|
+
verbose: Whether to print execution details
|
|
41
|
+
execution_log: List of execution events
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
verbose: bool = False,
|
|
47
|
+
log_level: int = logging.INFO,
|
|
48
|
+
):
|
|
49
|
+
"""
|
|
50
|
+
Initialize the callback handler.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
verbose: If True, print execution details to stdout
|
|
54
|
+
log_level: Logging level for internal logging
|
|
55
|
+
"""
|
|
56
|
+
super().__init__()
|
|
57
|
+
self.verbose = verbose
|
|
58
|
+
self.log_level = log_level
|
|
59
|
+
self.execution_log: List[Dict[str, Any]] = []
|
|
60
|
+
self._current_tool: Optional[str] = None
|
|
61
|
+
|
|
62
|
+
def on_tool_start(
|
|
63
|
+
self,
|
|
64
|
+
serialized: Dict[str, Any],
|
|
65
|
+
input_str: str,
|
|
66
|
+
*,
|
|
67
|
+
run_id: UUID,
|
|
68
|
+
parent_run_id: Optional[UUID] = None,
|
|
69
|
+
tags: Optional[List[str]] = None,
|
|
70
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
71
|
+
inputs: Optional[Dict[str, Any]] = None,
|
|
72
|
+
**kwargs: Any,
|
|
73
|
+
) -> None:
|
|
74
|
+
"""Called when a tool starts running."""
|
|
75
|
+
tool_name = serialized.get("name", "unknown")
|
|
76
|
+
self._current_tool = tool_name
|
|
77
|
+
|
|
78
|
+
event = {
|
|
79
|
+
"event": "tool_start",
|
|
80
|
+
"tool_name": tool_name,
|
|
81
|
+
"run_id": str(run_id),
|
|
82
|
+
"input": input_str[:200] if input_str else None,
|
|
83
|
+
}
|
|
84
|
+
self.execution_log.append(event)
|
|
85
|
+
|
|
86
|
+
if self.verbose:
|
|
87
|
+
print(f"🔧 [SkillLite] Starting tool: {tool_name}")
|
|
88
|
+
logger.log(self.log_level, f"Tool started: {tool_name}")
|
|
89
|
+
|
|
90
|
+
def on_tool_end(
|
|
91
|
+
self,
|
|
92
|
+
output: Any,
|
|
93
|
+
*,
|
|
94
|
+
run_id: UUID,
|
|
95
|
+
parent_run_id: Optional[UUID] = None,
|
|
96
|
+
**kwargs: Any,
|
|
97
|
+
) -> None:
|
|
98
|
+
"""Called when a tool finishes."""
|
|
99
|
+
# Handle different output types (str, ToolMessage, etc.)
|
|
100
|
+
if isinstance(output, str):
|
|
101
|
+
output_preview = output[:200] if output else None
|
|
102
|
+
elif hasattr(output, "content"):
|
|
103
|
+
# ToolMessage or similar object
|
|
104
|
+
content = output.content
|
|
105
|
+
output_preview = content[:200] if isinstance(content, str) and content else str(content)[:200]
|
|
106
|
+
else:
|
|
107
|
+
output_preview = str(output)[:200] if output else None
|
|
108
|
+
|
|
109
|
+
event = {
|
|
110
|
+
"event": "tool_end",
|
|
111
|
+
"tool_name": self._current_tool,
|
|
112
|
+
"run_id": str(run_id),
|
|
113
|
+
"output_preview": output_preview,
|
|
114
|
+
"success": True,
|
|
115
|
+
}
|
|
116
|
+
self.execution_log.append(event)
|
|
117
|
+
|
|
118
|
+
if self.verbose:
|
|
119
|
+
print(f"✅ [SkillLite] Tool completed: {self._current_tool}")
|
|
120
|
+
logger.log(self.log_level, f"Tool completed: {self._current_tool}")
|
|
121
|
+
|
|
122
|
+
self._current_tool = None
|
|
123
|
+
|
|
124
|
+
def on_tool_error(
|
|
125
|
+
self,
|
|
126
|
+
error: BaseException,
|
|
127
|
+
*,
|
|
128
|
+
run_id: UUID,
|
|
129
|
+
parent_run_id: Optional[UUID] = None,
|
|
130
|
+
**kwargs: Any,
|
|
131
|
+
) -> None:
|
|
132
|
+
"""Called when a tool errors."""
|
|
133
|
+
event = {
|
|
134
|
+
"event": "tool_error",
|
|
135
|
+
"tool_name": self._current_tool,
|
|
136
|
+
"run_id": str(run_id),
|
|
137
|
+
"error": str(error),
|
|
138
|
+
"success": False,
|
|
139
|
+
}
|
|
140
|
+
self.execution_log.append(event)
|
|
141
|
+
|
|
142
|
+
if self.verbose:
|
|
143
|
+
print(f"❌ [SkillLite] Tool error: {self._current_tool} - {error}")
|
|
144
|
+
logger.error(f"Tool error: {self._current_tool} - {error}")
|
|
145
|
+
|
|
146
|
+
self._current_tool = None
|
|
147
|
+
|
|
148
|
+
def get_execution_summary(self) -> Dict[str, Any]:
|
|
149
|
+
"""Get a summary of all execution events."""
|
|
150
|
+
total = len(self.execution_log)
|
|
151
|
+
tool_starts = sum(1 for e in self.execution_log if e["event"] == "tool_start")
|
|
152
|
+
tool_ends = sum(1 for e in self.execution_log if e["event"] == "tool_end")
|
|
153
|
+
tool_errors = sum(1 for e in self.execution_log if e["event"] == "tool_error")
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
"total_events": total,
|
|
157
|
+
"tool_executions": tool_starts,
|
|
158
|
+
"successful": tool_ends,
|
|
159
|
+
"errors": tool_errors,
|
|
160
|
+
"success_rate": tool_ends / tool_starts if tool_starts > 0 else 0,
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
def clear_log(self) -> None:
|
|
164
|
+
"""Clear the execution log."""
|
|
165
|
+
self.execution_log.clear()
|
|
166
|
+
|
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LangChain tools for SkillLite skill execution.
|
|
3
|
+
|
|
4
|
+
This module provides LangChain-compatible tool wrappers for SkillLite skills.
|
|
5
|
+
All execution logic is delegated to the skilllite core package.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import hashlib
|
|
12
|
+
import os
|
|
13
|
+
import time
|
|
14
|
+
import uuid
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
from typing import Any, Callable, Dict, List, Optional, Type, TYPE_CHECKING
|
|
17
|
+
|
|
18
|
+
from langchain_core.tools import BaseTool
|
|
19
|
+
from langchain_core.callbacks import (
|
|
20
|
+
CallbackManagerForToolRun,
|
|
21
|
+
AsyncCallbackManagerForToolRun,
|
|
22
|
+
)
|
|
23
|
+
from pydantic import BaseModel, Field, ConfigDict
|
|
24
|
+
|
|
25
|
+
# Import from skilllite core - this is the key dependency
|
|
26
|
+
from skilllite import SkillManager, SkillInfo
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
|
|
31
|
+
# Type aliases for confirmation callbacks
|
|
32
|
+
ConfirmationCallback = Callable[[str, str], bool]
|
|
33
|
+
AsyncConfirmationCallback = Callable[[str, str], "asyncio.Future[bool]"]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class SecurityScanResult:
|
|
38
|
+
"""Result of a security scan for skill execution."""
|
|
39
|
+
|
|
40
|
+
is_safe: bool
|
|
41
|
+
issues: List[Dict[str, Any]] = field(default_factory=list)
|
|
42
|
+
scan_id: str = ""
|
|
43
|
+
code_hash: str = ""
|
|
44
|
+
high_severity_count: int = 0
|
|
45
|
+
medium_severity_count: int = 0
|
|
46
|
+
low_severity_count: int = 0
|
|
47
|
+
timestamp: float = field(default_factory=time.time)
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def requires_confirmation(self) -> bool:
|
|
51
|
+
"""Check if user confirmation is required."""
|
|
52
|
+
return self.high_severity_count > 0
|
|
53
|
+
|
|
54
|
+
def format_report(self) -> str:
|
|
55
|
+
"""Format a human-readable security report."""
|
|
56
|
+
if not self.issues:
|
|
57
|
+
return "✅ Security scan passed. No issues found."
|
|
58
|
+
|
|
59
|
+
lines = [
|
|
60
|
+
f"📋 Security Scan Report (ID: {self.scan_id[:8]})",
|
|
61
|
+
f" Found {len(self.issues)} item(s) for review:",
|
|
62
|
+
"",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
severity_icons = {
|
|
66
|
+
"Critical": "🔴",
|
|
67
|
+
"High": "🟠",
|
|
68
|
+
"Medium": "🟡",
|
|
69
|
+
"Low": "🟢",
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
for idx, issue in enumerate(self.issues, 1):
|
|
73
|
+
severity = issue.get("severity", "Medium")
|
|
74
|
+
icon = severity_icons.get(severity, "⚪")
|
|
75
|
+
lines.append(f" {icon} #{idx} [{severity}] {issue.get('issue_type', 'Unknown')}")
|
|
76
|
+
lines.append(f" ├─ Rule: {issue.get('rule_id', 'N/A')}")
|
|
77
|
+
lines.append(f" ├─ Line {issue.get('line_number', '?')}: {issue.get('description', '')}")
|
|
78
|
+
snippet = issue.get('code_snippet', '')
|
|
79
|
+
lines.append(f" └─ Code: {snippet[:60]}{'...' if len(snippet) > 60 else ''}")
|
|
80
|
+
lines.append("")
|
|
81
|
+
|
|
82
|
+
if self.high_severity_count > 0:
|
|
83
|
+
lines.append("⚠️ High severity issues found. Confirmation required to execute.")
|
|
84
|
+
else:
|
|
85
|
+
lines.append("ℹ️ Only low/medium severity issues found. Safe to execute.")
|
|
86
|
+
|
|
87
|
+
return "\n".join(lines)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class SkillLiteTool(BaseTool):
|
|
91
|
+
"""
|
|
92
|
+
LangChain BaseTool adapter for a single SkillLite skill.
|
|
93
|
+
|
|
94
|
+
This wraps a SkillLite skill as a LangChain tool, enabling it to be
|
|
95
|
+
used with LangChain agents and LangGraph.
|
|
96
|
+
|
|
97
|
+
All execution is delegated to the skilllite core package.
|
|
98
|
+
|
|
99
|
+
Attributes:
|
|
100
|
+
name: Tool name (same as skill name)
|
|
101
|
+
description: Tool description from SKILL.md
|
|
102
|
+
manager: SkillManager instance (from skilllite)
|
|
103
|
+
skill_name: Name of the skill to execute
|
|
104
|
+
sandbox_level: Sandbox security level (1/2/3)
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
name: str = Field(description="Tool name")
|
|
108
|
+
description: str = Field(description="Tool description")
|
|
109
|
+
args_schema: Optional[Type[BaseModel]] = Field(
|
|
110
|
+
default=None, description="Pydantic schema for arguments"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# SkillLite specific fields - delegated to skilllite core
|
|
114
|
+
manager: Any = Field(exclude=True) # SkillManager instance
|
|
115
|
+
skill_name: str = Field(description="SkillLite skill name")
|
|
116
|
+
allow_network: bool = Field(default=False, description="Allow network access")
|
|
117
|
+
timeout: Optional[int] = Field(default=None, description="Execution timeout in seconds")
|
|
118
|
+
|
|
119
|
+
# Security confirmation fields
|
|
120
|
+
sandbox_level: int = Field(default=3, description="Sandbox security level (1/2/3)")
|
|
121
|
+
confirmation_callback: Optional[Any] = Field(
|
|
122
|
+
default=None,
|
|
123
|
+
exclude=True,
|
|
124
|
+
description="Sync callback: (report: str, scan_id: str) -> bool"
|
|
125
|
+
)
|
|
126
|
+
async_confirmation_callback: Optional[Any] = Field(
|
|
127
|
+
default=None,
|
|
128
|
+
exclude=True,
|
|
129
|
+
description="Async callback: (report: str, scan_id: str) -> Future[bool]"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Internal cache
|
|
133
|
+
_scan_cache: Dict[str, SecurityScanResult] = {}
|
|
134
|
+
_confirmed_skills: Dict[str, float] = {} # skill_name -> confirmation timestamp
|
|
135
|
+
_SCAN_CACHE_TTL: int = 300 # 5 minutes
|
|
136
|
+
_CONFIRMATION_TTL: int = 3600 # 1 hour - once confirmed, don't ask again
|
|
137
|
+
|
|
138
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
139
|
+
|
|
140
|
+
def _generate_input_hash(self, input_data: Dict[str, Any]) -> str:
|
|
141
|
+
"""Generate a hash of the input data for verification."""
|
|
142
|
+
import json
|
|
143
|
+
content = json.dumps(input_data, sort_keys=True, ensure_ascii=False)
|
|
144
|
+
return hashlib.sha256(content.encode()).hexdigest()[:16]
|
|
145
|
+
|
|
146
|
+
def _cleanup_expired_scans(self) -> None:
|
|
147
|
+
"""Remove expired scan results from cache."""
|
|
148
|
+
current_time = time.time()
|
|
149
|
+
expired_keys = [
|
|
150
|
+
k for k, v in self._scan_cache.items()
|
|
151
|
+
if current_time - v.timestamp > self._SCAN_CACHE_TTL
|
|
152
|
+
]
|
|
153
|
+
for key in expired_keys:
|
|
154
|
+
del self._scan_cache[key]
|
|
155
|
+
|
|
156
|
+
def _perform_security_scan(self, input_data: Dict[str, Any]) -> SecurityScanResult:
|
|
157
|
+
"""
|
|
158
|
+
Perform a security scan on the skill execution.
|
|
159
|
+
Delegates to skilllite's skillbox binary for actual scanning.
|
|
160
|
+
"""
|
|
161
|
+
self._cleanup_expired_scans()
|
|
162
|
+
input_hash = self._generate_input_hash(input_data)
|
|
163
|
+
scan_id = str(uuid.uuid4())
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
# Get skill info from skilllite's registry
|
|
167
|
+
skill_info = self.manager._registry.get_skill(self.skill_name)
|
|
168
|
+
entry_point = skill_info.metadata.entry_point if skill_info and skill_info.metadata else None
|
|
169
|
+
|
|
170
|
+
if skill_info and entry_point:
|
|
171
|
+
entry_script = skill_info.path / entry_point
|
|
172
|
+
if entry_script.exists():
|
|
173
|
+
# Use skilllite's skillbox for security scanning
|
|
174
|
+
from skilllite.sandbox.skillbox import find_binary
|
|
175
|
+
import subprocess
|
|
176
|
+
|
|
177
|
+
skillbox_path = find_binary()
|
|
178
|
+
if skillbox_path:
|
|
179
|
+
result = subprocess.run(
|
|
180
|
+
[skillbox_path, "security-scan", str(entry_script)],
|
|
181
|
+
capture_output=True,
|
|
182
|
+
text=True,
|
|
183
|
+
timeout=30
|
|
184
|
+
)
|
|
185
|
+
issues = self._parse_scan_output(result.stdout + result.stderr)
|
|
186
|
+
high_count = sum(1 for i in issues if i.get("severity") in ["Critical", "High"])
|
|
187
|
+
medium_count = sum(1 for i in issues if i.get("severity") == "Medium")
|
|
188
|
+
low_count = sum(1 for i in issues if i.get("severity") == "Low")
|
|
189
|
+
|
|
190
|
+
scan_result = SecurityScanResult(
|
|
191
|
+
is_safe=high_count == 0,
|
|
192
|
+
issues=issues,
|
|
193
|
+
scan_id=scan_id,
|
|
194
|
+
code_hash=input_hash,
|
|
195
|
+
high_severity_count=high_count,
|
|
196
|
+
medium_severity_count=medium_count,
|
|
197
|
+
low_severity_count=low_count,
|
|
198
|
+
)
|
|
199
|
+
self._scan_cache[scan_id] = scan_result
|
|
200
|
+
return scan_result
|
|
201
|
+
except Exception:
|
|
202
|
+
pass
|
|
203
|
+
|
|
204
|
+
# Default: no issues found
|
|
205
|
+
scan_result = SecurityScanResult(
|
|
206
|
+
is_safe=True,
|
|
207
|
+
issues=[],
|
|
208
|
+
scan_id=scan_id,
|
|
209
|
+
code_hash=input_hash,
|
|
210
|
+
)
|
|
211
|
+
self._scan_cache[scan_id] = scan_result
|
|
212
|
+
return scan_result
|
|
213
|
+
|
|
214
|
+
def _parse_scan_output(self, output: str) -> List[Dict[str, Any]]:
|
|
215
|
+
"""Parse skillbox scan output into structured issues."""
|
|
216
|
+
issues = []
|
|
217
|
+
current_issue: Optional[Dict[str, Any]] = None
|
|
218
|
+
|
|
219
|
+
for line in output.split('\n'):
|
|
220
|
+
line = line.strip()
|
|
221
|
+
if not line:
|
|
222
|
+
continue
|
|
223
|
+
|
|
224
|
+
if any(sev in line for sev in ['[Critical]', '[High]', '[Medium]', '[Low]']):
|
|
225
|
+
if current_issue:
|
|
226
|
+
issues.append(current_issue)
|
|
227
|
+
|
|
228
|
+
severity = "Medium"
|
|
229
|
+
for sev in ['Critical', 'High', 'Medium', 'Low']:
|
|
230
|
+
if f'[{sev}]' in line:
|
|
231
|
+
severity = sev
|
|
232
|
+
break
|
|
233
|
+
|
|
234
|
+
current_issue = {
|
|
235
|
+
"severity": severity,
|
|
236
|
+
"issue_type": "SecurityIssue",
|
|
237
|
+
"description": line,
|
|
238
|
+
"rule_id": "unknown",
|
|
239
|
+
"line_number": 0,
|
|
240
|
+
"code_snippet": ""
|
|
241
|
+
}
|
|
242
|
+
elif current_issue:
|
|
243
|
+
if 'Rule:' in line:
|
|
244
|
+
current_issue["rule_id"] = line.split('Rule:')[-1].strip()
|
|
245
|
+
elif 'Line' in line:
|
|
246
|
+
try:
|
|
247
|
+
line_num = int(line.split('Line')[-1].split(':')[0].strip())
|
|
248
|
+
current_issue["line_number"] = line_num
|
|
249
|
+
except ValueError:
|
|
250
|
+
pass
|
|
251
|
+
elif 'Code:' in line or '│' in line:
|
|
252
|
+
current_issue["code_snippet"] = line.split('Code:')[-1].strip() if 'Code:' in line else line
|
|
253
|
+
|
|
254
|
+
if current_issue:
|
|
255
|
+
issues.append(current_issue)
|
|
256
|
+
|
|
257
|
+
return issues
|
|
258
|
+
|
|
259
|
+
def _is_skill_confirmed(self) -> bool:
|
|
260
|
+
"""Check if this skill has been confirmed recently."""
|
|
261
|
+
if self.skill_name in self._confirmed_skills:
|
|
262
|
+
confirmed_at = self._confirmed_skills[self.skill_name]
|
|
263
|
+
if time.time() - confirmed_at < self._CONFIRMATION_TTL:
|
|
264
|
+
return True
|
|
265
|
+
# Expired, remove from cache
|
|
266
|
+
del self._confirmed_skills[self.skill_name]
|
|
267
|
+
return False
|
|
268
|
+
|
|
269
|
+
def _mark_skill_confirmed(self) -> None:
|
|
270
|
+
"""Mark this skill as confirmed."""
|
|
271
|
+
self._confirmed_skills[self.skill_name] = time.time()
|
|
272
|
+
|
|
273
|
+
def _run(
|
|
274
|
+
self,
|
|
275
|
+
run_manager: Optional[CallbackManagerForToolRun] = None,
|
|
276
|
+
**kwargs: Any
|
|
277
|
+
) -> str:
|
|
278
|
+
"""Execute the skill synchronously. Delegates to skilllite core."""
|
|
279
|
+
skip_skillbox_confirmation = False
|
|
280
|
+
old_sandbox_level = None
|
|
281
|
+
|
|
282
|
+
try:
|
|
283
|
+
# Security scan for sandbox level 3
|
|
284
|
+
if self.sandbox_level >= 3:
|
|
285
|
+
# Check if already confirmed in this session
|
|
286
|
+
if self._is_skill_confirmed():
|
|
287
|
+
skip_skillbox_confirmation = True
|
|
288
|
+
else:
|
|
289
|
+
scan_result = self._perform_security_scan(kwargs)
|
|
290
|
+
|
|
291
|
+
if scan_result.requires_confirmation:
|
|
292
|
+
if self.confirmation_callback:
|
|
293
|
+
report = scan_result.format_report()
|
|
294
|
+
confirmed = self.confirmation_callback(report, scan_result.scan_id)
|
|
295
|
+
|
|
296
|
+
if not confirmed:
|
|
297
|
+
return (
|
|
298
|
+
f"🔐 Execution cancelled by user.\n\n"
|
|
299
|
+
f"{report}\n\n"
|
|
300
|
+
f"User declined to proceed with execution."
|
|
301
|
+
)
|
|
302
|
+
# Mark as confirmed so we don't ask again
|
|
303
|
+
self._mark_skill_confirmed()
|
|
304
|
+
skip_skillbox_confirmation = True
|
|
305
|
+
else:
|
|
306
|
+
return (
|
|
307
|
+
f"🔐 Security Review Required\n\n"
|
|
308
|
+
f"{scan_result.format_report()}\n\n"
|
|
309
|
+
f"Provide a confirmation_callback when creating the tool."
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
# Adjust sandbox level if user confirmed
|
|
313
|
+
if skip_skillbox_confirmation:
|
|
314
|
+
old_sandbox_level = os.environ.get("SKILLBOX_SANDBOX_LEVEL")
|
|
315
|
+
os.environ["SKILLBOX_SANDBOX_LEVEL"] = "1"
|
|
316
|
+
|
|
317
|
+
# Delegate execution to skilllite core
|
|
318
|
+
result = self.manager.execute(
|
|
319
|
+
self.skill_name,
|
|
320
|
+
kwargs,
|
|
321
|
+
allow_network=self.allow_network,
|
|
322
|
+
timeout=self.timeout
|
|
323
|
+
)
|
|
324
|
+
if result.success:
|
|
325
|
+
return result.output or "Execution completed successfully"
|
|
326
|
+
else:
|
|
327
|
+
return f"Error: {result.error}"
|
|
328
|
+
except Exception as e:
|
|
329
|
+
return f"Execution failed: {str(e)}"
|
|
330
|
+
finally:
|
|
331
|
+
if skip_skillbox_confirmation:
|
|
332
|
+
if old_sandbox_level is not None:
|
|
333
|
+
os.environ["SKILLBOX_SANDBOX_LEVEL"] = old_sandbox_level
|
|
334
|
+
elif "SKILLBOX_SANDBOX_LEVEL" in os.environ:
|
|
335
|
+
del os.environ["SKILLBOX_SANDBOX_LEVEL"]
|
|
336
|
+
|
|
337
|
+
async def _arun(
|
|
338
|
+
self,
|
|
339
|
+
run_manager: Optional[AsyncCallbackManagerForToolRun] = None,
|
|
340
|
+
**kwargs: Any
|
|
341
|
+
) -> str:
|
|
342
|
+
"""Execute the skill asynchronously. Delegates to skilllite core."""
|
|
343
|
+
skip_skillbox_confirmation = False
|
|
344
|
+
old_sandbox_level = None
|
|
345
|
+
|
|
346
|
+
try:
|
|
347
|
+
if self.sandbox_level >= 3:
|
|
348
|
+
# Check if already confirmed in this session
|
|
349
|
+
if self._is_skill_confirmed():
|
|
350
|
+
skip_skillbox_confirmation = True
|
|
351
|
+
else:
|
|
352
|
+
scan_result = await asyncio.to_thread(self._perform_security_scan, kwargs)
|
|
353
|
+
|
|
354
|
+
if scan_result.requires_confirmation:
|
|
355
|
+
if self.async_confirmation_callback:
|
|
356
|
+
report = scan_result.format_report()
|
|
357
|
+
confirmed = await self.async_confirmation_callback(report, scan_result.scan_id)
|
|
358
|
+
if not confirmed:
|
|
359
|
+
return f"🔐 Execution cancelled by user.\n\n{report}"
|
|
360
|
+
self._mark_skill_confirmed()
|
|
361
|
+
skip_skillbox_confirmation = True
|
|
362
|
+
elif self.confirmation_callback:
|
|
363
|
+
report = scan_result.format_report()
|
|
364
|
+
confirmed = await asyncio.to_thread(
|
|
365
|
+
self.confirmation_callback, report, scan_result.scan_id
|
|
366
|
+
)
|
|
367
|
+
if not confirmed:
|
|
368
|
+
return f"🔐 Execution cancelled by user.\n\n{report}"
|
|
369
|
+
self._mark_skill_confirmed()
|
|
370
|
+
skip_skillbox_confirmation = True
|
|
371
|
+
else:
|
|
372
|
+
return (
|
|
373
|
+
f"🔐 Security Review Required\n\n"
|
|
374
|
+
f"{scan_result.format_report()}"
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
if skip_skillbox_confirmation:
|
|
378
|
+
old_sandbox_level = os.environ.get("SKILLBOX_SANDBOX_LEVEL")
|
|
379
|
+
os.environ["SKILLBOX_SANDBOX_LEVEL"] = "1"
|
|
380
|
+
|
|
381
|
+
# Delegate to skilllite core
|
|
382
|
+
result = await asyncio.to_thread(
|
|
383
|
+
self.manager.execute,
|
|
384
|
+
self.skill_name,
|
|
385
|
+
kwargs,
|
|
386
|
+
self.allow_network,
|
|
387
|
+
self.timeout
|
|
388
|
+
)
|
|
389
|
+
if result.success:
|
|
390
|
+
return result.output or "Execution completed successfully"
|
|
391
|
+
else:
|
|
392
|
+
return f"Error: {result.error}"
|
|
393
|
+
except Exception as e:
|
|
394
|
+
return f"Execution failed: {str(e)}"
|
|
395
|
+
finally:
|
|
396
|
+
if skip_skillbox_confirmation:
|
|
397
|
+
if old_sandbox_level is not None:
|
|
398
|
+
os.environ["SKILLBOX_SANDBOX_LEVEL"] = old_sandbox_level
|
|
399
|
+
elif "SKILLBOX_SANDBOX_LEVEL" in os.environ:
|
|
400
|
+
del os.environ["SKILLBOX_SANDBOX_LEVEL"]
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
class SkillLiteToolkit:
|
|
404
|
+
"""
|
|
405
|
+
LangChain Toolkit for SkillLite.
|
|
406
|
+
|
|
407
|
+
Provides a convenient way to create LangChain tools from all skills
|
|
408
|
+
registered in a SkillManager.
|
|
409
|
+
|
|
410
|
+
Usage:
|
|
411
|
+
manager = SkillManager(skills_dir="./skills")
|
|
412
|
+
tools = SkillLiteToolkit.from_manager(manager)
|
|
413
|
+
|
|
414
|
+
# Or with options
|
|
415
|
+
tools = SkillLiteToolkit.from_manager(
|
|
416
|
+
manager,
|
|
417
|
+
skill_names=["calculator", "web_search"],
|
|
418
|
+
allow_network=True,
|
|
419
|
+
timeout=60
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
# With security confirmation callback (for sandbox level 3)
|
|
423
|
+
def confirm_execution(report: str, scan_id: str) -> bool:
|
|
424
|
+
print(report)
|
|
425
|
+
return input("Continue? [y/N]: ").lower() == 'y'
|
|
426
|
+
|
|
427
|
+
tools = SkillLiteToolkit.from_manager(
|
|
428
|
+
manager,
|
|
429
|
+
sandbox_level=3,
|
|
430
|
+
confirmation_callback=confirm_execution
|
|
431
|
+
)
|
|
432
|
+
"""
|
|
433
|
+
|
|
434
|
+
@staticmethod
|
|
435
|
+
def from_manager(
|
|
436
|
+
manager: "SkillManager",
|
|
437
|
+
skill_names: Optional[List[str]] = None,
|
|
438
|
+
allow_network: bool = False,
|
|
439
|
+
timeout: Optional[int] = None,
|
|
440
|
+
sandbox_level: int = 3,
|
|
441
|
+
confirmation_callback: Optional[ConfirmationCallback] = None,
|
|
442
|
+
async_confirmation_callback: Optional[AsyncConfirmationCallback] = None,
|
|
443
|
+
) -> List[SkillLiteTool]:
|
|
444
|
+
"""
|
|
445
|
+
Create LangChain tools from a SkillManager.
|
|
446
|
+
|
|
447
|
+
Args:
|
|
448
|
+
manager: SkillManager instance with registered skills
|
|
449
|
+
skill_names: Optional list of skill names to include (default: all)
|
|
450
|
+
allow_network: Whether to allow network access for all tools
|
|
451
|
+
timeout: Execution timeout in seconds for all tools
|
|
452
|
+
sandbox_level: Sandbox security level (1/2/3, default: 3)
|
|
453
|
+
- Level 1: No sandbox - direct execution
|
|
454
|
+
- Level 2: Sandbox isolation only
|
|
455
|
+
- Level 3: Sandbox + security scanning (requires confirmation)
|
|
456
|
+
confirmation_callback: Sync callback for security confirmation.
|
|
457
|
+
Signature: (security_report: str, scan_id: str) -> bool
|
|
458
|
+
async_confirmation_callback: Async callback for security confirmation.
|
|
459
|
+
Signature: (security_report: str, scan_id: str) -> Future[bool]
|
|
460
|
+
|
|
461
|
+
Returns:
|
|
462
|
+
List of SkillLiteTool instances
|
|
463
|
+
"""
|
|
464
|
+
tools = []
|
|
465
|
+
|
|
466
|
+
# Get executable skills from skilllite core
|
|
467
|
+
skills = manager.list_executable_skills()
|
|
468
|
+
|
|
469
|
+
for skill in skills:
|
|
470
|
+
# Filter by name if specified
|
|
471
|
+
if skill_names and skill.name not in skill_names:
|
|
472
|
+
continue
|
|
473
|
+
|
|
474
|
+
# Create tool with security confirmation support
|
|
475
|
+
tool = SkillLiteTool(
|
|
476
|
+
name=skill.name,
|
|
477
|
+
description=skill.description or f"Execute the {skill.name} skill",
|
|
478
|
+
manager=manager,
|
|
479
|
+
skill_name=skill.name,
|
|
480
|
+
allow_network=allow_network,
|
|
481
|
+
timeout=timeout,
|
|
482
|
+
sandbox_level=sandbox_level,
|
|
483
|
+
confirmation_callback=confirmation_callback,
|
|
484
|
+
async_confirmation_callback=async_confirmation_callback,
|
|
485
|
+
)
|
|
486
|
+
tools.append(tool)
|
|
487
|
+
|
|
488
|
+
return tools
|
|
489
|
+
|
|
490
|
+
@staticmethod
|
|
491
|
+
def from_directory(
|
|
492
|
+
skills_dir: str,
|
|
493
|
+
skill_names: Optional[List[str]] = None,
|
|
494
|
+
allow_network: bool = False,
|
|
495
|
+
timeout: Optional[int] = None,
|
|
496
|
+
sandbox_level: int = 3,
|
|
497
|
+
confirmation_callback: Optional[ConfirmationCallback] = None,
|
|
498
|
+
async_confirmation_callback: Optional[AsyncConfirmationCallback] = None,
|
|
499
|
+
) -> List[SkillLiteTool]:
|
|
500
|
+
"""
|
|
501
|
+
Create LangChain tools from a skills directory.
|
|
502
|
+
|
|
503
|
+
Convenience method that creates a SkillManager and loads all skills.
|
|
504
|
+
|
|
505
|
+
Args:
|
|
506
|
+
skills_dir: Path to directory containing skill folders
|
|
507
|
+
skill_names: Optional list of skill names to include
|
|
508
|
+
allow_network: Whether to allow network access
|
|
509
|
+
timeout: Execution timeout in seconds
|
|
510
|
+
sandbox_level: Sandbox security level (1/2/3)
|
|
511
|
+
confirmation_callback: Sync callback for security confirmation
|
|
512
|
+
async_confirmation_callback: Async callback for security confirmation
|
|
513
|
+
|
|
514
|
+
Returns:
|
|
515
|
+
List of SkillLiteTool instances
|
|
516
|
+
"""
|
|
517
|
+
manager = SkillManager(skills_dir=skills_dir)
|
|
518
|
+
return SkillLiteToolkit.from_manager(
|
|
519
|
+
manager=manager,
|
|
520
|
+
skill_names=skill_names,
|
|
521
|
+
allow_network=allow_network,
|
|
522
|
+
timeout=timeout,
|
|
523
|
+
sandbox_level=sandbox_level,
|
|
524
|
+
confirmation_callback=confirmation_callback,
|
|
525
|
+
async_confirmation_callback=async_confirmation_callback,
|
|
526
|
+
)
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: langchain-skilllite
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: LangChain integration for SkillLite - Lightweight sandboxed Python skill execution engine
|
|
5
|
+
Author-email: SkillLite Team <skilllite@example.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/EXboys/langchain-skilllite
|
|
8
|
+
Project-URL: Documentation, https://github.com/EXboys/langchain-skilllite#readme
|
|
9
|
+
Project-URL: Repository, https://github.com/EXboys/langchain-skilllite
|
|
10
|
+
Project-URL: Issues, https://github.com/EXboys/langchain-skilllite/issues
|
|
11
|
+
Project-URL: LangChain, https://python.langchain.com/
|
|
12
|
+
Project-URL: SkillLite, https://github.com/EXboys/skilllite
|
|
13
|
+
Keywords: langchain,skilllite,agent,tools,sandbox,llm,skills,python-execution
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: MacOS
|
|
18
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
26
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
27
|
+
Requires-Python: >=3.9
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Requires-Dist: langchain-core>=0.3.0
|
|
31
|
+
Requires-Dist: skilllite>=0.1.1
|
|
32
|
+
Provides-Extra: langgraph
|
|
33
|
+
Requires-Dist: langgraph>=0.2.0; extra == "langgraph"
|
|
34
|
+
Provides-Extra: test
|
|
35
|
+
Requires-Dist: pytest>=7.0; extra == "test"
|
|
36
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "test"
|
|
37
|
+
Requires-Dist: langchain-tests>=0.3.0; extra == "test"
|
|
38
|
+
Provides-Extra: dev
|
|
39
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
40
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
41
|
+
Requires-Dist: langchain-tests>=0.3.0; extra == "dev"
|
|
42
|
+
Requires-Dist: black>=23.0; extra == "dev"
|
|
43
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
44
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
45
|
+
Dynamic: license-file
|
|
46
|
+
|
|
47
|
+
# langchain-skilllite
|
|
48
|
+
|
|
49
|
+
[](https://badge.fury.io/py/langchain-skilllite)
|
|
50
|
+
[](https://opensource.org/licenses/MIT)
|
|
51
|
+
|
|
52
|
+
LangChain integration for [SkillLite](https://github.com/EXboys/skilllite) - a lightweight sandboxed Python skill execution engine.
|
|
53
|
+
|
|
54
|
+
## Features
|
|
55
|
+
|
|
56
|
+
- 🔒 **Sandboxed Execution** - All skills run in a Rust-based sandbox (skillbox)
|
|
57
|
+
- 📝 **Declarative Skills** - Define skills via SKILL.md, no Python wrappers needed
|
|
58
|
+
- 🔍 **Security Scanning** - Pre-execution code analysis for dangerous operations
|
|
59
|
+
- ✅ **Confirmation Callbacks** - User approval for high-severity security issues
|
|
60
|
+
- ⚡ **Async Support** - Full async support for LangGraph agents
|
|
61
|
+
|
|
62
|
+
## Installation
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install langchain-skilllite
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
This will also install the required dependencies:
|
|
69
|
+
- `langchain-core>=0.3.0`
|
|
70
|
+
- `skilllite>=0.1.1`
|
|
71
|
+
|
|
72
|
+
## Quick Start
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from langchain_skilllite import SkillLiteToolkit
|
|
76
|
+
from langchain_openai import ChatOpenAI
|
|
77
|
+
from langgraph.prebuilt import create_react_agent
|
|
78
|
+
|
|
79
|
+
# Load all skills from a directory as LangChain tools
|
|
80
|
+
tools = SkillLiteToolkit.from_directory("./skills")
|
|
81
|
+
|
|
82
|
+
# Create a LangGraph agent
|
|
83
|
+
agent = create_react_agent(ChatOpenAI(model="gpt-4"), tools)
|
|
84
|
+
|
|
85
|
+
# Run the agent
|
|
86
|
+
result = agent.invoke({
|
|
87
|
+
"messages": [("user", "Calculate 15 + 27 using the calculator skill")]
|
|
88
|
+
})
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Usage
|
|
92
|
+
|
|
93
|
+
### Basic Usage with SkillManager
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from skilllite import SkillManager
|
|
97
|
+
from langchain_skilllite import SkillLiteToolkit
|
|
98
|
+
|
|
99
|
+
# Create a SkillManager
|
|
100
|
+
manager = SkillManager(skills_dir="./skills")
|
|
101
|
+
|
|
102
|
+
# Convert all skills to LangChain tools
|
|
103
|
+
tools = SkillLiteToolkit.from_manager(manager)
|
|
104
|
+
|
|
105
|
+
# Or select specific skills
|
|
106
|
+
tools = SkillLiteToolkit.from_manager(
|
|
107
|
+
manager,
|
|
108
|
+
skill_names=["calculator", "web_search"],
|
|
109
|
+
allow_network=True,
|
|
110
|
+
timeout=60
|
|
111
|
+
)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Security Levels
|
|
115
|
+
|
|
116
|
+
SkillLite supports three sandbox security levels:
|
|
117
|
+
|
|
118
|
+
| Level | Description |
|
|
119
|
+
|-------|-------------|
|
|
120
|
+
| 1 | No sandbox - direct execution (fastest, least secure) |
|
|
121
|
+
| 2 | Sandbox isolation only |
|
|
122
|
+
| 3 | Sandbox + security scanning (default, most secure) |
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
# Level 3 with confirmation callback for high-severity issues
|
|
126
|
+
def confirm_execution(report: str, scan_id: str) -> bool:
|
|
127
|
+
print(report)
|
|
128
|
+
return input("Proceed? [y/N]: ").lower() == 'y'
|
|
129
|
+
|
|
130
|
+
tools = SkillLiteToolkit.from_directory(
|
|
131
|
+
"./skills",
|
|
132
|
+
sandbox_level=3,
|
|
133
|
+
confirmation_callback=confirm_execution
|
|
134
|
+
)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Async Confirmation (for LangGraph)
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
import asyncio
|
|
141
|
+
|
|
142
|
+
async def async_confirm(report: str, scan_id: str) -> bool:
|
|
143
|
+
print(report)
|
|
144
|
+
# In a real app, this might be a UI prompt
|
|
145
|
+
return True
|
|
146
|
+
|
|
147
|
+
tools = SkillLiteToolkit.from_directory(
|
|
148
|
+
"./skills",
|
|
149
|
+
sandbox_level=3,
|
|
150
|
+
async_confirmation_callback=async_confirm
|
|
151
|
+
)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Callback Handler for Monitoring
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from langchain_skilllite import SkillLiteCallbackHandler
|
|
158
|
+
|
|
159
|
+
handler = SkillLiteCallbackHandler(verbose=True)
|
|
160
|
+
|
|
161
|
+
# Use with agent
|
|
162
|
+
result = agent.invoke(
|
|
163
|
+
{"messages": [("user", "Run my skill")]},
|
|
164
|
+
config={"callbacks": [handler]}
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
# Get execution summary
|
|
168
|
+
print(handler.get_execution_summary())
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## API Reference
|
|
172
|
+
|
|
173
|
+
### SkillLiteTool
|
|
174
|
+
|
|
175
|
+
LangChain `BaseTool` adapter for a single SkillLite skill.
|
|
176
|
+
|
|
177
|
+
### SkillLiteToolkit
|
|
178
|
+
|
|
179
|
+
Factory class for creating multiple `SkillLiteTool` instances.
|
|
180
|
+
|
|
181
|
+
- `from_manager(manager, ...)` - Create tools from a SkillManager
|
|
182
|
+
- `from_directory(skills_dir, ...)` - Create tools from a skills directory
|
|
183
|
+
|
|
184
|
+
### SkillLiteCallbackHandler
|
|
185
|
+
|
|
186
|
+
LangChain callback handler for monitoring skill execution.
|
|
187
|
+
|
|
188
|
+
## Requirements
|
|
189
|
+
|
|
190
|
+
- Python >= 3.9
|
|
191
|
+
- langchain-core >= 0.3.0
|
|
192
|
+
- skilllite >= 0.1.1
|
|
193
|
+
|
|
194
|
+
## License
|
|
195
|
+
|
|
196
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
197
|
+
|
|
198
|
+
## Links
|
|
199
|
+
|
|
200
|
+
- [SkillLite Repository](https://github.com/EXboys/skilllite)
|
|
201
|
+
- [LangChain Documentation](https://python.langchain.com/)
|
|
202
|
+
- [LangGraph Documentation](https://langchain-ai.github.io/langgraph/)
|
|
203
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
langchain_skilllite/__init__.py,sha256=pkzWP7nP7A_hRsfl0TbgA65WWq-UpMMBCyy4VLslD8Q,1461
|
|
2
|
+
langchain_skilllite/_version.py,sha256=UluS3ysGlayfko8ludKoiC9cT8bp9A2iMfbL6sP5VqQ,75
|
|
3
|
+
langchain_skilllite/callbacks.py,sha256=L-_Qguh_R1jd7Qtx_QN0kaFE5wX7t-9USOoUNaUug6k,5254
|
|
4
|
+
langchain_skilllite/tools.py,sha256=2OBxA2GwNAwnQzYAUmV63FkmI6qB7-r36_LagtVu27E,20921
|
|
5
|
+
langchain_skilllite-0.1.0.dist-info/licenses/LICENSE,sha256=HcK5iz9Y3FKj6oiQH6Q0tx1fYDXhKqkrCUUM8XRngRk,1072
|
|
6
|
+
langchain_skilllite-0.1.0.dist-info/METADATA,sha256=icBeT2u8kwcdN-naTw5zlvaBdsxzD7cdoWzvUjMke84,6077
|
|
7
|
+
langchain_skilllite-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
8
|
+
langchain_skilllite-0.1.0.dist-info/top_level.txt,sha256=XmA85AHn71wM124Kssl3hQRQElz3JJuDUjtuLJlL7nE,20
|
|
9
|
+
langchain_skilllite-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SkillLite Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
langchain_skilllite
|