cortex-llm 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.
- cortex/__init__.py +73 -0
- cortex/__main__.py +83 -0
- cortex/config.py +329 -0
- cortex/conversation_manager.py +468 -0
- cortex/fine_tuning/__init__.py +8 -0
- cortex/fine_tuning/dataset.py +332 -0
- cortex/fine_tuning/mlx_lora_trainer.py +502 -0
- cortex/fine_tuning/trainer.py +957 -0
- cortex/fine_tuning/wizard.py +707 -0
- cortex/gpu_validator.py +467 -0
- cortex/inference_engine.py +727 -0
- cortex/metal/__init__.py +275 -0
- cortex/metal/gpu_validator.py +177 -0
- cortex/metal/memory_pool.py +886 -0
- cortex/metal/mlx_accelerator.py +678 -0
- cortex/metal/mlx_converter.py +638 -0
- cortex/metal/mps_optimizer.py +417 -0
- cortex/metal/optimizer.py +665 -0
- cortex/metal/performance_profiler.py +364 -0
- cortex/model_downloader.py +130 -0
- cortex/model_manager.py +2187 -0
- cortex/quantization/__init__.py +5 -0
- cortex/quantization/dynamic_quantizer.py +736 -0
- cortex/template_registry/__init__.py +15 -0
- cortex/template_registry/auto_detector.py +144 -0
- cortex/template_registry/config_manager.py +234 -0
- cortex/template_registry/interactive.py +260 -0
- cortex/template_registry/registry.py +347 -0
- cortex/template_registry/template_profiles/__init__.py +5 -0
- cortex/template_registry/template_profiles/base.py +142 -0
- cortex/template_registry/template_profiles/complex/__init__.py +5 -0
- cortex/template_registry/template_profiles/complex/reasoning.py +263 -0
- cortex/template_registry/template_profiles/standard/__init__.py +9 -0
- cortex/template_registry/template_profiles/standard/alpaca.py +73 -0
- cortex/template_registry/template_profiles/standard/chatml.py +82 -0
- cortex/template_registry/template_profiles/standard/gemma.py +103 -0
- cortex/template_registry/template_profiles/standard/llama.py +87 -0
- cortex/template_registry/template_profiles/standard/simple.py +65 -0
- cortex/ui/__init__.py +120 -0
- cortex/ui/cli.py +1685 -0
- cortex/ui/markdown_render.py +185 -0
- cortex/ui/terminal_app.py +534 -0
- cortex_llm-1.0.0.dist-info/METADATA +275 -0
- cortex_llm-1.0.0.dist-info/RECORD +48 -0
- cortex_llm-1.0.0.dist-info/WHEEL +5 -0
- cortex_llm-1.0.0.dist-info/entry_points.txt +2 -0
- cortex_llm-1.0.0.dist-info/licenses/LICENSE +21 -0
- cortex_llm-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
"""Conversation management for Cortex."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sqlite3
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Dict, Any, Optional, List, Tuple
|
|
7
|
+
from dataclasses import dataclass, asdict
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from enum import Enum
|
|
10
|
+
import hashlib
|
|
11
|
+
import uuid
|
|
12
|
+
|
|
13
|
+
class MessageRole(Enum):
|
|
14
|
+
"""Role of message sender."""
|
|
15
|
+
SYSTEM = "system"
|
|
16
|
+
USER = "user"
|
|
17
|
+
ASSISTANT = "assistant"
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class Message:
|
|
21
|
+
"""A single message in a conversation."""
|
|
22
|
+
role: MessageRole
|
|
23
|
+
content: str
|
|
24
|
+
timestamp: datetime
|
|
25
|
+
tokens: Optional[int] = None
|
|
26
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
27
|
+
message_id: Optional[str] = None
|
|
28
|
+
|
|
29
|
+
def __post_init__(self):
|
|
30
|
+
if self.message_id is None:
|
|
31
|
+
self.message_id = str(uuid.uuid4())
|
|
32
|
+
if self.metadata is None:
|
|
33
|
+
self.metadata = {}
|
|
34
|
+
|
|
35
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
36
|
+
"""Convert to dictionary."""
|
|
37
|
+
return {
|
|
38
|
+
'role': self.role.value,
|
|
39
|
+
'content': self.content,
|
|
40
|
+
'timestamp': self.timestamp.isoformat(),
|
|
41
|
+
'tokens': self.tokens,
|
|
42
|
+
'metadata': self.metadata,
|
|
43
|
+
'message_id': self.message_id
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def from_dict(cls, data: Dict[str, Any]) -> 'Message':
|
|
48
|
+
"""Create from dictionary."""
|
|
49
|
+
return cls(
|
|
50
|
+
role=MessageRole(data['role']),
|
|
51
|
+
content=data['content'],
|
|
52
|
+
timestamp=datetime.fromisoformat(data['timestamp']),
|
|
53
|
+
tokens=data.get('tokens'),
|
|
54
|
+
metadata=data.get('metadata', {}),
|
|
55
|
+
message_id=data.get('message_id')
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class Conversation:
|
|
60
|
+
"""A conversation thread."""
|
|
61
|
+
conversation_id: str
|
|
62
|
+
title: Optional[str]
|
|
63
|
+
created_at: datetime
|
|
64
|
+
updated_at: datetime
|
|
65
|
+
messages: List[Message]
|
|
66
|
+
metadata: Dict[str, Any]
|
|
67
|
+
parent_id: Optional[str] = None
|
|
68
|
+
|
|
69
|
+
def __post_init__(self):
|
|
70
|
+
if not self.conversation_id:
|
|
71
|
+
self.conversation_id = str(uuid.uuid4())
|
|
72
|
+
if self.metadata is None:
|
|
73
|
+
self.metadata = {}
|
|
74
|
+
|
|
75
|
+
def add_message(self, role: MessageRole, content: str, **kwargs) -> Message:
|
|
76
|
+
"""Add a message to the conversation."""
|
|
77
|
+
message = Message(
|
|
78
|
+
role=role,
|
|
79
|
+
content=content,
|
|
80
|
+
timestamp=datetime.now(),
|
|
81
|
+
**kwargs
|
|
82
|
+
)
|
|
83
|
+
self.messages.append(message)
|
|
84
|
+
self.updated_at = datetime.now()
|
|
85
|
+
return message
|
|
86
|
+
|
|
87
|
+
def get_context(self, max_tokens: Optional[int] = None) -> List[Message]:
|
|
88
|
+
"""Get conversation context within token limit."""
|
|
89
|
+
if max_tokens is None:
|
|
90
|
+
return self.messages
|
|
91
|
+
|
|
92
|
+
total_tokens = 0
|
|
93
|
+
context = []
|
|
94
|
+
|
|
95
|
+
for message in reversed(self.messages):
|
|
96
|
+
message_tokens = message.tokens or len(message.content.split()) * 1.3
|
|
97
|
+
if total_tokens + message_tokens > max_tokens:
|
|
98
|
+
break
|
|
99
|
+
context.insert(0, message)
|
|
100
|
+
total_tokens += message_tokens
|
|
101
|
+
|
|
102
|
+
return context
|
|
103
|
+
|
|
104
|
+
def branch(self, from_message_id: str) -> 'Conversation':
|
|
105
|
+
"""Create a branch from a specific message."""
|
|
106
|
+
branch_point = None
|
|
107
|
+
for i, msg in enumerate(self.messages):
|
|
108
|
+
if msg.message_id == from_message_id:
|
|
109
|
+
branch_point = i
|
|
110
|
+
break
|
|
111
|
+
|
|
112
|
+
if branch_point is None:
|
|
113
|
+
raise ValueError(f"Message {from_message_id} not found")
|
|
114
|
+
|
|
115
|
+
return Conversation(
|
|
116
|
+
conversation_id=str(uuid.uuid4()),
|
|
117
|
+
title=f"{self.title} (branch)" if self.title else None,
|
|
118
|
+
created_at=datetime.now(),
|
|
119
|
+
updated_at=datetime.now(),
|
|
120
|
+
messages=self.messages[:branch_point + 1].copy(),
|
|
121
|
+
metadata={'branched_from': self.conversation_id},
|
|
122
|
+
parent_id=self.conversation_id
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
126
|
+
"""Convert to dictionary."""
|
|
127
|
+
return {
|
|
128
|
+
'conversation_id': self.conversation_id,
|
|
129
|
+
'title': self.title,
|
|
130
|
+
'created_at': self.created_at.isoformat(),
|
|
131
|
+
'updated_at': self.updated_at.isoformat(),
|
|
132
|
+
'messages': [msg.to_dict() for msg in self.messages],
|
|
133
|
+
'metadata': self.metadata,
|
|
134
|
+
'parent_id': self.parent_id
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@classmethod
|
|
138
|
+
def from_dict(cls, data: Dict[str, Any]) -> 'Conversation':
|
|
139
|
+
"""Create from dictionary."""
|
|
140
|
+
return cls(
|
|
141
|
+
conversation_id=data['conversation_id'],
|
|
142
|
+
title=data.get('title'),
|
|
143
|
+
created_at=datetime.fromisoformat(data['created_at']),
|
|
144
|
+
updated_at=datetime.fromisoformat(data['updated_at']),
|
|
145
|
+
messages=[Message.from_dict(msg) for msg in data['messages']],
|
|
146
|
+
metadata=data.get('metadata', {}),
|
|
147
|
+
parent_id=data.get('parent_id')
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
def to_markdown(self) -> str:
|
|
151
|
+
"""Export conversation to Markdown format."""
|
|
152
|
+
lines = []
|
|
153
|
+
|
|
154
|
+
if self.title:
|
|
155
|
+
lines.append(f"# {self.title}")
|
|
156
|
+
lines.append("")
|
|
157
|
+
|
|
158
|
+
lines.append(f"*Created: {self.created_at.strftime('%Y-%m-%d %H:%M:%S')}*")
|
|
159
|
+
lines.append(f"*Updated: {self.updated_at.strftime('%Y-%m-%d %H:%M:%S')}*")
|
|
160
|
+
lines.append("")
|
|
161
|
+
lines.append("---")
|
|
162
|
+
lines.append("")
|
|
163
|
+
|
|
164
|
+
for message in self.messages:
|
|
165
|
+
role_header = {
|
|
166
|
+
MessageRole.SYSTEM: "### System",
|
|
167
|
+
MessageRole.USER: "### User",
|
|
168
|
+
MessageRole.ASSISTANT: "### Assistant"
|
|
169
|
+
}[message.role]
|
|
170
|
+
|
|
171
|
+
lines.append(role_header)
|
|
172
|
+
lines.append("")
|
|
173
|
+
lines.append(message.content)
|
|
174
|
+
lines.append("")
|
|
175
|
+
|
|
176
|
+
return "\n".join(lines)
|
|
177
|
+
|
|
178
|
+
class ConversationManager:
|
|
179
|
+
"""Manage conversations and persistence."""
|
|
180
|
+
|
|
181
|
+
def __init__(self, config: Any):
|
|
182
|
+
"""Initialize conversation manager."""
|
|
183
|
+
self.config = config
|
|
184
|
+
self.conversations: Dict[str, Conversation] = {}
|
|
185
|
+
self.current_conversation_id: Optional[str] = None
|
|
186
|
+
|
|
187
|
+
self.save_dir = Path(config.conversation.save_directory)
|
|
188
|
+
self.save_dir.mkdir(parents=True, exist_ok=True)
|
|
189
|
+
|
|
190
|
+
if config.conversation.auto_save:
|
|
191
|
+
self._init_database()
|
|
192
|
+
|
|
193
|
+
self._load_recent_conversations()
|
|
194
|
+
|
|
195
|
+
def _init_database(self) -> None:
|
|
196
|
+
"""Initialize SQLite database for persistence."""
|
|
197
|
+
self.db_path = self.save_dir / "conversations.db"
|
|
198
|
+
|
|
199
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
200
|
+
conn.execute("""
|
|
201
|
+
CREATE TABLE IF NOT EXISTS conversations (
|
|
202
|
+
conversation_id TEXT PRIMARY KEY,
|
|
203
|
+
title TEXT,
|
|
204
|
+
created_at TEXT,
|
|
205
|
+
updated_at TEXT,
|
|
206
|
+
parent_id TEXT,
|
|
207
|
+
metadata TEXT,
|
|
208
|
+
FOREIGN KEY (parent_id) REFERENCES conversations(conversation_id)
|
|
209
|
+
)
|
|
210
|
+
""")
|
|
211
|
+
|
|
212
|
+
conn.execute("""
|
|
213
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
214
|
+
message_id TEXT PRIMARY KEY,
|
|
215
|
+
conversation_id TEXT,
|
|
216
|
+
role TEXT,
|
|
217
|
+
content TEXT,
|
|
218
|
+
timestamp TEXT,
|
|
219
|
+
tokens INTEGER,
|
|
220
|
+
metadata TEXT,
|
|
221
|
+
FOREIGN KEY (conversation_id) REFERENCES conversations(conversation_id)
|
|
222
|
+
)
|
|
223
|
+
""")
|
|
224
|
+
|
|
225
|
+
conn.execute("""
|
|
226
|
+
CREATE INDEX IF NOT EXISTS idx_messages_conversation
|
|
227
|
+
ON messages(conversation_id)
|
|
228
|
+
""")
|
|
229
|
+
|
|
230
|
+
def _load_recent_conversations(self) -> None:
|
|
231
|
+
"""Load recent conversations from storage."""
|
|
232
|
+
if not hasattr(self, 'db_path') or not self.db_path.exists():
|
|
233
|
+
return
|
|
234
|
+
|
|
235
|
+
try:
|
|
236
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
237
|
+
cursor = conn.execute("""
|
|
238
|
+
SELECT conversation_id, title, created_at, updated_at, parent_id, metadata
|
|
239
|
+
FROM conversations
|
|
240
|
+
ORDER BY updated_at DESC
|
|
241
|
+
LIMIT ?
|
|
242
|
+
""", (self.config.conversation.max_conversation_history,))
|
|
243
|
+
|
|
244
|
+
for row in cursor:
|
|
245
|
+
conv_id = row[0]
|
|
246
|
+
|
|
247
|
+
messages_cursor = conn.execute("""
|
|
248
|
+
SELECT role, content, timestamp, tokens, metadata, message_id
|
|
249
|
+
FROM messages
|
|
250
|
+
WHERE conversation_id = ?
|
|
251
|
+
ORDER BY timestamp ASC
|
|
252
|
+
""", (conv_id,))
|
|
253
|
+
|
|
254
|
+
messages = []
|
|
255
|
+
for msg_row in messages_cursor:
|
|
256
|
+
messages.append(Message(
|
|
257
|
+
role=MessageRole(msg_row[0]),
|
|
258
|
+
content=msg_row[1],
|
|
259
|
+
timestamp=datetime.fromisoformat(msg_row[2]),
|
|
260
|
+
tokens=msg_row[3],
|
|
261
|
+
metadata=json.loads(msg_row[4]) if msg_row[4] else {},
|
|
262
|
+
message_id=msg_row[5]
|
|
263
|
+
))
|
|
264
|
+
|
|
265
|
+
conversation = Conversation(
|
|
266
|
+
conversation_id=conv_id,
|
|
267
|
+
title=row[1],
|
|
268
|
+
created_at=datetime.fromisoformat(row[2]),
|
|
269
|
+
updated_at=datetime.fromisoformat(row[3]),
|
|
270
|
+
messages=messages,
|
|
271
|
+
metadata=json.loads(row[5]) if row[5] else {},
|
|
272
|
+
parent_id=row[4]
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
self.conversations[conv_id] = conversation
|
|
276
|
+
|
|
277
|
+
except Exception as e:
|
|
278
|
+
print(f"Warning: Failed to load conversations: {e}")
|
|
279
|
+
|
|
280
|
+
def new_conversation(self, title: Optional[str] = None) -> Conversation:
|
|
281
|
+
"""Create a new conversation."""
|
|
282
|
+
conversation = Conversation(
|
|
283
|
+
conversation_id=str(uuid.uuid4()),
|
|
284
|
+
title=title,
|
|
285
|
+
created_at=datetime.now(),
|
|
286
|
+
updated_at=datetime.now(),
|
|
287
|
+
messages=[],
|
|
288
|
+
metadata={}
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
self.conversations[conversation.conversation_id] = conversation
|
|
292
|
+
self.current_conversation_id = conversation.conversation_id
|
|
293
|
+
|
|
294
|
+
if self.config.conversation.auto_save:
|
|
295
|
+
self._save_conversation(conversation)
|
|
296
|
+
|
|
297
|
+
return conversation
|
|
298
|
+
|
|
299
|
+
def get_current_conversation(self) -> Optional[Conversation]:
|
|
300
|
+
"""Get the current active conversation."""
|
|
301
|
+
if self.current_conversation_id:
|
|
302
|
+
return self.conversations.get(self.current_conversation_id)
|
|
303
|
+
return None
|
|
304
|
+
|
|
305
|
+
def switch_conversation(self, conversation_id: str) -> bool:
|
|
306
|
+
"""Switch to a different conversation."""
|
|
307
|
+
if conversation_id in self.conversations:
|
|
308
|
+
self.current_conversation_id = conversation_id
|
|
309
|
+
return True
|
|
310
|
+
return False
|
|
311
|
+
|
|
312
|
+
def add_message(
|
|
313
|
+
self,
|
|
314
|
+
role: MessageRole,
|
|
315
|
+
content: str,
|
|
316
|
+
conversation_id: Optional[str] = None,
|
|
317
|
+
**kwargs
|
|
318
|
+
) -> Message:
|
|
319
|
+
"""Add a message to a conversation."""
|
|
320
|
+
conv_id = conversation_id or self.current_conversation_id
|
|
321
|
+
|
|
322
|
+
if not conv_id:
|
|
323
|
+
conversation = self.new_conversation()
|
|
324
|
+
conv_id = conversation.conversation_id
|
|
325
|
+
|
|
326
|
+
conversation = self.conversations.get(conv_id)
|
|
327
|
+
if not conversation:
|
|
328
|
+
raise ValueError(f"Conversation {conv_id} not found")
|
|
329
|
+
|
|
330
|
+
message = conversation.add_message(role, content, **kwargs)
|
|
331
|
+
|
|
332
|
+
if self.config.conversation.auto_save:
|
|
333
|
+
self._save_message(conv_id, message)
|
|
334
|
+
|
|
335
|
+
return message
|
|
336
|
+
|
|
337
|
+
def _save_conversation(self, conversation: Conversation) -> None:
|
|
338
|
+
"""Save conversation to database."""
|
|
339
|
+
if not hasattr(self, 'db_path'):
|
|
340
|
+
return
|
|
341
|
+
|
|
342
|
+
try:
|
|
343
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
344
|
+
conn.execute("""
|
|
345
|
+
INSERT OR REPLACE INTO conversations
|
|
346
|
+
(conversation_id, title, created_at, updated_at, parent_id, metadata)
|
|
347
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
348
|
+
""", (
|
|
349
|
+
conversation.conversation_id,
|
|
350
|
+
conversation.title,
|
|
351
|
+
conversation.created_at.isoformat(),
|
|
352
|
+
conversation.updated_at.isoformat(),
|
|
353
|
+
conversation.parent_id,
|
|
354
|
+
json.dumps(conversation.metadata)
|
|
355
|
+
))
|
|
356
|
+
except Exception as e:
|
|
357
|
+
print(f"Warning: Failed to save conversation: {e}")
|
|
358
|
+
|
|
359
|
+
def _save_message(self, conversation_id: str, message: Message) -> None:
|
|
360
|
+
"""Save message to database."""
|
|
361
|
+
if not hasattr(self, 'db_path'):
|
|
362
|
+
return
|
|
363
|
+
|
|
364
|
+
try:
|
|
365
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
366
|
+
conn.execute("""
|
|
367
|
+
INSERT OR REPLACE INTO messages
|
|
368
|
+
(message_id, conversation_id, role, content, timestamp, tokens, metadata)
|
|
369
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
370
|
+
""", (
|
|
371
|
+
message.message_id,
|
|
372
|
+
conversation_id,
|
|
373
|
+
message.role.value,
|
|
374
|
+
message.content,
|
|
375
|
+
message.timestamp.isoformat(),
|
|
376
|
+
message.tokens,
|
|
377
|
+
json.dumps(message.metadata)
|
|
378
|
+
))
|
|
379
|
+
except Exception as e:
|
|
380
|
+
print(f"Warning: Failed to save message: {e}")
|
|
381
|
+
|
|
382
|
+
def export_conversation(
|
|
383
|
+
self,
|
|
384
|
+
conversation_id: Optional[str] = None,
|
|
385
|
+
format: str = "json"
|
|
386
|
+
) -> str:
|
|
387
|
+
"""Export conversation to specified format."""
|
|
388
|
+
conv_id = conversation_id or self.current_conversation_id
|
|
389
|
+
|
|
390
|
+
if not conv_id or conv_id not in self.conversations:
|
|
391
|
+
raise ValueError("No conversation to export")
|
|
392
|
+
|
|
393
|
+
conversation = self.conversations[conv_id]
|
|
394
|
+
|
|
395
|
+
if format == "json":
|
|
396
|
+
return json.dumps(conversation.to_dict(), indent=2)
|
|
397
|
+
elif format == "markdown":
|
|
398
|
+
return conversation.to_markdown()
|
|
399
|
+
else:
|
|
400
|
+
raise ValueError(f"Unsupported format: {format}")
|
|
401
|
+
|
|
402
|
+
def import_conversation(self, data: str, format: str = "json") -> Conversation:
|
|
403
|
+
"""Import conversation from string."""
|
|
404
|
+
if format == "json":
|
|
405
|
+
conv_dict = json.loads(data)
|
|
406
|
+
conversation = Conversation.from_dict(conv_dict)
|
|
407
|
+
else:
|
|
408
|
+
raise ValueError(f"Unsupported format: {format}")
|
|
409
|
+
|
|
410
|
+
self.conversations[conversation.conversation_id] = conversation
|
|
411
|
+
|
|
412
|
+
if self.config.conversation.auto_save:
|
|
413
|
+
self._save_conversation(conversation)
|
|
414
|
+
for message in conversation.messages:
|
|
415
|
+
self._save_message(conversation.conversation_id, message)
|
|
416
|
+
|
|
417
|
+
return conversation
|
|
418
|
+
|
|
419
|
+
def list_conversations(self) -> List[Dict[str, Any]]:
|
|
420
|
+
"""List all conversations."""
|
|
421
|
+
return [
|
|
422
|
+
{
|
|
423
|
+
'id': conv.conversation_id,
|
|
424
|
+
'title': conv.title or f"Conversation {conv.created_at.strftime('%Y-%m-%d %H:%M')}",
|
|
425
|
+
'created_at': conv.created_at.isoformat(),
|
|
426
|
+
'updated_at': conv.updated_at.isoformat(),
|
|
427
|
+
'message_count': len(conv.messages),
|
|
428
|
+
'is_current': conv.conversation_id == self.current_conversation_id
|
|
429
|
+
}
|
|
430
|
+
for conv in sorted(
|
|
431
|
+
self.conversations.values(),
|
|
432
|
+
key=lambda x: x.updated_at,
|
|
433
|
+
reverse=True
|
|
434
|
+
)
|
|
435
|
+
]
|
|
436
|
+
|
|
437
|
+
def delete_conversation(self, conversation_id: str) -> bool:
|
|
438
|
+
"""Delete a conversation."""
|
|
439
|
+
if conversation_id not in self.conversations:
|
|
440
|
+
return False
|
|
441
|
+
|
|
442
|
+
del self.conversations[conversation_id]
|
|
443
|
+
|
|
444
|
+
if self.current_conversation_id == conversation_id:
|
|
445
|
+
self.current_conversation_id = None
|
|
446
|
+
|
|
447
|
+
if hasattr(self, 'db_path'):
|
|
448
|
+
try:
|
|
449
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
450
|
+
conn.execute("DELETE FROM messages WHERE conversation_id = ?", (conversation_id,))
|
|
451
|
+
conn.execute("DELETE FROM conversations WHERE conversation_id = ?", (conversation_id,))
|
|
452
|
+
except Exception as e:
|
|
453
|
+
print(f"Warning: Failed to delete conversation from database: {e}")
|
|
454
|
+
|
|
455
|
+
return True
|
|
456
|
+
|
|
457
|
+
def clear_all(self) -> None:
|
|
458
|
+
"""Clear all conversations."""
|
|
459
|
+
self.conversations.clear()
|
|
460
|
+
self.current_conversation_id = None
|
|
461
|
+
|
|
462
|
+
if hasattr(self, 'db_path'):
|
|
463
|
+
try:
|
|
464
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
465
|
+
conn.execute("DELETE FROM messages")
|
|
466
|
+
conn.execute("DELETE FROM conversations")
|
|
467
|
+
except Exception as e:
|
|
468
|
+
print(f"Warning: Failed to clear database: {e}")
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Fine-tuning module for Cortex."""
|
|
2
|
+
|
|
3
|
+
from .wizard import FineTuneWizard
|
|
4
|
+
from .trainer import LoRATrainer
|
|
5
|
+
from .dataset import DatasetPreparer
|
|
6
|
+
from .mlx_lora_trainer import MLXLoRATrainer
|
|
7
|
+
|
|
8
|
+
__all__ = ['FineTuneWizard', 'LoRATrainer', 'DatasetPreparer', 'MLXLoRATrainer']
|