cost-katana 1.0.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.
- cost_katana/__init__.py +69 -0
- cost_katana/cli.py +303 -0
- cost_katana/client.py +235 -0
- cost_katana/config.py +181 -0
- cost_katana/exceptions.py +39 -0
- cost_katana/models.py +343 -0
- cost_katana-1.0.0.dist-info/METADATA +425 -0
- cost_katana-1.0.0.dist-info/RECORD +12 -0
- cost_katana-1.0.0.dist-info/WHEEL +5 -0
- cost_katana-1.0.0.dist-info/entry_points.txt +2 -0
- cost_katana-1.0.0.dist-info/licenses/LICENSE +21 -0
- cost_katana-1.0.0.dist-info/top_level.txt +1 -0
cost_katana/__init__.py
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
"""
|
2
|
+
Cost Katana - Unified AI Interface with Cost Optimization
|
3
|
+
|
4
|
+
Simple interface for AI models that routes through Cost Katana for:
|
5
|
+
- Cost optimization and tracking
|
6
|
+
- Automatic failover between providers
|
7
|
+
- Usage analytics and insights
|
8
|
+
- No API key management needed in your code
|
9
|
+
|
10
|
+
Example:
|
11
|
+
import cost_katana as ck
|
12
|
+
|
13
|
+
# Configure once
|
14
|
+
ck.configure(config_file='config.json')
|
15
|
+
|
16
|
+
# Use like any AI library
|
17
|
+
model = ck.GenerativeModel('gemini-2.0-flash')
|
18
|
+
chat = model.start_chat()
|
19
|
+
response = chat.send_message("Hello!")
|
20
|
+
print(response.text)
|
21
|
+
"""
|
22
|
+
|
23
|
+
from .client import CostKatanaClient, get_global_client
|
24
|
+
from .models import GenerativeModel, ChatSession
|
25
|
+
from .exceptions import (
|
26
|
+
CostKatanaError,
|
27
|
+
AuthenticationError,
|
28
|
+
ModelNotAvailableError,
|
29
|
+
RateLimitError,
|
30
|
+
CostLimitExceededError
|
31
|
+
)
|
32
|
+
from .config import Config
|
33
|
+
|
34
|
+
__version__ = "1.0.0"
|
35
|
+
__all__ = [
|
36
|
+
"configure",
|
37
|
+
"GenerativeModel",
|
38
|
+
"ChatSession",
|
39
|
+
"CostKatanaClient",
|
40
|
+
"CostKatanaError",
|
41
|
+
"AuthenticationError",
|
42
|
+
"ModelNotAvailableError",
|
43
|
+
"RateLimitError",
|
44
|
+
"CostLimitExceededError",
|
45
|
+
"Config"
|
46
|
+
]
|
47
|
+
|
48
|
+
# Import configure function from client
|
49
|
+
from .client import configure
|
50
|
+
|
51
|
+
def GenerativeModel(model_name: str, **kwargs):
|
52
|
+
"""
|
53
|
+
Create a generative model instance.
|
54
|
+
|
55
|
+
Args:
|
56
|
+
model_name: Name of the model (e.g., 'gemini-2.0-flash', 'claude-3-sonnet', 'gpt-4')
|
57
|
+
**kwargs: Additional model configuration
|
58
|
+
|
59
|
+
Returns:
|
60
|
+
GenerativeModel instance
|
61
|
+
|
62
|
+
Example:
|
63
|
+
model = cost_katana.GenerativeModel('gemini-2.0-flash')
|
64
|
+
response = model.generate_content("Hello, world!")
|
65
|
+
"""
|
66
|
+
client = get_global_client()
|
67
|
+
|
68
|
+
from .models import GenerativeModel as GM
|
69
|
+
return GM(client, model_name, **kwargs)
|
cost_katana/cli.py
ADDED
@@ -0,0 +1,303 @@
|
|
1
|
+
"""
|
2
|
+
Command-line interface for Cost Katana
|
3
|
+
"""
|
4
|
+
|
5
|
+
import argparse
|
6
|
+
import json
|
7
|
+
import sys
|
8
|
+
from pathlib import Path
|
9
|
+
from typing import Optional
|
10
|
+
from rich.console import Console
|
11
|
+
from rich.table import Table
|
12
|
+
from rich.panel import Panel
|
13
|
+
from rich.prompt import Prompt, Confirm
|
14
|
+
from rich.syntax import Syntax
|
15
|
+
|
16
|
+
try:
|
17
|
+
from . import configure, GenerativeModel, CostKatanaClient
|
18
|
+
from .config import Config
|
19
|
+
from .exceptions import CostKatanaError
|
20
|
+
except ImportError:
|
21
|
+
# Handle case when running as script
|
22
|
+
import cost_katana as ck
|
23
|
+
from cost_katana.config import Config
|
24
|
+
from cost_katana.exceptions import CostKatanaError
|
25
|
+
|
26
|
+
console = Console()
|
27
|
+
|
28
|
+
def create_sample_config():
|
29
|
+
"""Create a sample configuration file"""
|
30
|
+
sample_config = {
|
31
|
+
"api_key": "dak_your_api_key_here",
|
32
|
+
"base_url": "https://api.costkatana.com",
|
33
|
+
"default_model": "gemini-2.0-flash",
|
34
|
+
"default_temperature": 0.7,
|
35
|
+
"default_max_tokens": 2000,
|
36
|
+
"cost_limit_per_day": 50.0,
|
37
|
+
"enable_analytics": True,
|
38
|
+
"enable_optimization": True,
|
39
|
+
"enable_failover": True,
|
40
|
+
"model_mappings": {
|
41
|
+
"gemini": "gemini-2.0-flash-exp",
|
42
|
+
"claude": "anthropic.claude-3-sonnet-20240229-v1:0",
|
43
|
+
"gpt4": "gpt-4-turbo-preview"
|
44
|
+
},
|
45
|
+
"providers": {
|
46
|
+
"google": {
|
47
|
+
"priority": 1,
|
48
|
+
"models": ["gemini-2.0-flash", "gemini-pro"]
|
49
|
+
},
|
50
|
+
"anthropic": {
|
51
|
+
"priority": 2,
|
52
|
+
"models": ["claude-3-sonnet", "claude-3-haiku"]
|
53
|
+
},
|
54
|
+
"openai": {
|
55
|
+
"priority": 3,
|
56
|
+
"models": ["gpt-4", "gpt-3.5-turbo"]
|
57
|
+
}
|
58
|
+
}
|
59
|
+
}
|
60
|
+
return sample_config
|
61
|
+
|
62
|
+
def init_config(args):
|
63
|
+
"""Initialize configuration"""
|
64
|
+
config_path = Path(args.config or 'cost_katana_config.json')
|
65
|
+
|
66
|
+
if config_path.exists() and not args.force:
|
67
|
+
console.print(f"[yellow]Configuration file already exists: {config_path}[/yellow]")
|
68
|
+
if not Confirm.ask("Overwrite existing configuration?"):
|
69
|
+
return
|
70
|
+
|
71
|
+
console.print("[bold blue]Setting up Cost Katana configuration...[/bold blue]")
|
72
|
+
|
73
|
+
# Get API key
|
74
|
+
api_key = Prompt.ask(
|
75
|
+
"Enter your Cost Katana API key",
|
76
|
+
default=args.api_key if args.api_key else None
|
77
|
+
)
|
78
|
+
|
79
|
+
# Get base URL
|
80
|
+
base_url = Prompt.ask(
|
81
|
+
"Enter base URL",
|
82
|
+
default="https://api.costkatana.com"
|
83
|
+
)
|
84
|
+
|
85
|
+
# Get default model
|
86
|
+
default_model = Prompt.ask(
|
87
|
+
"Enter default model",
|
88
|
+
default="gemini-2.0-flash",
|
89
|
+
choices=["gemini-2.0-flash", "claude-3-sonnet", "gpt-4", "nova-pro"]
|
90
|
+
)
|
91
|
+
|
92
|
+
# Create configuration
|
93
|
+
config_data = create_sample_config()
|
94
|
+
config_data.update({
|
95
|
+
"api_key": api_key,
|
96
|
+
"base_url": base_url,
|
97
|
+
"default_model": default_model
|
98
|
+
})
|
99
|
+
|
100
|
+
# Save configuration
|
101
|
+
try:
|
102
|
+
with open(config_path, 'w') as f:
|
103
|
+
json.dump(config_data, f, indent=2)
|
104
|
+
|
105
|
+
console.print(f"[green]Configuration saved to: {config_path}[/green]")
|
106
|
+
console.print("\n[bold]Next steps:[/bold]")
|
107
|
+
console.print("1. Test the configuration: [cyan]cost-katana test[/cyan]")
|
108
|
+
console.print("2. Start a chat session: [cyan]cost-katana chat[/cyan]")
|
109
|
+
console.print("3. See available models: [cyan]cost-katana models[/cyan]")
|
110
|
+
|
111
|
+
except Exception as e:
|
112
|
+
console.print(f"[red]Failed to save configuration: {e}[/red]")
|
113
|
+
sys.exit(1)
|
114
|
+
|
115
|
+
def test_connection(args):
|
116
|
+
"""Test connection to Cost Katana API"""
|
117
|
+
try:
|
118
|
+
config_path = args.config or 'cost_katana_config.json'
|
119
|
+
|
120
|
+
if Path(config_path).exists():
|
121
|
+
configure(config_file=config_path)
|
122
|
+
elif args.api_key:
|
123
|
+
configure(api_key=args.api_key)
|
124
|
+
else:
|
125
|
+
console.print("[red]No configuration found. Run 'cost-katana init' first.[/red]")
|
126
|
+
return
|
127
|
+
|
128
|
+
console.print("[bold blue]Testing Cost Katana connection...[/bold blue]")
|
129
|
+
|
130
|
+
# Test with a simple model
|
131
|
+
model = GenerativeModel('gemini-2.0-flash')
|
132
|
+
response = model.generate_content("Hello! Please respond with just 'OK' to test the connection.")
|
133
|
+
|
134
|
+
console.print(Panel(
|
135
|
+
f"[green]✓ Connection successful![/green]\n"
|
136
|
+
f"Model: {response.usage_metadata.model}\n"
|
137
|
+
f"Response: {response.text}\n"
|
138
|
+
f"Cost: ${response.usage_metadata.cost:.4f}\n"
|
139
|
+
f"Latency: {response.usage_metadata.latency:.2f}s",
|
140
|
+
title="Test Results"
|
141
|
+
))
|
142
|
+
|
143
|
+
except Exception as e:
|
144
|
+
console.print(f"[red]✗ Connection failed: {e}[/red]")
|
145
|
+
sys.exit(1)
|
146
|
+
|
147
|
+
def list_models(args):
|
148
|
+
"""List available models"""
|
149
|
+
try:
|
150
|
+
config_path = args.config or 'cost_katana_config.json'
|
151
|
+
|
152
|
+
if Path(config_path).exists():
|
153
|
+
configure(config_file=config_path)
|
154
|
+
elif args.api_key:
|
155
|
+
configure(api_key=args.api_key)
|
156
|
+
else:
|
157
|
+
console.print("[red]No configuration found. Run 'cost-katana init' first.[/red]")
|
158
|
+
return
|
159
|
+
|
160
|
+
client = CostKatanaClient(config_file=config_path if Path(config_path).exists() else None)
|
161
|
+
models = client.get_available_models()
|
162
|
+
|
163
|
+
table = Table(title="Available Models")
|
164
|
+
table.add_column("Model ID", style="cyan", no_wrap=True)
|
165
|
+
table.add_column("Display Name", style="magenta")
|
166
|
+
table.add_column("Provider", style="green")
|
167
|
+
table.add_column("Type", style="yellow")
|
168
|
+
|
169
|
+
for model in models:
|
170
|
+
model_id = model.get('id', model.get('modelId', 'Unknown'))
|
171
|
+
name = model.get('name', model.get('displayName', model_id))
|
172
|
+
provider = model.get('provider', 'Unknown')
|
173
|
+
model_type = model.get('type', 'Text')
|
174
|
+
|
175
|
+
table.add_row(model_id, name, provider, model_type)
|
176
|
+
|
177
|
+
console.print(table)
|
178
|
+
|
179
|
+
except Exception as e:
|
180
|
+
console.print(f"[red]Failed to fetch models: {e}[/red]")
|
181
|
+
sys.exit(1)
|
182
|
+
|
183
|
+
def start_chat(args):
|
184
|
+
"""Start an interactive chat session"""
|
185
|
+
try:
|
186
|
+
config_path = args.config or 'cost_katana_config.json'
|
187
|
+
|
188
|
+
if Path(config_path).exists():
|
189
|
+
configure(config_file=config_path)
|
190
|
+
config = Config.from_file(config_path)
|
191
|
+
elif args.api_key:
|
192
|
+
configure(api_key=args.api_key)
|
193
|
+
config = Config(api_key=args.api_key)
|
194
|
+
else:
|
195
|
+
console.print("[red]No configuration found. Run 'cost-katana init' first.[/red]")
|
196
|
+
return
|
197
|
+
|
198
|
+
model_name = args.model or config.default_model
|
199
|
+
|
200
|
+
console.print(Panel(
|
201
|
+
f"[bold blue]Cost Katana Chat Session[/bold blue]\n"
|
202
|
+
f"Model: {model_name}\n"
|
203
|
+
f"Type 'quit' to exit, 'clear' to clear history",
|
204
|
+
title="Welcome"
|
205
|
+
))
|
206
|
+
|
207
|
+
model = GenerativeModel(model_name)
|
208
|
+
chat = model.start_chat()
|
209
|
+
|
210
|
+
total_cost = 0.0
|
211
|
+
|
212
|
+
while True:
|
213
|
+
try:
|
214
|
+
message = Prompt.ask("[bold cyan]You[/bold cyan]")
|
215
|
+
|
216
|
+
if message.lower() in ['quit', 'exit', 'q']:
|
217
|
+
break
|
218
|
+
elif message.lower() == 'clear':
|
219
|
+
chat.clear_history()
|
220
|
+
console.print("[yellow]Chat history cleared.[/yellow]")
|
221
|
+
continue
|
222
|
+
elif message.lower() == 'cost':
|
223
|
+
console.print(f"[green]Total session cost: ${total_cost:.4f}[/green]")
|
224
|
+
continue
|
225
|
+
|
226
|
+
console.print("[bold green]Assistant[/bold green]: ", end="")
|
227
|
+
|
228
|
+
with console.status("Thinking..."):
|
229
|
+
response = chat.send_message(message)
|
230
|
+
|
231
|
+
console.print(response.text)
|
232
|
+
|
233
|
+
# Show cost info
|
234
|
+
total_cost += response.usage_metadata.cost
|
235
|
+
console.print(
|
236
|
+
f"[dim]Cost: ${response.usage_metadata.cost:.4f} | "
|
237
|
+
f"Total: ${total_cost:.4f} | "
|
238
|
+
f"Tokens: {response.usage_metadata.total_tokens}[/dim]\n"
|
239
|
+
)
|
240
|
+
|
241
|
+
except KeyboardInterrupt:
|
242
|
+
console.print("\n[yellow]Chat session interrupted.[/yellow]")
|
243
|
+
break
|
244
|
+
except Exception as e:
|
245
|
+
console.print(f"[red]Error: {e}[/red]")
|
246
|
+
continue
|
247
|
+
|
248
|
+
console.print(f"\n[bold]Session Summary:[/bold]")
|
249
|
+
console.print(f"Total Cost: ${total_cost:.4f}")
|
250
|
+
console.print("Thanks for using Cost Katana!")
|
251
|
+
|
252
|
+
except Exception as e:
|
253
|
+
console.print(f"[red]Failed to start chat: {e}[/red]")
|
254
|
+
sys.exit(1)
|
255
|
+
|
256
|
+
def main():
|
257
|
+
"""Main CLI entry point"""
|
258
|
+
parser = argparse.ArgumentParser(
|
259
|
+
description="Cost Katana - Unified AI interface with cost optimization"
|
260
|
+
)
|
261
|
+
parser.add_argument(
|
262
|
+
'--config', '-c',
|
263
|
+
help='Configuration file path (default: cost_katana_config.json)'
|
264
|
+
)
|
265
|
+
parser.add_argument(
|
266
|
+
'--api-key', '-k',
|
267
|
+
help='Cost Katana API key'
|
268
|
+
)
|
269
|
+
|
270
|
+
subparsers = parser.add_subparsers(dest='command', help='Available commands')
|
271
|
+
|
272
|
+
# Init command
|
273
|
+
init_parser = subparsers.add_parser('init', help='Initialize configuration')
|
274
|
+
init_parser.add_argument('--force', action='store_true', help='Overwrite existing config')
|
275
|
+
|
276
|
+
# Test command
|
277
|
+
subparsers.add_parser('test', help='Test API connection')
|
278
|
+
|
279
|
+
# Models command
|
280
|
+
subparsers.add_parser('models', help='List available models')
|
281
|
+
|
282
|
+
# Chat command
|
283
|
+
chat_parser = subparsers.add_parser('chat', help='Start interactive chat')
|
284
|
+
chat_parser.add_argument('--model', '-m', help='Model to use for chat')
|
285
|
+
|
286
|
+
args = parser.parse_args()
|
287
|
+
|
288
|
+
if not args.command:
|
289
|
+
parser.print_help()
|
290
|
+
return
|
291
|
+
|
292
|
+
# Route to appropriate function
|
293
|
+
if args.command == 'init':
|
294
|
+
init_config(args)
|
295
|
+
elif args.command == 'test':
|
296
|
+
test_connection(args)
|
297
|
+
elif args.command == 'models':
|
298
|
+
list_models(args)
|
299
|
+
elif args.command == 'chat':
|
300
|
+
start_chat(args)
|
301
|
+
|
302
|
+
if __name__ == '__main__':
|
303
|
+
main()
|
cost_katana/client.py
ADDED
@@ -0,0 +1,235 @@
|
|
1
|
+
"""
|
2
|
+
Cost Katana HTTP Client
|
3
|
+
Handles communication with the Cost Katana backend API
|
4
|
+
"""
|
5
|
+
|
6
|
+
import json
|
7
|
+
import os
|
8
|
+
from typing import Dict, Any, Optional, List
|
9
|
+
import httpx
|
10
|
+
from .config import Config
|
11
|
+
from .exceptions import (
|
12
|
+
CostKatanaError,
|
13
|
+
AuthenticationError,
|
14
|
+
ModelNotAvailableError,
|
15
|
+
RateLimitError,
|
16
|
+
CostLimitExceededError
|
17
|
+
)
|
18
|
+
|
19
|
+
# Global client instance for the configure function
|
20
|
+
_global_client = None
|
21
|
+
|
22
|
+
def configure(
|
23
|
+
api_key: str = None,
|
24
|
+
base_url: str = None,
|
25
|
+
config_file: str = None,
|
26
|
+
**kwargs
|
27
|
+
):
|
28
|
+
"""
|
29
|
+
Configure Cost Katana client globally.
|
30
|
+
|
31
|
+
Args:
|
32
|
+
api_key: Your Cost Katana API key (starts with 'dak_')
|
33
|
+
base_url: Base URL for Cost Katana API (optional)
|
34
|
+
config_file: Path to JSON configuration file (optional)
|
35
|
+
**kwargs: Additional configuration options
|
36
|
+
|
37
|
+
Example:
|
38
|
+
# Using API key
|
39
|
+
cost_katana.configure(api_key='dak_your_key_here')
|
40
|
+
|
41
|
+
# Using config file
|
42
|
+
cost_katana.configure(config_file='config.json')
|
43
|
+
"""
|
44
|
+
global _global_client
|
45
|
+
_global_client = CostKatanaClient(
|
46
|
+
api_key=api_key,
|
47
|
+
base_url=base_url,
|
48
|
+
config_file=config_file,
|
49
|
+
**kwargs
|
50
|
+
)
|
51
|
+
return _global_client
|
52
|
+
|
53
|
+
def get_global_client():
|
54
|
+
"""Get the global client instance"""
|
55
|
+
if _global_client is None:
|
56
|
+
raise CostKatanaError("Cost Katana not configured. Call cost_katana.configure() first.")
|
57
|
+
return _global_client
|
58
|
+
|
59
|
+
class CostKatanaClient:
|
60
|
+
"""HTTP client for Cost Katana API"""
|
61
|
+
|
62
|
+
def __init__(
|
63
|
+
self,
|
64
|
+
api_key: str = None,
|
65
|
+
base_url: str = None,
|
66
|
+
config_file: str = None,
|
67
|
+
timeout: int = 30,
|
68
|
+
**kwargs
|
69
|
+
):
|
70
|
+
"""
|
71
|
+
Initialize Cost Katana client.
|
72
|
+
|
73
|
+
Args:
|
74
|
+
api_key: Your Cost Katana API key
|
75
|
+
base_url: Base URL for the API
|
76
|
+
config_file: Path to JSON configuration file
|
77
|
+
timeout: Request timeout in seconds
|
78
|
+
"""
|
79
|
+
self.config = Config.from_file(config_file) if config_file else Config()
|
80
|
+
|
81
|
+
# Override with provided parameters
|
82
|
+
if api_key:
|
83
|
+
self.config.api_key = api_key
|
84
|
+
if base_url:
|
85
|
+
self.config.base_url = base_url
|
86
|
+
|
87
|
+
# Apply additional config
|
88
|
+
for key, value in kwargs.items():
|
89
|
+
setattr(self.config, key, value)
|
90
|
+
|
91
|
+
# Validate configuration
|
92
|
+
if not self.config.api_key:
|
93
|
+
raise AuthenticationError(
|
94
|
+
"API key is required. Get one from https://costkatana.com/dashboard/api-keys"
|
95
|
+
)
|
96
|
+
|
97
|
+
# Initialize HTTP client
|
98
|
+
self.client = httpx.Client(
|
99
|
+
base_url=self.config.base_url,
|
100
|
+
timeout=timeout,
|
101
|
+
headers={
|
102
|
+
'Authorization': f'Bearer {self.config.api_key}',
|
103
|
+
'Content-Type': 'application/json',
|
104
|
+
'User-Agent': f'cost-katana-python/1.0.0'
|
105
|
+
}
|
106
|
+
)
|
107
|
+
|
108
|
+
def __enter__(self):
|
109
|
+
return self
|
110
|
+
|
111
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
112
|
+
self.close()
|
113
|
+
|
114
|
+
def close(self):
|
115
|
+
"""Close the HTTP client"""
|
116
|
+
if hasattr(self, 'client'):
|
117
|
+
self.client.close()
|
118
|
+
|
119
|
+
def _handle_response(self, response: httpx.Response) -> Dict[str, Any]:
|
120
|
+
"""Handle HTTP response and raise appropriate exceptions"""
|
121
|
+
try:
|
122
|
+
data = response.json()
|
123
|
+
except json.JSONDecodeError:
|
124
|
+
raise CostKatanaError(f"Invalid JSON response: {response.text}")
|
125
|
+
|
126
|
+
if response.status_code == 401:
|
127
|
+
raise AuthenticationError(data.get('message', 'Authentication failed'))
|
128
|
+
elif response.status_code == 403:
|
129
|
+
raise AuthenticationError(data.get('message', 'Access forbidden'))
|
130
|
+
elif response.status_code == 404:
|
131
|
+
raise ModelNotAvailableError(data.get('message', 'Model not found'))
|
132
|
+
elif response.status_code == 429:
|
133
|
+
raise RateLimitError(data.get('message', 'Rate limit exceeded'))
|
134
|
+
elif response.status_code == 400 and 'cost' in data.get('message', '').lower():
|
135
|
+
raise CostLimitExceededError(data.get('message', 'Cost limit exceeded'))
|
136
|
+
elif not response.is_success:
|
137
|
+
raise CostKatanaError(
|
138
|
+
data.get('message', f'API error: {response.status_code}')
|
139
|
+
)
|
140
|
+
|
141
|
+
return data
|
142
|
+
|
143
|
+
def get_available_models(self) -> List[Dict[str, Any]]:
|
144
|
+
"""Get list of available models"""
|
145
|
+
try:
|
146
|
+
response = self.client.get('/api/chat/models')
|
147
|
+
data = self._handle_response(response)
|
148
|
+
return data.get('data', [])
|
149
|
+
except Exception as e:
|
150
|
+
if isinstance(e, CostKatanaError):
|
151
|
+
raise
|
152
|
+
raise CostKatanaError(f"Failed to get models: {str(e)}")
|
153
|
+
|
154
|
+
def send_message(
|
155
|
+
self,
|
156
|
+
message: str,
|
157
|
+
model_id: str,
|
158
|
+
conversation_id: str = None,
|
159
|
+
temperature: float = 0.7,
|
160
|
+
max_tokens: int = 2000,
|
161
|
+
chat_mode: str = 'balanced',
|
162
|
+
use_multi_agent: bool = False,
|
163
|
+
**kwargs
|
164
|
+
) -> Dict[str, Any]:
|
165
|
+
"""
|
166
|
+
Send a message to the AI model via Cost Katana.
|
167
|
+
|
168
|
+
Args:
|
169
|
+
message: The message to send
|
170
|
+
model_id: ID of the model to use
|
171
|
+
conversation_id: Optional conversation ID
|
172
|
+
temperature: Sampling temperature (0.0 to 1.0)
|
173
|
+
max_tokens: Maximum tokens to generate
|
174
|
+
chat_mode: Chat optimization mode ('fastest', 'cheapest', 'balanced')
|
175
|
+
use_multi_agent: Whether to use multi-agent processing
|
176
|
+
|
177
|
+
Returns:
|
178
|
+
Response data from the API
|
179
|
+
"""
|
180
|
+
payload = {
|
181
|
+
'message': message,
|
182
|
+
'modelId': model_id,
|
183
|
+
'temperature': temperature,
|
184
|
+
'maxTokens': max_tokens,
|
185
|
+
'chatMode': chat_mode,
|
186
|
+
'useMultiAgent': use_multi_agent,
|
187
|
+
**kwargs
|
188
|
+
}
|
189
|
+
|
190
|
+
if conversation_id:
|
191
|
+
payload['conversationId'] = conversation_id
|
192
|
+
|
193
|
+
try:
|
194
|
+
response = self.client.post('/api/chat/message', json=payload)
|
195
|
+
return self._handle_response(response)
|
196
|
+
except Exception as e:
|
197
|
+
if isinstance(e, CostKatanaError):
|
198
|
+
raise
|
199
|
+
raise CostKatanaError(f"Failed to send message: {str(e)}")
|
200
|
+
|
201
|
+
def create_conversation(self, title: str = None, model_id: str = None) -> Dict[str, Any]:
|
202
|
+
"""Create a new conversation"""
|
203
|
+
payload = {}
|
204
|
+
if title:
|
205
|
+
payload['title'] = title
|
206
|
+
if model_id:
|
207
|
+
payload['modelId'] = model_id
|
208
|
+
|
209
|
+
try:
|
210
|
+
response = self.client.post('/api/chat/conversations', json=payload)
|
211
|
+
return self._handle_response(response)
|
212
|
+
except Exception as e:
|
213
|
+
if isinstance(e, CostKatanaError):
|
214
|
+
raise
|
215
|
+
raise CostKatanaError(f"Failed to create conversation: {str(e)}")
|
216
|
+
|
217
|
+
def get_conversation_history(self, conversation_id: str) -> Dict[str, Any]:
|
218
|
+
"""Get conversation history"""
|
219
|
+
try:
|
220
|
+
response = self.client.get(f'/api/chat/conversations/{conversation_id}/history')
|
221
|
+
return self._handle_response(response)
|
222
|
+
except Exception as e:
|
223
|
+
if isinstance(e, CostKatanaError):
|
224
|
+
raise
|
225
|
+
raise CostKatanaError(f"Failed to get conversation history: {str(e)}")
|
226
|
+
|
227
|
+
def delete_conversation(self, conversation_id: str) -> Dict[str, Any]:
|
228
|
+
"""Delete a conversation"""
|
229
|
+
try:
|
230
|
+
response = self.client.delete(f'/api/chat/conversations/{conversation_id}')
|
231
|
+
return self._handle_response(response)
|
232
|
+
except Exception as e:
|
233
|
+
if isinstance(e, CostKatanaError):
|
234
|
+
raise
|
235
|
+
raise CostKatanaError(f"Failed to delete conversation: {str(e)}")
|