airtrain 0.1.43__py3-none-any.whl → 0.1.45__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 +1 -1
  2. airtrain/integrations/__init__.py +11 -0
  3. airtrain/integrations/anthropic/__init__.py +10 -0
  4. airtrain/integrations/anthropic/list_models.py +110 -0
  5. airtrain/integrations/anthropic/skills.py +1 -1
  6. airtrain/integrations/aws/skills.py +1 -1
  7. airtrain/integrations/cerebras/skills.py +1 -1
  8. airtrain/integrations/combined/__init__.py +21 -0
  9. airtrain/integrations/combined/groq_fireworks_skills.py +126 -0
  10. airtrain/integrations/combined/list_models_factory.py +172 -0
  11. airtrain/integrations/fireworks/completion_skills.py +1 -1
  12. airtrain/integrations/fireworks/conversation_manager.py +1 -1
  13. airtrain/integrations/fireworks/requests_skills.py +1 -1
  14. airtrain/integrations/fireworks/skills.py +1 -1
  15. airtrain/integrations/fireworks/structured_completion_skills.py +1 -1
  16. airtrain/integrations/fireworks/structured_requests_skills.py +1 -1
  17. airtrain/integrations/fireworks/structured_skills.py +1 -1
  18. airtrain/integrations/groq/skills.py +1 -1
  19. airtrain/integrations/ollama/skills.py +1 -1
  20. airtrain/integrations/openai/__init__.py +12 -0
  21. airtrain/integrations/openai/chinese_assistant.py +1 -1
  22. airtrain/integrations/openai/list_models.py +112 -0
  23. airtrain/integrations/openai/skills.py +1 -1
  24. airtrain/integrations/together/models_config.py +2 -2
  25. {airtrain-0.1.43.dist-info → airtrain-0.1.45.dist-info}/METADATA +2 -2
  26. {airtrain-0.1.43.dist-info → airtrain-0.1.45.dist-info}/RECORD +29 -24
  27. {airtrain-0.1.43.dist-info → airtrain-0.1.45.dist-info}/WHEEL +1 -1
  28. {airtrain-0.1.43.dist-info → airtrain-0.1.45.dist-info}/entry_points.txt +0 -0
  29. {airtrain-0.1.43.dist-info → airtrain-0.1.45.dist-info}/top_level.txt +0 -0
airtrain/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """Airtrain - A platform for building and deploying AI agents with structured skills"""
2
2
 
3
- __version__ = "0.1.43"
3
+ __version__ = "0.1.45"
4
4
 
5
5
  # Core imports
6
6
  from .core.skills import Skill, ProcessingError
@@ -26,6 +26,13 @@ from .cerebras.skills import CerebrasChatSkill
26
26
  from .openai.models_config import OPENAI_MODELS, OpenAIModelConfig
27
27
  from .anthropic.models_config import ANTHROPIC_MODELS, AnthropicModelConfig
28
28
 
29
+ # Combined modules
30
+ from .combined.list_models_factory import (
31
+ ListModelsSkillFactory,
32
+ GenericListModelsInput,
33
+ GenericListModelsOutput
34
+ )
35
+
29
36
  __all__ = [
30
37
  # Credentials
31
38
  "OpenAICredentials",
@@ -53,4 +60,8 @@ __all__ = [
53
60
  "OpenAIModelConfig",
54
61
  "ANTHROPIC_MODELS",
55
62
  "AnthropicModelConfig",
63
+ # Combined modules
64
+ "ListModelsSkillFactory",
65
+ "GenericListModelsInput",
66
+ "GenericListModelsOutput",
56
67
  ]
@@ -9,6 +9,12 @@ from .models_config import (
9
9
  get_default_model,
10
10
  calculate_cost,
11
11
  )
12
+ from .list_models import (
13
+ AnthropicListModelsSkill,
14
+ AnthropicListModelsInput,
15
+ AnthropicListModelsOutput,
16
+ AnthropicModel,
17
+ )
12
18
 
13
19
  __all__ = [
14
20
  "AnthropicCredentials",
@@ -20,4 +26,8 @@ __all__ = [
20
26
  "get_model_config",
21
27
  "get_default_model",
22
28
  "calculate_cost",
29
+ "AnthropicListModelsSkill",
30
+ "AnthropicListModelsInput",
31
+ "AnthropicListModelsOutput",
32
+ "AnthropicModel",
23
33
  ]
@@ -0,0 +1,110 @@
1
+ from typing import Optional, List, Dict, Any
2
+ from pydantic import Field
3
+
4
+ from airtrain.core.skills import Skill, ProcessingError
5
+ from airtrain.core.schemas import InputSchema, OutputSchema
6
+ from .credentials import AnthropicCredentials
7
+ from .models_config import ANTHROPIC_MODELS, AnthropicModelConfig
8
+
9
+
10
+ class AnthropicModel:
11
+ """Class to represent an Anthropic model."""
12
+
13
+ def __init__(self, model_id: str, config: AnthropicModelConfig):
14
+ """Initialize the Anthropic model."""
15
+ self.id = model_id
16
+ self.display_name = config.display_name
17
+ self.base_model = config.base_model
18
+ self.input_price = config.input_price
19
+ self.cached_write_price = config.cached_write_price
20
+ self.cached_read_price = config.cached_read_price
21
+ self.output_price = config.output_price
22
+
23
+ def dict(self, exclude_none=False):
24
+ """Convert the model to a dictionary."""
25
+ result = {
26
+ "id": self.id,
27
+ "display_name": self.display_name,
28
+ "base_model": self.base_model,
29
+ "input_price": float(self.input_price),
30
+ "output_price": float(self.output_price),
31
+ }
32
+
33
+ if self.cached_write_price is not None:
34
+ result["cached_write_price"] = float(self.cached_write_price)
35
+ elif not exclude_none:
36
+ result["cached_write_price"] = None
37
+
38
+ if self.cached_read_price is not None:
39
+ result["cached_read_price"] = float(self.cached_read_price)
40
+ elif not exclude_none:
41
+ result["cached_read_price"] = None
42
+
43
+ return result
44
+
45
+
46
+ class AnthropicListModelsInput(InputSchema):
47
+ """Schema for Anthropic list models input"""
48
+
49
+ api_models_only: bool = Field(
50
+ default=False,
51
+ description=(
52
+ "If True, fetch models from the API only. If False, use local config."
53
+ )
54
+ )
55
+
56
+
57
+ class AnthropicListModelsOutput(OutputSchema):
58
+ """Schema for Anthropic list models output"""
59
+
60
+ models: List[Dict[str, Any]] = Field(
61
+ default_factory=list,
62
+ description="List of Anthropic models"
63
+ )
64
+
65
+
66
+ class AnthropicListModelsSkill(
67
+ Skill[AnthropicListModelsInput, AnthropicListModelsOutput]
68
+ ):
69
+ """Skill for listing Anthropic models"""
70
+
71
+ input_schema = AnthropicListModelsInput
72
+ output_schema = AnthropicListModelsOutput
73
+
74
+ def __init__(self, credentials: Optional[AnthropicCredentials] = None):
75
+ """Initialize the skill with optional credentials"""
76
+ super().__init__()
77
+ self.credentials = credentials
78
+
79
+ def process(
80
+ self, input_data: AnthropicListModelsInput
81
+ ) -> AnthropicListModelsOutput:
82
+ """Process the input and return a list of models."""
83
+ try:
84
+ models = []
85
+
86
+ if input_data.api_models_only:
87
+ # Fetch models from Anthropic API
88
+ # Require credentials if using API models
89
+ if not self.credentials:
90
+ raise ProcessingError(
91
+ "Anthropic credentials required for API models"
92
+ )
93
+
94
+ # Note: Anthropic doesn't have a public models list endpoint
95
+ # We'll raise an error instead
96
+ raise ProcessingError(
97
+ "Anthropic API does not provide a models list endpoint. "
98
+ "Use api_models_only=False to list models from local config."
99
+ )
100
+ else:
101
+ # Use local model config - no credentials needed
102
+ for model_id, config in ANTHROPIC_MODELS.items():
103
+ model = AnthropicModel(model_id, config)
104
+ models.append(model.dict())
105
+
106
+ # Return the output
107
+ return AnthropicListModelsOutput(models=models)
108
+
109
+ except Exception as e:
110
+ raise ProcessingError(f"Failed to list Anthropic models: {str(e)}")
@@ -26,7 +26,7 @@ class AnthropicInput(InputSchema):
26
26
  default="claude-3-opus-20240229", description="Anthropic model to use"
27
27
  )
28
28
  max_tokens: Optional[int] = Field(
29
- default=1024, description="Maximum tokens in response"
29
+ default=131072, description="Maximum tokens in response"
30
30
  )
31
31
  temperature: float = Field(
32
32
  default=0.7, description="Temperature for response generation", ge=0, le=1
@@ -21,7 +21,7 @@ class AWSBedrockInput(InputSchema):
21
21
  default="anthropic.claude-3-sonnet-20240229-v1:0",
22
22
  description="AWS Bedrock model to use",
23
23
  )
24
- max_tokens: int = Field(default=1024, description="Maximum tokens in response")
24
+ max_tokens: int = Field(default=131072, description="Maximum tokens in response")
25
25
  temperature: float = Field(
26
26
  default=0.7, description="Temperature for response generation", ge=0, le=1
27
27
  )
@@ -22,7 +22,7 @@ class CerebrasInput(InputSchema):
22
22
  )
23
23
  model: str = Field(default="llama3.1-8b", description="Cerebras model to use")
24
24
  max_tokens: Optional[int] = Field(
25
- default=1024, description="Maximum tokens in response"
25
+ default=131072, description="Maximum tokens in response"
26
26
  )
27
27
  temperature: float = Field(
28
28
  default=0.7, description="Temperature for response generation", ge=0, le=1
@@ -0,0 +1,21 @@
1
+ """Combined integration modules for Airtrain"""
2
+
3
+ from .groq_fireworks_skills import (
4
+ GroqFireworksSkill,
5
+ GroqFireworksInput,
6
+ GroqFireworksOutput
7
+ )
8
+ from .list_models_factory import (
9
+ ListModelsSkillFactory,
10
+ GenericListModelsInput,
11
+ GenericListModelsOutput
12
+ )
13
+
14
+ __all__ = [
15
+ "GroqFireworksSkill",
16
+ "GroqFireworksInput",
17
+ "GroqFireworksOutput",
18
+ "ListModelsSkillFactory",
19
+ "GenericListModelsInput",
20
+ "GenericListModelsOutput"
21
+ ]
@@ -0,0 +1,126 @@
1
+ from typing import Optional, Dict, Any, List
2
+ from pydantic import Field
3
+ import requests
4
+ from groq import Groq
5
+
6
+ from airtrain.core.skills import Skill, ProcessingError
7
+ from airtrain.core.schemas import InputSchema, OutputSchema
8
+ from airtrain.integrations.fireworks.completion_skills import (
9
+ FireworksCompletionSkill,
10
+ FireworksCompletionInput,
11
+ )
12
+
13
+
14
+ class GroqFireworksInput(InputSchema):
15
+ """Schema for combined Groq and Fireworks input"""
16
+
17
+ user_input: str = Field(..., description="User's input text")
18
+ groq_model: str = Field(
19
+ default="mixtral-8x7b-32768", description="Groq model to use"
20
+ )
21
+ fireworks_model: str = Field(
22
+ default="accounts/fireworks/models/deepseek-r1",
23
+ description="Fireworks model to use",
24
+ )
25
+ temperature: float = Field(
26
+ default=0.7, description="Temperature for response generation"
27
+ )
28
+ max_tokens: int = Field(default=131072, description="Maximum tokens in response")
29
+
30
+
31
+ class GroqFireworksOutput(OutputSchema):
32
+ """Schema for combined Groq and Fireworks output"""
33
+
34
+ combined_response: str
35
+ groq_response: str
36
+ fireworks_response: str
37
+ used_models: Dict[str, str]
38
+ usage: Dict[str, Dict[str, int]]
39
+
40
+
41
+ class GroqFireworksSkill(Skill[GroqFireworksInput, GroqFireworksOutput]):
42
+ """Skill combining Groq and Fireworks responses"""
43
+
44
+ input_schema = GroqFireworksInput
45
+ output_schema = GroqFireworksOutput
46
+
47
+ def __init__(
48
+ self,
49
+ groq_api_key: Optional[str] = None,
50
+ fireworks_skill: Optional[FireworksCompletionSkill] = None,
51
+ ):
52
+ """Initialize the skill with optional API keys"""
53
+ super().__init__()
54
+ self.groq_client = Groq(api_key=groq_api_key)
55
+ self.fireworks_skill = fireworks_skill or FireworksCompletionSkill()
56
+
57
+ def _get_groq_response(self, input_data: GroqFireworksInput) -> Dict[str, Any]:
58
+ """Get response from Groq"""
59
+ try:
60
+ completion = self.groq_client.chat.completions.create(
61
+ model=input_data.groq_model,
62
+ messages=[{"role": "user", "content": input_data.user_input}],
63
+ temperature=input_data.temperature,
64
+ max_tokens=input_data.max_tokens,
65
+ )
66
+ return {
67
+ "response": completion.choices[0].message.content,
68
+ "usage": completion.usage.model_dump(),
69
+ }
70
+ except Exception as e:
71
+ raise ProcessingError(f"Groq request failed: {str(e)}")
72
+
73
+ def _get_fireworks_response(
74
+ self, groq_response: str, input_data: GroqFireworksInput
75
+ ) -> Dict[str, Any]:
76
+ """Get response from Fireworks"""
77
+ try:
78
+ formatted_prompt = (
79
+ f"<USER>{input_data.user_input}</USER>\n<ASSISTANT>{groq_response}"
80
+ )
81
+
82
+ fireworks_input = FireworksCompletionInput(
83
+ prompt=formatted_prompt,
84
+ model=input_data.fireworks_model,
85
+ temperature=input_data.temperature,
86
+ max_tokens=input_data.max_tokens,
87
+ )
88
+
89
+ result = self.fireworks_skill.process(fireworks_input)
90
+ return {"response": result.response, "usage": result.usage}
91
+ except Exception as e:
92
+ raise ProcessingError(f"Fireworks request failed: {str(e)}")
93
+
94
+ def process(self, input_data: GroqFireworksInput) -> GroqFireworksOutput:
95
+ """Process the input using both Groq and Fireworks"""
96
+ try:
97
+ # Get Groq response
98
+ groq_result = self._get_groq_response(input_data)
99
+
100
+ # Get Fireworks response
101
+ fireworks_result = self._get_fireworks_response(
102
+ groq_result["response"], input_data
103
+ )
104
+
105
+ # Combine responses in the required format
106
+ combined_response = (
107
+ f"<USER>{input_data.user_input}</USER>\n"
108
+ f"<ASSISTANT>{groq_result['response']} {fireworks_result['response']}"
109
+ )
110
+
111
+ return GroqFireworksOutput(
112
+ combined_response=combined_response,
113
+ groq_response=groq_result["response"],
114
+ fireworks_response=fireworks_result["response"],
115
+ used_models={
116
+ "groq": input_data.groq_model,
117
+ "fireworks": input_data.fireworks_model,
118
+ },
119
+ usage={
120
+ "groq": groq_result["usage"],
121
+ "fireworks": fireworks_result["usage"],
122
+ },
123
+ )
124
+
125
+ except Exception as e:
126
+ raise ProcessingError(f"Combined processing failed: {str(e)}")
@@ -0,0 +1,172 @@
1
+ from typing import Optional, Dict, Any, List
2
+ from pydantic import Field
3
+
4
+ from airtrain.core.skills import Skill, ProcessingError
5
+ from airtrain.core.schemas import InputSchema, OutputSchema
6
+ from airtrain.core.credentials import BaseCredentials
7
+
8
+ # Import existing list models skills
9
+ from airtrain.integrations.openai.list_models import OpenAIListModelsSkill
10
+ from airtrain.integrations.anthropic.list_models import AnthropicListModelsSkill
11
+ from airtrain.integrations.together.list_models import TogetherListModelsSkill
12
+ from airtrain.integrations.fireworks.list_models import FireworksListModelsSkill
13
+
14
+ # Import credentials
15
+ from airtrain.integrations.groq.credentials import GroqCredentials
16
+ from airtrain.integrations.cerebras.credentials import CerebrasCredentials
17
+ from airtrain.integrations.sambanova.credentials import SambanovaCredentials
18
+
19
+
20
+ # Generic list models input schema
21
+ class GenericListModelsInput(InputSchema):
22
+ """Generic schema for listing models from any provider"""
23
+
24
+ api_models_only: bool = Field(
25
+ default=False,
26
+ description=(
27
+ "If True, fetch models from the API only. If False, use local config."
28
+ )
29
+ )
30
+
31
+
32
+ # Generic list models output schema
33
+ class GenericListModelsOutput(OutputSchema):
34
+ """Generic schema for list models output from any provider"""
35
+
36
+ models: List[Dict[str, Any]] = Field(
37
+ default_factory=list,
38
+ description="List of models"
39
+ )
40
+ provider: str = Field(
41
+ ...,
42
+ description="Provider name"
43
+ )
44
+
45
+
46
+ # Base class for stub implementations
47
+ class BaseListModelsSkill(Skill[GenericListModelsInput, GenericListModelsOutput]):
48
+ """Base skill for listing models"""
49
+
50
+ input_schema = GenericListModelsInput
51
+ output_schema = GenericListModelsOutput
52
+
53
+ def __init__(self, provider: str, credentials: Optional[BaseCredentials] = None):
54
+ """Initialize the skill with provider name and optional credentials"""
55
+ super().__init__()
56
+ self.provider = provider
57
+ self.credentials = credentials
58
+
59
+ def get_models(self) -> List[Dict[str, Any]]:
60
+ """Return list of models. To be implemented by subclasses."""
61
+ raise NotImplementedError("Subclasses must implement get_models()")
62
+
63
+ def process(self, input_data: GenericListModelsInput) -> GenericListModelsOutput:
64
+ """Process the input and return a list of models."""
65
+ try:
66
+ models = self.get_models()
67
+ return GenericListModelsOutput(models=models, provider=self.provider)
68
+ except Exception as e:
69
+ raise ProcessingError(f"Failed to list {self.provider} models: {str(e)}")
70
+
71
+
72
+ # Groq implementation
73
+ class GroqListModelsSkill(BaseListModelsSkill):
74
+ """Skill for listing Groq models"""
75
+
76
+ def __init__(self, credentials: Optional[GroqCredentials] = None):
77
+ """Initialize the skill with optional credentials"""
78
+ super().__init__(provider="groq", credentials=credentials)
79
+
80
+ def get_models(self) -> List[Dict[str, Any]]:
81
+ """Return list of Groq models."""
82
+ # Default Groq models from trmx_agent config
83
+ models = [
84
+ {"id": "llama-3-70b-8192", "display_name": "Llama 3 70B (8K)"},
85
+ {"id": "mixtral-8x7b-32768", "display_name": "Mixtral 8x7B (32K)"},
86
+ {"id": "gemma-7b-it", "display_name": "Gemma 7B Instruct"}
87
+ ]
88
+ return models
89
+
90
+
91
+ # Cerebras implementation
92
+ class CerebrasListModelsSkill(BaseListModelsSkill):
93
+ """Skill for listing Cerebras models"""
94
+
95
+ def __init__(self, credentials: Optional[CerebrasCredentials] = None):
96
+ """Initialize the skill with optional credentials"""
97
+ super().__init__(provider="cerebras", credentials=credentials)
98
+
99
+ def get_models(self) -> List[Dict[str, Any]]:
100
+ """Return list of Cerebras models."""
101
+ # Default Cerebras models from trmx_agent config
102
+ models = [
103
+ {"id": "cerebras/Cerebras-GPT-13B-v0.1", "display_name": "Cerebras GPT 13B v0.1"},
104
+ {"id": "cerebras/Cerebras-GPT-111M-v0.9", "display_name": "Cerebras GPT 111M v0.9"},
105
+ {"id": "cerebras/Cerebras-GPT-590M-v0.7", "display_name": "Cerebras GPT 590M v0.7"}
106
+ ]
107
+ return models
108
+
109
+
110
+ # Sambanova implementation
111
+ class SambanovaListModelsSkill(BaseListModelsSkill):
112
+ """Skill for listing Sambanova models"""
113
+
114
+ def __init__(self, credentials: Optional[SambanovaCredentials] = None):
115
+ """Initialize the skill with optional credentials"""
116
+ super().__init__(provider="sambanova", credentials=credentials)
117
+
118
+ def get_models(self) -> List[Dict[str, Any]]:
119
+ """Return list of Sambanova models."""
120
+ # Limited Sambanova model information
121
+ models = [
122
+ {"id": "sambanova/samba-1", "display_name": "Samba-1"},
123
+ {"id": "sambanova/samba-2", "display_name": "Samba-2"}
124
+ ]
125
+ return models
126
+
127
+
128
+ # Factory class
129
+ class ListModelsSkillFactory:
130
+ """Factory for creating list models skills for different providers"""
131
+
132
+ # Map provider names to their corresponding list models skills
133
+ _PROVIDER_MAP = {
134
+ "openai": OpenAIListModelsSkill,
135
+ "anthropic": AnthropicListModelsSkill,
136
+ "together": TogetherListModelsSkill,
137
+ "fireworks": FireworksListModelsSkill,
138
+ "groq": GroqListModelsSkill,
139
+ "cerebras": CerebrasListModelsSkill,
140
+ "sambanova": SambanovaListModelsSkill
141
+ }
142
+
143
+ @classmethod
144
+ def get_skill(cls, provider: str, credentials=None):
145
+ """Return a list models skill for the specified provider
146
+
147
+ Args:
148
+ provider (str): The provider name (case-insensitive)
149
+ credentials: Optional credentials for the provider
150
+
151
+ Returns:
152
+ A ListModelsSkill instance for the specified provider
153
+
154
+ Raises:
155
+ ValueError: If the provider is not supported
156
+ """
157
+ provider = provider.lower()
158
+
159
+ if provider not in cls._PROVIDER_MAP:
160
+ supported = ", ".join(cls.get_supported_providers())
161
+ raise ValueError(
162
+ f"Unsupported provider: {provider}. "
163
+ f"Supported providers are: {supported}"
164
+ )
165
+
166
+ skill_class = cls._PROVIDER_MAP[provider]
167
+ return skill_class(credentials=credentials)
168
+
169
+ @classmethod
170
+ def get_supported_providers(cls):
171
+ """Return a list of supported provider names"""
172
+ return list(cls._PROVIDER_MAP.keys())
@@ -17,7 +17,7 @@ class FireworksCompletionInput(InputSchema):
17
17
  default="accounts/fireworks/models/deepseek-r1",
18
18
  description="Fireworks AI model to use",
19
19
  )
20
- max_tokens: int = Field(default=4096, description="Maximum tokens in response")
20
+ max_tokens: int = Field(default=131072, description="Maximum tokens in response")
21
21
  temperature: float = Field(
22
22
  default=0.7, description="Temperature for response generation", ge=0, le=1
23
23
  )
@@ -20,7 +20,7 @@ class ConversationState(BaseModel):
20
20
  description="Model being used for the conversation",
21
21
  )
22
22
  temperature: float = Field(default=0.7, description="Temperature setting")
23
- max_tokens: Optional[int] = Field(default=None, description="Max tokens setting")
23
+ max_tokens: Optional[int] = Field(default=131072, description="Max tokens setting")
24
24
 
25
25
 
26
26
  class FireworksConversationManager:
@@ -29,7 +29,7 @@ class FireworksRequestInput(InputSchema):
29
29
  temperature: float = Field(
30
30
  default=0.7, description="Temperature for response generation", ge=0, le=1
31
31
  )
32
- max_tokens: int = Field(default=4096, description="Maximum tokens in response")
32
+ max_tokens: int = Field(default=131072, description="Maximum tokens in response")
33
33
  top_p: float = Field(
34
34
  default=1.0, description="Top p sampling parameter", ge=0, le=1
35
35
  )
@@ -27,7 +27,7 @@ class FireworksInput(InputSchema):
27
27
  default=0.7, description="Temperature for response generation", ge=0, le=1
28
28
  )
29
29
  max_tokens: Optional[int] = Field(
30
- default=None, description="Maximum tokens in response"
30
+ default=131072, description="Maximum tokens in response"
31
31
  )
32
32
  context_length_exceeded_behavior: str = Field(
33
33
  default="truncate", description="Behavior when context length is exceeded"
@@ -21,7 +21,7 @@ class FireworksStructuredCompletionInput(InputSchema):
21
21
  temperature: float = Field(
22
22
  default=0.7, description="Temperature for response generation", ge=0, le=1
23
23
  )
24
- max_tokens: int = Field(default=4096, description="Maximum tokens in response")
24
+ max_tokens: int = Field(default=131072, description="Maximum tokens in response")
25
25
  response_model: Type[ResponseT]
26
26
  stream: bool = Field(
27
27
  default=False,
@@ -31,7 +31,7 @@ class FireworksStructuredRequestInput(InputSchema):
31
31
  temperature: float = Field(
32
32
  default=0.7, description="Temperature for response generation", ge=0, le=1
33
33
  )
34
- max_tokens: int = Field(default=4096, description="Maximum tokens in response")
34
+ max_tokens: int = Field(default=131072, description="Maximum tokens in response")
35
35
  response_model: Type[ResponseT]
36
36
  stream: bool = Field(
37
37
  default=False, description="Whether to stream the response token by token"
@@ -17,7 +17,7 @@ class FireworksParserInput(InputSchema):
17
17
  system_prompt: str = "You are a helpful assistant that provides structured data."
18
18
  model: str = "accounts/fireworks/models/deepseek-r1"
19
19
  temperature: float = 0.7
20
- max_tokens: Optional[int] = None
20
+ max_tokens: Optional[int] = 131072
21
21
  response_model: Type[ResponseT]
22
22
  conversation_history: List[Dict[str, str]] = Field(
23
23
  default_factory=list,
@@ -21,7 +21,7 @@ class GroqInput(InputSchema):
21
21
  model: str = Field(
22
22
  default="deepseek-r1-distill-llama-70b-specdec", description="Groq model to use"
23
23
  )
24
- max_tokens: int = Field(default=1024, description="Maximum tokens in response")
24
+ max_tokens: int = Field(default=131072, description="Maximum tokens in response")
25
25
  temperature: float = Field(
26
26
  default=0.7, description="Temperature for response generation", ge=0, le=1
27
27
  )
@@ -14,7 +14,7 @@ class OllamaInput(InputSchema):
14
14
  description="System prompt to guide the model's behavior",
15
15
  )
16
16
  model: str = Field(default="llama2", description="Ollama model to use")
17
- max_tokens: int = Field(default=1024, description="Maximum tokens in response")
17
+ max_tokens: int = Field(default=131072, description="Maximum tokens in response")
18
18
  temperature: float = Field(
19
19
  default=0.7, description="Temperature for response generation", ge=0, le=1
20
20
  )
@@ -1,3 +1,5 @@
1
+ """OpenAI API integration."""
2
+
1
3
  from .skills import (
2
4
  OpenAIChatSkill,
3
5
  OpenAIInput,
@@ -10,6 +12,12 @@ from .skills import (
10
12
  OpenAIEmbeddingsOutput,
11
13
  )
12
14
  from .credentials import OpenAICredentials
15
+ from .list_models import (
16
+ OpenAIListModelsSkill,
17
+ OpenAIListModelsInput,
18
+ OpenAIListModelsOutput,
19
+ OpenAIModel,
20
+ )
13
21
 
14
22
  __all__ = [
15
23
  "OpenAIChatSkill",
@@ -22,4 +30,8 @@ __all__ = [
22
30
  "OpenAIEmbeddingsSkill",
23
31
  "OpenAIEmbeddingsInput",
24
32
  "OpenAIEmbeddingsOutput",
33
+ "OpenAIListModelsSkill",
34
+ "OpenAIListModelsInput",
35
+ "OpenAIListModelsOutput",
36
+ "OpenAIModel",
25
37
  ]
@@ -17,7 +17,7 @@ class ChineseAssistantInput(OpenAIInput):
17
17
  description="System prompt in Chinese",
18
18
  )
19
19
  model: str = Field(default="gpt-4o", description="OpenAI model to use")
20
- max_tokens: int = Field(default=8096, description="Maximum tokens in response")
20
+ max_tokens: int = Field(default=131072, description="Maximum tokens in response")
21
21
  temperature: float = Field(
22
22
  default=0.7, description="Temperature for response generation", ge=0, le=1
23
23
  )
@@ -0,0 +1,112 @@
1
+ from typing import Optional, List, Dict, Any
2
+ from pydantic import Field
3
+
4
+ from airtrain.core.skills import Skill, ProcessingError
5
+ from airtrain.core.schemas import InputSchema, OutputSchema
6
+ from .credentials import OpenAICredentials
7
+ from .models_config import OPENAI_MODELS, OpenAIModelConfig
8
+
9
+
10
+ class OpenAIModel:
11
+ """Class to represent an OpenAI model."""
12
+
13
+ def __init__(self, model_id: str, config: OpenAIModelConfig):
14
+ """Initialize the OpenAI model."""
15
+ self.id = model_id
16
+ self.display_name = config.display_name
17
+ self.base_model = config.base_model
18
+ self.input_price = config.input_price
19
+ self.cached_input_price = config.cached_input_price
20
+ self.output_price = config.output_price
21
+
22
+ def dict(self, exclude_none=False):
23
+ """Convert the model to a dictionary."""
24
+ result = {
25
+ "id": self.id,
26
+ "display_name": self.display_name,
27
+ "base_model": self.base_model,
28
+ "input_price": float(self.input_price),
29
+ "output_price": float(self.output_price),
30
+ }
31
+ if self.cached_input_price is not None:
32
+ result["cached_input_price"] = float(self.cached_input_price)
33
+ elif not exclude_none:
34
+ result["cached_input_price"] = None
35
+ return result
36
+
37
+
38
+ class OpenAIListModelsInput(InputSchema):
39
+ """Schema for OpenAI list models input"""
40
+
41
+ api_models_only: bool = Field(
42
+ default=False,
43
+ description=(
44
+ "If True, fetch models from the API only. If False, use local config."
45
+ )
46
+ )
47
+
48
+
49
+ class OpenAIListModelsOutput(OutputSchema):
50
+ """Schema for OpenAI list models output"""
51
+
52
+ models: List[Dict[str, Any]] = Field(
53
+ default_factory=list,
54
+ description="List of OpenAI models"
55
+ )
56
+
57
+
58
+ class OpenAIListModelsSkill(Skill[OpenAIListModelsInput, OpenAIListModelsOutput]):
59
+ """Skill for listing OpenAI models"""
60
+
61
+ input_schema = OpenAIListModelsInput
62
+ output_schema = OpenAIListModelsOutput
63
+
64
+ def __init__(self, credentials: Optional[OpenAICredentials] = None):
65
+ """Initialize the skill with optional credentials"""
66
+ super().__init__()
67
+ self.credentials = credentials
68
+
69
+ def process(
70
+ self, input_data: OpenAIListModelsInput
71
+ ) -> OpenAIListModelsOutput:
72
+ """Process the input and return a list of models."""
73
+ try:
74
+ models = []
75
+
76
+ if input_data.api_models_only:
77
+ # Fetch models from OpenAI API - requires credentials
78
+ if not self.credentials:
79
+ raise ProcessingError(
80
+ "OpenAI credentials required for API models"
81
+ )
82
+
83
+ from openai import OpenAI
84
+ client = OpenAI(
85
+ api_key=self.credentials.openai_api_key.get_secret_value(),
86
+ organization=self.credentials.openai_organization_id,
87
+ )
88
+
89
+ # Make API call to get models
90
+ response = client.models.list()
91
+
92
+ # Convert response to our format
93
+ for model in response.data:
94
+ models.append({
95
+ "id": model.id,
96
+ "display_name": model.id, # API doesn't provide display_name
97
+ "base_model": model.id, # API doesn't provide base_model
98
+ "created": model.created,
99
+ "owned_by": model.owned_by,
100
+ # Pricing info not available from API
101
+ })
102
+ else:
103
+ # Use local model config - no credentials needed
104
+ for model_id, config in OPENAI_MODELS.items():
105
+ model = OpenAIModel(model_id, config)
106
+ models.append(model.dict())
107
+
108
+ # Return the output
109
+ return OpenAIListModelsOutput(models=models)
110
+
111
+ except Exception as e:
112
+ raise ProcessingError(f"Failed to list OpenAI models: {str(e)}")
@@ -29,7 +29,7 @@ class OpenAIInput(InputSchema):
29
29
  default=0.7, description="Temperature for response generation", ge=0, le=1
30
30
  )
31
31
  max_tokens: Optional[int] = Field(
32
- default=None, description="Maximum tokens in response"
32
+ default=131072, description="Maximum tokens in response"
33
33
  )
34
34
  stream: bool = Field(
35
35
  default=False,
@@ -13,7 +13,7 @@ TOGETHER_MODELS: Dict[str, ModelConfig] = {
13
13
  "deepseek-ai/DeepSeek-R1": ModelConfig(
14
14
  organization="DeepSeek",
15
15
  display_name="DeepSeek-R1",
16
- context_length=32768,
16
+ context_length=131072,
17
17
  quantization="FP8",
18
18
  ),
19
19
  "deepseek-ai/DeepSeek-R1-Distill-Llama-70B": ModelConfig(
@@ -37,7 +37,7 @@ TOGETHER_MODELS: Dict[str, ModelConfig] = {
37
37
  "deepseek-ai/DeepSeek-V3": ModelConfig(
38
38
  organization="DeepSeek",
39
39
  display_name="DeepSeek-V3",
40
- context_length=16384,
40
+ context_length=131072,
41
41
  quantization="FP8",
42
42
  ),
43
43
  # Meta Models
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: airtrain
3
- Version: 0.1.43
3
+ Version: 0.1.45
4
4
  Summary: A platform for building and deploying AI agents with structured skills
5
5
  Home-page: https://github.com/rosaboyle/airtrain.dev
6
6
  Author: Dheeraj Pai
@@ -1,4 +1,4 @@
1
- airtrain/__init__.py,sha256=f32eUcBlLHtZ3NDoxvRR_a36tp6PkovEYV-G9IG__mw,2099
1
+ airtrain/__init__.py,sha256=r8knBRelQBR1dv6YknyXNm00o3Eo7UmfPDuHYDxLtL4,2099
2
2
  airtrain/__main__.py,sha256=EU8ffFmCdC1G-UcHHt0Oo3lB1PGqfC6kwzH39CnYSwU,72
3
3
  airtrain/builder/__init__.py,sha256=D33sr0k_WAe6FAJkk8rUaivEzFaeVqLXkQgyFWEhfPU,110
4
4
  airtrain/builder/agent_builder.py,sha256=3XnGUAcK_6lWoUDtL0TanliQZuh7u0unhNbnrz1z2-I,5018
@@ -13,42 +13,47 @@ airtrain/core/__init__.py,sha256=9h7iKwTzZocCPc9bU6j8bA02BokteWIOcO1uaqGMcrk,254
13
13
  airtrain/core/credentials.py,sha256=PgQotrQc46J5djidKnkK1znUv3fyNkUFDO-m2Kn_Gzo,4006
14
14
  airtrain/core/schemas.py,sha256=MMXrDviC4gRea_QaPpbjgO--B_UKxnD7YrxqZOLJZZU,7003
15
15
  airtrain/core/skills.py,sha256=LljalzeSHK5eQPTAOEAYc5D8Qn1kVSfiz9WgziTD5UM,4688
16
- airtrain/integrations/__init__.py,sha256=rk9QFl0Dd7Qp4rULhi_u4smwsJwk69Kg_-fv0GQ43iw,1782
17
- airtrain/integrations/anthropic/__init__.py,sha256=F4kB5fuj7nYgTVcgzeHGc91LT96FZfsCJVBVCnTRh-k,541
16
+ airtrain/integrations/__init__.py,sha256=Y0yJt6c6uKOkMXdGPELSc6NYiLrEYr_38acViNPXWNk,2046
17
+ airtrain/integrations/anthropic/__init__.py,sha256=K741w3v7fWsCknTo38ARqDL0D3HPlwDIvDuuBao9Tto,800
18
18
  airtrain/integrations/anthropic/credentials.py,sha256=hlTSw9HX66kYNaeQUtn0JjdZQBMNkzzFOJOoLOOzvcY,1246
19
+ airtrain/integrations/anthropic/list_models.py,sha256=o7FABp0Cq3gs76zOF-CM9ohmYslWT6vK9qtQabV9XzI,3973
19
20
  airtrain/integrations/anthropic/models_config.py,sha256=TZt31hLcT-9YK-NxqiarMyOwvUWMgXAzAcPfSwzDSiQ,3347
20
- airtrain/integrations/anthropic/skills.py,sha256=WV-9254H2VqUAq_7Zr1xG5IhejeC_gQSqyH0hwW1_tY,5870
21
+ airtrain/integrations/anthropic/skills.py,sha256=bs9OYHeY1ETsvXt60vq6xNj2PQK_sBULnaVGvSQ9vXQ,5872
21
22
  airtrain/integrations/aws/__init__.py,sha256=3x7v2NxpAfI-U-YgwQeH5PtsmUrNLPMfLyUGFLiBjbs,155
22
23
  airtrain/integrations/aws/credentials.py,sha256=nN-daKAl7qOb_VdRpsThG8gN5GeSUkx-ji5E_gF_vYw,1444
23
- airtrain/integrations/aws/skills.py,sha256=TQiMXeXRRcJ14fe8Xi7Uk20iS6_INbcznuLGtMorcKY,3870
24
+ airtrain/integrations/aws/skills.py,sha256=2l16Y5zYeNd9trrPca6Rbhvl6a-GJBuCQMu7RqX9txo,3872
24
25
  airtrain/integrations/cerebras/__init__.py,sha256=zAD-qV38OzHhMCz1z-NvjjqcYEhURbm8RWTOKHNqbew,174
25
26
  airtrain/integrations/cerebras/credentials.py,sha256=KDEH4r8FGT68L9p34MLZWK65wq_a703pqIF3ODaSbts,694
26
- airtrain/integrations/cerebras/skills.py,sha256=hmqcnF-nkFk5YJVf8f-TiKBfb8kYCfnC30W67VZ7CKU,4922
27
+ airtrain/integrations/cerebras/skills.py,sha256=BJEb_7TglCYAukD3kcx37R8ibnJWdxVrBrwf3ZTYP-4,4924
28
+ airtrain/integrations/combined/__init__.py,sha256=EL_uZs8436abeZA6NbM-gLdur7kJr4YfGGOYybKOhjc,467
29
+ airtrain/integrations/combined/groq_fireworks_skills.py,sha256=Kz8UDU4-Rl71znz3ml9qVMRz669iWx4sUl37iafW0NU,4612
30
+ airtrain/integrations/combined/list_models_factory.py,sha256=tDhfu7-MdAgPXCDkASNezSu4gfNjIC7Zf4XLw_Rx0sk,6500
27
31
  airtrain/integrations/fireworks/__init__.py,sha256=GstUg0rYC-7Pg0DVbDXwL5eO1hp3WCSfroWazbGpfi0,545
28
- airtrain/integrations/fireworks/completion_skills.py,sha256=G657xWd7izLOxXq7RmqdupBF4DHqXQgXuhQ-MW7mtqc,5613
29
- airtrain/integrations/fireworks/conversation_manager.py,sha256=m6VEHijqpYEYawkKhuHtb8RQxw4kxGWFWdbSK6zGuro,3704
32
+ airtrain/integrations/fireworks/completion_skills.py,sha256=zxx7aNlum9scQMri5Ek0qN8VfAomhyUp3u8JJo_AFWM,5615
33
+ airtrain/integrations/fireworks/conversation_manager.py,sha256=ifscKHYKWM_NDElin-oTzpRhyoh6pzBnklmMuH5geOY,3706
30
34
  airtrain/integrations/fireworks/credentials.py,sha256=eeV9y_4pTe8LZX02I7kfA_YNY2D7MSbFl7JEZVn22zQ,864
31
35
  airtrain/integrations/fireworks/list_models.py,sha256=o4fP0K3qstBopO7va2LysLp4_KUf5Iz_YROrYkaNtVs,4686
32
36
  airtrain/integrations/fireworks/models.py,sha256=yo4xtweSi4qQftg04r4naRddx3KjU9Jluzqf5C7V9f4,4626
33
- airtrain/integrations/fireworks/requests_skills.py,sha256=c84Vy_4EcBrwJfp3jqizzlcja_LsEtvWh59qiaIjukg,8233
34
- airtrain/integrations/fireworks/skills.py,sha256=o9OY69cC10P8BtBBYRYLCyR_GwxmNlF6YhnrXiNS53o,7154
35
- airtrain/integrations/fireworks/structured_completion_skills.py,sha256=-AJTaOFC8vkFiEjHW24VL8ymcNSVbhZp6xb4enkL95U,6620
36
- airtrain/integrations/fireworks/structured_requests_skills.py,sha256=FgUdWb6_GI2ZBWhK2wp-WqKZUkwCkKNBBjYcRkHtjog,11850
37
- airtrain/integrations/fireworks/structured_skills.py,sha256=BZaLqSOTC11QdZ4kDORS4JnwF_YXBAa-IiwQ5dJiHXw,3895
37
+ airtrain/integrations/fireworks/requests_skills.py,sha256=h6HRV5dGvV7t3zyjD-awW47RyeDbu8onNevhcgSSy94,8235
38
+ airtrain/integrations/fireworks/skills.py,sha256=Ns1tXXTVtTeeVYadzm4dnmmOboo430WTMu2o56oWTDc,7156
39
+ airtrain/integrations/fireworks/structured_completion_skills.py,sha256=airYakYWXzYRS9nfNfrH90N3eeN8YW7GaY3ygLSiBO8,6622
40
+ airtrain/integrations/fireworks/structured_requests_skills.py,sha256=uQR-nygtWmdGTwvU-aUdMNOMit_PiBVPYRa80ZloHLs,11852
41
+ airtrain/integrations/fireworks/structured_skills.py,sha256=1wZ_7QDUhKWCSv_1lSEF6VnAqEeEA3jWHq7n0fWicgw,3897
38
42
  airtrain/integrations/google/__init__.py,sha256=ElwgcXfbg_gGMm6zbkMXCQPFKZUb-yTJk986o19A7Cs,214
39
43
  airtrain/integrations/google/credentials.py,sha256=KSvWNqW8Mjr4MkysRvUqlrOSGdShNIe5u2OPO6vRrWY,2047
40
44
  airtrain/integrations/google/skills.py,sha256=ytsoksCY4qbfRO9Brnxhc2694fAj0ytnHX20SXS_FOM,4547
41
45
  airtrain/integrations/groq/__init__.py,sha256=B_X2fXbsJfFD6GquKeVCsEJjwd9Ygbq1uEHlV4Jy7YE,154
42
46
  airtrain/integrations/groq/credentials.py,sha256=bdTHykcIeaQ7td8KZlQBPfEFAkvJuxk2f_cbTLPD_I4,844
43
- airtrain/integrations/groq/skills.py,sha256=qFyxC_2xZYnByAPo5p2aHbrqhdHYCoIdvDRAauSfnjk,4821
47
+ airtrain/integrations/groq/skills.py,sha256=XNwGE2fjb9MDth3NI5uqSiEQYLsLBuCFF7YTu_xoTug,4823
44
48
  airtrain/integrations/ollama/__init__.py,sha256=zMHBsGzViVrvxAeJmfq6r-ZfSE6Dy5QcKLhe4d5fEcM,164
45
49
  airtrain/integrations/ollama/credentials.py,sha256=D7O4kUAb_VHs5s1ncUN9Ezhu5PvLfgj3RifAkB9sEZk,940
46
- airtrain/integrations/ollama/skills.py,sha256=M_Un8D5VJ5XtPEq9IClzqV3jCPBoFTSm2ve6EO8W2JU,1556
47
- airtrain/integrations/openai/__init__.py,sha256=w5V7lxvrKtrrjyqGoppEKg9ORKKQ2cxaLOpgCZdm_H8,541
48
- airtrain/integrations/openai/chinese_assistant.py,sha256=MMhv4NBOoEQ0O22ZZtP255rd5ajHC9l6FPWIjpqxBOA,1581
50
+ airtrain/integrations/ollama/skills.py,sha256=QHEvIrFmuwFuC3ZAmy6xL2hNNGZWii1z9Y884JuOvnI,1558
51
+ airtrain/integrations/openai/__init__.py,sha256=LYLxgDOAMUhw0ChBqj7XAJSTMNt9JiS2hHJDnJWS6IE,807
52
+ airtrain/integrations/openai/chinese_assistant.py,sha256=F8bMeUUDly7BYG6wO648cAEIj5S_frVE5tm1Xno63Gc,1583
49
53
  airtrain/integrations/openai/credentials.py,sha256=NfRyp1QgEtgm8cxt2-BOLq-6d0X-Pcm80NnfHM8p0FY,1470
54
+ airtrain/integrations/openai/list_models.py,sha256=vg8pZwLZ3F2Fx42X18WykpJOzZD9JG-2KJi49XWgSKo,4121
50
55
  airtrain/integrations/openai/models_config.py,sha256=W9mu_z9tCC4ZUKHSJ6Hk4X09TRZLqEhT7TtRY5JEk5g,8007
51
- airtrain/integrations/openai/skills.py,sha256=1dvRJYrnU2hOmGRlkHBtyR6P8D7aIwHZfUKmjlReWrQ,12821
56
+ airtrain/integrations/openai/skills.py,sha256=kEDe5q0Zlv_X-JGOYtb552ktb3aQQYVUYczVwMH0jxA,12823
52
57
  airtrain/integrations/sambanova/__init__.py,sha256=dp_263iOckM_J9pOEvyqpf3FrejD6-_x33r0edMCTe0,179
53
58
  airtrain/integrations/sambanova/credentials.py,sha256=JyN8sbMCoXuXAjim46aI3LTicBijoemS7Ao0rn4yBJU,824
54
59
  airtrain/integrations/sambanova/skills.py,sha256=SZ_GAimMiOCILiNkzyhNflyRR6bdC5r0Tnog19K8geU,4997
@@ -60,14 +65,14 @@ airtrain/integrations/together/image_models_config.py,sha256=JlCozrphI9zE4uYpGfj
60
65
  airtrain/integrations/together/image_skill.py,sha256=wQ8wSzfL-QHpM_esYGLNXf8ciOPPsz-QJw6zSrxZT68,5214
61
66
  airtrain/integrations/together/list_models.py,sha256=_QLGqweBiK6saz3n4xTQRmXSSs-qGFnV9kma-MSaE9o,2520
62
67
  airtrain/integrations/together/models.py,sha256=q5KsouOK7IvyzGZ7nhSjTpZw-CcLfPghJr6o_UU9uMo,3652
63
- airtrain/integrations/together/models_config.py,sha256=XMKp0Oq1nWWnMMdNAZxkFXmJaURwWrwLE18kFXsMsRw,8829
68
+ airtrain/integrations/together/models_config.py,sha256=lCPouXjKa49lebGbMaaL2SU-CMYxOc-dJviUOir2e_w,8831
64
69
  airtrain/integrations/together/rerank_models_config.py,sha256=coCg0IOG2tU4L2uc2uPtPdoBwGjSc_zQwxENwdDuwHE,1188
65
70
  airtrain/integrations/together/rerank_skill.py,sha256=gjH24hLWCweWKPyyfKZMG3K_g9gWzm80WgiJNjkA9eg,1894
66
71
  airtrain/integrations/together/schemas.py,sha256=pBMrbX67oxPCr-sg4K8_Xqu1DWbaC4uLCloVSascROg,1210
67
72
  airtrain/integrations/together/skills.py,sha256=8DwkexMJu1Gm6QmNDfNasYStQ31QsXBbFP99zR-YCf0,7598
68
73
  airtrain/integrations/together/vision_models_config.py,sha256=m28HwYDk2Kup_J-a1FtynIa2ZVcbl37kltfoHnK8zxs,1544
69
- airtrain-0.1.43.dist-info/METADATA,sha256=FM114Wh1dKSK_-PH7H2b9vHIXdc7-EoHvSsOClJWDkk,5375
70
- airtrain-0.1.43.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
71
- airtrain-0.1.43.dist-info/entry_points.txt,sha256=rrJ36IUsyq6n1dSfTWXqVAgpQLPRWDfCqwd6_3B-G0U,52
72
- airtrain-0.1.43.dist-info/top_level.txt,sha256=cFWW1vY6VMCb3AGVdz6jBDpZ65xxBRSqlsPyySxTkxY,9
73
- airtrain-0.1.43.dist-info/RECORD,,
74
+ airtrain-0.1.45.dist-info/METADATA,sha256=PXbwhd_qiTdj56DAAG1iwZ9CBgThiTRaaf4xw6A_G7o,5375
75
+ airtrain-0.1.45.dist-info/WHEEL,sha256=tTnHoFhvKQHCh4jz3yCn0WPTYIy7wXx3CJtJ7SJGV7c,91
76
+ airtrain-0.1.45.dist-info/entry_points.txt,sha256=rrJ36IUsyq6n1dSfTWXqVAgpQLPRWDfCqwd6_3B-G0U,52
77
+ airtrain-0.1.45.dist-info/top_level.txt,sha256=cFWW1vY6VMCb3AGVdz6jBDpZ65xxBRSqlsPyySxTkxY,9
78
+ airtrain-0.1.45.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (76.1.0)
2
+ Generator: setuptools (77.0.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5