featrixsphere 0.2.6708__py3-none-any.whl → 0.2.6710__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.
- featrixsphere/__init__.py +1 -1
- featrixsphere/api/client.py +28 -0
- featrixsphere/api/foundational_model.py +110 -24
- {featrixsphere-0.2.6708.dist-info → featrixsphere-0.2.6710.dist-info}/METADATA +1 -1
- {featrixsphere-0.2.6708.dist-info → featrixsphere-0.2.6710.dist-info}/RECORD +8 -8
- {featrixsphere-0.2.6708.dist-info → featrixsphere-0.2.6710.dist-info}/WHEEL +0 -0
- {featrixsphere-0.2.6708.dist-info → featrixsphere-0.2.6710.dist-info}/entry_points.txt +0 -0
- {featrixsphere-0.2.6708.dist-info → featrixsphere-0.2.6710.dist-info}/top_level.txt +0 -0
featrixsphere/__init__.py
CHANGED
featrixsphere/api/client.py
CHANGED
|
@@ -342,6 +342,34 @@ class FeatrixSphere(HTTPClientMixin):
|
|
|
342
342
|
ground_truth=ground_truth
|
|
343
343
|
)
|
|
344
344
|
|
|
345
|
+
def list_sessions(
|
|
346
|
+
self,
|
|
347
|
+
name_prefix: str = "",
|
|
348
|
+
) -> List[str]:
|
|
349
|
+
"""
|
|
350
|
+
List sessions matching a name prefix/search term.
|
|
351
|
+
|
|
352
|
+
Searches session directory names on the compute cluster for
|
|
353
|
+
partial matches (not just prefix).
|
|
354
|
+
|
|
355
|
+
Args:
|
|
356
|
+
name_prefix: Term to match in session names
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
List of matching session ID strings
|
|
360
|
+
|
|
361
|
+
Example:
|
|
362
|
+
sessions = featrix.list_sessions(name_prefix="customer")
|
|
363
|
+
for sid in sessions:
|
|
364
|
+
fm = featrix.foundational_model(sid)
|
|
365
|
+
print(f"{sid}: {fm.status}")
|
|
366
|
+
"""
|
|
367
|
+
params = {}
|
|
368
|
+
if name_prefix:
|
|
369
|
+
params['name_prefix'] = name_prefix
|
|
370
|
+
response = self._get_json("/compute/sessions-for-org", params=params)
|
|
371
|
+
return response.get('sessions', [])
|
|
372
|
+
|
|
345
373
|
def health_check(self) -> Dict[str, Any]:
|
|
346
374
|
"""
|
|
347
375
|
Check if the API server is healthy.
|
|
@@ -22,6 +22,21 @@ from .reference_record import ReferenceRecord
|
|
|
22
22
|
logger = logging.getLogger(__name__)
|
|
23
23
|
|
|
24
24
|
|
|
25
|
+
def _parse_datetime(value) -> Optional[datetime]:
|
|
26
|
+
"""Parse a datetime from ISO string or return as-is if already datetime."""
|
|
27
|
+
if value is None:
|
|
28
|
+
return None
|
|
29
|
+
if isinstance(value, datetime):
|
|
30
|
+
return value
|
|
31
|
+
if isinstance(value, str):
|
|
32
|
+
try:
|
|
33
|
+
# Handle ISO format with or without timezone
|
|
34
|
+
return datetime.fromisoformat(value.replace('Z', '+00:00'))
|
|
35
|
+
except (ValueError, AttributeError):
|
|
36
|
+
return None
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
|
|
25
40
|
@dataclass
|
|
26
41
|
class FoundationalModel:
|
|
27
42
|
"""
|
|
@@ -68,6 +83,11 @@ class FoundationalModel:
|
|
|
68
83
|
epochs: Optional[int] = None
|
|
69
84
|
final_loss: Optional[float] = None
|
|
70
85
|
created_at: Optional[datetime] = None
|
|
86
|
+
updated_at: Optional[datetime] = None
|
|
87
|
+
session_type: Optional[str] = None
|
|
88
|
+
compute_cluster: Optional[str] = None
|
|
89
|
+
error_message: Optional[str] = None
|
|
90
|
+
training_progress: Optional[Dict[str, Any]] = None
|
|
71
91
|
|
|
72
92
|
# Internal
|
|
73
93
|
_ctx: Optional['ClientContext'] = field(default=None, repr=False)
|
|
@@ -88,7 +108,9 @@ class FoundationalModel:
|
|
|
88
108
|
dimensions=response.get('d_model') or response.get('dimensions'),
|
|
89
109
|
epochs=response.get('epochs') or response.get('final_epoch'),
|
|
90
110
|
final_loss=response.get('final_loss'),
|
|
91
|
-
created_at=
|
|
111
|
+
created_at=_parse_datetime(response.get('created_at')),
|
|
112
|
+
session_type=response.get('session_type'),
|
|
113
|
+
compute_cluster=response.get('compute_cluster'),
|
|
92
114
|
_ctx=ctx,
|
|
93
115
|
)
|
|
94
116
|
|
|
@@ -99,19 +121,22 @@ class FoundationalModel:
|
|
|
99
121
|
ctx: 'ClientContext'
|
|
100
122
|
) -> 'FoundationalModel':
|
|
101
123
|
"""Load FoundationalModel from session ID."""
|
|
102
|
-
# Get session info
|
|
103
|
-
|
|
124
|
+
# Get session info - response has {"session": {...}, "jobs": {...}}
|
|
125
|
+
response_data = ctx.get_json(f"/compute/session/{session_id}")
|
|
126
|
+
session = response_data.get('session', response_data)
|
|
104
127
|
|
|
105
128
|
fm = cls(
|
|
106
129
|
id=session_id,
|
|
107
|
-
name=
|
|
108
|
-
status=
|
|
109
|
-
created_at=
|
|
130
|
+
name=session.get('name'),
|
|
131
|
+
status=session.get('status'),
|
|
132
|
+
created_at=_parse_datetime(session.get('created_at')),
|
|
133
|
+
session_type=session.get('session_type'),
|
|
134
|
+
compute_cluster=session.get('compute_cluster'),
|
|
110
135
|
_ctx=ctx,
|
|
111
136
|
)
|
|
112
137
|
|
|
113
|
-
#
|
|
114
|
-
fm._update_from_session(
|
|
138
|
+
# Extract model info, training stats, jobs, error_message
|
|
139
|
+
fm._update_from_session(response_data)
|
|
115
140
|
|
|
116
141
|
return fm
|
|
117
142
|
|
|
@@ -439,10 +464,11 @@ class FoundationalModel:
|
|
|
439
464
|
last_status = None
|
|
440
465
|
|
|
441
466
|
while time.time() - start_time < max_wait_time:
|
|
442
|
-
# Get session status
|
|
443
|
-
|
|
467
|
+
# Get session status - response has {"session": {...}, "jobs": {...}}
|
|
468
|
+
response_data = self._ctx.get_json(f"/compute/session/{self.id}")
|
|
469
|
+
session_data = response_data.get('session', response_data)
|
|
444
470
|
status = session_data.get('status', 'unknown')
|
|
445
|
-
jobs =
|
|
471
|
+
jobs = response_data.get('jobs', {})
|
|
446
472
|
|
|
447
473
|
# Look for ES training job
|
|
448
474
|
es_job = None
|
|
@@ -475,7 +501,7 @@ class FoundationalModel:
|
|
|
475
501
|
# Check completion
|
|
476
502
|
if job_status == 'done' or status == 'done':
|
|
477
503
|
self.status = 'done'
|
|
478
|
-
self._update_from_session(
|
|
504
|
+
self._update_from_session(response_data)
|
|
479
505
|
if show_progress:
|
|
480
506
|
print(f"Training complete!")
|
|
481
507
|
if self.dimensions:
|
|
@@ -627,27 +653,73 @@ class FoundationalModel:
|
|
|
627
653
|
|
|
628
654
|
return predictors
|
|
629
655
|
|
|
630
|
-
def _update_from_session(self,
|
|
631
|
-
"""Update fields from session
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
656
|
+
def _update_from_session(self, response_data: Dict[str, Any]) -> None:
|
|
657
|
+
"""Update fields from session API response.
|
|
658
|
+
|
|
659
|
+
The response from GET /session/{id} has structure:
|
|
660
|
+
{"session": {...}, "jobs": {...}, ...}
|
|
661
|
+
"""
|
|
662
|
+
# Handle both nested and flat response formats
|
|
663
|
+
session = response_data.get('session', response_data)
|
|
664
|
+
jobs = response_data.get('jobs', {})
|
|
665
|
+
|
|
666
|
+
# Core session fields
|
|
667
|
+
if session.get('name') and not self.name:
|
|
668
|
+
self.name = session['name']
|
|
669
|
+
if session.get('status'):
|
|
670
|
+
self.status = session['status']
|
|
671
|
+
if session.get('session_type'):
|
|
672
|
+
self.session_type = session['session_type']
|
|
673
|
+
if session.get('compute_cluster'):
|
|
674
|
+
self.compute_cluster = session['compute_cluster']
|
|
675
|
+
if session.get('created_at') and not self.created_at:
|
|
676
|
+
self.created_at = _parse_datetime(session['created_at'])
|
|
677
|
+
if session.get('finished_at'):
|
|
678
|
+
self.updated_at = _parse_datetime(session['finished_at'])
|
|
679
|
+
elif session.get('started_at'):
|
|
680
|
+
self.updated_at = _parse_datetime(session['started_at'])
|
|
681
|
+
|
|
682
|
+
# Model info from session
|
|
683
|
+
model_info = session.get('model_info', {})
|
|
684
|
+
training_stats = session.get('training_stats', {})
|
|
635
685
|
|
|
636
686
|
self.dimensions = (
|
|
637
687
|
model_info.get('d_model') or
|
|
638
688
|
model_info.get('embedding_dim') or
|
|
639
|
-
|
|
689
|
+
session.get('d_model')
|
|
640
690
|
)
|
|
641
691
|
self.epochs = (
|
|
642
692
|
training_stats.get('final_epoch') or
|
|
643
693
|
training_stats.get('epochs_trained') or
|
|
644
|
-
|
|
694
|
+
session.get('epochs')
|
|
645
695
|
)
|
|
646
696
|
self.final_loss = (
|
|
647
697
|
training_stats.get('final_loss') or
|
|
648
|
-
|
|
698
|
+
session.get('final_loss')
|
|
649
699
|
)
|
|
650
700
|
|
|
701
|
+
# Extract error_message and training_progress from jobs
|
|
702
|
+
for job_id, job in jobs.items():
|
|
703
|
+
job_type = job.get('job_type', '')
|
|
704
|
+
job_status = job.get('status', '')
|
|
705
|
+
|
|
706
|
+
# Training progress from ES training job
|
|
707
|
+
if job_type in ('train_embedding_space', 'train_es', 'training'):
|
|
708
|
+
current_epoch = job.get('current_epoch') or job.get('epoch')
|
|
709
|
+
total_epochs = job.get('total_epochs') or job.get('epochs')
|
|
710
|
+
if current_epoch or total_epochs:
|
|
711
|
+
self.training_progress = {
|
|
712
|
+
'current_epoch': current_epoch,
|
|
713
|
+
'total_epochs': total_epochs,
|
|
714
|
+
'job_status': job_status,
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
# Error message from any failed job
|
|
718
|
+
if job_status in ('failed', 'error'):
|
|
719
|
+
err = job.get('error') or job.get('error_message')
|
|
720
|
+
if err:
|
|
721
|
+
self.error_message = err
|
|
722
|
+
|
|
651
723
|
def _clean_record(self, record: Dict[str, Any]) -> Dict[str, Any]:
|
|
652
724
|
"""Clean a record for API submission."""
|
|
653
725
|
import math
|
|
@@ -684,6 +756,17 @@ class FoundationalModel:
|
|
|
684
756
|
"""Column names in this foundational model's embedding space."""
|
|
685
757
|
return self.get_columns()
|
|
686
758
|
|
|
759
|
+
@property
|
|
760
|
+
def schema_metadata(self) -> Dict[str, Any]:
|
|
761
|
+
"""Get schema metadata including column names and types.
|
|
762
|
+
|
|
763
|
+
Returns:
|
|
764
|
+
Dict with 'column_names', 'column_types', and 'num_columns'
|
|
765
|
+
"""
|
|
766
|
+
if not self._ctx:
|
|
767
|
+
raise ValueError("FoundationalModel not connected to client")
|
|
768
|
+
return self._ctx.get_json(f"/compute/session/{self.id}/columns")
|
|
769
|
+
|
|
687
770
|
def clone(
|
|
688
771
|
self,
|
|
689
772
|
target_compute_cluster: Optional[str] = None,
|
|
@@ -749,7 +832,6 @@ class FoundationalModel:
|
|
|
749
832
|
|
|
750
833
|
data = self._ctx.get_json(f"/compute/session/{self.id}")
|
|
751
834
|
self._update_from_session(data)
|
|
752
|
-
self.status = data.get('status', self.status)
|
|
753
835
|
return data
|
|
754
836
|
|
|
755
837
|
def is_ready(self) -> bool:
|
|
@@ -767,9 +849,8 @@ class FoundationalModel:
|
|
|
767
849
|
raise ValueError("FoundationalModel not connected to client")
|
|
768
850
|
|
|
769
851
|
data = self._ctx.get_json(f"/compute/session/{self.id}")
|
|
770
|
-
|
|
771
|
-
self.status
|
|
772
|
-
return status == 'done'
|
|
852
|
+
self._update_from_session(data)
|
|
853
|
+
return self.status == 'done'
|
|
773
854
|
|
|
774
855
|
def publish(
|
|
775
856
|
self,
|
|
@@ -938,6 +1019,11 @@ class FoundationalModel:
|
|
|
938
1019
|
'epochs': self.epochs,
|
|
939
1020
|
'final_loss': self.final_loss,
|
|
940
1021
|
'created_at': self.created_at.isoformat() if self.created_at else None,
|
|
1022
|
+
'updated_at': self.updated_at.isoformat() if self.updated_at else None,
|
|
1023
|
+
'session_type': self.session_type,
|
|
1024
|
+
'compute_cluster': self.compute_cluster,
|
|
1025
|
+
'error_message': self.error_message,
|
|
1026
|
+
'training_progress': self.training_progress,
|
|
941
1027
|
}
|
|
942
1028
|
|
|
943
1029
|
def __repr__(self) -> str:
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
featrixsphere/__init__.py,sha256=
|
|
1
|
+
featrixsphere/__init__.py,sha256=QQglKOYv0bjonuO0-wOkeyyXMWhv3yK0_s5Uaap-GVk,2190
|
|
2
2
|
featrixsphere/client.py,sha256=JNYAHDFtxmhQVuclO3dWEphJuxNFLi-PfvWylZWKF_4,452929
|
|
3
3
|
featrixsphere/api/__init__.py,sha256=quyvuPphVj9wb6v8Dio0SMG9iHgJAmY3asHk3f_zF10,1269
|
|
4
4
|
featrixsphere/api/api_endpoint.py,sha256=i3eCWuaUXftnH1Ai6MFZ7md7pC2FcRAIRO87CBZhyEQ,9000
|
|
5
|
-
featrixsphere/api/client.py,sha256=
|
|
6
|
-
featrixsphere/api/foundational_model.py,sha256=
|
|
5
|
+
featrixsphere/api/client.py,sha256=TvNqrzSPQdw0A4kW48M0S3SDrBRmkc6kTY8UkzO4eRs,13426
|
|
6
|
+
featrixsphere/api/foundational_model.py,sha256=0ZFO-mJs66nVRXQbM0o1fB4HmhzLBXUqbTCF46LVH1k,34925
|
|
7
7
|
featrixsphere/api/http_client.py,sha256=q59-41fHua_7AwtPFCvshlSUKJ-fS0X337L9Ooyn0DI,8440
|
|
8
8
|
featrixsphere/api/notebook_helper.py,sha256=xY9jsao26eaNiFh2s0_TlRZnR8xZ4P_e0EOKr2PtoVs,20060
|
|
9
9
|
featrixsphere/api/prediction_result.py,sha256=HQsJdr89zWxdRx395nevN3aP7ZXZuZxB4UGX5Ykhkfk,12235
|
|
10
10
|
featrixsphere/api/predictor.py,sha256=1v0ffkEjmrO3BP0PNWAXtiAU-AlOQJSiDICmW1bQbGU,20300
|
|
11
11
|
featrixsphere/api/reference_record.py,sha256=-XOTF6ynznB3ouz06w3AF8X9SVId0g_dO20VvGNesUQ,7095
|
|
12
12
|
featrixsphere/api/vector_database.py,sha256=BplxKkPnAbcBX1A4KxFBJVb3qkQ-FH9zi9v2dWG5CgY,7976
|
|
13
|
-
featrixsphere-0.2.
|
|
14
|
-
featrixsphere-0.2.
|
|
15
|
-
featrixsphere-0.2.
|
|
16
|
-
featrixsphere-0.2.
|
|
17
|
-
featrixsphere-0.2.
|
|
13
|
+
featrixsphere-0.2.6710.dist-info/METADATA,sha256=_gDwyRsSfEa0thWd6IjhMeCEZF5dMc8BfLBL4J2b5HQ,16232
|
|
14
|
+
featrixsphere-0.2.6710.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
15
|
+
featrixsphere-0.2.6710.dist-info/entry_points.txt,sha256=QreJeYfD_VWvbEqPmMXZ3pqqlFlJ1qZb-NtqnyhEldc,51
|
|
16
|
+
featrixsphere-0.2.6710.dist-info/top_level.txt,sha256=AyN4wjfzlD0hWnDieuEHX0KckphIk_aC73XCG4df5uU,14
|
|
17
|
+
featrixsphere-0.2.6710.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|