noesium 0.1.0__py3-none-any.whl → 0.2.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.
Files changed (59) hide show
  1. noesium/agents/askura_agent/__init__.py +22 -0
  2. noesium/agents/askura_agent/askura_agent.py +480 -0
  3. noesium/agents/askura_agent/conversation.py +164 -0
  4. noesium/agents/askura_agent/extractor.py +175 -0
  5. noesium/agents/askura_agent/memory.py +14 -0
  6. noesium/agents/askura_agent/models.py +239 -0
  7. noesium/agents/askura_agent/prompts.py +202 -0
  8. noesium/agents/askura_agent/reflection.py +234 -0
  9. noesium/agents/askura_agent/summarizer.py +30 -0
  10. noesium/agents/askura_agent/utils.py +6 -0
  11. noesium/agents/deep_research/__init__.py +13 -0
  12. noesium/agents/deep_research/agent.py +398 -0
  13. noesium/agents/deep_research/prompts.py +84 -0
  14. noesium/agents/deep_research/schemas.py +42 -0
  15. noesium/agents/deep_research/state.py +54 -0
  16. noesium/agents/search/__init__.py +5 -0
  17. noesium/agents/search/agent.py +474 -0
  18. noesium/agents/search/state.py +28 -0
  19. noesium/core/__init__.py +1 -1
  20. noesium/core/agent/base.py +10 -2
  21. noesium/core/goalith/decomposer/llm_decomposer.py +1 -1
  22. noesium/core/llm/__init__.py +1 -1
  23. noesium/core/llm/base.py +2 -2
  24. noesium/core/llm/litellm.py +42 -21
  25. noesium/core/llm/llamacpp.py +25 -4
  26. noesium/core/llm/ollama.py +43 -22
  27. noesium/core/llm/openai.py +25 -5
  28. noesium/core/llm/openrouter.py +1 -1
  29. noesium/core/toolify/base.py +9 -2
  30. noesium/core/toolify/config.py +2 -2
  31. noesium/core/toolify/registry.py +21 -5
  32. noesium/core/tracing/opik_tracing.py +7 -7
  33. noesium/core/vector_store/__init__.py +2 -2
  34. noesium/core/vector_store/base.py +1 -1
  35. noesium/core/vector_store/pgvector.py +10 -13
  36. noesium/core/vector_store/weaviate.py +2 -1
  37. noesium/toolkits/__init__.py +1 -0
  38. noesium/toolkits/arxiv_toolkit.py +310 -0
  39. noesium/toolkits/audio_aliyun_toolkit.py +441 -0
  40. noesium/toolkits/audio_toolkit.py +370 -0
  41. noesium/toolkits/bash_toolkit.py +332 -0
  42. noesium/toolkits/document_toolkit.py +454 -0
  43. noesium/toolkits/file_edit_toolkit.py +552 -0
  44. noesium/toolkits/github_toolkit.py +395 -0
  45. noesium/toolkits/gmail_toolkit.py +575 -0
  46. noesium/toolkits/image_toolkit.py +425 -0
  47. noesium/toolkits/memory_toolkit.py +398 -0
  48. noesium/toolkits/python_executor_toolkit.py +334 -0
  49. noesium/toolkits/search_toolkit.py +451 -0
  50. noesium/toolkits/serper_toolkit.py +623 -0
  51. noesium/toolkits/tabular_data_toolkit.py +537 -0
  52. noesium/toolkits/user_interaction_toolkit.py +365 -0
  53. noesium/toolkits/video_toolkit.py +168 -0
  54. noesium/toolkits/wikipedia_toolkit.py +420 -0
  55. {noesium-0.1.0.dist-info → noesium-0.2.0.dist-info}/METADATA +56 -48
  56. {noesium-0.1.0.dist-info → noesium-0.2.0.dist-info}/RECORD +59 -23
  57. {noesium-0.1.0.dist-info → noesium-0.2.0.dist-info}/licenses/LICENSE +1 -1
  58. {noesium-0.1.0.dist-info → noesium-0.2.0.dist-info}/WHEEL +0 -0
  59. {noesium-0.1.0.dist-info → noesium-0.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,395 @@
1
+ """
2
+ GitHub toolkit for repository information and operations.
3
+
4
+ Provides tools for interacting with GitHub repositories through the GitHub API,
5
+ including repository information retrieval, file operations, and more.
6
+ """
7
+
8
+ import os
9
+ from typing import Callable, Dict, Optional
10
+ from urllib.parse import urlparse
11
+
12
+ import aiohttp
13
+
14
+ from noesium.core.toolify.base import AsyncBaseToolkit
15
+ from noesium.core.toolify.config import ToolkitConfig
16
+ from noesium.core.toolify.registry import register_toolkit
17
+ from noesium.core.utils.logging import get_logger
18
+
19
+ logger = get_logger(__name__)
20
+
21
+
22
+ @register_toolkit("github")
23
+ class GitHubToolkit(AsyncBaseToolkit):
24
+ """
25
+ Toolkit for GitHub repository operations.
26
+
27
+ This toolkit provides functionality for:
28
+ - Repository information retrieval
29
+ - File content access
30
+ - Repository statistics
31
+ - Issue and PR information
32
+ - User and organization data
33
+
34
+ Features:
35
+ - GitHub API v4 (GraphQL) and REST API support
36
+ - Authentication with personal access tokens
37
+ - Rate limiting awareness
38
+ - Comprehensive error handling
39
+ - Repository URL parsing and validation
40
+
41
+ Required configuration:
42
+ - GITHUB_TOKEN: GitHub personal access token for API access
43
+ """
44
+
45
+ def __init__(self, config: ToolkitConfig = None):
46
+ """
47
+ Initialize the GitHub toolkit.
48
+
49
+ Args:
50
+ config: Toolkit configuration containing GitHub token and settings
51
+ """
52
+ super().__init__(config)
53
+
54
+ # Get GitHub token from config or environment
55
+ self.github_token = self.config.config.get("GITHUB_TOKEN") or os.getenv("GITHUB_TOKEN")
56
+
57
+ if not self.github_token:
58
+ self.logger.warning("GITHUB_TOKEN not found - API rate limits will be restricted")
59
+
60
+ # API configuration
61
+ self.api_base_url = "https://api.github.com"
62
+ self.api_version = "2022-11-28"
63
+
64
+ # Request headers
65
+ self.headers = {
66
+ "Accept": "application/vnd.github.v3+json",
67
+ "X-GitHub-Api-Version": self.api_version,
68
+ "User-Agent": "noesium-github-toolkit",
69
+ }
70
+
71
+ if self.github_token:
72
+ self.headers["Authorization"] = f"Bearer {self.github_token}"
73
+
74
+ def _parse_github_url(self, github_url: str) -> Optional[Dict[str, str]]:
75
+ """
76
+ Parse GitHub URL to extract owner and repository name.
77
+
78
+ Args:
79
+ github_url: GitHub repository URL
80
+
81
+ Returns:
82
+ Dictionary with owner and repo, or None if invalid
83
+ """
84
+ try:
85
+ parsed_url = urlparse(github_url)
86
+
87
+ # Handle different GitHub URL formats
88
+ if parsed_url.netloc not in ["github.com", "www.github.com"]:
89
+ return None
90
+
91
+ path_parts = parsed_url.path.strip("/").split("/")
92
+
93
+ if len(path_parts) < 2:
94
+ return None
95
+
96
+ return {"owner": path_parts[0], "repo": path_parts[1]}
97
+
98
+ except Exception as e:
99
+ self.logger.error(f"Failed to parse GitHub URL: {e}")
100
+ return None
101
+
102
+ async def _make_api_request(self, endpoint: str, params: Optional[Dict] = None) -> Dict:
103
+ """
104
+ Make an authenticated request to the GitHub API.
105
+
106
+ Args:
107
+ endpoint: API endpoint (without base URL)
108
+ params: Query parameters
109
+
110
+ Returns:
111
+ API response as dictionary
112
+ """
113
+ url = f"{self.api_base_url}/{endpoint.lstrip('/')}"
114
+
115
+ try:
116
+ async with aiohttp.ClientSession() as session:
117
+ async with session.get(url, headers=self.headers, params=params) as response:
118
+ if response.status == 404:
119
+ return {"error": "Repository not found or not accessible"}
120
+ elif response.status == 403:
121
+ return {"error": "API rate limit exceeded or insufficient permissions"}
122
+ elif response.status != 200:
123
+ error_text = await response.text()
124
+ return {"error": f"API request failed: {response.status} - {error_text}"}
125
+
126
+ return await response.json()
127
+
128
+ except Exception as e:
129
+ self.logger.error(f"GitHub API request failed: {e}")
130
+ return {"error": f"Request failed: {str(e)}"}
131
+
132
+ async def get_repo_info(self, github_url: str) -> Dict:
133
+ """
134
+ Get comprehensive information about a GitHub repository.
135
+
136
+ This tool retrieves detailed information about a GitHub repository including
137
+ statistics, metadata, and current status. It's useful for analyzing
138
+ repositories, checking project activity, and gathering development metrics.
139
+
140
+ Args:
141
+ github_url: GitHub repository URL (e.g., "https://github.com/owner/repo")
142
+
143
+ Returns:
144
+ Dictionary containing repository information:
145
+ - name: Repository name
146
+ - owner: Repository owner/organization
147
+ - description: Repository description
148
+ - language: Primary programming language
149
+ - stars: Number of stars
150
+ - forks: Number of forks
151
+ - watchers: Number of watchers
152
+ - open_issues: Number of open issues
153
+ - license: License information
154
+ - created_at: Creation date
155
+ - updated_at: Last update date
156
+ - size: Repository size in KB
157
+ - default_branch: Default branch name
158
+ - topics: Repository topics/tags
159
+ - homepage: Project homepage URL
160
+ - archived: Whether the repository is archived
161
+ - disabled: Whether the repository is disabled
162
+
163
+ Example:
164
+ info = await get_repo_info("https://github.com/microsoft/vscode")
165
+ print(f"Stars: {info['stars']}, Language: {info['language']}")
166
+ """
167
+ self.logger.info(f"Getting repository info for: {github_url}")
168
+
169
+ # Parse the GitHub URL
170
+ parsed = self._parse_github_url(github_url)
171
+ if not parsed:
172
+ return {"error": "Invalid GitHub repository URL"}
173
+
174
+ owner, repo = parsed["owner"], parsed["repo"]
175
+
176
+ # Make API request
177
+ endpoint = f"repos/{owner}/{repo}"
178
+ repo_data = await self._make_api_request(endpoint)
179
+
180
+ if "error" in repo_data:
181
+ return repo_data
182
+
183
+ try:
184
+ # Extract license information
185
+ license_info = repo_data.get("license")
186
+ license_name = license_info.get("name") if license_info else "Not specified"
187
+
188
+ # Format the response
189
+ info = {
190
+ "name": repo_data["name"],
191
+ "owner": repo_data["owner"]["login"],
192
+ "full_name": repo_data["full_name"],
193
+ "description": repo_data.get("description") or "No description provided",
194
+ "language": repo_data.get("language") or "Not specified",
195
+ "stars": repo_data["stargazers_count"],
196
+ "forks": repo_data["forks_count"],
197
+ "watchers": repo_data["watchers_count"],
198
+ "open_issues": repo_data["open_issues_count"],
199
+ "license": license_name,
200
+ "created_at": repo_data["created_at"],
201
+ "updated_at": repo_data["updated_at"],
202
+ "pushed_at": repo_data.get("pushed_at"),
203
+ "size": repo_data["size"], # Size in KB
204
+ "default_branch": repo_data["default_branch"],
205
+ "topics": repo_data.get("topics", []),
206
+ "homepage": repo_data.get("homepage"),
207
+ "html_url": repo_data["html_url"],
208
+ "clone_url": repo_data["clone_url"],
209
+ "archived": repo_data["archived"],
210
+ "disabled": repo_data["disabled"],
211
+ "private": repo_data["private"],
212
+ "has_issues": repo_data["has_issues"],
213
+ "has_projects": repo_data["has_projects"],
214
+ "has_wiki": repo_data["has_wiki"],
215
+ "has_pages": repo_data["has_pages"],
216
+ "has_downloads": repo_data["has_downloads"],
217
+ }
218
+
219
+ self.logger.info(f"Retrieved info for {info['full_name']} ({info['stars']} stars)")
220
+ return info
221
+
222
+ except KeyError as e:
223
+ return {"error": f"Unexpected API response format: missing {e}"}
224
+
225
+ async def get_repo_contents(self, github_url: str, path: str = "") -> Dict:
226
+ """
227
+ Get the contents of a directory or file in a GitHub repository.
228
+
229
+ Args:
230
+ github_url: GitHub repository URL
231
+ path: Path within the repository (empty for root directory)
232
+
233
+ Returns:
234
+ Dictionary with file/directory information or file content
235
+ """
236
+ self.logger.info(f"Getting contents for {github_url} at path: {path}")
237
+
238
+ parsed = self._parse_github_url(github_url)
239
+ if not parsed:
240
+ return {"error": "Invalid GitHub repository URL"}
241
+
242
+ owner, repo = parsed["owner"], parsed["repo"]
243
+
244
+ # Make API request
245
+ endpoint = f"repos/{owner}/{repo}/contents/{path}"
246
+ contents_data = await self._make_api_request(endpoint)
247
+
248
+ if "error" in contents_data:
249
+ return contents_data
250
+
251
+ try:
252
+ if isinstance(contents_data, list):
253
+ # Directory contents
254
+ items = []
255
+ for item in contents_data:
256
+ items.append(
257
+ {
258
+ "name": item["name"],
259
+ "path": item["path"],
260
+ "type": item["type"], # file, dir, symlink
261
+ "size": item.get("size", 0),
262
+ "download_url": item.get("download_url"),
263
+ "html_url": item.get("html_url"),
264
+ }
265
+ )
266
+
267
+ return {"type": "directory", "path": path, "items": items, "count": len(items)}
268
+ else:
269
+ # Single file
270
+ return {
271
+ "type": "file",
272
+ "name": contents_data["name"],
273
+ "path": contents_data["path"],
274
+ "size": contents_data["size"],
275
+ "encoding": contents_data.get("encoding"),
276
+ "content": contents_data.get("content"), # Base64 encoded
277
+ "download_url": contents_data.get("download_url"),
278
+ "html_url": contents_data.get("html_url"),
279
+ }
280
+
281
+ except (KeyError, TypeError) as e:
282
+ return {"error": f"Failed to parse contents: {str(e)}"}
283
+
284
+ async def get_repo_releases(self, github_url: str, limit: int = 10) -> Dict:
285
+ """
286
+ Get recent releases for a GitHub repository.
287
+
288
+ Args:
289
+ github_url: GitHub repository URL
290
+ limit: Maximum number of releases to return
291
+
292
+ Returns:
293
+ Dictionary with release information
294
+ """
295
+ parsed = self._parse_github_url(github_url)
296
+ if not parsed:
297
+ return {"error": "Invalid GitHub repository URL"}
298
+
299
+ owner, repo = parsed["owner"], parsed["repo"]
300
+
301
+ endpoint = f"repos/{owner}/{repo}/releases"
302
+ params = {"per_page": min(limit, 100)}
303
+
304
+ releases_data = await self._make_api_request(endpoint, params)
305
+
306
+ if "error" in releases_data:
307
+ return releases_data
308
+
309
+ try:
310
+ releases = []
311
+ for release in releases_data[:limit]:
312
+ releases.append(
313
+ {
314
+ "tag_name": release["tag_name"],
315
+ "name": release.get("name") or release["tag_name"],
316
+ "body": release.get("body", ""),
317
+ "published_at": release.get("published_at"),
318
+ "created_at": release["created_at"],
319
+ "author": release["author"]["login"],
320
+ "prerelease": release["prerelease"],
321
+ "draft": release["draft"],
322
+ "html_url": release["html_url"],
323
+ "tarball_url": release["tarball_url"],
324
+ "zipball_url": release["zipball_url"],
325
+ "assets_count": len(release.get("assets", [])),
326
+ }
327
+ )
328
+
329
+ return {"releases": releases, "count": len(releases)}
330
+
331
+ except (KeyError, TypeError) as e:
332
+ return {"error": f"Failed to parse releases: {str(e)}"}
333
+
334
+ async def search_repositories(self, query: str, sort: str = "stars", limit: int = 10) -> Dict:
335
+ """
336
+ Search for GitHub repositories.
337
+
338
+ Args:
339
+ query: Search query (supports GitHub search syntax)
340
+ sort: Sort by 'stars', 'forks', 'help-wanted-issues', 'updated'
341
+ limit: Maximum number of results to return
342
+
343
+ Returns:
344
+ Dictionary with search results
345
+ """
346
+ self.logger.info(f"Searching repositories for: {query}")
347
+
348
+ endpoint = "search/repositories"
349
+ params = {"q": query, "sort": sort, "order": "desc", "per_page": min(limit, 100)}
350
+
351
+ search_data = await self._make_api_request(endpoint, params)
352
+
353
+ if "error" in search_data:
354
+ return search_data
355
+
356
+ try:
357
+ repositories = []
358
+ for repo in search_data.get("items", [])[:limit]:
359
+ repositories.append(
360
+ {
361
+ "name": repo["name"],
362
+ "full_name": repo["full_name"],
363
+ "owner": repo["owner"]["login"],
364
+ "description": repo.get("description", ""),
365
+ "language": repo.get("language"),
366
+ "stars": repo["stargazers_count"],
367
+ "forks": repo["forks_count"],
368
+ "updated_at": repo["updated_at"],
369
+ "html_url": repo["html_url"],
370
+ "topics": repo.get("topics", []),
371
+ }
372
+ )
373
+
374
+ return {
375
+ "repositories": repositories,
376
+ "total_count": search_data.get("total_count", 0),
377
+ "count": len(repositories),
378
+ }
379
+
380
+ except (KeyError, TypeError) as e:
381
+ return {"error": f"Failed to parse search results: {str(e)}"}
382
+
383
+ async def get_tools_map(self) -> Dict[str, Callable]:
384
+ """
385
+ Get the mapping of tool names to their implementation functions.
386
+
387
+ Returns:
388
+ Dictionary mapping tool names to callable functions
389
+ """
390
+ return {
391
+ "get_repo_info": self.get_repo_info,
392
+ "get_repo_contents": self.get_repo_contents,
393
+ "get_repo_releases": self.get_repo_releases,
394
+ "search_repositories": self.search_repositories,
395
+ }