@archetypeai/ds-cli 0.5.6 → 0.5.9

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.
@@ -1,419 +0,0 @@
1
- ---
2
- name: embedding-from-sensor
3
- description: Run an Embedding Lens by streaming real-time data from a physical sensor (BLE, USB, UDP, or recording playback). Use when extracting live embeddings from sensor hardware for real-time visualization or clustering.
4
- argument-hint: [source-type]
5
- ---
6
-
7
- # Embedding Lens — Stream from Sensor
8
-
9
- Generate a script that streams real-time IMU sensor data to the Archetype AI Embedding Lens for live embedding extraction. Supports both Python and JavaScript/Web.
10
-
11
- ## Frontend Architecture
12
-
13
- Decompose the UI into components. See `@rules/frontend-architecture` for conventions.
14
-
15
- ### Recommended decomposition
16
-
17
- | UI Area | Component | Pattern/Primitives | Key Props |
18
- |---------|-----------|-------------------|-----------|
19
- | Sensor input | `DataInput.svelte` | BackgroundCard, Button, Input | `onselect`, `status` |
20
- | Scatter plot | Reuse ScatterChart pattern | BackgroundCard, Chart | `data[]`, `categories` |
21
- | Progress | `StreamProgress.svelte` | BackgroundCard, Progress | `current`, `total` |
22
-
23
- - Use `@skills/create-dashboard` for the page layout
24
- - Extract streaming and session logic into `$lib/api/embeddings.js`
25
-
26
- ---
27
-
28
- ## Python Implementation
29
-
30
- ### Requirements
31
-
32
- - `archetypeai` Python package
33
- - `numpy`
34
- - `bleak` (for BLE sources)
35
- - `pyserial` (for USB sources)
36
- - Environment variables: `ATAI_API_KEY`, optionally `ATAI_API_ENDPOINT`
37
-
38
- ### Supported Source Types
39
-
40
- | Source | Description | Extra args |
41
- |--------|-------------|------------|
42
- | `ble` | Bluetooth Low Energy IMU device | None (auto-discovers) |
43
- | `usb` | USB serial IMU device | `--sensor-port` |
44
- | `udp` | UDP relay (from BLE relay server) | `--udp-port` |
45
- | `recording` | Replay a CSV recording | `--file-path` |
46
-
47
- ### Architecture
48
-
49
- #### 1. API Client Setup
50
-
51
- ```python
52
- from archetypeai.api_client import ArchetypeAI
53
-
54
- client = ArchetypeAI(api_key, api_endpoint=api_endpoint)
55
- ```
56
-
57
- #### 2. Lens YAML Config
58
-
59
- No n-shot files or KNN config — just the embedding processor:
60
-
61
- ```yaml
62
- lens_name: Embedding Lens
63
- lens_config:
64
- model_pipeline:
65
- - processor_name: lens_timeseries_embedding_processor
66
- processor_config: {}
67
- model_parameters:
68
- model_name: OmegaEncoder
69
- model_version: OmegaEncoder::omega_embeddings_01
70
- normalize_input: true
71
- buffer_size: {window_size}
72
- csv_configs:
73
- timestamp_column: timestamp
74
- data_columns: ['a1', 'a2', 'a3', 'a4']
75
- window_size: {window_size}
76
- step_size: {step_size}
77
- output_streams:
78
- - stream_type: server_sent_events_writer
79
- ```
80
-
81
- #### 3. ImuReceiver — Multi-Source Data Acquisition
82
-
83
- Same `ImuReceiver` class as the machine state sensor skill:
84
-
85
- ```python
86
- class ImuReceiver:
87
- def __init__(self, incoming_data, num_samples_per_packet=10,
88
- num_sensor_packets_per_packets_out=10):
89
- self.packet_queue = queue.Queue()
90
- # ... source detection (recording/sensor/ble)
91
-
92
- def get_data(self):
93
- """Returns (packet_out, timestamp) or (None, None)"""
94
- if self.packet_queue.qsize() >= self.num_sensor_packets_per_packets_out:
95
- packets = [self.packet_queue.get()
96
- for _ in range(self.num_sensor_packets_per_packets_out)]
97
- packet_out = np.vstack([p['data'] for p in packets]).tolist()
98
- return packet_out, packets[-1]['sensor_timestamp']
99
- return None, None
100
- ```
101
-
102
- See the machine-state-from-sensor skill for full BLE/USB/UDP acquisition implementations.
103
-
104
- #### 4. Real-Time Streaming with Buffering
105
-
106
- ```python
107
- from collections import deque
108
-
109
- data_buffer = deque(maxlen=window_size * 4)
110
- embeddings = []
111
-
112
- while not stop_event.is_set():
113
- packet_out, packet_timestamp = imu_receiver.get_data()
114
-
115
- if packet_out is not None:
116
- for row in packet_out[:, :3] if hasattr(packet_out, 'shape') else packet_out:
117
- ax, ay, az = int(row[0]), int(row[1]), int(row[2])
118
- a4 = int((ax*ax + ay*ay + az*az) ** 0.5)
119
- data_buffer.append((ax, ay, az, a4))
120
-
121
- if len(data_buffer) >= window_size:
122
- window_rows = list(data_buffer)[:window_size]
123
-
124
- a1 = [r[0] for r in window_rows]
125
- a2 = [r[1] for r in window_rows]
126
- a3 = [r[2] for r in window_rows]
127
- a4 = [r[3] for r in window_rows]
128
-
129
- payload = {
130
- "type": "session.update",
131
- "event_data": {
132
- "type": "data.json",
133
- "event_data": {
134
- "sensor_data": [a1, a2, a3, a4],
135
- "sensor_metadata": {
136
- "sensor_timestamp": packet_timestamp,
137
- "sensor_id": f"live_sensor_{counter}"
138
- }
139
- }
140
- }
141
- }
142
- client.lens.sessions.process_event(session_id, payload)
143
-
144
- # Advance by step_size
145
- for _ in range(min(step_size, len(data_buffer))):
146
- data_buffer.popleft()
147
- ```
148
-
149
- #### 5. SSE Event Listening — Collect Embeddings
150
-
151
- ```python
152
- sse_reader = client.lens.sessions.create_sse_consumer(
153
- session_id, max_read_time_sec=max_run_time_sec
154
- )
155
-
156
- for event in sse_reader.read(block=True):
157
- if event.get("type") == "inference.result":
158
- embedding = event["event_data"].get("response")
159
-
160
- # Flatten 4×768 → 3072D
161
- if isinstance(embedding, list) and len(embedding) > 0:
162
- if isinstance(embedding[0], list):
163
- flat = [val for row in embedding for val in row]
164
- else:
165
- flat = embedding
166
-
167
- embeddings.append(flat)
168
- print(f"Embedding {len(embeddings)}: {len(flat)}D")
169
- ```
170
-
171
- #### 6. Threading Model
172
-
173
- ```
174
- Main Thread: session_callback → starts SSE listener
175
- Thread 1: ImuReceiver (BLE async / USB serial / UDP socket)
176
- Thread 2: Streaming loop (buffer → API)
177
- ```
178
-
179
- ### Embedding Response Structure
180
-
181
- - `response`: nested list `(4, 768)` — one 768D vector per channel (a1, a2, a3, a4)
182
- - Flatten to `3072D` by concatenating rows
183
- - `query_metadata.sensor_id`: which sensor window this came from
184
-
185
- ### CLI Arguments
186
-
187
- ```
188
- --api-key API key (fallback to ATAI_API_KEY env var)
189
- --api-endpoint API endpoint (default from SDK)
190
- --source-type {ble, usb, recording, udp} (required)
191
- --file-path Recording file path (for recording mode)
192
- --sensor-port USB serial port (default: /dev/tty.usbmodem1101)
193
- --udp-port UDP relay port (default: 5556)
194
- --window-size Window size in samples (default: 100)
195
- --step-size Step size in samples (default: 100)
196
- --max-run-time-sec Max runtime (default: 500)
197
- --output-file Path to save embeddings CSV (optional)
198
- ```
199
-
200
- ---
201
-
202
- ## Web / JavaScript Implementation
203
-
204
- Uses direct `fetch` calls to the Archetype AI REST API with Web Bluetooth API or WebSocket for sensor data.
205
-
206
- ### API Reference
207
-
208
- | Operation | Method | Endpoint | Body |
209
- |-----------|--------|----------|------|
210
- | Register lens | POST | `/lens/register` | `{ lens_config: config }` |
211
- | Create session | POST | `/lens/sessions/create` | `{ lens_id }` |
212
- | Process event | POST | `/lens/sessions/events/process` | `{ session_id, event }` |
213
- | Delete lens | POST | `/lens/delete` | `{ lens_id }` |
214
- | Destroy session | POST | `/lens/sessions/destroy` | `{ session_id }` |
215
- | SSE consumer | GET | `/lens/sessions/consumer/{sessionId}` | — |
216
-
217
- ### Helper: API fetch wrapper
218
-
219
- ```typescript
220
- const API_ENDPOINT = 'https://api.u1.archetypeai.app/v0.5'
221
-
222
- async function apiPost<T>(path: string, apiKey: string, body: unknown, timeoutMs = 5000): Promise<T> {
223
- const controller = new AbortController()
224
- const timeoutId = setTimeout(() => controller.abort(), timeoutMs)
225
-
226
- try {
227
- const response = await fetch(`${API_ENDPOINT}${path}`, {
228
- method: 'POST',
229
- headers: {
230
- Authorization: `Bearer ${apiKey}`,
231
- 'Content-Type': 'application/json',
232
- },
233
- body: JSON.stringify(body),
234
- signal: controller.signal,
235
- })
236
-
237
- if (!response.ok) {
238
- const errorBody = await response.json().catch(() => ({}))
239
- throw new Error(`API POST ${path} failed: ${response.status} - ${JSON.stringify(errorBody)}`)
240
- }
241
-
242
- return response.json()
243
- } finally {
244
- clearTimeout(timeoutId)
245
- }
246
- }
247
- ```
248
-
249
- ### Step 1: Register embedding lens and create session
250
-
251
- ```typescript
252
- const windowSize = 100
253
- const stepSize = 100
254
-
255
- const lensConfig = {
256
- lens_name: 'embedding_lens',
257
- lens_config: {
258
- model_pipeline: [
259
- { processor_name: 'lens_timeseries_embedding_processor', processor_config: {} },
260
- ],
261
- model_parameters: {
262
- model_name: 'OmegaEncoder',
263
- model_version: 'OmegaEncoder::omega_embeddings_01',
264
- normalize_input: true,
265
- buffer_size: windowSize,
266
- csv_configs: {
267
- timestamp_column: 'timestamp',
268
- data_columns: ['a1', 'a2', 'a3', 'a4'],
269
- window_size: windowSize,
270
- step_size: stepSize,
271
- },
272
- },
273
- output_streams: [
274
- { stream_type: 'server_sent_events_writer' },
275
- ],
276
- },
277
- }
278
-
279
- const registeredLens = await apiPost<{ lens_id: string }>(
280
- '/lens/register', apiKey, { lens_config: lensConfig }
281
- )
282
- const lensId = registeredLens.lens_id
283
-
284
- const session = await apiPost<{ session_id: string }>(
285
- '/lens/sessions/create', apiKey, { lens_id: lensId }
286
- )
287
- const sessionId = session.session_id
288
-
289
- await apiPost('/lens/delete', apiKey, { lens_id: lensId })
290
-
291
- // Wait for session ready (same waitForSessionReady as machine state skills)
292
- ```
293
-
294
- ### Step 2: Acquire sensor data (Web Bluetooth)
295
-
296
- ```typescript
297
- const IMU_SERVICE = '0000fff0-0000-1000-8000-00805f9b34fb'
298
- const IMU_CHARACTERISTIC = '0000fff1-0000-1000-8000-00805f9b34fb'
299
-
300
- const device = await navigator.bluetooth.requestDevice({
301
- filters: [{ services: [IMU_SERVICE] }],
302
- })
303
- const server = await device.gatt.connect()
304
- const service = await server.getPrimaryService(IMU_SERVICE)
305
- const characteristic = await service.getCharacteristic(IMU_CHARACTERISTIC)
306
-
307
- const dataBuffer: [number, number, number][] = []
308
-
309
- characteristic.addEventListener('characteristicvaluechanged', (event) => {
310
- const value = (event.target as BluetoothRemoteGATTCharacteristic).value!
311
- const samples = new Int16Array(value.buffer)
312
- const payload = samples.slice(1)
313
- for (let i = 0; i + 2 < payload.length; i += 3) {
314
- dataBuffer.push([payload[i], payload[i + 1], payload[i + 2]])
315
- }
316
- })
317
-
318
- await characteristic.startNotifications()
319
- ```
320
-
321
- ### Step 3: Stream buffered data in windows
322
-
323
- ```typescript
324
- let counter = 0
325
-
326
- const streamLoop = setInterval(async () => {
327
- if (dataBuffer.length < windowSize) return
328
-
329
- const window = dataBuffer.splice(0, windowSize)
330
-
331
- const a1 = window.map(r => r[0])
332
- const a2 = window.map(r => r[1])
333
- const a3 = window.map(r => r[2])
334
- const a4 = window.map(([ax, ay, az]) =>
335
- Math.floor(Math.sqrt(ax * ax + ay * ay + az * az))
336
- )
337
-
338
- await apiPost('/lens/sessions/events/process', apiKey, {
339
- session_id: sessionId,
340
- event: {
341
- type: 'session.update',
342
- event_data: {
343
- type: 'data.json',
344
- event_data: {
345
- sensor_data: [a1, a2, a3, a4],
346
- sensor_metadata: {
347
- sensor_timestamp: Date.now() / 1000,
348
- sensor_id: `web_ble_sensor_${counter++}`,
349
- },
350
- },
351
- },
352
- },
353
- }, 10000)
354
- }, 200)
355
- ```
356
-
357
- ### Step 4: Consume SSE embedding results
358
-
359
- ```typescript
360
- import { fetchEventSource } from '@microsoft/fetch-event-source'
361
-
362
- interface EmbeddingResult {
363
- windowIndex: number
364
- embedding: number[]
365
- }
366
-
367
- const embeddings: EmbeddingResult[] = []
368
- const abortController = new AbortController()
369
-
370
- fetchEventSource(`${API_ENDPOINT}/lens/sessions/consumer/${sessionId}`, {
371
- headers: { Authorization: `Bearer ${apiKey}` },
372
- signal: abortController.signal,
373
- onmessage(event) {
374
- const parsed = JSON.parse(event.data)
375
-
376
- if (parsed.type === 'inference.result') {
377
- const response = parsed.event_data.response
378
- const flat = Array.isArray(response[0]) ? response.flat() : response
379
-
380
- embeddings.push({
381
- windowIndex: embeddings.length,
382
- embedding: flat,
383
- })
384
- console.log(`Embedding ${embeddings.length}: ${flat.length}D`)
385
- }
386
- },
387
- })
388
- ```
389
-
390
- ### Step 5: Cleanup
391
-
392
- ```typescript
393
- clearInterval(streamLoop)
394
- abortController.abort()
395
- await device.gatt.disconnect()
396
- await apiPost('/lens/sessions/destroy', apiKey, { session_id: sessionId })
397
- ```
398
-
399
- ### Web Lifecycle Summary
400
-
401
- ```
402
- 1. Register lens -> POST /lens/register { lens_config: config }
403
- 2. Create session -> POST /lens/sessions/create { lens_id }
404
- 3. Wait for ready -> POST /lens/sessions/events/process (poll)
405
- 4. Connect sensor (BLE / WS) -> Web Bluetooth API or WebSocket
406
- 5. Buffer + stream windows -> POST /lens/sessions/events/process (loop)
407
- 6. Consume SSE embeddings -> GET /lens/sessions/consumer/{sessionId}
408
- 7. Disconnect + destroy -> POST /lens/sessions/destroy { session_id }
409
- ```
410
-
411
- ---
412
-
413
- ## Key Implementation Notes
414
-
415
- - Default `window_size` and `step_size`: **100**
416
- - Embeddings are `(4, 768)` per window — flatten to `3072D` for downstream use
417
- - No n-shot files needed — this lens outputs raw embeddings, not classifications
418
- - Use UMAP or t-SNE to reduce to 2D/3D for visualization
419
- - Combine with machine state lens to overlay class labels on embedding plots