skillnet-ai 0.0.1__py3-none-any.whl → 0.0.2__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.
skillnet_ai/client.py ADDED
@@ -0,0 +1,316 @@
1
+ import os
2
+ from typing import List, Optional, Dict, Any, Union
3
+ from pathlib import Path
4
+
5
+ from skillnet_ai.creator import SkillCreator
6
+ from skillnet_ai.downloader import SkillDownloader
7
+ from skillnet_ai.evaluator import SkillEvaluator, EvaluatorConfig
8
+ from skillnet_ai.searcher import SkillNetSearcher
9
+ from skillnet_ai.analyzer import SkillRelationshipAnalyzer
10
+
11
+ class SkillNetError(Exception):
12
+ """Custom exception class for SkillNet Client errors."""
13
+ pass
14
+
15
+ class SkillNetClient:
16
+ """
17
+ A Python SDK client for interacting with SkillNet AI services.
18
+
19
+ This client aggregates Search, Download, Creation, and Evaluation functionalities.
20
+ """
21
+
22
+ def __init__(
23
+ self,
24
+ api_key: Optional[str] = None,
25
+ base_url: Optional[str] = None,
26
+ github_token: Optional[str] = None
27
+ ):
28
+ """
29
+ Initialize the SkillNet Client.
30
+
31
+ Args:
32
+ api_key: OpenAI/SkillNet API Key. Defaults to env var API_KEY.
33
+ base_url: Base URL for the LLM API. Defaults to env var BASE_URL or OpenAI default.
34
+ github_token: GitHub token for downloading private skills or avoiding rate limits.
35
+ Defaults to env var GITHUB_TOKEN.
36
+ """
37
+ self.api_key = api_key or os.getenv("API_KEY")
38
+ self.base_url = base_url or os.getenv("BASE_URL")
39
+ self.github_token = github_token or os.getenv("GITHUB_TOKEN")
40
+
41
+
42
+ def search(
43
+ self,
44
+ q: str,
45
+ mode: str = "keyword",
46
+ category: Optional[str] = None,
47
+ limit: int = 20,
48
+ page: int = 1,
49
+ min_stars: int = 0,
50
+ sort_by: str = "stars",
51
+ threshold: float = 0.8
52
+ ) -> List[Any]:
53
+ """
54
+ Search for skills on SkillNet.
55
+
56
+ Args:
57
+ q: The search query.
58
+ mode: 'keyword' or 'vector'.
59
+ category: Filter by category.
60
+ limit: Max results.
61
+ page: Page number (keyword mode only).
62
+ min_stars: Filter by stars (keyword mode only).
63
+ sort_by: 'stars' or 'recent' (keyword mode only).
64
+ threshold: Similarity threshold (vector mode only).
65
+
66
+ Returns:
67
+ List[SkillModel]: A list of skill objects found.
68
+ """
69
+ try:
70
+ searcher = SkillNetSearcher()
71
+ results = searcher.search(
72
+ q=q,
73
+ mode=mode,
74
+ category=category,
75
+ limit=limit,
76
+ page=page,
77
+ min_stars=min_stars,
78
+ sort_by=sort_by,
79
+ threshold=threshold
80
+ )
81
+ return results
82
+ except Exception as e:
83
+ raise SkillNetError(f"Search failed: {str(e)}") from e
84
+
85
+ def download(
86
+ self,
87
+ url: str,
88
+ target_dir: str = ".",
89
+ token: Optional[str] = None
90
+ ) -> str:
91
+ """
92
+ Download a skill from a GitHub URL.
93
+
94
+ Args:
95
+ url: The GitHub URL of the specific skill folder.
96
+ target_dir: Local directory to install into.
97
+ token: Optional override for GitHub token.
98
+
99
+ Returns:
100
+ str: The absolute path to the installed skill folder.
101
+
102
+ Raises:
103
+ SkillNetError: If download fails.
104
+ """
105
+ # Use instance token if specific token not provided
106
+ use_token = token if token else self.github_token
107
+ downloader = SkillDownloader(api_token=use_token)
108
+
109
+ try:
110
+ installed_path = downloader.download(folder_url=url, target_dir=target_dir)
111
+ if not installed_path:
112
+ raise SkillNetError("Download returned None. Check URL validity or permissions.")
113
+ return os.path.abspath(installed_path)
114
+ except Exception as e:
115
+ raise SkillNetError(f"Download failed: {str(e)}") from e
116
+
117
+ def create(
118
+ self,
119
+ input_type: str = "auto",
120
+ trajectory_content: Optional[str] = None,
121
+ github_url: Optional[str] = None,
122
+ office_file: Optional[str] = None,
123
+ prompt: Optional[str] = None,
124
+ output_dir: Union[str, Path] = "./generated_skills",
125
+ model: str = "gpt-4o",
126
+ max_files: int = 20
127
+ ) -> List[str]:
128
+ """
129
+ Generate executable skills from various input sources.
130
+
131
+ Args:
132
+ input_type: Input source type. One of:
133
+ - "auto": Auto-detect based on provided parameters (default)
134
+ - "github": Create from GitHub repository
135
+ - "trajectory": Create from execution log/trajectory
136
+ - "office": Create from PDF/PPT/Word document
137
+ - "prompt": Create from user's direct description
138
+ trajectory_content: The text content of the execution log/trajectory.
139
+ github_url: Full URL to GitHub repository.
140
+ office_file: Path to office document (PDF, PPT, Word).
141
+ prompt: User's description for prompt-based skill creation.
142
+ output_dir: Directory where new skills will be saved.
143
+ model: The LLM model to use.
144
+ max_files: Maximum Python files to analyze (GitHub mode only).
145
+
146
+ Returns:
147
+ List[str]: A list of paths to the generated skill folders.
148
+ """
149
+ if not self.api_key:
150
+ raise SkillNetError("API_KEY is required for skill creation.")
151
+
152
+ # Auto-detect input type if not specified
153
+ if input_type == "auto":
154
+ if github_url:
155
+ input_type = "github"
156
+ elif trajectory_content:
157
+ input_type = "trajectory"
158
+ elif office_file:
159
+ input_type = "office"
160
+ elif prompt:
161
+ input_type = "prompt"
162
+ else:
163
+ raise SkillNetError(
164
+ "Must provide one of: trajectory_content, github_url, "
165
+ "office_file, or diy_prompt."
166
+ )
167
+
168
+ # Validate input_type
169
+ valid_types = {"github", "trajectory", "office", "prompt", "auto"}
170
+ if input_type not in valid_types:
171
+ raise SkillNetError(f"Invalid input_type: {input_type}. Must be one of {valid_types}")
172
+
173
+ try:
174
+ creator = SkillCreator(
175
+ api_key=self.api_key,
176
+ base_url=self.base_url,
177
+ model=model
178
+ )
179
+
180
+ if input_type == "github":
181
+ if not github_url:
182
+ raise SkillNetError("github_url is required for github input type.")
183
+ created_paths = creator.create_from_github(
184
+ github_url=github_url,
185
+ output_dir=str(output_dir),
186
+ api_token=self.github_token,
187
+ max_files=max_files
188
+ )
189
+ elif input_type == "trajectory":
190
+ if not trajectory_content:
191
+ raise SkillNetError("trajectory_content is required for trajectory input type.")
192
+ created_paths = creator.create_from_trajectory(
193
+ trajectory=trajectory_content,
194
+ output_dir=str(output_dir)
195
+ )
196
+ elif input_type == "office":
197
+ if not office_file:
198
+ raise SkillNetError("office_file is required for office input type.")
199
+ created_paths = creator.create_from_office(
200
+ file_path=office_file,
201
+ output_dir=str(output_dir)
202
+ )
203
+ elif input_type == "prompt":
204
+ if not prompt:
205
+ raise SkillNetError("prompt is required for prompt input type.")
206
+ created_paths = creator.create_from_prompt(
207
+ user_input=prompt,
208
+ output_dir=str(output_dir)
209
+ )
210
+ else:
211
+ raise SkillNetError(f"Unknown input_type: {input_type}")
212
+
213
+ return created_paths if created_paths else []
214
+ except Exception as e:
215
+ raise SkillNetError(f"Creation failed: {str(e)}") from e
216
+
217
+ def evaluate(
218
+ self,
219
+ target: str,
220
+ name: Optional[str] = None,
221
+ category: Optional[str] = None,
222
+ description: Optional[str] = None,
223
+ model: str = "gpt-4o",
224
+ max_workers: int = 5,
225
+ cache_dir: Union[str, Path] = "./evaluate_cache_dir"
226
+ ) -> Dict[str, Any]:
227
+ """
228
+ Evaluate a skill (local path or URL).
229
+
230
+ Args:
231
+ target: Local folder path OR GitHub URL.
232
+ name: Override skill name.
233
+ category: Override skill category.
234
+ description: Override skill description.
235
+ model: LLM model for evaluation.
236
+ max_workers: Concurrency limit.
237
+
238
+ Returns:
239
+ Dict[str, Any]: The evaluation report dictionary.
240
+ """
241
+ if not self.api_key:
242
+ raise SkillNetError("API_KEY is required for evaluation.")
243
+
244
+ config = EvaluatorConfig(
245
+ api_key=self.api_key,
246
+ base_url=self.base_url,
247
+ model=model,
248
+ max_workers=max_workers,
249
+ cache_dir=cache_dir,
250
+ github_token=self.github_token
251
+ )
252
+ evaluator = SkillEvaluator(config)
253
+
254
+ try:
255
+ is_url = target.startswith("http://") or target.startswith("https://")
256
+
257
+ if is_url:
258
+ result = evaluator.evaluate_from_url(
259
+ url=target,
260
+ name=name,
261
+ category=category,
262
+ description=description
263
+ )
264
+ else:
265
+ result = evaluator.evaluate_from_path(
266
+ path=target,
267
+ name=name,
268
+ category=category,
269
+ description=description
270
+ )
271
+
272
+ if "error" in result:
273
+ raise SkillNetError(f"Evaluation logic returned error: {result['error']}")
274
+
275
+ return result
276
+
277
+ except Exception as e:
278
+ raise SkillNetError(f"Evaluation process failed: {str(e)}") from e
279
+
280
+ def analyze(
281
+ self,
282
+ skills_dir: Union[str, Path],
283
+ save_to_file: bool = True,
284
+ model: str = "gpt-4o"
285
+ ) -> List[Dict[str, Any]]:
286
+ """
287
+ Analyze a local directory containing multiple skills to infer relationships between them.
288
+
289
+ This builds a knowledge graph (edges) between skills based on their names and descriptions.
290
+ Relationships detected: similar_to, belong_to, compose_with, depend_on.
291
+
292
+ Args:
293
+ skills_dir: Path to the directory containing skill folders.
294
+ save_to_file: If True, saves a 'relationships.json' file in the skills_dir.
295
+ model: The LLM model to use for analysis.
296
+
297
+ Returns:
298
+ List[Dict[str, Any]]: A list of relationship edges (source, target, type, reason).
299
+ """
300
+ if not self.api_key:
301
+ raise SkillNetError("API_KEY is required for relationship analysis.")
302
+
303
+ try:
304
+ analyzer = SkillRelationshipAnalyzer(
305
+ api_key=self.api_key,
306
+ base_url=self.base_url,
307
+ model=model
308
+ )
309
+
310
+ results = analyzer.analyze_local_skills(
311
+ skills_dir=str(skills_dir),
312
+ save_to_file=save_to_file
313
+ )
314
+ return results
315
+ except Exception as e:
316
+ raise SkillNetError(f"Relationship analysis failed: {str(e)}") from e