airtrain 0.1.57__py3-none-any.whl → 0.1.61__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 (29) hide show
  1. airtrain/__init__.py +94 -45
  2. airtrain/__pycache__/__init__.cpython-313.pyc +0 -0
  3. airtrain/core/__pycache__/__init__.cpython-313.pyc +0 -0
  4. airtrain/core/__pycache__/schemas.cpython-313.pyc +0 -0
  5. airtrain/core/__pycache__/skills.cpython-313.pyc +0 -0
  6. airtrain/core/credentials.py +59 -13
  7. airtrain/integrations/__init__.py +8 -1
  8. airtrain/integrations/combined/list_models_factory.py +68 -40
  9. airtrain/integrations/perplexity/__init__.py +49 -0
  10. airtrain/integrations/perplexity/credentials.py +43 -0
  11. airtrain/integrations/perplexity/list_models.py +112 -0
  12. airtrain/integrations/perplexity/models_config.py +128 -0
  13. airtrain/integrations/perplexity/skills.py +279 -0
  14. airtrain/integrations/search/__init__.py +21 -0
  15. airtrain/integrations/search/exa/__init__.py +23 -0
  16. airtrain/integrations/search/exa/credentials.py +30 -0
  17. airtrain/integrations/search/exa/schemas.py +114 -0
  18. airtrain/integrations/search/exa/skills.py +115 -0
  19. airtrain/telemetry/__init__.py +4 -0
  20. airtrain/telemetry/views.py +65 -1
  21. airtrain/tools/__init__.py +9 -5
  22. airtrain/tools/command.py +248 -61
  23. airtrain/tools/search.py +450 -0
  24. airtrain/tools/testing.py +135 -0
  25. {airtrain-0.1.57.dist-info → airtrain-0.1.61.dist-info}/METADATA +1 -1
  26. {airtrain-0.1.57.dist-info → airtrain-0.1.61.dist-info}/RECORD +29 -13
  27. {airtrain-0.1.57.dist-info → airtrain-0.1.61.dist-info}/WHEEL +1 -1
  28. {airtrain-0.1.57.dist-info → airtrain-0.1.61.dist-info}/entry_points.txt +0 -0
  29. {airtrain-0.1.57.dist-info → airtrain-0.1.61.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,114 @@
1
+ """
2
+ Schemas for Exa Search API.
3
+
4
+ This module defines the input and output schemas for the Exa search API.
5
+ """
6
+
7
+ from typing import Dict, List, Optional, Any, Union, bool
8
+ from pydantic import BaseModel, Field, HttpUrl
9
+
10
+ from airtrain.core.schemas import InputSchema, OutputSchema
11
+
12
+
13
+ class ExaContentConfig(BaseModel):
14
+ """Configuration for the content to be returned by Exa search."""
15
+
16
+ text: bool = Field(default=True, description="Whether to return text content.")
17
+ extractedText: Optional[bool] = Field(
18
+ default=None, description="Whether to return extracted text content."
19
+ )
20
+ embedded: Optional[bool] = Field(
21
+ default=None, description="Whether to return embedded content."
22
+ )
23
+ links: Optional[bool] = Field(
24
+ default=None, description="Whether to return links from the content."
25
+ )
26
+ screenshot: Optional[bool] = Field(
27
+ default=None, description="Whether to return screenshots of the content."
28
+ )
29
+ highlighted: Optional[bool] = Field(
30
+ default=None, description="Whether to return highlighted text."
31
+ )
32
+
33
+
34
+ class ExaSearchInputSchema(InputSchema):
35
+ """Input schema for Exa search API."""
36
+
37
+ query: str = Field(description="The search query to execute.")
38
+ numResults: Optional[int] = Field(
39
+ default=None, description="Number of results to return."
40
+ )
41
+ contents: Optional[ExaContentConfig] = Field(
42
+ default_factory=ExaContentConfig,
43
+ description="Configuration for the content to be returned.",
44
+ )
45
+ highlights: Optional[dict] = Field(
46
+ default=None, description="Highlighting configuration for search results."
47
+ )
48
+ useAutoprompt: Optional[bool] = Field(
49
+ default=None, description="Whether to use autoprompt for the search."
50
+ )
51
+ type: Optional[str] = Field(default=None, description="Type of search to perform.")
52
+ includeDomains: Optional[List[str]] = Field(
53
+ default=None, description="List of domains to include in the search."
54
+ )
55
+ excludeDomains: Optional[List[str]] = Field(
56
+ default=None, description="List of domains to exclude from the search."
57
+ )
58
+
59
+
60
+ class ExaModerationConfig(BaseModel):
61
+ """Moderation configuration returned in search results."""
62
+
63
+ llamaguardS1: Optional[bool] = None
64
+ llamaguardS3: Optional[bool] = None
65
+ llamaguardS4: Optional[bool] = None
66
+ llamaguardS12: Optional[bool] = None
67
+ domainBlacklisted: Optional[bool] = None
68
+ domainBlacklistedMedia: Optional[bool] = None
69
+
70
+
71
+ class ExaHighlight(BaseModel):
72
+ """Highlight information for a search result."""
73
+
74
+ text: str
75
+ score: float
76
+
77
+
78
+ class ExaSearchResult(BaseModel):
79
+ """Individual search result from Exa."""
80
+
81
+ id: str
82
+ url: str
83
+ title: Optional[str] = None
84
+ text: Optional[str] = None
85
+ extractedText: Optional[str] = None
86
+ embedded: Optional[Dict[str, Any]] = None
87
+ score: float
88
+ published: Optional[str] = None
89
+ author: Optional[str] = None
90
+ highlights: Optional[List[ExaHighlight]] = None
91
+ robotsAllowed: Optional[bool] = None
92
+ moderationConfig: Optional[ExaModerationConfig] = None
93
+ urls: Optional[List[str]] = None
94
+
95
+
96
+ class ExaCostDetails(BaseModel):
97
+ """Cost details for an Exa search request."""
98
+
99
+ total: float
100
+ search: Dict[str, float]
101
+ contents: Dict[str, float]
102
+
103
+
104
+ class ExaSearchOutputSchema(OutputSchema):
105
+ """Output schema for Exa search API."""
106
+
107
+ results: List[ExaSearchResult] = Field(description="List of search results.")
108
+ query: str = Field(description="The original search query.")
109
+ autopromptString: Optional[str] = Field(
110
+ default=None, description="Autoprompt string used for the search if enabled."
111
+ )
112
+ costDollars: Optional[ExaCostDetails] = Field(
113
+ default=None, description="Cost details for the search request."
114
+ )
@@ -0,0 +1,115 @@
1
+ """
2
+ Skills for Exa Search API.
3
+
4
+ This module provides skills for using the Exa search API.
5
+ """
6
+
7
+ import json
8
+ import logging
9
+ import httpx
10
+ from typing import Optional, Dict, Any, List, cast
11
+
12
+ from pydantic import ValidationError
13
+
14
+ from airtrain.core.skills import Skill
15
+ from airtrain.core.errors import ProcessingError
16
+ from .credentials import ExaCredentials
17
+ from .schemas import ExaSearchInputSchema, ExaSearchOutputSchema, ExaSearchResult
18
+
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class ExaSearchSkill(Skill[ExaSearchInputSchema, ExaSearchOutputSchema]):
24
+ """Skill for searching the web using the Exa search API."""
25
+
26
+ input_schema = ExaSearchInputSchema
27
+ output_schema = ExaSearchOutputSchema
28
+
29
+ EXA_API_ENDPOINT = "https://api.exa.ai/search"
30
+
31
+ def __init__(
32
+ self,
33
+ credentials: ExaCredentials,
34
+ timeout: float = 60.0,
35
+ max_retries: int = 3,
36
+ **kwargs,
37
+ ):
38
+ """
39
+ Initialize the Exa search skill.
40
+
41
+ Args:
42
+ credentials: Credentials for accessing the Exa API
43
+ timeout: Timeout for API requests in seconds
44
+ max_retries: Maximum number of retries for failed requests
45
+ """
46
+ super().__init__(**kwargs)
47
+ self.credentials = credentials
48
+ self.timeout = timeout
49
+ self.max_retries = max_retries
50
+
51
+ async def process(self, input_data: ExaSearchInputSchema) -> ExaSearchOutputSchema:
52
+ """
53
+ Process a search request using the Exa API.
54
+
55
+ Args:
56
+ input_data: Search input parameters
57
+
58
+ Returns:
59
+ Search results from Exa
60
+
61
+ Raises:
62
+ ProcessingError: If there's an issue with the API request
63
+ """
64
+ try:
65
+ # Prepare request payload
66
+ payload = input_data.model_dump(exclude_none=True)
67
+
68
+ # Build request headers
69
+ headers = {
70
+ "content-type": "application/json",
71
+ "Authorization": f"Bearer {self.credentials.api_key.get_secret_value()}",
72
+ }
73
+
74
+ # Make the API request
75
+ async with httpx.AsyncClient() as client:
76
+ response = await client.post(
77
+ self.EXA_API_ENDPOINT,
78
+ headers=headers,
79
+ json=payload,
80
+ timeout=self.timeout,
81
+ )
82
+
83
+ # Check for successful response
84
+ if response.status_code == 200:
85
+ result_data = response.json()
86
+
87
+ # Construct the output schema
88
+ output = ExaSearchOutputSchema(
89
+ results=result_data.get("results", []),
90
+ query=input_data.query,
91
+ autopromptString=result_data.get("autopromptString"),
92
+ costDollars=result_data.get("costDollars"),
93
+ )
94
+
95
+ return output
96
+ else:
97
+ # Handle error responses
98
+ error_message = f"Exa API returned status code {response.status_code}: {response.text}"
99
+ logger.error(error_message)
100
+ raise ProcessingError(error_message)
101
+
102
+ except httpx.TimeoutException:
103
+ error_message = f"Timeout while querying Exa API (timeout={self.timeout}s)"
104
+ logger.error(error_message)
105
+ raise ProcessingError(error_message)
106
+
107
+ except ValidationError as e:
108
+ error_message = f"Schema validation error: {str(e)}"
109
+ logger.error(error_message)
110
+ raise ProcessingError(error_message)
111
+
112
+ except Exception as e:
113
+ error_message = f"Unexpected error while querying Exa API: {str(e)}"
114
+ logger.error(error_message)
115
+ raise ProcessingError(error_message)
@@ -16,6 +16,8 @@ from airtrain.telemetry.views import (
16
16
  UserFeedbackTelemetryEvent,
17
17
  SkillInitTelemetryEvent,
18
18
  SkillProcessTelemetryEvent,
19
+ PackageInstallTelemetryEvent,
20
+ PackageImportTelemetryEvent,
19
21
  )
20
22
 
21
23
  __all__ = [
@@ -28,6 +30,8 @@ __all__ = [
28
30
  "UserFeedbackTelemetryEvent",
29
31
  "SkillInitTelemetryEvent",
30
32
  "SkillProcessTelemetryEvent",
33
+ "PackageInstallTelemetryEvent",
34
+ "PackageImportTelemetryEvent",
31
35
  ]
32
36
 
33
37
  # Create a singleton instance for easy import
@@ -170,4 +170,68 @@ class SkillProcessTelemetryEvent(BaseTelemetryEvent):
170
170
  input_data: Optional[Dict[str, Any]] = None
171
171
  duration_seconds: float = 0.0
172
172
  error: Optional[str] = None
173
- name: str = 'skill_process'
173
+ name: str = 'skill_process'
174
+
175
+
176
+ @dataclass
177
+ class PackageInstallTelemetryEvent(BaseTelemetryEvent):
178
+ """Event type to capture package installation"""
179
+ version: str
180
+ python_version: str
181
+ install_method: Optional[str] = None # pip, conda, source, etc.
182
+ platform: Optional[str] = None # Operating system
183
+ dependencies: Optional[Dict[str, str]] = None # Installed dependencies
184
+ name: str = 'package_install'
185
+
186
+ def __post_init__(self):
187
+ # Collect platform info if not provided
188
+ if self.platform is None:
189
+ import platform
190
+ self.platform = platform.platform()
191
+
192
+ # Collect dependency info if not provided
193
+ if self.dependencies is None:
194
+ # Try to get installed package versions for key dependencies
195
+ self.dependencies = {}
196
+ import importlib.metadata
197
+ try:
198
+ for package in ["openai", "anthropic", "groq", "together"]:
199
+ try:
200
+ self.dependencies[package] = importlib.metadata.version(package)
201
+ except importlib.metadata.PackageNotFoundError:
202
+ pass
203
+ except (ImportError, Exception):
204
+ pass
205
+
206
+
207
+ @dataclass
208
+ class PackageImportTelemetryEvent(BaseTelemetryEvent):
209
+ """Event type to capture package import"""
210
+ version: str
211
+ python_version: str
212
+ import_context: Optional[str] = None # Information about what imported the package
213
+ platform: Optional[str] = None # Operating system
214
+ name: str = 'package_import'
215
+
216
+ def __post_init__(self):
217
+ # Collect platform info if not provided
218
+ if self.platform is None:
219
+ import platform
220
+ self.platform = platform.platform()
221
+
222
+ # Try to get import context from traceback
223
+ if self.import_context is None:
224
+ try:
225
+ import inspect
226
+ frames = inspect.stack()
227
+ # Skip the first few frames which are inside our code
228
+ # Look for the first frame that's not in our module
229
+ import_frames = []
230
+ for frame in frames[3:10]: # Skip first 3, take up to 7 more
231
+ module = frame.frame.f_globals.get('__name__', '')
232
+ if not module.startswith('airtrain'):
233
+ import_frames.append(f"{module}:{frame.function}")
234
+ if import_frames:
235
+ self.import_context = " -> ".join(import_frames)
236
+ except Exception:
237
+ pass
@@ -12,30 +12,34 @@ from .registry import (
12
12
  ToolFactory,
13
13
  ToolValidationError,
14
14
  register_tool,
15
- execute_tool_call
15
+ execute_tool_call,
16
16
  )
17
17
 
18
18
  # Import standard tools
19
19
  from .filesystem import ListDirectoryTool, DirectoryTreeTool
20
20
  from .network import ApiCallTool
21
- from .command import ExecuteCommandTool, FindFilesTool
21
+ from .command import ExecuteCommandTool, FindFilesTool, TerminalNavigationTool
22
+ from .search import SearchTermTool, WebSearchTool
23
+ from .testing import RunPytestTool
22
24
 
23
25
  __all__ = [
24
26
  # Base classes
25
27
  "BaseTool",
26
28
  "StatelessTool",
27
29
  "StatefulTool",
28
-
29
30
  # Registry components
30
31
  "ToolFactory",
31
32
  "ToolValidationError",
32
33
  "register_tool",
33
34
  "execute_tool_call",
34
-
35
35
  # Standard tools
36
36
  "ListDirectoryTool",
37
37
  "DirectoryTreeTool",
38
38
  "ApiCallTool",
39
39
  "ExecuteCommandTool",
40
40
  "FindFilesTool",
41
- ]
41
+ "TerminalNavigationTool",
42
+ "SearchTermTool",
43
+ "WebSearchTool",
44
+ "RunPytestTool",
45
+ ]