xray-sdk 0.1.0 → 0.1.1
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.
- package/README.md +447 -300
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,426 +1,573 @@
|
|
|
1
|
-
#
|
|
1
|
+
# xray-sdk
|
|
2
2
|
|
|
3
3
|
**AI-powered observability for multi-step pipelines**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Track your pipeline executions with automatic AI reasoning that explains "WHY" decisions were made. Debug faster with step-by-step insights and visual exploration.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
---
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
🤖 **AI-Powered Reasoning** - Generate natural language explanations for each step using OpenAI
|
|
11
|
-
💾 **Flexible Storage** - In-memory or database-backed (Prisma + PostgreSQL)
|
|
12
|
-
⚡ **Async Processing** - Non-blocking reasoning generation with retry logic
|
|
13
|
-
🔄 **On-Demand Generation** - Only generate reasoning when you need it (cost savings)
|
|
14
|
-
📊 **Job Queue** - Built-in queue for managing concurrent LLM calls
|
|
15
|
-
|
|
16
|
-
## Installation
|
|
9
|
+
## 📦 Installation
|
|
17
10
|
|
|
18
11
|
```bash
|
|
19
|
-
npm install xray-sdk
|
|
12
|
+
npm install xray-sdk
|
|
20
13
|
```
|
|
21
14
|
|
|
22
|
-
|
|
15
|
+
---
|
|
23
16
|
|
|
24
|
-
|
|
25
|
-
# For database storage
|
|
26
|
-
npm install @prisma/client
|
|
17
|
+
## 🚀 Complete Setup Guide
|
|
27
18
|
|
|
28
|
-
|
|
29
|
-
npm install openai
|
|
30
|
-
```
|
|
19
|
+
Follow these steps to integrate X-Ray into your pipeline:
|
|
31
20
|
|
|
32
|
-
|
|
21
|
+
### Step 1: Get Your API Key
|
|
33
22
|
|
|
34
|
-
|
|
23
|
+
1. Visit the X-Ray Dashboard: **https://x-ray-library-sdk-git-main-devdurgesh619s-projects.vercel.app**
|
|
24
|
+
2. Click **"Create Account"** to sign up
|
|
25
|
+
3. Go to **"API Keys"** page: https://x-ray-library-sdk-git-main-devdurgesh619s-projects.vercel.app/api-key
|
|
26
|
+
4. Click **"Generate New Key"** and copy your API key (format: `xray_xxxxx...`)
|
|
35
27
|
|
|
36
|
-
|
|
37
|
-
import { XRay, MemoryStorage } from 'xray-sdk'
|
|
28
|
+
### Step 2: Configure Environment Variables
|
|
38
29
|
|
|
39
|
-
|
|
40
|
-
const xray = new XRay('my-execution-1', { projectId: 'demo' }, storage)
|
|
30
|
+
Create a `.env` file in your project root:
|
|
41
31
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
xray.endStep('fetch_data', { results: data.length })
|
|
32
|
+
```bash
|
|
33
|
+
# X-Ray Dashboard Configuration
|
|
34
|
+
XRAY_API_URL="https://x-ray-library-sdk-git-main-devdurgesh619s-projects.vercel.app"
|
|
46
35
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
await xray.save()
|
|
36
|
+
# Your API key from Step 1
|
|
37
|
+
XRAY_API_KEY="xray_your_api_key_here"
|
|
50
38
|
|
|
51
|
-
|
|
39
|
+
# Optional: Only needed for client-side reasoning (advanced)
|
|
40
|
+
OPENAI_API_KEY="sk-..."
|
|
52
41
|
```
|
|
53
42
|
|
|
54
|
-
###
|
|
43
|
+
### Step 3: Create HTTP Client Wrapper
|
|
44
|
+
|
|
45
|
+
Create a file `src/lib/xrayClient.ts` to handle API communication:
|
|
55
46
|
|
|
56
47
|
```typescript
|
|
57
|
-
|
|
58
|
-
|
|
48
|
+
interface Execution {
|
|
49
|
+
executionId: string
|
|
50
|
+
startedAt: string
|
|
51
|
+
endedAt?: string
|
|
52
|
+
steps: Array<{
|
|
53
|
+
name: string
|
|
54
|
+
input?: any
|
|
55
|
+
output?: any
|
|
56
|
+
error?: string
|
|
57
|
+
durationMs?: number
|
|
58
|
+
}>
|
|
59
|
+
finalOutcome?: any
|
|
60
|
+
metadata?: Record<string, any>
|
|
61
|
+
}
|
|
59
62
|
|
|
60
|
-
|
|
61
|
-
|
|
63
|
+
export class XRayClient {
|
|
64
|
+
private apiUrl: string
|
|
65
|
+
private apiKey: string
|
|
62
66
|
|
|
63
|
-
|
|
67
|
+
constructor(apiUrl: string, apiKey: string) {
|
|
68
|
+
this.apiUrl = apiUrl
|
|
69
|
+
this.apiKey = apiKey
|
|
70
|
+
}
|
|
64
71
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
async saveExecution(execution: Execution): Promise<{ executionId: string }> {
|
|
73
|
+
const response = await fetch(\`\${this.apiUrl}/api/logs\`, {
|
|
74
|
+
method: 'POST',
|
|
75
|
+
headers: {
|
|
76
|
+
'x-api-key': this.apiKey,
|
|
77
|
+
'Content-Type': 'application/json'
|
|
78
|
+
},
|
|
79
|
+
body: JSON.stringify(execution)
|
|
80
|
+
})
|
|
68
81
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
```
|
|
82
|
+
if (!response.ok) {
|
|
83
|
+
throw new Error(\`X-Ray API Error: \${response.statusText}\`)
|
|
84
|
+
}
|
|
73
85
|
|
|
74
|
-
|
|
86
|
+
return await response.json()
|
|
87
|
+
}
|
|
88
|
+
}
|
|
75
89
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
ReasoningQueue,
|
|
81
|
-
createOpenAIGenerator
|
|
82
|
-
} from '@xray/sdk'
|
|
83
|
-
import { PrismaClient } from '@prisma/client'
|
|
84
|
-
import OpenAI from 'openai'
|
|
90
|
+
// Helper function to create client from environment variables
|
|
91
|
+
export function createXRayClient(): XRayClient {
|
|
92
|
+
const apiUrl = process.env.XRAY_API_URL
|
|
93
|
+
const apiKey = process.env.XRAY_API_KEY
|
|
85
94
|
|
|
86
|
-
|
|
87
|
-
|
|
95
|
+
if (!apiUrl || !apiKey) {
|
|
96
|
+
throw new Error('Missing XRAY_API_URL or XRAY_API_KEY in environment')
|
|
97
|
+
}
|
|
88
98
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const queue = new ReasoningQueue(
|
|
93
|
-
storage,
|
|
94
|
-
generator,
|
|
95
|
-
{ concurrency: 3, debug: true },
|
|
96
|
-
prisma
|
|
97
|
-
)
|
|
99
|
+
return new XRayClient(apiUrl, apiKey)
|
|
100
|
+
}
|
|
101
|
+
```
|
|
98
102
|
|
|
99
|
-
|
|
100
|
-
const xray = new XRay('my-execution-3', { projectId: 'demo' }, storage)
|
|
103
|
+
**💡 Tip:** See the full implementation at \`demo-app/src/lib/xrayClient.ts\` in the repository.
|
|
101
104
|
|
|
102
|
-
|
|
103
|
-
const results = await search('laptops')
|
|
104
|
-
xray.endStep('search', { count: results.length })
|
|
105
|
+
### Step 4: Track Your Pipeline with XRay
|
|
105
106
|
|
|
106
|
-
|
|
107
|
-
const filtered = results.filter(r => r.rating >= 4.5)
|
|
108
|
-
xray.endStep('filter', { remaining: filtered.length })
|
|
107
|
+
Wrap your pipeline logic with X-Ray tracking:
|
|
109
108
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
109
|
+
```typescript
|
|
110
|
+
import { XRay } from 'xray-sdk'
|
|
111
|
+
import { createXRayClient } from './lib/xrayClient'
|
|
112
|
+
|
|
113
|
+
async function myPipeline() {
|
|
114
|
+
// 1. Create XRay instance with unique execution ID
|
|
115
|
+
const executionId = \`pipeline-\${Date.now()}\`
|
|
116
|
+
const xray = new XRay(executionId, {
|
|
117
|
+
pipeline: 'my-pipeline-name',
|
|
118
|
+
domain: 'data-processing' // Optional metadata
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
// 2. Track each step with startStep() and endStep()
|
|
122
|
+
|
|
123
|
+
// Step 1: Fetch data
|
|
124
|
+
xray.startStep('fetch_data', { source: 'api.example.com', limit: 100 })
|
|
125
|
+
const data = await fetchData()
|
|
126
|
+
xray.endStep('fetch_data', { records: data.length, size_kb: 45 })
|
|
127
|
+
|
|
128
|
+
// Step 2: Process data
|
|
129
|
+
xray.startStep('process_data', { records: data.length })
|
|
130
|
+
const processed = processData(data)
|
|
131
|
+
xray.endStep('process_data', {
|
|
132
|
+
processed: processed.length,
|
|
133
|
+
skipped: data.length - processed.length
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
// Step 3: Save results
|
|
137
|
+
xray.startStep('save_results', { records: processed.length })
|
|
138
|
+
await saveResults(processed)
|
|
139
|
+
xray.endStep('save_results', { success: true })
|
|
140
|
+
|
|
141
|
+
// 3. Complete execution
|
|
142
|
+
const execution = xray.end({
|
|
143
|
+
status: 'success',
|
|
144
|
+
total_processed: processed.length
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
// 4. Send to X-Ray Dashboard
|
|
148
|
+
const client = createXRayClient()
|
|
149
|
+
await client.saveExecution(execution)
|
|
150
|
+
|
|
151
|
+
// 5. View in dashboard
|
|
152
|
+
console.log(\`✅ View execution: https://x-ray-library-sdk-git-main-devdurgesh619s-projects.vercel.app/execution/\${executionId}\`)
|
|
153
|
+
}
|
|
154
|
+
```
|
|
113
155
|
|
|
114
|
-
|
|
115
|
-
await xray.enqueueReasoning(queue)
|
|
156
|
+
### Step 5: View Results in Dashboard
|
|
116
157
|
|
|
117
|
-
|
|
118
|
-
|
|
158
|
+
1. Run your pipeline: \`npm run your-pipeline-command\`
|
|
159
|
+
2. Copy the execution URL from console output
|
|
160
|
+
3. Open the URL in your browser
|
|
161
|
+
4. Watch AI reasoning generate automatically for each step
|
|
119
162
|
|
|
120
|
-
|
|
163
|
+
**✨ AI Reasoning explains:**
|
|
164
|
+
- WHY this step produced this output
|
|
165
|
+
- What metrics/thresholds drove the decision
|
|
166
|
+
- What might be wrong if numbers look suspicious
|
|
121
167
|
|
|
122
|
-
|
|
168
|
+
Example reasoning:
|
|
169
|
+
> "Only 3/10 candidates met minRating≥4.0 AND minReviews≥100; 7 failed due to low ratings/reviews"
|
|
123
170
|
|
|
124
|
-
|
|
125
|
-
// Pipeline API - just save execution
|
|
126
|
-
const execution = xray.end({ success: true })
|
|
127
|
-
await xray.save()
|
|
128
|
-
return { executionId: execution.executionId } // Returns instantly
|
|
171
|
+
---
|
|
129
172
|
|
|
130
|
-
|
|
131
|
-
await queue.processExecution(executionId) // Generate reasoning now
|
|
132
|
-
```
|
|
173
|
+
## 📚 Complete Examples
|
|
133
174
|
|
|
134
|
-
|
|
135
|
-
- ✅ API responds instantly (~150ms)
|
|
136
|
-
- ✅ Saves LLM costs (only generate for executions users actually view)
|
|
137
|
-
- ✅ Better user experience
|
|
175
|
+
See the \`demo-app/\` directory for production-ready examples:
|
|
138
176
|
|
|
139
|
-
|
|
177
|
+
### Example 1: Basic Data Pipeline
|
|
140
178
|
|
|
141
|
-
|
|
179
|
+
**File:** \`demo-app/src/1-basic-example.ts\`
|
|
142
180
|
|
|
143
181
|
```typescript
|
|
144
|
-
|
|
145
|
-
|
|
182
|
+
import { XRay } from 'xray-sdk'
|
|
183
|
+
import { createXRayClient } from './lib/xrayClient'
|
|
146
184
|
|
|
147
|
-
|
|
148
|
-
|
|
185
|
+
const xray = new XRay(\`basic-\${Date.now()}\`, {
|
|
186
|
+
pipeline: 'data-ingestion'
|
|
187
|
+
})
|
|
149
188
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
189
|
+
// Track data ingestion
|
|
190
|
+
xray.startStep('ingest', { source: 'api', limit: 1000 })
|
|
191
|
+
const rawData = await fetch('https://api.example.com/data')
|
|
192
|
+
xray.endStep('ingest', { records: 1000, size_mb: 2.3 })
|
|
193
|
+
|
|
194
|
+
// Track validation
|
|
195
|
+
xray.startStep('validate', { records: 1000 })
|
|
196
|
+
const valid = rawData.filter(isValid)
|
|
197
|
+
xray.endStep('validate', {
|
|
198
|
+
valid: valid.length,
|
|
199
|
+
invalid: rawData.length - valid.length
|
|
200
|
+
})
|
|
154
201
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
enqueueReasoning(queue: ReasoningQueue): Promise<void>
|
|
158
|
-
getExecution(): Execution
|
|
159
|
-
}
|
|
202
|
+
const execution = xray.end({ status: 'success' })
|
|
203
|
+
await createXRayClient().saveExecution(execution)
|
|
160
204
|
```
|
|
161
205
|
|
|
162
|
-
###
|
|
206
|
+
### Example 2: E-Commerce Competitor Selection
|
|
207
|
+
|
|
208
|
+
**File:** \`demo-app/src/2-ecommerce-example.ts\`
|
|
163
209
|
|
|
164
210
|
```typescript
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
getExecutionById(executionId: string): Promise<Execution | undefined>
|
|
168
|
-
getAllExecutions(): Promise<Execution[]>
|
|
169
|
-
updateStepReasoning(executionId: string, stepName: string, reasoning: string): Promise<void>
|
|
170
|
-
}
|
|
171
|
-
```
|
|
211
|
+
import { XRay } from 'xray-sdk'
|
|
212
|
+
import { createXRayClient } from './lib/xrayClient'
|
|
172
213
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
214
|
+
const xray = new XRay(\`ecommerce-\${Date.now()}\`, {
|
|
215
|
+
pipeline: 'competitor-selection',
|
|
216
|
+
domain: 'e-commerce'
|
|
217
|
+
})
|
|
176
218
|
|
|
177
|
-
|
|
219
|
+
// Step 1: Generate search keywords
|
|
220
|
+
xray.startStep('generate_keywords', {
|
|
221
|
+
product_title: 'Water Bottle 32oz Insulated'
|
|
222
|
+
})
|
|
223
|
+
const keywords = ['water bottle', 'insulated bottle', '32oz bottle']
|
|
224
|
+
xray.endStep('generate_keywords', { keywords, count: 3 })
|
|
225
|
+
|
|
226
|
+
// Step 2: Search for candidates
|
|
227
|
+
xray.startStep('search_competitors', { keywords, limit: 50 })
|
|
228
|
+
const candidates = await searchAmazon(keywords)
|
|
229
|
+
xray.endStep('search_competitors', {
|
|
230
|
+
total_results: 2847,
|
|
231
|
+
candidates_fetched: 10
|
|
232
|
+
})
|
|
178
233
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
234
|
+
// Step 3: Filter and select best competitor
|
|
235
|
+
xray.startStep('filter_and_select', {
|
|
236
|
+
candidates: 10,
|
|
237
|
+
filters: { minRating: 4.0, minReviews: 100 }
|
|
238
|
+
})
|
|
239
|
+
const filtered = candidates.filter(c => c.rating >= 4.0 && c.reviews >= 100)
|
|
240
|
+
const selected = filtered[0]
|
|
241
|
+
xray.endStep('filter_and_select', {
|
|
242
|
+
passed: 3,
|
|
243
|
+
failed: 7,
|
|
244
|
+
selected: selected.title
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
const execution = xray.end({
|
|
248
|
+
competitor: selected,
|
|
249
|
+
confidence: 0.95
|
|
250
|
+
})
|
|
251
|
+
await createXRayClient().saveExecution(execution)
|
|
194
252
|
```
|
|
195
253
|
|
|
196
|
-
###
|
|
254
|
+
### Example 3: Error Handling
|
|
255
|
+
|
|
256
|
+
**File:** \`demo-app/src/3-error-handling-example.ts\`
|
|
197
257
|
|
|
198
258
|
```typescript
|
|
199
|
-
|
|
200
|
-
|
|
259
|
+
import { XRay } from 'xray-sdk'
|
|
260
|
+
import { createXRayClient } from './lib/xrayClient'
|
|
201
261
|
|
|
202
|
-
|
|
203
|
-
|
|
262
|
+
const xray = new XRay(\`error-demo-\${Date.now()}\`, {
|
|
263
|
+
pipeline: 'risky-pipeline'
|
|
264
|
+
})
|
|
204
265
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
266
|
+
xray.startStep('risky_operation', { input: 'data' })
|
|
267
|
+
try {
|
|
268
|
+
const result = await riskyOperation()
|
|
269
|
+
xray.endStep('risky_operation', { result })
|
|
270
|
+
} catch (error) {
|
|
271
|
+
// Track errors with errorStep()
|
|
272
|
+
xray.errorStep('risky_operation', error as Error)
|
|
208
273
|
}
|
|
274
|
+
|
|
275
|
+
const execution = xray.end({ status: 'failed' })
|
|
276
|
+
await createXRayClient().saveExecution(execution)
|
|
277
|
+
|
|
278
|
+
// Dashboard will show error with AI reasoning explaining what went wrong
|
|
209
279
|
```
|
|
210
280
|
|
|
211
|
-
|
|
281
|
+
### Example 4: Movie Recommendation Pipeline
|
|
212
282
|
|
|
213
|
-
|
|
283
|
+
**File:** \`demo-app/src/4-movie-example.ts\`
|
|
214
284
|
|
|
215
285
|
```typescript
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
maxRetries: number // Max retries per job (default: 4)
|
|
219
|
-
retryDelays: number[] // Backoff delays in ms (default: [1000, 2000, 4000, 8000])
|
|
220
|
-
debug: boolean // Enable logging (default: false)
|
|
221
|
-
}
|
|
286
|
+
import { XRay } from 'xray-sdk'
|
|
287
|
+
import { createXRayClient } from './lib/xrayClient'
|
|
222
288
|
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
debug: true
|
|
289
|
+
const xray = new XRay(\`movie-\${Date.now()}\`, {
|
|
290
|
+
pipeline: 'movie-recommendation',
|
|
291
|
+
domain: 'entertainment'
|
|
227
292
|
})
|
|
228
293
|
|
|
229
|
-
|
|
294
|
+
// Step 1: Extract themes from favorite movie
|
|
295
|
+
xray.startStep('extract_themes', { movie: 'Inception' })
|
|
296
|
+
const themes = ['time manipulation', 'mind-bending', 'layered reality']
|
|
297
|
+
xray.endStep('extract_themes', { themes, count: 3 })
|
|
298
|
+
|
|
299
|
+
// Step 2: Search for similar movies
|
|
300
|
+
xray.startStep('search_movies', { themes, limit: 20 })
|
|
301
|
+
const candidates = await searchMovies(themes)
|
|
302
|
+
xray.endStep('search_movies', { total: 47, fetched: 20 })
|
|
303
|
+
|
|
304
|
+
// Step 3: Filter by criteria
|
|
305
|
+
xray.startStep('filter_movies', {
|
|
306
|
+
candidates: 20,
|
|
307
|
+
minRating: 7.5,
|
|
308
|
+
maxAge: 15
|
|
309
|
+
})
|
|
310
|
+
const filtered = candidates.filter(m => m.rating >= 7.5 && m.yearsSinceRelease <= 15)
|
|
311
|
+
xray.endStep('filter_movies', { passed: 5, failed: 15 })
|
|
312
|
+
|
|
313
|
+
// Step 4: Score and rank
|
|
314
|
+
xray.startStep('score_and_rank', { movies: 5 })
|
|
315
|
+
const scored = scoreMovies(filtered)
|
|
316
|
+
const topPick = scored[0]
|
|
317
|
+
xray.endStep('score_and_rank', { top_score: topPick.score })
|
|
318
|
+
|
|
319
|
+
// Step 5: Get metadata
|
|
320
|
+
xray.startStep('get_metadata', { movie_id: topPick.id })
|
|
321
|
+
const metadata = await getMovieMetadata(topPick.id)
|
|
322
|
+
xray.endStep('get_metadata', { title: metadata.title })
|
|
323
|
+
|
|
324
|
+
const execution = xray.end({
|
|
325
|
+
recommendation: metadata,
|
|
326
|
+
confidence: 0.92
|
|
327
|
+
})
|
|
328
|
+
await createXRayClient().saveExecution(execution)
|
|
230
329
|
```
|
|
231
330
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
If using `DatabaseStorage`, add this to your Prisma schema:
|
|
235
|
-
|
|
236
|
-
```prisma
|
|
237
|
-
model Execution {
|
|
238
|
-
id String @id @default(cuid())
|
|
239
|
-
executionId String @unique
|
|
240
|
-
projectId String @default("default")
|
|
241
|
-
metadata Json?
|
|
242
|
-
finalOutcome Json?
|
|
243
|
-
startedAt DateTime @default(now())
|
|
244
|
-
completedAt DateTime?
|
|
245
|
-
steps Step[]
|
|
246
|
-
reasoningJobs ReasoningJob[]
|
|
247
|
-
}
|
|
331
|
+
---
|
|
248
332
|
|
|
249
|
-
|
|
250
|
-
id String @id @default(cuid())
|
|
251
|
-
executionId String
|
|
252
|
-
execution Execution @relation(fields: [executionId], references: [id], onDelete: Cascade)
|
|
253
|
-
name String
|
|
254
|
-
input Json?
|
|
255
|
-
output Json?
|
|
256
|
-
error String?
|
|
257
|
-
durationMs Int?
|
|
258
|
-
reasoning String?
|
|
259
|
-
createdAt DateTime @default(now())
|
|
260
|
-
}
|
|
333
|
+
## 🎯 Core API Reference
|
|
261
334
|
|
|
262
|
-
|
|
263
|
-
id String @id @default(cuid())
|
|
264
|
-
executionId String
|
|
265
|
-
execution Execution @relation(fields: [executionId], references: [id], onDelete: Cascade)
|
|
266
|
-
stepName String
|
|
267
|
-
status String
|
|
268
|
-
reasoning String?
|
|
269
|
-
error String?
|
|
270
|
-
attempts Int @default(0)
|
|
271
|
-
createdAt DateTime @default(now())
|
|
272
|
-
completedAt DateTime?
|
|
273
|
-
|
|
274
|
-
@@unique([executionId, stepName])
|
|
275
|
-
}
|
|
276
|
-
```
|
|
335
|
+
### XRay Class
|
|
277
336
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
npx prisma migrate dev --name add_xray
|
|
281
|
-
npx prisma generate
|
|
282
|
-
```
|
|
337
|
+
```typescript
|
|
338
|
+
import { XRay } from 'xray-sdk'
|
|
283
339
|
|
|
284
|
-
|
|
340
|
+
// Create instance
|
|
341
|
+
const xray = new XRay(executionId: string, metadata?: Record<string, any>)
|
|
285
342
|
|
|
286
|
-
|
|
343
|
+
// Track steps
|
|
344
|
+
xray.startStep(name: string, input?: any)
|
|
345
|
+
xray.endStep(name: string, output?: any)
|
|
346
|
+
xray.errorStep(name: string, error: Error)
|
|
287
347
|
|
|
288
|
-
|
|
289
|
-
|
|
348
|
+
// Complete execution
|
|
349
|
+
const execution = xray.end(finalOutcome?: any)
|
|
350
|
+
```
|
|
290
351
|
|
|
291
|
-
|
|
292
|
-
const storage = new MemoryStorage()
|
|
293
|
-
const xray = new XRay('exec-1', {}, storage)
|
|
352
|
+
### Key Methods
|
|
294
353
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
354
|
+
| Method | Description | Example |
|
|
355
|
+
|--------|-------------|---------|
|
|
356
|
+
| \`startStep(name, input?)\` | Start tracking a step | \`xray.startStep('fetch_data', { source: 'api' })\` |
|
|
357
|
+
| \`endStep(name, output?)\` | End step successfully | \`xray.endStep('fetch_data', { records: 1000 })\` |
|
|
358
|
+
| \`errorStep(name, error)\` | Mark step as failed | \`xray.errorStep('fetch_data', new Error('timeout'))\` |
|
|
359
|
+
| \`end(finalOutcome?)\` | Complete execution | \`xray.end({ status: 'success', total: 1000 })\` |
|
|
298
360
|
|
|
299
|
-
|
|
300
|
-
const processed = data.map(d => d.value * 2)
|
|
301
|
-
xray.endStep('process', { result: processed })
|
|
361
|
+
---
|
|
302
362
|
|
|
303
|
-
|
|
304
|
-
await xray.save()
|
|
363
|
+
## 🔒 Security & Reasoning Options
|
|
305
364
|
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
```
|
|
365
|
+
X-Ray offers **two secure ways** to generate AI reasoning:
|
|
309
366
|
|
|
310
|
-
###
|
|
367
|
+
### Option 1: Server-Side Reasoning (Default, Recommended)
|
|
311
368
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
xray.endStep('risky_operation', { result })
|
|
317
|
-
} catch (error) {
|
|
318
|
-
xray.errorStep('risky_operation', error as Error)
|
|
319
|
-
}
|
|
320
|
-
```
|
|
369
|
+
**How it works:**
|
|
370
|
+
1. Send execution WITHOUT reasoning to dashboard
|
|
371
|
+
2. Dashboard generates reasoning using its own OpenAI key
|
|
372
|
+
3. Reasoning appears automatically when you view the execution
|
|
321
373
|
|
|
322
|
-
|
|
374
|
+
**Pros:**
|
|
375
|
+
- ✅ No OpenAI API key needed from you
|
|
376
|
+
- ✅ Zero cost for you
|
|
377
|
+
- ✅ Zero setup required
|
|
323
378
|
|
|
379
|
+
**Code:**
|
|
324
380
|
```typescript
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
async saveExecution(execution: Execution): Promise<void> {
|
|
329
|
-
// Upload to S3
|
|
330
|
-
await s3.putObject({
|
|
331
|
-
Bucket: 'my-bucket',
|
|
332
|
-
Key: `executions/${execution.executionId}.json`,
|
|
333
|
-
Body: JSON.stringify(execution)
|
|
334
|
-
})
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
async getExecutionById(id: string): Promise<Execution | undefined> {
|
|
338
|
-
// Download from S3
|
|
339
|
-
const obj = await s3.getObject({
|
|
340
|
-
Bucket: 'my-bucket',
|
|
341
|
-
Key: `executions/${id}.json`
|
|
342
|
-
})
|
|
343
|
-
return JSON.parse(obj.Body.toString())
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// ... implement other methods
|
|
347
|
-
}
|
|
381
|
+
const execution = xray.end({ status: 'success' })
|
|
382
|
+
await client.saveExecution(execution)
|
|
383
|
+
// Dashboard will generate reasoning automatically
|
|
348
384
|
```
|
|
349
385
|
|
|
350
|
-
|
|
386
|
+
### Option 2: Client-Side Reasoning (Advanced)
|
|
351
387
|
|
|
352
|
-
|
|
388
|
+
**How it works:**
|
|
389
|
+
1. Generate reasoning locally using YOUR OpenAI key
|
|
390
|
+
2. Send execution WITH reasoning to dashboard
|
|
391
|
+
3. Your API key never leaves your infrastructure
|
|
353
392
|
|
|
354
|
-
|
|
393
|
+
**Pros:**
|
|
394
|
+
- ✅ Full control over OpenAI usage
|
|
395
|
+
- ✅ Works with sensitive data (never sent to dashboard)
|
|
396
|
+
- ✅ No dependency on dashboard's rate limits
|
|
355
397
|
|
|
398
|
+
**Code:**
|
|
356
399
|
```typescript
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
await xray.enqueueReasoning(queue) // Blocks API response
|
|
360
|
-
return { executionId }
|
|
400
|
+
import { XRay, MemoryStorage, ReasoningQueue, createOpenAIGenerator } from 'xray-sdk'
|
|
401
|
+
import OpenAI from 'openai'
|
|
361
402
|
|
|
362
|
-
//
|
|
363
|
-
|
|
364
|
-
|
|
403
|
+
// 1. Track execution
|
|
404
|
+
const xray = new XRay(executionId, { pipeline: 'my-pipeline' })
|
|
405
|
+
xray.startStep('process', { input: 'data' })
|
|
406
|
+
xray.endStep('process', { output: 'result' })
|
|
407
|
+
const execution = xray.end({ status: 'success' })
|
|
365
408
|
|
|
366
|
-
//
|
|
409
|
+
// 2. Generate reasoning CLIENT-SIDE
|
|
410
|
+
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY })
|
|
411
|
+
const storage = new MemoryStorage()
|
|
412
|
+
await storage.saveExecution(execution)
|
|
413
|
+
|
|
414
|
+
const generator = createOpenAIGenerator(openai)
|
|
415
|
+
const queue = new ReasoningQueue(storage, generator)
|
|
367
416
|
await queue.processExecution(executionId)
|
|
417
|
+
|
|
418
|
+
// 3. Get execution with reasoning
|
|
419
|
+
const executionWithReasoning = await storage.getExecutionById(executionId)
|
|
420
|
+
|
|
421
|
+
// 4. Send to dashboard
|
|
422
|
+
await client.saveExecution(executionWithReasoning)
|
|
368
423
|
```
|
|
369
424
|
|
|
370
|
-
|
|
425
|
+
**See:** \`demo-app/src/7-standalone-reasoning.ts\` for complete example
|
|
426
|
+
|
|
427
|
+
---
|
|
428
|
+
|
|
429
|
+
## 🏗️ Advanced Features
|
|
430
|
+
|
|
431
|
+
### Custom Storage
|
|
371
432
|
|
|
372
433
|
```typescript
|
|
373
|
-
|
|
434
|
+
import { MemoryStorage, DatabaseStorage } from 'xray-sdk'
|
|
435
|
+
|
|
436
|
+
// In-memory (for testing)
|
|
374
437
|
const storage = new MemoryStorage()
|
|
375
438
|
|
|
376
|
-
//
|
|
377
|
-
const storage = new DatabaseStorage(prisma)
|
|
439
|
+
// Database (requires Prisma setup)
|
|
440
|
+
const storage = new DatabaseStorage(prisma, userId)
|
|
378
441
|
```
|
|
379
442
|
|
|
380
|
-
###
|
|
443
|
+
### Reasoning Queue Configuration
|
|
381
444
|
|
|
382
445
|
```typescript
|
|
383
|
-
|
|
446
|
+
import { ReasoningQueue, createOpenAIGenerator } from 'xray-sdk'
|
|
447
|
+
|
|
384
448
|
const queue = new ReasoningQueue(storage, generator, {
|
|
385
|
-
concurrency: 3,
|
|
386
|
-
maxRetries: 4,
|
|
387
|
-
debug: true
|
|
449
|
+
concurrency: 3, // Process 3 steps in parallel
|
|
450
|
+
maxRetries: 4, // Retry failed reasoning jobs
|
|
451
|
+
debug: true // Enable detailed logging
|
|
388
452
|
})
|
|
453
|
+
|
|
454
|
+
await queue.processExecution(executionId)
|
|
389
455
|
```
|
|
390
456
|
|
|
391
|
-
###
|
|
457
|
+
### TypeScript Types
|
|
392
458
|
|
|
393
459
|
```typescript
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
460
|
+
import { Execution, Step, XRay } from 'xray-sdk'
|
|
461
|
+
|
|
462
|
+
const execution: Execution = {
|
|
463
|
+
executionId: 'exec-123',
|
|
464
|
+
startedAt: '2024-01-01T00:00:00Z',
|
|
465
|
+
endedAt: '2024-01-01T00:01:00Z',
|
|
466
|
+
steps: [
|
|
467
|
+
{
|
|
468
|
+
name: 'step1',
|
|
469
|
+
input: { data: 'test' },
|
|
470
|
+
output: { result: 'success' },
|
|
471
|
+
durationMs: 150
|
|
472
|
+
}
|
|
473
|
+
],
|
|
474
|
+
finalOutcome: { status: 'success' }
|
|
401
475
|
}
|
|
402
476
|
```
|
|
403
477
|
|
|
404
|
-
|
|
478
|
+
---
|
|
405
479
|
|
|
406
|
-
|
|
480
|
+
## 📊 Dashboard Features
|
|
407
481
|
|
|
408
|
-
|
|
409
|
-
|
|
482
|
+
The X-Ray Dashboard provides:
|
|
483
|
+
|
|
484
|
+
| Feature | Description |
|
|
485
|
+
|---------|-------------|
|
|
486
|
+
| **Execution List** | Browse all pipeline runs with status indicators |
|
|
487
|
+
| **Step-by-Step View** | Detailed breakdown showing input/output for each step |
|
|
488
|
+
| **AI Reasoning** | Automatic "WHY" explanations for decisions |
|
|
489
|
+
| **Real-Time Updates** | Watch reasoning generate live (polls every 2 seconds) |
|
|
490
|
+
| **JSON Viewer** | Inspect raw execution data |
|
|
491
|
+
| **Search & Filter** | Find executions by ID, pipeline name, or metadata |
|
|
492
|
+
|
|
493
|
+
**Dashboard URL:** https://x-ray-library-sdk-git-main-devdurgesh619s-projects.vercel.app
|
|
494
|
+
|
|
495
|
+
---
|
|
496
|
+
|
|
497
|
+
## 🎯 Use Cases
|
|
498
|
+
|
|
499
|
+
- **E-Commerce**: Competitor selection, product matching, price optimization
|
|
500
|
+
- **Content Recommendation**: Movie/music recommendations, personalization engines
|
|
501
|
+
- **Data Pipelines**: ETL processes, data validation, transformation workflows
|
|
502
|
+
- **LLM Workflows**: Multi-step AI reasoning, autonomous agent systems
|
|
503
|
+
- **Debugging**: Understand why pipeline decisions were made, identify bottlenecks
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
## ✅ Integration Checklist
|
|
410
508
|
|
|
411
|
-
|
|
412
|
-
|
|
509
|
+
Use this checklist to verify your integration:
|
|
510
|
+
|
|
511
|
+
- [ ] Install \`xray-sdk\` package (\`npm install xray-sdk\`)
|
|
512
|
+
- [ ] Create account on dashboard
|
|
513
|
+
- [ ] Get API key from https://x-ray-library-sdk-git-main-devdurgesh619s-projects.vercel.app/api-key
|
|
514
|
+
- [ ] Set environment variables (\`XRAY_API_URL\`, \`XRAY_API_KEY\`)
|
|
515
|
+
- [ ] Create HTTP client wrapper (\`src/lib/xrayClient.ts\`)
|
|
516
|
+
- [ ] Add \`XRay\` tracking to your pipeline (startStep/endStep)
|
|
517
|
+
- [ ] Test with a simple pipeline execution
|
|
518
|
+
- [ ] Verify execution appears in dashboard
|
|
519
|
+
- [ ] Verify AI reasoning generates automatically
|
|
520
|
+
- [ ] (Optional) Set up client-side reasoning for sensitive workloads
|
|
521
|
+
|
|
522
|
+
---
|
|
523
|
+
|
|
524
|
+
## 🚀 Quick Start Summary
|
|
525
|
+
|
|
526
|
+
```bash
|
|
527
|
+
# 1. Install
|
|
528
|
+
npm install xray-sdk
|
|
529
|
+
|
|
530
|
+
# 2. Get API key
|
|
531
|
+
# Visit: https://x-ray-library-sdk-git-main-devdurgesh619s-projects.vercel.app/api-key
|
|
532
|
+
|
|
533
|
+
# 3. Configure .env
|
|
534
|
+
XRAY_API_URL="https://x-ray-library-sdk-git-main-devdurgesh619s-projects.vercel.app"
|
|
535
|
+
XRAY_API_KEY="xray_your_key_here"
|
|
536
|
+
|
|
537
|
+
# 4. Copy HTTP client
|
|
538
|
+
# See: demo-app/src/lib/xrayClient.ts
|
|
539
|
+
|
|
540
|
+
# 5. Track your pipeline
|
|
541
|
+
# See: demo-app/src/2-ecommerce-example.ts
|
|
542
|
+
|
|
543
|
+
# 6. Run and view results
|
|
544
|
+
npm run your-pipeline
|
|
545
|
+
# Open execution URL in browser
|
|
413
546
|
```
|
|
414
547
|
|
|
415
|
-
|
|
548
|
+
---
|
|
549
|
+
|
|
550
|
+
## 📚 Additional Resources
|
|
551
|
+
|
|
552
|
+
- **Demo App**: See \`demo-app/\` directory for 5 production-ready examples
|
|
553
|
+
- **Main README**: See \`../README.md\` for project overview
|
|
554
|
+
- **Security Guide**: See \`../SECURITY.md\` for best practices
|
|
555
|
+
- **Architecture**: See \`../XRAY_ARCHITECTURE_OVERVIEW.md\` for system design
|
|
556
|
+
|
|
557
|
+
---
|
|
558
|
+
|
|
559
|
+
## 📝 License
|
|
560
|
+
|
|
561
|
+
ISC
|
|
416
562
|
|
|
417
|
-
|
|
563
|
+
---
|
|
418
564
|
|
|
419
|
-
##
|
|
565
|
+
## 🆘 Support
|
|
420
566
|
|
|
421
|
-
|
|
567
|
+
- **Dashboard**: https://x-ray-library-sdk-git-main-devdurgesh619s-projects.vercel.app
|
|
568
|
+
- **Get API Key**: https://x-ray-library-sdk-git-main-devdurgesh619s-projects.vercel.app/api-key
|
|
569
|
+
- **Examples**: See \`demo-app/\` directory in the repository
|
|
422
570
|
|
|
423
|
-
|
|
571
|
+
---
|
|
424
572
|
|
|
425
|
-
|
|
426
|
-
- Documentation: https://xray-sdk.dev
|
|
573
|
+
Start tracking your pipelines today with X-Ray! 🚀
|