projectdavid-orm 1.1.5__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.
- projectdavid_orm/__init__.py +5 -0
- projectdavid_orm/constants/__init__.py +8 -0
- projectdavid_orm/ormInterface.py +42 -0
- projectdavid_orm/projectdavid_orm/__init__.py +0 -0
- projectdavid_orm/projectdavid_orm/base.py +3 -0
- projectdavid_orm/projectdavid_orm/models.py +986 -0
- projectdavid_orm/utilities/__init__.py +0 -0
- projectdavid_orm-1.1.5.dist-info/METADATA +40 -0
- projectdavid_orm-1.1.5.dist-info/RECORD +12 -0
- projectdavid_orm-1.1.5.dist-info/WHEEL +5 -0
- projectdavid_orm-1.1.5.dist-info/licenses/LICENSE +131 -0
- projectdavid_orm-1.1.5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# src/projectdavid_orm/ormInterface.py
|
|
2
|
+
from projectdavid_orm.projectdavid_orm.models import *
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class OrmInterface:
|
|
6
|
+
"""
|
|
7
|
+
Exposes Pydantic validation classes, retaining their original naming.
|
|
8
|
+
|
|
9
|
+
This interface allows consumers to access the various schemas like:
|
|
10
|
+
- ValidationInterface.FileUploadRequest
|
|
11
|
+
- ValidationInterface.ActionCreate
|
|
12
|
+
- etc.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
# Api Key model
|
|
16
|
+
ApiKey = ApiKey
|
|
17
|
+
# User model
|
|
18
|
+
User = User
|
|
19
|
+
# GDPR Audit log
|
|
20
|
+
AuditLog = AuditLog
|
|
21
|
+
# Thread model
|
|
22
|
+
Thread = Thread
|
|
23
|
+
# Message model
|
|
24
|
+
Message = Message
|
|
25
|
+
# Run model
|
|
26
|
+
Run = Run
|
|
27
|
+
# Assistant model
|
|
28
|
+
Assistant = Assistant
|
|
29
|
+
# Action model
|
|
30
|
+
Action = Action
|
|
31
|
+
# Sandbox model
|
|
32
|
+
Sandbox = Sandbox
|
|
33
|
+
# File model
|
|
34
|
+
File = File
|
|
35
|
+
# FileStorage model
|
|
36
|
+
FileStorage = FileStorage
|
|
37
|
+
# Batfish model
|
|
38
|
+
BatfishSnapshot = BatfishSnapshot
|
|
39
|
+
# VectorStore model
|
|
40
|
+
VectorStore = VectorStore
|
|
41
|
+
# VectorStoreFile model
|
|
42
|
+
VectorStoreFile = VectorStoreFile
|
|
File without changes
|
|
@@ -0,0 +1,986 @@
|
|
|
1
|
+
import secrets
|
|
2
|
+
import time
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from enum import Enum as PyEnum
|
|
5
|
+
|
|
6
|
+
from passlib.context import CryptContext
|
|
7
|
+
from projectdavid_common import ValidationInterface
|
|
8
|
+
from projectdavid_common.projectdavid_orm.base import Base
|
|
9
|
+
from projectdavid_common.schemas.enums import StatusEnum
|
|
10
|
+
from projectdavid_common.utilities.logging_service import LoggingUtility
|
|
11
|
+
from sqlalchemy import (
|
|
12
|
+
JSON,
|
|
13
|
+
BigInteger,
|
|
14
|
+
Boolean,
|
|
15
|
+
Column,
|
|
16
|
+
DateTime,
|
|
17
|
+
)
|
|
18
|
+
from sqlalchemy import Enum as SAEnum
|
|
19
|
+
from sqlalchemy import (
|
|
20
|
+
Float,
|
|
21
|
+
ForeignKey,
|
|
22
|
+
Index,
|
|
23
|
+
Integer,
|
|
24
|
+
String,
|
|
25
|
+
Table,
|
|
26
|
+
Text,
|
|
27
|
+
UniqueConstraint,
|
|
28
|
+
)
|
|
29
|
+
from sqlalchemy.ext.mutable import MutableDict
|
|
30
|
+
from sqlalchemy.orm import joinedload, relationship
|
|
31
|
+
|
|
32
|
+
logger = LoggingUtility()
|
|
33
|
+
|
|
34
|
+
validation = ValidationInterface
|
|
35
|
+
|
|
36
|
+
# --- Association Tables ---
|
|
37
|
+
|
|
38
|
+
thread_participants = Table(
|
|
39
|
+
"thread_participants",
|
|
40
|
+
Base.metadata,
|
|
41
|
+
Column(
|
|
42
|
+
"thread_id",
|
|
43
|
+
String(64),
|
|
44
|
+
ForeignKey("threads.id", ondelete="CASCADE"),
|
|
45
|
+
primary_key=True,
|
|
46
|
+
),
|
|
47
|
+
Column(
|
|
48
|
+
"user_id",
|
|
49
|
+
String(64),
|
|
50
|
+
ForeignKey("users.id", ondelete="CASCADE"),
|
|
51
|
+
primary_key=True,
|
|
52
|
+
),
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
user_assistants = Table(
|
|
56
|
+
"user_assistants",
|
|
57
|
+
Base.metadata,
|
|
58
|
+
Column(
|
|
59
|
+
"user_id",
|
|
60
|
+
String(64),
|
|
61
|
+
ForeignKey("users.id", ondelete="CASCADE"),
|
|
62
|
+
primary_key=True,
|
|
63
|
+
),
|
|
64
|
+
Column(
|
|
65
|
+
"assistant_id",
|
|
66
|
+
String(64),
|
|
67
|
+
ForeignKey("assistants.id", ondelete="CASCADE"),
|
|
68
|
+
primary_key=True,
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# --- Enums & Context ---
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class StatusEnum(PyEnum):
|
|
77
|
+
deleted = "deleted"
|
|
78
|
+
active = "active"
|
|
79
|
+
queued = "queued"
|
|
80
|
+
in_progress = "in_progress"
|
|
81
|
+
pending_action = "action_required"
|
|
82
|
+
completed = "completed"
|
|
83
|
+
failed = "failed"
|
|
84
|
+
cancelling = "cancelling"
|
|
85
|
+
cancelled = "cancelled"
|
|
86
|
+
pending = "pending"
|
|
87
|
+
processing = "processing"
|
|
88
|
+
expired = "expired"
|
|
89
|
+
retrying = "retrying"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# --- Core Models ---
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class ApiKey(Base):
|
|
99
|
+
__tablename__ = "api_keys"
|
|
100
|
+
id = Column(Integer, primary_key=True, index=True)
|
|
101
|
+
key_name = Column(String(100), nullable=True)
|
|
102
|
+
hashed_key = Column(String(255), unique=True, nullable=False, index=True)
|
|
103
|
+
prefix = Column(String(8), unique=True, nullable=False)
|
|
104
|
+
user_id = Column(
|
|
105
|
+
String(64),
|
|
106
|
+
ForeignKey("users.id", ondelete="CASCADE"),
|
|
107
|
+
nullable=False,
|
|
108
|
+
index=True,
|
|
109
|
+
)
|
|
110
|
+
created_at = Column(DateTime, default=datetime.utcnow)
|
|
111
|
+
expires_at = Column(DateTime, nullable=True)
|
|
112
|
+
last_used_at = Column(DateTime, nullable=True)
|
|
113
|
+
is_active = Column(Boolean, default=True, nullable=False)
|
|
114
|
+
user = relationship("User", back_populates="api_keys")
|
|
115
|
+
__table_args__ = (Index("idx_apikey_user_id_active", "user_id", "is_active"),)
|
|
116
|
+
|
|
117
|
+
@staticmethod
|
|
118
|
+
def generate_key(prefix="sk_"):
|
|
119
|
+
return f"{prefix}{secrets.token_urlsafe(32)}"
|
|
120
|
+
|
|
121
|
+
@staticmethod
|
|
122
|
+
def hash_key(key: str) -> str:
|
|
123
|
+
return pwd_context.hash(key)
|
|
124
|
+
|
|
125
|
+
def verify_key(self, plain_key: str) -> bool:
|
|
126
|
+
return pwd_context.verify(plain_key, self.hashed_key)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class User(Base):
|
|
130
|
+
__tablename__ = "users"
|
|
131
|
+
id = Column(
|
|
132
|
+
String(64),
|
|
133
|
+
primary_key=True,
|
|
134
|
+
index=True,
|
|
135
|
+
comment="Internal unique identifier for the user (e.g., user_...)",
|
|
136
|
+
)
|
|
137
|
+
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
|
|
138
|
+
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
|
|
139
|
+
is_admin = Column(
|
|
140
|
+
Boolean,
|
|
141
|
+
default=False,
|
|
142
|
+
nullable=False,
|
|
143
|
+
server_default="0",
|
|
144
|
+
comment="Flag indicating administrative privileges",
|
|
145
|
+
)
|
|
146
|
+
email = Column(
|
|
147
|
+
String(255),
|
|
148
|
+
unique=True,
|
|
149
|
+
index=True,
|
|
150
|
+
nullable=True,
|
|
151
|
+
comment="Primary email address, potentially verified by OAuth provider",
|
|
152
|
+
)
|
|
153
|
+
email_verified = Column(
|
|
154
|
+
Boolean,
|
|
155
|
+
default=False,
|
|
156
|
+
nullable=True,
|
|
157
|
+
comment="Whether the email address has been verified",
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
full_name = Column(String(255), nullable=True, comment="User's full display name")
|
|
161
|
+
given_name = Column(String(128), nullable=True, comment="First name")
|
|
162
|
+
family_name = Column(String(128), nullable=True, comment="Last name")
|
|
163
|
+
picture_url = Column(Text, nullable=True, comment="URL to the user's profile picture")
|
|
164
|
+
|
|
165
|
+
oauth_provider = Column(
|
|
166
|
+
String(50),
|
|
167
|
+
nullable=True,
|
|
168
|
+
index=True,
|
|
169
|
+
comment="Name of the OAuth provider (e.g., 'google', 'github', 'local')",
|
|
170
|
+
)
|
|
171
|
+
provider_user_id = Column(
|
|
172
|
+
String(255),
|
|
173
|
+
nullable=True,
|
|
174
|
+
index=True,
|
|
175
|
+
comment="The unique ID assigned by the OAuth provider",
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# Relationships
|
|
179
|
+
api_keys = relationship(
|
|
180
|
+
"ApiKey", back_populates="user", cascade="all, delete-orphan", lazy="select"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
threads = relationship(
|
|
184
|
+
"Thread",
|
|
185
|
+
secondary=thread_participants,
|
|
186
|
+
back_populates="participants",
|
|
187
|
+
lazy="select",
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
assistants = relationship(
|
|
191
|
+
"Assistant", secondary=user_assistants, back_populates="users", lazy="select"
|
|
192
|
+
)
|
|
193
|
+
sandboxes = relationship(
|
|
194
|
+
"Sandbox", back_populates="user", cascade="all, delete-orphan", lazy="select"
|
|
195
|
+
)
|
|
196
|
+
vector_stores = relationship("VectorStore", back_populates="user", lazy="select")
|
|
197
|
+
files = relationship("File", back_populates="user", cascade="all, delete-orphan", lazy="select")
|
|
198
|
+
runs = relationship("Run", back_populates="user", lazy="select")
|
|
199
|
+
|
|
200
|
+
audit_logs = relationship("AuditLog", back_populates="user", lazy="dynamic")
|
|
201
|
+
|
|
202
|
+
__table_args__ = (
|
|
203
|
+
UniqueConstraint("oauth_provider", "provider_user_id", name="uq_user_oauth_provider_id"),
|
|
204
|
+
Index("idx_user_email", "email"),
|
|
205
|
+
Index("idx_user_is_admin", "is_admin"),
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
# ───────────────────────────────────────────────
|
|
210
|
+
# AUDIT LOGGING (GDPR & Enterprise Compliance)
|
|
211
|
+
# ───────────────────────────────────────────────
|
|
212
|
+
class AuditLog(Base):
|
|
213
|
+
"""
|
|
214
|
+
Immutable record of system actions.
|
|
215
|
+
Supports GDPR compliance by tracking deletions and administrative access.
|
|
216
|
+
|
|
217
|
+
FK: user_id → SET NULL on user deletion.
|
|
218
|
+
Audit records must survive user erasure — they are the compliance trail.
|
|
219
|
+
"""
|
|
220
|
+
|
|
221
|
+
__tablename__ = "audit_logs"
|
|
222
|
+
|
|
223
|
+
id = Column(BigInteger, primary_key=True, index=True, autoincrement=True)
|
|
224
|
+
|
|
225
|
+
# Who performed the action.
|
|
226
|
+
# Nullable: SET NULL when the user is erased (record survives, identity is anonymised).
|
|
227
|
+
user_id = Column(
|
|
228
|
+
String(64),
|
|
229
|
+
ForeignKey("users.id", ondelete="SET NULL"),
|
|
230
|
+
nullable=True,
|
|
231
|
+
index=True,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# What was done
|
|
235
|
+
action = Column(
|
|
236
|
+
String(32),
|
|
237
|
+
nullable=False,
|
|
238
|
+
index=True,
|
|
239
|
+
comment="e.g. CREATE, UPDATE, DELETE, HARD_DELETE, ERASE",
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
# What entity was affected
|
|
243
|
+
entity_type = Column(
|
|
244
|
+
String(64), nullable=False, index=True, comment="e.g. Assistant, User, Thread"
|
|
245
|
+
)
|
|
246
|
+
entity_id = Column(String(64), nullable=False, index=True)
|
|
247
|
+
|
|
248
|
+
# Context
|
|
249
|
+
timestamp = Column(DateTime, default=datetime.utcnow, nullable=False, index=True)
|
|
250
|
+
ip_address = Column(String(45), nullable=True)
|
|
251
|
+
|
|
252
|
+
# Detailed Payload (snapshot of changes)
|
|
253
|
+
details = Column(
|
|
254
|
+
JSON, nullable=True, comment="Stores before/after state or reasoning for action"
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
# Relationship
|
|
258
|
+
user = relationship("User", back_populates="audit_logs")
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class Thread(Base):
|
|
262
|
+
__tablename__ = "threads"
|
|
263
|
+
id = Column(String(64), primary_key=True, index=True)
|
|
264
|
+
created_at = Column(Integer, nullable=False)
|
|
265
|
+
meta_data = Column(JSON, nullable=False, default={})
|
|
266
|
+
object = Column(String(64), nullable=False)
|
|
267
|
+
tool_resources = Column(JSON, nullable=False, default={})
|
|
268
|
+
participants = relationship("User", secondary=thread_participants, back_populates="threads")
|
|
269
|
+
|
|
270
|
+
# SET NULL on user deletion — thread record is preserved, ownership is cleared.
|
|
271
|
+
# Application-level erase_user() handles physical cleanup of thread content.
|
|
272
|
+
owner_id = Column(
|
|
273
|
+
String(64),
|
|
274
|
+
ForeignKey("users.id", ondelete="SET NULL"),
|
|
275
|
+
nullable=True,
|
|
276
|
+
index=True,
|
|
277
|
+
comment="Canonical creator/owner of this thread. Used for row-level access control.",
|
|
278
|
+
)
|
|
279
|
+
owner = relationship("User", foreign_keys=[owner_id], lazy="select")
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class Message(Base):
|
|
283
|
+
__tablename__ = "messages"
|
|
284
|
+
id = Column(String(64), primary_key=True, index=True)
|
|
285
|
+
assistant_id = Column(String(64), index=True)
|
|
286
|
+
attachments = Column(JSON, default=[])
|
|
287
|
+
completed_at = Column(Integer, nullable=True)
|
|
288
|
+
|
|
289
|
+
# --- The "Brain" Output ---
|
|
290
|
+
content = Column(Text(length=4294967295), nullable=False)
|
|
291
|
+
reasoning = Column(
|
|
292
|
+
Text(length=4294967295),
|
|
293
|
+
nullable=True,
|
|
294
|
+
comment="Stores the internal 'thinking' or reasoning tokens from the model.",
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
# --- Native Tool Metadata (Critical for Stage 2/Level 3) ---
|
|
298
|
+
tool_calls = Column(
|
|
299
|
+
JSON,
|
|
300
|
+
nullable=True,
|
|
301
|
+
comment="Stores the native JSON array of tool calls generated by the assistant.",
|
|
302
|
+
)
|
|
303
|
+
tool_call_id = Column(
|
|
304
|
+
String(64),
|
|
305
|
+
nullable=True,
|
|
306
|
+
comment="For messages with role='tool', this links back to the specific tool_call_id.",
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
created_at = Column(Integer, nullable=False)
|
|
310
|
+
incomplete_at = Column(Integer, nullable=True)
|
|
311
|
+
incomplete_details = Column(JSON, nullable=True)
|
|
312
|
+
meta_data = Column(JSON, nullable=False, default={})
|
|
313
|
+
object = Column(String(64), nullable=False)
|
|
314
|
+
role = Column(String(32), nullable=False)
|
|
315
|
+
run_id = Column(String(64), nullable=True)
|
|
316
|
+
|
|
317
|
+
# Kept as simple String, no longer linked to deleted Tool table
|
|
318
|
+
tool_id = Column(String(64), nullable=True)
|
|
319
|
+
|
|
320
|
+
status = Column(String(32), nullable=True)
|
|
321
|
+
|
|
322
|
+
# NOTE: thread_id is intentionally stored as a plain string with no FK constraint.
|
|
323
|
+
# Orphaned messages (after thread deletion) are handled by:
|
|
324
|
+
# a) application-level erase_user() during right-to-erasure
|
|
325
|
+
# b) purge_orphaned_threads daemon (periodic background cleanup)
|
|
326
|
+
thread_id = Column(String(64), nullable=False)
|
|
327
|
+
sender_id = Column(String(64), nullable=True)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
class Run(Base):
|
|
331
|
+
__tablename__ = "runs"
|
|
332
|
+
|
|
333
|
+
# CASCADE: runs are deleted when their owning user is erased.
|
|
334
|
+
user_id = Column(
|
|
335
|
+
String(64),
|
|
336
|
+
ForeignKey("users.id", ondelete="CASCADE"),
|
|
337
|
+
nullable=True,
|
|
338
|
+
index=True,
|
|
339
|
+
)
|
|
340
|
+
user = relationship("User", back_populates="runs")
|
|
341
|
+
|
|
342
|
+
id = Column(String(64), primary_key=True)
|
|
343
|
+
assistant_id = Column(String(64), nullable=False)
|
|
344
|
+
|
|
345
|
+
# timestamps as epoch seconds (ints)
|
|
346
|
+
cancelled_at = Column(Integer, nullable=True)
|
|
347
|
+
completed_at = Column(Integer, nullable=True)
|
|
348
|
+
created_at = Column(Integer, default=lambda: int(time.time()))
|
|
349
|
+
expires_at = Column(Integer, nullable=True)
|
|
350
|
+
failed_at = Column(Integer, nullable=True)
|
|
351
|
+
started_at = Column(Integer, nullable=True)
|
|
352
|
+
|
|
353
|
+
incomplete_details = Column(String(256), nullable=True)
|
|
354
|
+
|
|
355
|
+
instructions = Column(Text, nullable=True)
|
|
356
|
+
last_error = Column(String(256), nullable=True)
|
|
357
|
+
max_completion_tokens = Column(Integer, nullable=True)
|
|
358
|
+
max_prompt_tokens = Column(Integer, nullable=True)
|
|
359
|
+
|
|
360
|
+
meta_data = Column(MutableDict.as_mutable(JSON), nullable=True, default=dict)
|
|
361
|
+
|
|
362
|
+
model = Column(String(64), nullable=True)
|
|
363
|
+
object = Column(String(64), nullable=False)
|
|
364
|
+
parallel_tool_calls = Column(Boolean, default=False)
|
|
365
|
+
required_action = Column(String(256), nullable=True)
|
|
366
|
+
response_format = Column(String(64), nullable=True)
|
|
367
|
+
|
|
368
|
+
status = Column(SAEnum(validation.StatusEnum), nullable=False)
|
|
369
|
+
|
|
370
|
+
# NOTE: thread_id stored as plain string — no FK constraint (mirrors Message.thread_id).
|
|
371
|
+
thread_id = Column(String(64), nullable=False)
|
|
372
|
+
tool_choice = Column(String(64), nullable=True)
|
|
373
|
+
|
|
374
|
+
tools = Column(JSON, nullable=True)
|
|
375
|
+
|
|
376
|
+
# --- Agentic Behavior State (Level 3) ---
|
|
377
|
+
current_turn = Column(
|
|
378
|
+
Integer,
|
|
379
|
+
default=0,
|
|
380
|
+
server_default="0",
|
|
381
|
+
comment="The current iteration count of the autonomous loop.",
|
|
382
|
+
)
|
|
383
|
+
max_turns = Column(
|
|
384
|
+
Integer,
|
|
385
|
+
default=1,
|
|
386
|
+
server_default="1",
|
|
387
|
+
comment="Snapshot of Assistant.max_turns at run creation.",
|
|
388
|
+
)
|
|
389
|
+
agent_mode = Column(
|
|
390
|
+
String(32),
|
|
391
|
+
default="standard",
|
|
392
|
+
server_default="standard",
|
|
393
|
+
comment="Determines execution logic: 'standard' (Level 2) or 'autonomous' (Level 3).",
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
truncation_strategy = Column(
|
|
397
|
+
String(16),
|
|
398
|
+
nullable=True,
|
|
399
|
+
default="auto",
|
|
400
|
+
server_default="auto",
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
usage = Column(JSON, nullable=True)
|
|
404
|
+
temperature = Column(Integer, nullable=True)
|
|
405
|
+
top_p = Column(Integer, nullable=True)
|
|
406
|
+
tool_resources = Column(JSON, nullable=True)
|
|
407
|
+
|
|
408
|
+
actions = relationship("Action", back_populates="run")
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
class Assistant(Base):
|
|
412
|
+
__tablename__ = "assistants"
|
|
413
|
+
|
|
414
|
+
id = Column(String(64), primary_key=True, index=True)
|
|
415
|
+
object = Column(String(64), nullable=False)
|
|
416
|
+
created_at = Column(Integer, nullable=False)
|
|
417
|
+
|
|
418
|
+
name = Column(String(128), nullable=False)
|
|
419
|
+
description = Column(String(256))
|
|
420
|
+
model = Column(String(64))
|
|
421
|
+
instructions = Column(Text)
|
|
422
|
+
|
|
423
|
+
tool_configs = Column(JSON)
|
|
424
|
+
meta_data = Column(JSON)
|
|
425
|
+
top_p = Column(Integer)
|
|
426
|
+
temperature = Column(Integer)
|
|
427
|
+
response_format = Column(String(64))
|
|
428
|
+
|
|
429
|
+
tool_resources = Column(
|
|
430
|
+
JSON,
|
|
431
|
+
nullable=True,
|
|
432
|
+
comment='Resource map keyed by tool type, e.g. {"file_search": {"vector_store_ids": ["vs_123","vs_456"]}}',
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
# SET NULL on user deletion — assistant record is preserved (may be shared with
|
|
436
|
+
# other users via user_assistants). Application-level erase_user() decides
|
|
437
|
+
# whether to soft-delete exclusively-owned assistants.
|
|
438
|
+
owner_id = Column(
|
|
439
|
+
String(64),
|
|
440
|
+
ForeignKey("users.id", ondelete="SET NULL"),
|
|
441
|
+
nullable=True,
|
|
442
|
+
index=True,
|
|
443
|
+
comment=(
|
|
444
|
+
"Canonical owner of this assistant. "
|
|
445
|
+
"Primary key for row-level access filtering. "
|
|
446
|
+
"Separate from the many-to-many (user_assistants) which handles sharing."
|
|
447
|
+
),
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
# --- Agentic Behavior Extensions (Level 3) ---
|
|
451
|
+
max_turns = Column(
|
|
452
|
+
Integer,
|
|
453
|
+
default=1,
|
|
454
|
+
server_default="1",
|
|
455
|
+
comment="Max number of iterative loops for Level 3 agency. 1 = Standard Level 2 (ReAct).",
|
|
456
|
+
)
|
|
457
|
+
agent_mode = Column(
|
|
458
|
+
Boolean,
|
|
459
|
+
default=False,
|
|
460
|
+
server_default="0",
|
|
461
|
+
nullable=False,
|
|
462
|
+
comment="False = Standard (Level 2), True = Autonomous (Level 3).",
|
|
463
|
+
)
|
|
464
|
+
web_access = Column(
|
|
465
|
+
Boolean,
|
|
466
|
+
default=False,
|
|
467
|
+
server_default="0",
|
|
468
|
+
nullable=False,
|
|
469
|
+
comment="Enable live web search and browsing capabilities.",
|
|
470
|
+
)
|
|
471
|
+
deep_research = Column(
|
|
472
|
+
Boolean,
|
|
473
|
+
default=False,
|
|
474
|
+
server_default="0",
|
|
475
|
+
nullable=False,
|
|
476
|
+
comment="Enable deep research capabilities.",
|
|
477
|
+
)
|
|
478
|
+
engineer = Column(
|
|
479
|
+
Boolean,
|
|
480
|
+
default=False,
|
|
481
|
+
server_default="0",
|
|
482
|
+
nullable=False,
|
|
483
|
+
comment="Enable network engineering capabilities and inventory map access.",
|
|
484
|
+
)
|
|
485
|
+
decision_telemetry = Column(
|
|
486
|
+
Boolean,
|
|
487
|
+
default=False,
|
|
488
|
+
server_default="0",
|
|
489
|
+
nullable=False,
|
|
490
|
+
comment="If True, captures detailed reasoning payloads and confidence scores.",
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
# --- GDPR / Lifecycle Management ---
|
|
494
|
+
deleted_at = Column(
|
|
495
|
+
Integer,
|
|
496
|
+
nullable=True,
|
|
497
|
+
default=None,
|
|
498
|
+
comment="Unix timestamp of soft-deletion. If present, entity is in 'Recycle Bin'.",
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
# --- Relationships ---
|
|
502
|
+
owner = relationship(
|
|
503
|
+
"User",
|
|
504
|
+
foreign_keys=[owner_id],
|
|
505
|
+
lazy="select",
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
users = relationship(
|
|
509
|
+
"User",
|
|
510
|
+
secondary="user_assistants",
|
|
511
|
+
back_populates="assistants",
|
|
512
|
+
lazy="select",
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
class Action(Base):
|
|
517
|
+
__tablename__ = "actions"
|
|
518
|
+
|
|
519
|
+
id = Column(String(64), primary_key=True, index=True)
|
|
520
|
+
|
|
521
|
+
# CASCADE: actions are deleted when their parent run is deleted.
|
|
522
|
+
run_id = Column(
|
|
523
|
+
String(64),
|
|
524
|
+
ForeignKey("runs.id", ondelete="CASCADE"),
|
|
525
|
+
nullable=True,
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
# --- Agentic Tracking (Level 3) ---
|
|
529
|
+
tool_call_id = Column(
|
|
530
|
+
String(64),
|
|
531
|
+
nullable=True,
|
|
532
|
+
index=True,
|
|
533
|
+
comment="The unique ID linking this action to a specific LLM tool request.",
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
tool_name = Column(
|
|
537
|
+
String(64),
|
|
538
|
+
nullable=True,
|
|
539
|
+
comment="The name of the function/tool to be executed.",
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
turn_index = Column(
|
|
543
|
+
Integer,
|
|
544
|
+
default=0,
|
|
545
|
+
nullable=True,
|
|
546
|
+
comment="The iteration of the autonomous loop.",
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
decision_payload = Column(
|
|
550
|
+
JSON,
|
|
551
|
+
nullable=True,
|
|
552
|
+
comment="The full structured reasoning object (reason, policy, etc) preceding the tool call.",
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
confidence_score = Column(
|
|
556
|
+
Float,
|
|
557
|
+
nullable=True,
|
|
558
|
+
index=True,
|
|
559
|
+
comment="Extracted confidence score (0.0-1.0) to allow fast querying of 'uncertain' agent actions.",
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
triggered_at = Column(DateTime, default=datetime.utcnow)
|
|
563
|
+
expires_at = Column(DateTime, nullable=True)
|
|
564
|
+
is_processed = Column(Boolean, default=False)
|
|
565
|
+
processed_at = Column(DateTime, nullable=True)
|
|
566
|
+
status = Column(String(64), nullable=True)
|
|
567
|
+
function_args = Column(JSON, nullable=True)
|
|
568
|
+
result = Column(JSON, nullable=True)
|
|
569
|
+
|
|
570
|
+
# --- Relationships ---
|
|
571
|
+
run = relationship("Run", back_populates="actions")
|
|
572
|
+
|
|
573
|
+
@staticmethod
|
|
574
|
+
def get_full_action_query(session):
|
|
575
|
+
return session.query(Action).options(joinedload(Action.run))
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
class Sandbox(Base):
|
|
579
|
+
__tablename__ = "sandboxes"
|
|
580
|
+
id = Column(String(64), primary_key=True, index=True)
|
|
581
|
+
|
|
582
|
+
# CASCADE: sandboxes are deleted when their owning user is erased.
|
|
583
|
+
user_id = Column(
|
|
584
|
+
String(64),
|
|
585
|
+
ForeignKey("users.id", ondelete="CASCADE"),
|
|
586
|
+
nullable=False,
|
|
587
|
+
)
|
|
588
|
+
name = Column(String(128), nullable=False)
|
|
589
|
+
created_at = Column(DateTime, default=datetime.utcnow)
|
|
590
|
+
status = Column(String(32), nullable=False, default="active")
|
|
591
|
+
config = Column(JSON, nullable=True)
|
|
592
|
+
user = relationship("User", back_populates="sandboxes")
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
class File(Base):
|
|
596
|
+
__tablename__ = "files"
|
|
597
|
+
id = Column(String(64), primary_key=True, index=True)
|
|
598
|
+
object = Column(String(64), nullable=False, default="file")
|
|
599
|
+
bytes = Column(Integer, nullable=False)
|
|
600
|
+
created_at = Column(DateTime, nullable=False, default=datetime.utcnow)
|
|
601
|
+
expires_at = Column(DateTime, nullable=True)
|
|
602
|
+
filename = Column(String(256), nullable=False)
|
|
603
|
+
purpose = Column(String(64), nullable=False)
|
|
604
|
+
mime_type = Column(String(255))
|
|
605
|
+
|
|
606
|
+
# ── GDPR / Lifecycle ────────────────────────────────────────────
|
|
607
|
+
deleted_at = Column(
|
|
608
|
+
Integer,
|
|
609
|
+
nullable=True,
|
|
610
|
+
default=None,
|
|
611
|
+
index=True,
|
|
612
|
+
comment="Unix timestamp of soft-deletion. Non-null = file is in Recycle Bin.",
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
user_id = Column(
|
|
616
|
+
String(64),
|
|
617
|
+
ForeignKey("users.id", ondelete="CASCADE"),
|
|
618
|
+
nullable=False,
|
|
619
|
+
)
|
|
620
|
+
user = relationship("User", back_populates="files")
|
|
621
|
+
storage_locations = relationship(
|
|
622
|
+
"FileStorage", back_populates="file", cascade="all, delete-orphan"
|
|
623
|
+
)
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
class FileStorage(Base):
|
|
627
|
+
__tablename__ = "file_storage"
|
|
628
|
+
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
629
|
+
|
|
630
|
+
# CASCADE: storage records are deleted when their parent file is deleted.
|
|
631
|
+
file_id = Column(
|
|
632
|
+
String(64),
|
|
633
|
+
ForeignKey("files.id", ondelete="CASCADE"),
|
|
634
|
+
nullable=False,
|
|
635
|
+
)
|
|
636
|
+
storage_system = Column(
|
|
637
|
+
String(64),
|
|
638
|
+
nullable=False,
|
|
639
|
+
default="samba",
|
|
640
|
+
comment="Storage system type (samba, s3, etc.)",
|
|
641
|
+
)
|
|
642
|
+
storage_path = Column(
|
|
643
|
+
String(512),
|
|
644
|
+
nullable=True,
|
|
645
|
+
comment="Path to file in storage system (relative to share root)",
|
|
646
|
+
)
|
|
647
|
+
is_primary = Column(
|
|
648
|
+
Boolean,
|
|
649
|
+
default=True,
|
|
650
|
+
comment="Indicates if this is the primary storage location",
|
|
651
|
+
)
|
|
652
|
+
created_at = Column(
|
|
653
|
+
DateTime,
|
|
654
|
+
nullable=False,
|
|
655
|
+
default=datetime.utcnow,
|
|
656
|
+
comment="When this storage entry was created",
|
|
657
|
+
)
|
|
658
|
+
file = relationship("File", back_populates="storage_locations")
|
|
659
|
+
__table_args__ = (Index("idx_file_storage_file_id", "file_id"),)
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
class BatfishSnapshot(Base):
|
|
663
|
+
__tablename__ = "batfish_snapshots"
|
|
664
|
+
|
|
665
|
+
id = Column(
|
|
666
|
+
String(64),
|
|
667
|
+
primary_key=True,
|
|
668
|
+
index=True,
|
|
669
|
+
comment="Opaque snapshot ID returned to caller e.g. snap_abc123",
|
|
670
|
+
)
|
|
671
|
+
|
|
672
|
+
snapshot_name = Column(
|
|
673
|
+
String(128),
|
|
674
|
+
nullable=False,
|
|
675
|
+
comment="Caller-supplied label e.g. 'incident_001'",
|
|
676
|
+
)
|
|
677
|
+
|
|
678
|
+
snapshot_key = Column(
|
|
679
|
+
String(256),
|
|
680
|
+
nullable=False,
|
|
681
|
+
unique=True,
|
|
682
|
+
index=True,
|
|
683
|
+
comment="Namespaced isolation key: {user_id}_{id}",
|
|
684
|
+
)
|
|
685
|
+
|
|
686
|
+
# CASCADE: snapshots are deleted when their owning user is erased.
|
|
687
|
+
user_id = Column(
|
|
688
|
+
String(64),
|
|
689
|
+
ForeignKey("users.id", ondelete="CASCADE"),
|
|
690
|
+
nullable=False,
|
|
691
|
+
index=True,
|
|
692
|
+
)
|
|
693
|
+
|
|
694
|
+
configs_root = Column(String(512), nullable=True)
|
|
695
|
+
|
|
696
|
+
device_count = Column(Integer, default=0, nullable=False)
|
|
697
|
+
devices = Column(
|
|
698
|
+
JSON,
|
|
699
|
+
default=list,
|
|
700
|
+
nullable=False,
|
|
701
|
+
comment="List of hostnames ingested into this snapshot",
|
|
702
|
+
)
|
|
703
|
+
|
|
704
|
+
status = Column(
|
|
705
|
+
SAEnum(StatusEnum),
|
|
706
|
+
nullable=False,
|
|
707
|
+
default=StatusEnum.pending,
|
|
708
|
+
)
|
|
709
|
+
|
|
710
|
+
error_message = Column(Text, nullable=True)
|
|
711
|
+
|
|
712
|
+
created_at = Column(BigInteger, default=lambda: int(time.time()), nullable=False)
|
|
713
|
+
updated_at = Column(BigInteger, default=lambda: int(time.time()), nullable=False)
|
|
714
|
+
last_ingested_at = Column(BigInteger, nullable=True)
|
|
715
|
+
|
|
716
|
+
user = relationship("User", lazy="select")
|
|
717
|
+
|
|
718
|
+
__table_args__ = (
|
|
719
|
+
UniqueConstraint("user_id", "snapshot_name", name="uq_batfish_user_snapshot_name"),
|
|
720
|
+
Index("idx_batfish_user_id", "user_id"),
|
|
721
|
+
Index("idx_batfish_status", "status"),
|
|
722
|
+
)
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
class VectorStore(Base):
|
|
726
|
+
__tablename__ = "vector_stores"
|
|
727
|
+
id = Column(String(64), primary_key=True, index=True)
|
|
728
|
+
name = Column(String(128), nullable=False, unique=False)
|
|
729
|
+
|
|
730
|
+
user_id = Column(
|
|
731
|
+
String(64),
|
|
732
|
+
ForeignKey("users.id", ondelete="CASCADE"),
|
|
733
|
+
nullable=False,
|
|
734
|
+
)
|
|
735
|
+
collection_name = Column(String(128), nullable=False, unique=True)
|
|
736
|
+
vector_size = Column(Integer, nullable=False)
|
|
737
|
+
distance_metric = Column(String(32), nullable=False)
|
|
738
|
+
created_at = Column(BigInteger, default=lambda: int(datetime.now().timestamp()))
|
|
739
|
+
updated_at = Column(BigInteger, onupdate=lambda: int(datetime.now().timestamp()))
|
|
740
|
+
status = Column(SAEnum(StatusEnum), nullable=False)
|
|
741
|
+
config = Column(JSON, nullable=True)
|
|
742
|
+
file_count = Column(Integer, default=0, nullable=False)
|
|
743
|
+
|
|
744
|
+
# ── GDPR / Lifecycle ────────────────────────────────────────────────────
|
|
745
|
+
deleted_at = Column(
|
|
746
|
+
Integer,
|
|
747
|
+
nullable=True,
|
|
748
|
+
default=None,
|
|
749
|
+
index=True,
|
|
750
|
+
comment="Unix timestamp of soft-deletion. Non-null = store is in Recycle Bin.",
|
|
751
|
+
)
|
|
752
|
+
|
|
753
|
+
user = relationship("User", back_populates="vector_stores", lazy="select")
|
|
754
|
+
files = relationship(
|
|
755
|
+
"VectorStoreFile",
|
|
756
|
+
back_populates="vector_store",
|
|
757
|
+
cascade="all, delete-orphan",
|
|
758
|
+
lazy="dynamic",
|
|
759
|
+
)
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
class VectorStoreFile(Base):
|
|
763
|
+
__tablename__ = "vector_store_files"
|
|
764
|
+
id = Column(String(64), primary_key=True, index=True)
|
|
765
|
+
|
|
766
|
+
# CASCADE: vector store files are deleted when their parent store is deleted.
|
|
767
|
+
vector_store_id = Column(
|
|
768
|
+
String(64),
|
|
769
|
+
ForeignKey("vector_stores.id", ondelete="CASCADE"),
|
|
770
|
+
nullable=False,
|
|
771
|
+
)
|
|
772
|
+
file_name = Column(String(256), nullable=False)
|
|
773
|
+
file_path = Column(String(1024), nullable=False)
|
|
774
|
+
processed_at = Column(Integer, nullable=True)
|
|
775
|
+
status = Column(SAEnum(StatusEnum), default=StatusEnum.queued)
|
|
776
|
+
error_message = Column(Text, nullable=True)
|
|
777
|
+
meta_data = Column(JSON, nullable=True)
|
|
778
|
+
vector_store = relationship("VectorStore", back_populates="files")
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
# ---------------------------------------------------------------------------
|
|
782
|
+
# Training API
|
|
783
|
+
#
|
|
784
|
+
# -------------------------------------------------------------------------------
|
|
785
|
+
|
|
786
|
+
# src/api/training/models/models.py
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
class Dataset(Base):
|
|
790
|
+
__tablename__ = "datasets"
|
|
791
|
+
|
|
792
|
+
id = Column(
|
|
793
|
+
String(64),
|
|
794
|
+
primary_key=True,
|
|
795
|
+
index=True,
|
|
796
|
+
comment="Opaque dataset ID e.g. ds_abc123",
|
|
797
|
+
)
|
|
798
|
+
user_id = Column(
|
|
799
|
+
String(64),
|
|
800
|
+
ForeignKey("users.id", ondelete="CASCADE"),
|
|
801
|
+
nullable=False,
|
|
802
|
+
index=True,
|
|
803
|
+
)
|
|
804
|
+
name = Column(String(128), nullable=False)
|
|
805
|
+
description = Column(Text, nullable=True)
|
|
806
|
+
format = Column(
|
|
807
|
+
String(32),
|
|
808
|
+
nullable=False,
|
|
809
|
+
comment="Training format: chatml | alpaca | sharegpt | jsonl",
|
|
810
|
+
)
|
|
811
|
+
|
|
812
|
+
# Reference to the uploaded file in the core API files table.
|
|
813
|
+
# The actual Samba path is resolved via GET /v1/files/{file_id} at training time.
|
|
814
|
+
file_id = Column(
|
|
815
|
+
String(64),
|
|
816
|
+
nullable=False,
|
|
817
|
+
index=True,
|
|
818
|
+
comment="Reference to the file_id in the core API files table.",
|
|
819
|
+
)
|
|
820
|
+
|
|
821
|
+
# Populated by the training worker after it stages the file for training.
|
|
822
|
+
storage_path = Column(
|
|
823
|
+
String(512),
|
|
824
|
+
nullable=True,
|
|
825
|
+
comment="Resolved Samba path — populated by worker at training time.",
|
|
826
|
+
)
|
|
827
|
+
|
|
828
|
+
train_samples = Column(Integer, nullable=True)
|
|
829
|
+
eval_samples = Column(Integer, nullable=True)
|
|
830
|
+
config = Column(JSON, nullable=True)
|
|
831
|
+
status = Column(
|
|
832
|
+
SAEnum(StatusEnum),
|
|
833
|
+
nullable=False,
|
|
834
|
+
default=StatusEnum.pending,
|
|
835
|
+
comment="pending → processing → active → failed",
|
|
836
|
+
)
|
|
837
|
+
created_at = Column(BigInteger, default=lambda: int(time.time()), nullable=False)
|
|
838
|
+
updated_at = Column(BigInteger, default=lambda: int(time.time()), nullable=False)
|
|
839
|
+
deleted_at = Column(
|
|
840
|
+
Integer,
|
|
841
|
+
nullable=True,
|
|
842
|
+
default=None,
|
|
843
|
+
index=True,
|
|
844
|
+
comment="Unix timestamp of soft-deletion.",
|
|
845
|
+
)
|
|
846
|
+
|
|
847
|
+
training_jobs = relationship("TrainingJob", back_populates="dataset", lazy="dynamic")
|
|
848
|
+
|
|
849
|
+
__table_args__ = (
|
|
850
|
+
Index("idx_dataset_user_id", "user_id"),
|
|
851
|
+
Index("idx_dataset_status", "status"),
|
|
852
|
+
)
|
|
853
|
+
|
|
854
|
+
|
|
855
|
+
class TrainingJob(Base):
|
|
856
|
+
__tablename__ = "training_jobs"
|
|
857
|
+
|
|
858
|
+
id = Column(String(64), primary_key=True, index=True, comment="Opaque job ID e.g. tj_abc123")
|
|
859
|
+
user_id = Column(
|
|
860
|
+
String(64),
|
|
861
|
+
ForeignKey("users.id", ondelete="CASCADE"),
|
|
862
|
+
nullable=False,
|
|
863
|
+
index=True,
|
|
864
|
+
)
|
|
865
|
+
dataset_id = Column(
|
|
866
|
+
String(64),
|
|
867
|
+
ForeignKey("datasets.id", ondelete="SET NULL"),
|
|
868
|
+
nullable=True,
|
|
869
|
+
index=True,
|
|
870
|
+
comment="Source dataset. SET NULL if dataset is deleted — job record is preserved.",
|
|
871
|
+
)
|
|
872
|
+
base_model = Column(
|
|
873
|
+
String(256),
|
|
874
|
+
nullable=False,
|
|
875
|
+
comment="Base model identifier e.g. Qwen/Qwen2.5-7B-Instruct",
|
|
876
|
+
)
|
|
877
|
+
framework = Column(
|
|
878
|
+
String(32),
|
|
879
|
+
nullable=False,
|
|
880
|
+
default="axolotl",
|
|
881
|
+
comment="Training framework: axolotl | unsloth",
|
|
882
|
+
)
|
|
883
|
+
config = Column(
|
|
884
|
+
JSON,
|
|
885
|
+
nullable=True,
|
|
886
|
+
comment="Complete training configuration passed to the training container.",
|
|
887
|
+
)
|
|
888
|
+
status = Column(
|
|
889
|
+
SAEnum(StatusEnum),
|
|
890
|
+
nullable=False,
|
|
891
|
+
default=StatusEnum.queued,
|
|
892
|
+
comment="queued → in_progress → completed | failed | cancelled",
|
|
893
|
+
)
|
|
894
|
+
created_at = Column(BigInteger, default=lambda: int(time.time()), nullable=False)
|
|
895
|
+
started_at = Column(BigInteger, nullable=True)
|
|
896
|
+
completed_at = Column(BigInteger, nullable=True)
|
|
897
|
+
failed_at = Column(BigInteger, nullable=True)
|
|
898
|
+
last_error = Column(Text, nullable=True)
|
|
899
|
+
metrics = Column(
|
|
900
|
+
JSON,
|
|
901
|
+
nullable=True,
|
|
902
|
+
comment="Final training metrics: loss, eval_loss, perplexity etc.",
|
|
903
|
+
)
|
|
904
|
+
output_path = Column(
|
|
905
|
+
String(512),
|
|
906
|
+
nullable=True,
|
|
907
|
+
comment="Samba path to the training output checkpoint.",
|
|
908
|
+
)
|
|
909
|
+
|
|
910
|
+
dataset = relationship("Dataset", back_populates="training_jobs", lazy="select")
|
|
911
|
+
fine_tuned_model = relationship(
|
|
912
|
+
"FineTunedModel", back_populates="training_job", uselist=False, lazy="select"
|
|
913
|
+
)
|
|
914
|
+
|
|
915
|
+
__table_args__ = (
|
|
916
|
+
Index("idx_trainingjob_user_id", "user_id"),
|
|
917
|
+
Index("idx_trainingjob_status", "status"),
|
|
918
|
+
Index("idx_trainingjob_dataset_id", "dataset_id"),
|
|
919
|
+
)
|
|
920
|
+
|
|
921
|
+
|
|
922
|
+
class FineTunedModel(Base):
|
|
923
|
+
__tablename__ = "fine_tuned_models"
|
|
924
|
+
|
|
925
|
+
id = Column(
|
|
926
|
+
String(64),
|
|
927
|
+
primary_key=True,
|
|
928
|
+
index=True,
|
|
929
|
+
comment="Opaque model ID e.g. ftm_abc123",
|
|
930
|
+
)
|
|
931
|
+
user_id = Column(
|
|
932
|
+
String(64),
|
|
933
|
+
ForeignKey("users.id", ondelete="CASCADE"),
|
|
934
|
+
nullable=False,
|
|
935
|
+
index=True,
|
|
936
|
+
)
|
|
937
|
+
training_job_id = Column(
|
|
938
|
+
String(64),
|
|
939
|
+
ForeignKey("training_jobs.id", ondelete="SET NULL"),
|
|
940
|
+
nullable=True,
|
|
941
|
+
index=True,
|
|
942
|
+
comment="Source training job. SET NULL if job is deleted — model record is preserved.",
|
|
943
|
+
)
|
|
944
|
+
name = Column(String(128), nullable=False)
|
|
945
|
+
description = Column(Text, nullable=True)
|
|
946
|
+
base_model = Column(String(256), nullable=False, comment="Base model this was fine-tuned from.")
|
|
947
|
+
hf_repo = Column(
|
|
948
|
+
String(256),
|
|
949
|
+
nullable=True,
|
|
950
|
+
comment="HuggingFace repository ID e.g. your-org/your-model",
|
|
951
|
+
)
|
|
952
|
+
storage_path = Column(String(512), nullable=True, comment="Local Samba path to model weights.")
|
|
953
|
+
is_active = Column(
|
|
954
|
+
Boolean,
|
|
955
|
+
default=False,
|
|
956
|
+
nullable=False,
|
|
957
|
+
comment="True when this model is currently loaded in vLLM.",
|
|
958
|
+
)
|
|
959
|
+
vllm_model_id = Column(
|
|
960
|
+
String(256),
|
|
961
|
+
nullable=True,
|
|
962
|
+
comment="The VLLM_MODEL value used to serve this model.",
|
|
963
|
+
)
|
|
964
|
+
status = Column(
|
|
965
|
+
SAEnum(StatusEnum),
|
|
966
|
+
nullable=False,
|
|
967
|
+
default=StatusEnum.processing,
|
|
968
|
+
comment="processing → active → failed",
|
|
969
|
+
)
|
|
970
|
+
created_at = Column(BigInteger, default=lambda: int(time.time()), nullable=False)
|
|
971
|
+
updated_at = Column(BigInteger, default=lambda: int(time.time()), nullable=False)
|
|
972
|
+
deleted_at = Column(
|
|
973
|
+
Integer,
|
|
974
|
+
nullable=True,
|
|
975
|
+
default=None,
|
|
976
|
+
index=True,
|
|
977
|
+
comment="Unix timestamp of soft-deletion.",
|
|
978
|
+
)
|
|
979
|
+
|
|
980
|
+
training_job = relationship("TrainingJob", back_populates="fine_tuned_model", lazy="select")
|
|
981
|
+
|
|
982
|
+
__table_args__ = (
|
|
983
|
+
Index("idx_finetunedmodel_user_id", "user_id"),
|
|
984
|
+
Index("idx_finetunedmodel_status", "status"),
|
|
985
|
+
Index("idx_finetunedmodel_is_active", "is_active"),
|
|
986
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: projectdavid-orm
|
|
3
|
+
Version: 1.1.5
|
|
4
|
+
Summary: Shared SQLAlchemy ORM models for ProjectDavid services
|
|
5
|
+
Author-email: "Francis N." <francis.neequaye@projectdavid.co.uk>
|
|
6
|
+
License: Polyform
|
|
7
|
+
Project-URL: Homepage, https://github.com/frankie336/projectdavid-orm
|
|
8
|
+
Keywords: orm,sqlalchemy,models,projectdavid,internal
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: sqlalchemy>=2.0
|
|
13
|
+
Requires-Dist: pymysql
|
|
14
|
+
Requires-Dist: pydantic>=2.0
|
|
15
|
+
Requires-Dist: python-dotenv
|
|
16
|
+
Requires-Dist: projectdavid_common==0.50.0
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Requires-Dist: pytest; extra == "dev"
|
|
19
|
+
Requires-Dist: black; extra == "dev"
|
|
20
|
+
Requires-Dist: isort; extra == "dev"
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# projectdavid-orm
|
|
24
|
+
|
|
25
|
+
Shared SQLAlchemy ORM models and database utilities for the ProjectDavid platform.
|
|
26
|
+
|
|
27
|
+
Provides a single source of truth for all database models used across ProjectDavid microservices, eliminating duplication between the core API, training service, and any future services that require direct database access.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Overview
|
|
32
|
+
|
|
33
|
+
ProjectDavid is a self-hosted, multi-provider LLM runtime API. As the platform grows into multiple microservices (inference API, training API, sandbox, etc.), each service needs access to the same database schema. Rather than duplicating model definitions or making inter-service HTTP calls just to read data, `projectdavid-orm` exposes the full SQLAlchemy model layer as a shared, versioned package.
|
|
34
|
+
|
|
35
|
+
Any service that needs direct DB access simply installs this package and imports the models it needs.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
## Installation
|
|
39
|
+
```bash
|
|
40
|
+
pip install projectdavid-orm
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
projectdavid_orm/__init__.py,sha256=ez1VDbs5EGh3yWSeBBPgl0v-DweoA1DjRBd2Cv0n11M,74
|
|
2
|
+
projectdavid_orm/ormInterface.py,sha256=_0Wo1DN4xJuHdguhIGRH_d5ODOg56LwsrSY1eSfwFR8,1009
|
|
3
|
+
projectdavid_orm/constants/__init__.py,sha256=i2JnKwAKnlH8A49bQErfbZHP-PtSdJMl3TMTnup9x1M,250
|
|
4
|
+
projectdavid_orm/projectdavid_orm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
projectdavid_orm/projectdavid_orm/base.py,sha256=kha9xmklzhuQAK8QEkNBn-mAHq8dUKbOM-3abaBpWmQ,71
|
|
6
|
+
projectdavid_orm/projectdavid_orm/models.py,sha256=PzswjqmERzKTKBovjYVUXqq4JlRnrb9aEpEz7z6tCzI,31077
|
|
7
|
+
projectdavid_orm/utilities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
projectdavid_orm-1.1.5.dist-info/licenses/LICENSE,sha256=_8yjiEGttpS284BkfhXxfERqTRZW_tUaHiBB0GTJTMg,4563
|
|
9
|
+
projectdavid_orm-1.1.5.dist-info/METADATA,sha256=-nzTvhB1ibAhiaX9Y_5fzS_oWlSgGYWGLnHfPqqoIVA,1635
|
|
10
|
+
projectdavid_orm-1.1.5.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
11
|
+
projectdavid_orm-1.1.5.dist-info/top_level.txt,sha256=HyXao33WM-_0_6GrOa1nDUSE4NjA4kMyYFhXyXZduOQ,17
|
|
12
|
+
projectdavid_orm-1.1.5.dist-info/RECORD,,
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# PolyForm Noncommercial License 1.0.0
|
|
2
|
+
|
|
3
|
+
<https://polyformproject.org/licenses/noncommercial/1.0.0>
|
|
4
|
+
|
|
5
|
+
## Acceptance
|
|
6
|
+
|
|
7
|
+
In order to get any license under these terms, you must agree
|
|
8
|
+
to them as both strict obligations and conditions to all
|
|
9
|
+
your licenses.
|
|
10
|
+
|
|
11
|
+
## Copyright License
|
|
12
|
+
|
|
13
|
+
The licensor grants you a copyright license for the
|
|
14
|
+
software to do everything you might do with the software
|
|
15
|
+
that would otherwise infringe the licensor's copyright
|
|
16
|
+
in it for any permitted purpose. However, you may
|
|
17
|
+
only distribute the software according to [Distribution
|
|
18
|
+
License](#distribution-license) and make changes or new works
|
|
19
|
+
based on the software according to [Changes and New Works
|
|
20
|
+
License](#changes-and-new-works-license).
|
|
21
|
+
|
|
22
|
+
## Distribution License
|
|
23
|
+
|
|
24
|
+
The licensor grants you an additional copyright license
|
|
25
|
+
to distribute copies of the software. Your license
|
|
26
|
+
to distribute covers distributing the software with
|
|
27
|
+
changes and new works permitted by [Changes and New Works
|
|
28
|
+
License](#changes-and-new-works-license).
|
|
29
|
+
|
|
30
|
+
## Notices
|
|
31
|
+
|
|
32
|
+
You must ensure that anyone who gets a copy of any part of
|
|
33
|
+
the software from you also gets a copy of these terms or the
|
|
34
|
+
URL for them above, as well as copies of any plain-text lines
|
|
35
|
+
beginning with `Required Notice:` that the licensor provided
|
|
36
|
+
with the software. For example:
|
|
37
|
+
|
|
38
|
+
> Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
|
|
39
|
+
|
|
40
|
+
## Changes and New Works License
|
|
41
|
+
|
|
42
|
+
The licensor grants you an additional copyright license to
|
|
43
|
+
make changes and new works based on the software for any
|
|
44
|
+
permitted purpose.
|
|
45
|
+
|
|
46
|
+
## Patent License
|
|
47
|
+
|
|
48
|
+
The licensor grants you a patent license for the software that
|
|
49
|
+
covers patent claims the licensor can license, or becomes able
|
|
50
|
+
to license, that you would infringe by using the software.
|
|
51
|
+
|
|
52
|
+
## Noncommercial Purposes
|
|
53
|
+
|
|
54
|
+
Any noncommercial purpose is a permitted purpose.
|
|
55
|
+
|
|
56
|
+
## Personal Uses
|
|
57
|
+
|
|
58
|
+
Personal use for research, experiment, and testing for
|
|
59
|
+
the benefit of public knowledge, personal study, private
|
|
60
|
+
entertainment, hobby projects, amateur pursuits, or religious
|
|
61
|
+
observance, without any anticipated commercial application,
|
|
62
|
+
is use for a permitted purpose.
|
|
63
|
+
|
|
64
|
+
## Noncommercial Organizations
|
|
65
|
+
|
|
66
|
+
Use by any charitable organization, educational institution,
|
|
67
|
+
public research organization, public safety or health
|
|
68
|
+
organization, environmental protection organization,
|
|
69
|
+
or government institution is use for a permitted purpose
|
|
70
|
+
regardless of the source of funding or obligations resulting
|
|
71
|
+
from the funding.
|
|
72
|
+
|
|
73
|
+
## Fair Use
|
|
74
|
+
|
|
75
|
+
You may have "fair use" rights for the software under the
|
|
76
|
+
law. These terms do not limit them.
|
|
77
|
+
|
|
78
|
+
## No Other Rights
|
|
79
|
+
|
|
80
|
+
These terms do not allow you to sublicense or transfer any of
|
|
81
|
+
your licenses to anyone else, or prevent the licensor from
|
|
82
|
+
granting licenses to anyone else. These terms do not imply
|
|
83
|
+
any other licenses.
|
|
84
|
+
|
|
85
|
+
## Patent Defense
|
|
86
|
+
|
|
87
|
+
If you make any written claim that the software infringes or
|
|
88
|
+
contributes to infringement of any patent, your patent license
|
|
89
|
+
for the software granted under these terms ends immediately. If
|
|
90
|
+
your company makes such a claim, your patent license ends
|
|
91
|
+
immediately for work on behalf of your company.
|
|
92
|
+
|
|
93
|
+
## Violations
|
|
94
|
+
|
|
95
|
+
The first time you are notified in writing that you have
|
|
96
|
+
violated any of these terms, or done anything with the software
|
|
97
|
+
not covered by your licenses, your licenses can nonetheless
|
|
98
|
+
continue if you come into full compliance with these terms,
|
|
99
|
+
and take practical steps to correct past violations, within
|
|
100
|
+
32 days of receiving notice. Otherwise, all your licenses
|
|
101
|
+
end immediately.
|
|
102
|
+
|
|
103
|
+
## No Liability
|
|
104
|
+
|
|
105
|
+
***As far as the law allows, the software comes as is, without
|
|
106
|
+
any warranty or condition, and the licensor will not be liable
|
|
107
|
+
to you for any damages arising out of these terms or the use
|
|
108
|
+
or nature of the software, under any kind of legal claim.***
|
|
109
|
+
|
|
110
|
+
## Definitions
|
|
111
|
+
|
|
112
|
+
The **licensor** is the individual or entity offering these
|
|
113
|
+
terms, and the **software** is the software the licensor makes
|
|
114
|
+
available under these terms.
|
|
115
|
+
|
|
116
|
+
**You** refers to the individual or entity agreeing to these
|
|
117
|
+
terms.
|
|
118
|
+
|
|
119
|
+
**Your company** is any legal entity, sole proprietorship,
|
|
120
|
+
or other kind of organization that you work for, plus all
|
|
121
|
+
organizations that have control over, are under the control of,
|
|
122
|
+
or are under common control with that organization. **Control**
|
|
123
|
+
means ownership of substantially all the assets of an entity,
|
|
124
|
+
or the power to direct its management and policies by vote,
|
|
125
|
+
contract, or otherwise. Control can be direct or indirect.
|
|
126
|
+
|
|
127
|
+
**Your licenses** are all the licenses granted to you for the
|
|
128
|
+
software under these terms.
|
|
129
|
+
|
|
130
|
+
**Use** means anything you do with the software requiring one
|
|
131
|
+
of your licenses.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
projectdavid_orm
|