cost-katana 1.0.1__py3-none-any.whl → 1.0.3__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.
cost_katana/config.py CHANGED
@@ -5,13 +5,14 @@ Configuration management for Cost Katana
5
5
  import json
6
6
  import os
7
7
  from typing import Dict, Any, Optional
8
- from dataclasses import dataclass, asdict
8
+ from dataclasses import dataclass, asdict, fields
9
9
  from pathlib import Path
10
10
 
11
+
11
12
  @dataclass
12
13
  class Config:
13
14
  """Configuration class for Cost Katana client"""
14
-
15
+
15
16
  api_key: Optional[str] = None
16
17
  base_url: str = "https://cost-katana-backend.store"
17
18
  timeout: int = 30
@@ -26,35 +27,35 @@ class Config:
26
27
  enable_failover: bool = True
27
28
  cost_limit_per_request: Optional[float] = None
28
29
  cost_limit_per_day: Optional[float] = None
29
-
30
+
30
31
  def __post_init__(self):
31
32
  """Load from environment variables if not set"""
32
33
  if not self.api_key:
33
- self.api_key = os.getenv('COST_KATANA_API_KEY')
34
-
34
+ self.api_key = os.getenv("API_KEY")
35
+
35
36
  # Override with environment variables if they exist
36
- if os.getenv('COST_KATANA_BASE_URL'):
37
- self.base_url = os.getenv('COST_KATANA_BASE_URL')
38
- if os.getenv('COST_KATANA_DEFAULT_MODEL'):
39
- self.default_model = os.getenv('COST_KATANA_DEFAULT_MODEL')
40
- if os.getenv('COST_KATANA_TIMEOUT'):
41
- self.timeout = int(os.getenv('COST_KATANA_TIMEOUT'))
42
-
37
+ if os.getenv("COST_KATANA_BASE_URL"):
38
+ self.base_url = os.getenv("COST_KATANA_BASE_URL")
39
+ if os.getenv("COST_KATANA_DEFAULT_MODEL"):
40
+ self.default_model = os.getenv("COST_KATANA_DEFAULT_MODEL")
41
+ if os.getenv("COST_KATANA_TIMEOUT"):
42
+ self.timeout = int(os.getenv("COST_KATANA_TIMEOUT"))
43
+
43
44
  @classmethod
44
- def from_file(cls, config_path: str) -> 'Config':
45
+ def from_file(cls, config_path: str) -> "Config":
45
46
  """
46
47
  Load configuration from JSON file.
47
-
48
+
48
49
  Args:
49
50
  config_path: Path to JSON configuration file
50
-
51
+
51
52
  Returns:
52
53
  Config instance
53
-
54
+
54
55
  Example config.json:
55
56
  {
56
57
  "api_key": "dak_your_key_here",
57
- "base_url": "https://api.costkatana.com",
58
+ "base_url": "https://cost-katana-backend.store",
58
59
  "default_model": "claude-3-sonnet",
59
60
  "default_temperature": 0.3,
60
61
  "cost_limit_per_day": 100.0,
@@ -70,54 +71,56 @@ class Config:
70
71
  }
71
72
  }
72
73
  """
73
- config_path = Path(config_path).expanduser()
74
-
75
- if not config_path.exists():
74
+ config_path_obj = Path(config_path).expanduser()
75
+
76
+ if not config_path_obj.exists():
76
77
  raise FileNotFoundError(f"Configuration file not found: {config_path}")
77
-
78
+
78
79
  try:
79
- with open(config_path, 'r', encoding='utf-8') as f:
80
+ with open(config_path_obj, "r", encoding="utf-8") as f:
80
81
  data = json.load(f)
81
82
  except json.JSONDecodeError as e:
82
83
  raise ValueError(f"Invalid JSON in config file: {e}")
83
-
84
+
84
85
  # Extract known fields
85
- config_fields = {
86
- field.name for field in cls.__dataclass_fields__.values()
87
- }
88
-
86
+ config_fields = {field.name for field in fields(cls)}
87
+
89
88
  config_data = {k: v for k, v in data.items() if k in config_fields}
90
89
  config = cls(**config_data)
91
-
90
+
92
91
  # Store additional data
93
- config._extra_data = {k: v for k, v in data.items() if k not in config_fields}
94
-
92
+ setattr(
93
+ config,
94
+ "_extra_data",
95
+ {k: v for k, v in data.items() if k not in config_fields},
96
+ )
97
+
95
98
  return config
96
-
99
+
97
100
  def to_dict(self) -> Dict[str, Any]:
98
101
  """Convert config to dictionary"""
99
102
  result = asdict(self)
100
-
103
+
101
104
  # Add extra data if it exists
102
- if hasattr(self, '_extra_data'):
105
+ if hasattr(self, "_extra_data"):
103
106
  result.update(self._extra_data)
104
-
107
+
105
108
  return result
106
-
109
+
107
110
  def save(self, config_path: str):
108
111
  """Save configuration to JSON file"""
109
- config_path = Path(config_path).expanduser()
110
- config_path.parent.mkdir(parents=True, exist_ok=True)
111
-
112
- with open(config_path, 'w', encoding='utf-8') as f:
112
+ config_path_obj = Path(config_path).expanduser()
113
+ config_path_obj.parent.mkdir(parents=True, exist_ok=True)
114
+
115
+ with open(config_path_obj, "w", encoding="utf-8") as f:
113
116
  json.dump(self.to_dict(), f, indent=2, ensure_ascii=False)
114
-
117
+
115
118
  def get_provider_config(self, provider: str) -> Dict[str, Any]:
116
119
  """Get configuration for a specific provider"""
117
- if hasattr(self, '_extra_data') and 'providers' in self._extra_data:
118
- return self._extra_data['providers'].get(provider, {})
120
+ if hasattr(self, "_extra_data") and "providers" in self._extra_data:
121
+ return self._extra_data["providers"].get(provider, {})
119
122
  return {}
120
-
123
+
121
124
  def get_model_mapping(self, model_name: str) -> str:
122
125
  """
123
126
  Map user-friendly model names to internal model IDs.
@@ -128,54 +131,48 @@ class Config:
128
131
  # Based on actual models available from Cost Katana Backend
129
132
  default_mappings = {
130
133
  # Amazon Nova models (primary recommendation)
131
- 'nova-micro': 'amazon.nova-micro-v1:0',
132
- 'nova-lite': 'amazon.nova-lite-v1:0',
133
- 'nova-pro': 'amazon.nova-pro-v1:0',
134
- 'fast': 'amazon.nova-micro-v1:0',
135
- 'balanced': 'amazon.nova-lite-v1:0',
136
- 'powerful': 'amazon.nova-pro-v1:0',
137
-
134
+ "nova-micro": "amazon.nova-micro-v1:0",
135
+ "nova-lite": "amazon.nova-lite-v1:0",
136
+ "nova-pro": "amazon.nova-pro-v1:0",
137
+ "fast": "amazon.nova-micro-v1:0",
138
+ "balanced": "amazon.nova-lite-v1:0",
139
+ "powerful": "amazon.nova-pro-v1:0",
138
140
  # Anthropic Claude models
139
- 'claude-3-haiku': 'anthropic.claude-3-haiku-20240307-v1:0',
140
- 'claude-3-sonnet': 'anthropic.claude-3-sonnet-20240229-v1:0',
141
- 'claude-3-opus': 'anthropic.claude-3-opus-20240229-v1:0',
142
- 'claude-3.5-haiku': 'anthropic.claude-3-5-haiku-20241022-v1:0',
143
- 'claude-3.5-sonnet': 'anthropic.claude-3-5-sonnet-20241022-v2:0',
144
- 'claude': 'anthropic.claude-3-5-sonnet-20241022-v2:0',
145
-
141
+ "claude-3-haiku": "anthropic.claude-3-haiku-20240307-v1:0",
142
+ "claude-3-sonnet": "anthropic.claude-3-sonnet-20240229-v1:0",
143
+ "claude-3-opus": "anthropic.claude-3-opus-20240229-v1:0",
144
+ "claude-3.5-haiku": "anthropic.claude-3-5-haiku-20241022-v1:0",
145
+ "claude-3.5-sonnet": "anthropic.claude-3-5-sonnet-20241022-v2:0",
146
+ "claude": "anthropic.claude-3-5-sonnet-20241022-v2:0",
146
147
  # Meta Llama models
147
- 'llama-3.1-8b': 'meta.llama3-1-8b-instruct-v1:0',
148
- 'llama-3.1-70b': 'meta.llama3-1-70b-instruct-v1:0',
149
- 'llama-3.1-405b': 'meta.llama3-1-405b-instruct-v1:0',
150
- 'llama-3.2-1b': 'meta.llama3-2-1b-instruct-v1:0',
151
- 'llama-3.2-3b': 'meta.llama3-2-3b-instruct-v1:0',
152
-
148
+ "llama-3.1-8b": "meta.llama3-1-8b-instruct-v1:0",
149
+ "llama-3.1-70b": "meta.llama3-1-70b-instruct-v1:0",
150
+ "llama-3.1-405b": "meta.llama3-1-405b-instruct-v1:0",
151
+ "llama-3.2-1b": "meta.llama3-2-1b-instruct-v1:0",
152
+ "llama-3.2-3b": "meta.llama3-2-3b-instruct-v1:0",
153
153
  # Mistral models
154
- 'mistral-7b': 'mistral.mistral-7b-instruct-v0:2',
155
- 'mixtral-8x7b': 'mistral.mixtral-8x7b-instruct-v0:1',
156
- 'mistral-large': 'mistral.mistral-large-2402-v1:0',
157
-
154
+ "mistral-7b": "mistral.mistral-7b-instruct-v0:2",
155
+ "mixtral-8x7b": "mistral.mixtral-8x7b-instruct-v0:1",
156
+ "mistral-large": "mistral.mistral-large-2402-v1:0",
158
157
  # Cohere models
159
- 'command': 'cohere.command-text-v14',
160
- 'command-light': 'cohere.command-light-text-v14',
161
- 'command-r': 'cohere.command-r-v1:0',
162
- 'command-r-plus': 'cohere.command-r-plus-v1:0',
163
-
158
+ "command": "cohere.command-text-v14",
159
+ "command-light": "cohere.command-light-text-v14",
160
+ "command-r": "cohere.command-r-v1:0",
161
+ "command-r-plus": "cohere.command-r-plus-v1:0",
164
162
  # AI21 models
165
- 'jamba': 'ai21.jamba-instruct-v1:0',
166
- 'j2-ultra': 'ai21.j2-ultra-v1',
167
- 'j2-mid': 'ai21.j2-mid-v1',
168
-
163
+ "jamba": "ai21.jamba-instruct-v1:0",
164
+ "j2-ultra": "ai21.j2-ultra-v1",
165
+ "j2-mid": "ai21.j2-mid-v1",
169
166
  # Backwards compatibility aliases
170
- 'gemini-2.0-flash': 'amazon.nova-lite-v1:0', # Map to similar performance
171
- 'gemini-pro': 'amazon.nova-pro-v1:0',
172
- 'gpt-4': 'anthropic.claude-3-5-sonnet-20241022-v2:0',
173
- 'gpt-3.5-turbo': 'anthropic.claude-3-haiku-20240307-v1:0',
167
+ "gemini-2.0-flash": "amazon.nova-lite-v1:0", # Map to similar performance
168
+ "gemini-pro": "amazon.nova-pro-v1:0",
169
+ "gpt-4": "anthropic.claude-3-5-sonnet-20241022-v2:0",
170
+ "gpt-3.5-turbo": "anthropic.claude-3-haiku-20240307-v1:0",
174
171
  }
175
-
172
+
176
173
  # Check for custom mappings in config
177
- if hasattr(self, '_extra_data') and 'model_mappings' in self._extra_data:
178
- custom_mappings = self._extra_data['model_mappings']
174
+ if hasattr(self, "_extra_data") and "model_mappings" in self._extra_data:
175
+ custom_mappings = self._extra_data["model_mappings"]
179
176
  default_mappings.update(custom_mappings)
180
-
181
- return default_mappings.get(model_name, model_name)
177
+
178
+ return default_mappings.get(model_name, model_name)
cost_katana/exceptions.py CHANGED
@@ -2,38 +2,56 @@
2
2
  Custom exceptions for Cost Katana
3
3
  """
4
4
 
5
+
5
6
  class CostKatanaError(Exception):
6
7
  """Base exception for Cost Katana errors"""
8
+
7
9
  pass
8
10
 
11
+
9
12
  class AuthenticationError(CostKatanaError):
10
13
  """Raised when authentication fails"""
14
+
11
15
  pass
12
16
 
17
+
13
18
  class ModelNotAvailableError(CostKatanaError):
14
19
  """Raised when requested model is not available"""
20
+
15
21
  pass
16
22
 
23
+
17
24
  class RateLimitError(CostKatanaError):
18
25
  """Raised when rate limit is exceeded"""
26
+
19
27
  pass
20
28
 
29
+
21
30
  class CostLimitExceededError(CostKatanaError):
22
31
  """Raised when cost limits are exceeded"""
32
+
23
33
  pass
24
34
 
35
+
25
36
  class ConversationNotFoundError(CostKatanaError):
26
37
  """Raised when conversation is not found"""
38
+
27
39
  pass
28
40
 
41
+
29
42
  class InvalidConfigurationError(CostKatanaError):
30
43
  """Raised when configuration is invalid"""
44
+
31
45
  pass
32
46
 
47
+
33
48
  class NetworkError(CostKatanaError):
34
49
  """Raised when network requests fail"""
50
+
35
51
  pass
36
52
 
53
+
37
54
  class ModelTimeoutError(CostKatanaError):
38
55
  """Raised when model request times out"""
39
- pass
56
+
57
+ pass