@cephalization/phoenix-insight 0.4.0 → 1.0.2
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 +195 -1
- package/dist/chunk-KEQDYZIE.js +237 -0
- package/dist/chunk-KEQDYZIE.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +3950 -642
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +108 -0
- package/dist/index.js +13 -1
- package/dist/index.js.map +1 -0
- package/dist/ui/assets/code-block-F6WJLWQG-BTdTzfvl.js +154 -0
- package/dist/ui/assets/code-block-F6WJLWQG-BTdTzfvl.js.map +1 -0
- package/dist/ui/assets/index-CX8aDatf.css +1 -0
- package/dist/ui/assets/index-DjZuAW6Y.js +63 -0
- package/dist/ui/assets/index-DjZuAW6Y.js.map +1 -0
- package/dist/ui/assets/vendor-data-r1ZEkUds.js +40 -0
- package/dist/ui/assets/vendor-data-r1ZEkUds.js.map +1 -0
- package/dist/ui/assets/vendor-react-Cgg2GOmP.js +2 -0
- package/dist/ui/assets/vendor-react-Cgg2GOmP.js.map +1 -0
- package/dist/ui/assets/vendor-render-DoMl5bum.js +381 -0
- package/dist/ui/assets/vendor-render-DoMl5bum.js.map +1 -0
- package/dist/ui/assets/vendor-ui-Cg-YC4hK.js +46 -0
- package/dist/ui/assets/vendor-ui-Cg-YC4hK.js.map +1 -0
- package/dist/ui/index.html +18 -0
- package/dist/ui/vite.svg +1 -0
- package/package.json +14 -15
- package/dist/agent/index.js +0 -230
- package/dist/commands/index.js +0 -2
- package/dist/commands/px-fetch-more-spans.js +0 -98
- package/dist/commands/px-fetch-more-trace.js +0 -110
- package/dist/config/index.js +0 -165
- package/dist/config/loader.js +0 -141
- package/dist/config/schema.js +0 -53
- package/dist/modes/index.js +0 -17
- package/dist/modes/local.js +0 -134
- package/dist/modes/sandbox.js +0 -121
- package/dist/modes/types.js +0 -1
- package/dist/observability/index.js +0 -65
- package/dist/progress.js +0 -209
- package/dist/prompts/index.js +0 -1
- package/dist/prompts/system.js +0 -30
- package/dist/snapshot/client.js +0 -74
- package/dist/snapshot/context.js +0 -441
- package/dist/snapshot/datasets.js +0 -68
- package/dist/snapshot/experiments.js +0 -135
- package/dist/snapshot/index.js +0 -262
- package/dist/snapshot/projects.js +0 -44
- package/dist/snapshot/prompts.js +0 -199
- package/dist/snapshot/spans.js +0 -104
- package/dist/snapshot/utils.js +0 -112
- package/dist/tsconfig.esm.tsbuildinfo +0 -1
- package/src/agent/index.ts +0 -323
- package/src/cli.ts +0 -854
- package/src/commands/index.ts +0 -8
- package/src/commands/px-fetch-more-spans.ts +0 -174
- package/src/commands/px-fetch-more-trace.ts +0 -183
- package/src/config/index.ts +0 -225
- package/src/config/loader.ts +0 -173
- package/src/config/schema.ts +0 -66
- package/src/index.ts +0 -1
- package/src/modes/index.ts +0 -21
- package/src/modes/local.ts +0 -163
- package/src/modes/sandbox.ts +0 -144
- package/src/modes/types.ts +0 -31
- package/src/observability/index.ts +0 -90
- package/src/progress.ts +0 -239
- package/src/prompts/index.ts +0 -1
- package/src/prompts/system.ts +0 -31
- package/src/snapshot/client.ts +0 -129
- package/src/snapshot/context.ts +0 -587
- package/src/snapshot/datasets.ts +0 -132
- package/src/snapshot/experiments.ts +0 -246
- package/src/snapshot/index.ts +0 -403
- package/src/snapshot/projects.ts +0 -58
- package/src/snapshot/prompts.ts +0 -267
- package/src/snapshot/spans.ts +0 -163
- package/src/snapshot/utils.ts +0 -140
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="./vite.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>ui</title>
|
|
8
|
+
<script type="module" crossorigin src="./assets/index-DjZuAW6Y.js"></script>
|
|
9
|
+
<link rel="modulepreload" crossorigin href="./assets/vendor-react-Cgg2GOmP.js">
|
|
10
|
+
<link rel="modulepreload" crossorigin href="./assets/vendor-ui-Cg-YC4hK.js">
|
|
11
|
+
<link rel="modulepreload" crossorigin href="./assets/vendor-data-r1ZEkUds.js">
|
|
12
|
+
<link rel="modulepreload" crossorigin href="./assets/vendor-render-DoMl5bum.js">
|
|
13
|
+
<link rel="stylesheet" crossorigin href="./assets/index-CX8aDatf.css">
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<div id="root"></div>
|
|
17
|
+
</body>
|
|
18
|
+
</html>
|
package/dist/ui/vite.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cephalization/phoenix-insight",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "A CLI for Arize AI Phoenix data analysis with AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -17,9 +17,7 @@
|
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
|
-
"dist"
|
|
21
|
-
"src",
|
|
22
|
-
"package.json"
|
|
20
|
+
"dist"
|
|
23
21
|
],
|
|
24
22
|
"keywords": [
|
|
25
23
|
"phoenix",
|
|
@@ -29,12 +27,13 @@
|
|
|
29
27
|
"llmops",
|
|
30
28
|
"ai"
|
|
31
29
|
],
|
|
32
|
-
"author": "
|
|
30
|
+
"author": "Tony Powell <powell.anthonyd@proton.me>",
|
|
33
31
|
"license": "Apache-2.0",
|
|
34
|
-
"homepage": "https://github.com/cephalization/phoenix-insight#readme",
|
|
32
|
+
"homepage": "https://github.com/cephalization/phoenix-insight/tree/main/packages/cli#readme",
|
|
35
33
|
"repository": {
|
|
36
34
|
"type": "git",
|
|
37
|
-
"url": "git+https://github.com/cephalization/phoenix-insight.git"
|
|
35
|
+
"url": "git+https://github.com/cephalization/phoenix-insight.git",
|
|
36
|
+
"directory": "packages/cli"
|
|
38
37
|
},
|
|
39
38
|
"bugs": {
|
|
40
39
|
"url": "https://github.com/cephalization/phoenix-insight/issues"
|
|
@@ -49,26 +48,26 @@
|
|
|
49
48
|
"commander": "^12.0.0",
|
|
50
49
|
"just-bash": "^2.2.0",
|
|
51
50
|
"ora": "^9.0.0",
|
|
51
|
+
"ws": "^8.19.0",
|
|
52
52
|
"zod": "^4.3.5"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@changesets/cli": "^2.29.8",
|
|
56
55
|
"@faker-js/faker": "^10.2.0",
|
|
57
|
-
"@types/
|
|
56
|
+
"@types/ws": "^8.18.1",
|
|
58
57
|
"msw": "^2.12.7",
|
|
59
|
-
"rimraf": "^5.0.10",
|
|
60
58
|
"tsc-alias": "^1.8.11",
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"vitest": "^2.1.9"
|
|
59
|
+
"tsup": "^8.5.1",
|
|
60
|
+
"@cephalization/phoenix-insight-ui": "1.0.0"
|
|
64
61
|
},
|
|
65
62
|
"engines": {
|
|
66
63
|
"node": ">=18"
|
|
67
64
|
},
|
|
68
65
|
"scripts": {
|
|
69
66
|
"clean": "rimraf dist tsconfig.esm.tsbuildinfo tsconfig.tsbuildinfo",
|
|
70
|
-
"prebuild": "pnpm run clean",
|
|
71
|
-
"
|
|
67
|
+
"prebuild": "pnpm run clean && pnpm run verify-ui-dist",
|
|
68
|
+
"verify-ui-dist": "node -e \"const fs = require('fs'); const path = require('path'); const uiDist = path.resolve(__dirname, '../ui/dist/index.html'); if (!fs.existsSync(uiDist)) { console.error('ERROR: UI dist not found. Build UI package first with: pnpm --filter @cephalization/phoenix-insight-ui build'); process.exit(1); } console.log('UI dist verified:', uiDist);\"",
|
|
69
|
+
"build": "tsup && pnpm run copy-ui-dist",
|
|
70
|
+
"copy-ui-dist": "node -e \"const fs = require('fs'); const path = require('path'); const src = path.resolve(__dirname, '../ui/dist'); const dest = path.resolve(__dirname, 'dist/ui'); fs.cpSync(src, dest, { recursive: true }); console.log('UI dist copied to:', dest);\"",
|
|
72
71
|
"start": "node dist/cli.js",
|
|
73
72
|
"dev": "tsx src/cli.ts",
|
|
74
73
|
"typecheck": "tsc --noEmit",
|
package/dist/agent/index.js
DELETED
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Phoenix Insight AI agent setup using Vercel AI SDK
|
|
3
|
-
*/
|
|
4
|
-
import { generateText, streamText, tool, stepCountIs, } from "ai";
|
|
5
|
-
import { anthropic } from "@ai-sdk/anthropic";
|
|
6
|
-
import { z } from "zod";
|
|
7
|
-
import { INSIGHT_SYSTEM_PROMPT } from "../prompts/system.js";
|
|
8
|
-
import { fetchMoreSpans, fetchMoreTrace, } from "../commands/index.js";
|
|
9
|
-
import { PhoenixClientError } from "../snapshot/client.js";
|
|
10
|
-
/**
|
|
11
|
-
* Phoenix Insight Agent
|
|
12
|
-
*/
|
|
13
|
-
export class PhoenixInsightAgent {
|
|
14
|
-
mode;
|
|
15
|
-
client;
|
|
16
|
-
maxSteps;
|
|
17
|
-
tools = null;
|
|
18
|
-
model = anthropic("claude-sonnet-4-5");
|
|
19
|
-
constructor(config) {
|
|
20
|
-
this.mode = config.mode;
|
|
21
|
-
this.client = config.client;
|
|
22
|
-
this.maxSteps = config.maxSteps || 25;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Initialize the agent tools
|
|
26
|
-
*/
|
|
27
|
-
async initializeTools() {
|
|
28
|
-
if (this.tools)
|
|
29
|
-
return this.tools;
|
|
30
|
-
// Get the bash tool from the execution mode
|
|
31
|
-
const bashTool = await this.mode.getBashTool();
|
|
32
|
-
// Store references in closure for the custom tools
|
|
33
|
-
const client = this.client;
|
|
34
|
-
const mode = this.mode;
|
|
35
|
-
// Create custom px-fetch-more-spans tool using AI SDK's tool function
|
|
36
|
-
const pxFetchMoreSpans = tool({
|
|
37
|
-
description: "Fetch additional spans from Phoenix. Use when you need more span data than what's in the snapshot. You must provide both project name and optionally a limit.",
|
|
38
|
-
inputSchema: z.object({
|
|
39
|
-
project: z.string().describe("The project name"),
|
|
40
|
-
limit: z
|
|
41
|
-
.number()
|
|
42
|
-
.optional()
|
|
43
|
-
.describe("Number of spans to fetch (default: 500)"),
|
|
44
|
-
startTime: z
|
|
45
|
-
.string()
|
|
46
|
-
.optional()
|
|
47
|
-
.describe("Start time filter in ISO format"),
|
|
48
|
-
endTime: z
|
|
49
|
-
.string()
|
|
50
|
-
.optional()
|
|
51
|
-
.describe("End time filter in ISO format"),
|
|
52
|
-
}),
|
|
53
|
-
execute: async (params) => {
|
|
54
|
-
try {
|
|
55
|
-
const options = {
|
|
56
|
-
project: params.project,
|
|
57
|
-
limit: params.limit || 500,
|
|
58
|
-
startTime: params.startTime,
|
|
59
|
-
endTime: params.endTime,
|
|
60
|
-
};
|
|
61
|
-
await fetchMoreSpans(client, mode, options);
|
|
62
|
-
return {
|
|
63
|
-
success: true,
|
|
64
|
-
message: `Fetched additional spans for project ${params.project}. Data saved to /phoenix/projects/${params.project}/spans/`,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
catch (error) {
|
|
68
|
-
return {
|
|
69
|
-
success: false,
|
|
70
|
-
error: error instanceof Error ? error.message : String(error),
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
});
|
|
75
|
-
// Create custom px-fetch-more-trace tool using AI SDK's tool function
|
|
76
|
-
const pxFetchMoreTrace = tool({
|
|
77
|
-
description: "Fetch a specific trace by ID from Phoenix. Use when you need to examine a particular trace in detail. You must provide both the trace ID and the project name.",
|
|
78
|
-
inputSchema: z.object({
|
|
79
|
-
traceId: z.string().describe("The trace ID to fetch"),
|
|
80
|
-
project: z.string().describe("The project name to search in"),
|
|
81
|
-
}),
|
|
82
|
-
execute: async (params) => {
|
|
83
|
-
try {
|
|
84
|
-
const options = {
|
|
85
|
-
traceId: params.traceId,
|
|
86
|
-
project: params.project,
|
|
87
|
-
};
|
|
88
|
-
await fetchMoreTrace(client, mode, options);
|
|
89
|
-
return {
|
|
90
|
-
success: true,
|
|
91
|
-
message: `Fetched trace ${params.traceId}. Data saved to /phoenix/traces/${params.traceId}/`,
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
catch (error) {
|
|
95
|
-
return {
|
|
96
|
-
success: false,
|
|
97
|
-
error: error instanceof Error ? error.message : String(error),
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
},
|
|
101
|
-
});
|
|
102
|
-
this.tools = {
|
|
103
|
-
bash: bashTool,
|
|
104
|
-
px_fetch_more_spans: pxFetchMoreSpans,
|
|
105
|
-
px_fetch_more_trace: pxFetchMoreTrace,
|
|
106
|
-
};
|
|
107
|
-
return this.tools;
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Generate a response for a user query
|
|
111
|
-
*/
|
|
112
|
-
async generate(userQuery, options) {
|
|
113
|
-
let tools;
|
|
114
|
-
try {
|
|
115
|
-
tools = await this.initializeTools();
|
|
116
|
-
}
|
|
117
|
-
catch (error) {
|
|
118
|
-
throw new Error(`Failed to initialize agent tools: ${error instanceof Error ? error.message : String(error)}`);
|
|
119
|
-
}
|
|
120
|
-
try {
|
|
121
|
-
const result = await generateText({
|
|
122
|
-
model: this.model,
|
|
123
|
-
system: INSIGHT_SYSTEM_PROMPT,
|
|
124
|
-
prompt: userQuery,
|
|
125
|
-
tools,
|
|
126
|
-
stopWhen: stepCountIs(this.maxSteps),
|
|
127
|
-
onStepFinish: options?.onStepFinish,
|
|
128
|
-
experimental_telemetry: {
|
|
129
|
-
isEnabled: true,
|
|
130
|
-
},
|
|
131
|
-
});
|
|
132
|
-
return result;
|
|
133
|
-
}
|
|
134
|
-
catch (error) {
|
|
135
|
-
// Check for specific AI SDK errors
|
|
136
|
-
if (error instanceof Error) {
|
|
137
|
-
if (error.message.includes("rate limit")) {
|
|
138
|
-
throw new Error("AI model rate limit exceeded. Please wait and try again.");
|
|
139
|
-
}
|
|
140
|
-
if (error.message.includes("timeout")) {
|
|
141
|
-
throw new Error("AI model request timed out. Please try again.");
|
|
142
|
-
}
|
|
143
|
-
if (error.message.includes("authentication") ||
|
|
144
|
-
error.message.includes("API key")) {
|
|
145
|
-
throw new Error("AI model authentication failed. Check your API key configuration.");
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
throw new Error(`AI generation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Stream a response for a user query
|
|
153
|
-
*/
|
|
154
|
-
async stream(userQuery, options) {
|
|
155
|
-
let tools;
|
|
156
|
-
try {
|
|
157
|
-
tools = await this.initializeTools();
|
|
158
|
-
}
|
|
159
|
-
catch (error) {
|
|
160
|
-
throw new Error(`Failed to initialize agent tools: ${error instanceof Error ? error.message : String(error)}`);
|
|
161
|
-
}
|
|
162
|
-
try {
|
|
163
|
-
const result = streamText({
|
|
164
|
-
model: this.model,
|
|
165
|
-
system: INSIGHT_SYSTEM_PROMPT,
|
|
166
|
-
prompt: userQuery,
|
|
167
|
-
tools,
|
|
168
|
-
stopWhen: stepCountIs(this.maxSteps),
|
|
169
|
-
onStepFinish: options?.onStepFinish,
|
|
170
|
-
experimental_telemetry: {
|
|
171
|
-
isEnabled: true,
|
|
172
|
-
},
|
|
173
|
-
});
|
|
174
|
-
return result;
|
|
175
|
-
}
|
|
176
|
-
catch (error) {
|
|
177
|
-
// Check for specific AI SDK errors
|
|
178
|
-
if (error instanceof Error) {
|
|
179
|
-
if (error.message.includes("rate limit")) {
|
|
180
|
-
throw new Error("AI model rate limit exceeded. Please wait and try again.");
|
|
181
|
-
}
|
|
182
|
-
if (error.message.includes("timeout")) {
|
|
183
|
-
throw new Error("AI model request timed out. Please try again.");
|
|
184
|
-
}
|
|
185
|
-
if (error.message.includes("authentication") ||
|
|
186
|
-
error.message.includes("API key")) {
|
|
187
|
-
throw new Error("AI model authentication failed. Check your API key configuration.");
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
throw new Error(`AI streaming failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* Clean up resources
|
|
195
|
-
*/
|
|
196
|
-
async cleanup() {
|
|
197
|
-
await this.mode.cleanup();
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Creates a Phoenix Insight agent
|
|
202
|
-
*/
|
|
203
|
-
export async function createInsightAgent(config) {
|
|
204
|
-
return new PhoenixInsightAgent(config);
|
|
205
|
-
}
|
|
206
|
-
/**
|
|
207
|
-
* Run a query with the Phoenix Insight agent
|
|
208
|
-
*/
|
|
209
|
-
export async function runQuery(agent, userQuery, options) {
|
|
210
|
-
const { stream = false, ...callbacks } = options || {};
|
|
211
|
-
if (stream) {
|
|
212
|
-
return await agent.stream(userQuery, callbacks);
|
|
213
|
-
}
|
|
214
|
-
else {
|
|
215
|
-
return await agent.generate(userQuery, callbacks);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
/**
|
|
219
|
-
* Create and run a one-shot query
|
|
220
|
-
*/
|
|
221
|
-
export async function runOneShotQuery(config, userQuery, options) {
|
|
222
|
-
const agent = await createInsightAgent(config);
|
|
223
|
-
try {
|
|
224
|
-
const result = await runQuery(agent, userQuery, options);
|
|
225
|
-
return result;
|
|
226
|
-
}
|
|
227
|
-
finally {
|
|
228
|
-
await agent.cleanup();
|
|
229
|
-
}
|
|
230
|
-
}
|
package/dist/commands/index.js
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { withErrorHandling } from "../snapshot/client.js";
|
|
2
|
-
/**
|
|
3
|
-
* Fetches additional spans for a specific project on-demand
|
|
4
|
-
*
|
|
5
|
-
* @param client - Phoenix client instance
|
|
6
|
-
* @param mode - Execution mode for file operations
|
|
7
|
-
* @param options - Options for fetching spans
|
|
8
|
-
*/
|
|
9
|
-
export async function fetchMoreSpans(client, mode, options) {
|
|
10
|
-
const { project, limit, startTime, endTime } = options;
|
|
11
|
-
await withErrorHandling(async () => {
|
|
12
|
-
// Try to read existing metadata to get the last cursor
|
|
13
|
-
let existingMetadata = null;
|
|
14
|
-
let existingSpans = [];
|
|
15
|
-
try {
|
|
16
|
-
const metadataResult = await mode.exec(`cat /phoenix/projects/${project}/spans/metadata.json`);
|
|
17
|
-
if (metadataResult.exitCode === 0 && metadataResult.stdout) {
|
|
18
|
-
existingMetadata = JSON.parse(metadataResult.stdout);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
catch (error) {
|
|
22
|
-
// Metadata doesn't exist, that's okay
|
|
23
|
-
}
|
|
24
|
-
// Try to read existing spans
|
|
25
|
-
try {
|
|
26
|
-
const spansResult = await mode.exec(`cat /phoenix/projects/${project}/spans/index.jsonl`);
|
|
27
|
-
if (spansResult.exitCode === 0 && spansResult.stdout) {
|
|
28
|
-
existingSpans = spansResult.stdout
|
|
29
|
-
.trim()
|
|
30
|
-
.split("\n")
|
|
31
|
-
.filter((line) => line.length > 0)
|
|
32
|
-
.map((line) => JSON.parse(line));
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
catch (error) {
|
|
36
|
-
// Spans file doesn't exist, that's okay
|
|
37
|
-
}
|
|
38
|
-
// Fetch new spans
|
|
39
|
-
const newSpans = [];
|
|
40
|
-
let cursor = existingMetadata?.lastCursor ?? null;
|
|
41
|
-
let totalFetched = 0;
|
|
42
|
-
while (totalFetched < limit) {
|
|
43
|
-
const query = {
|
|
44
|
-
limit: Math.min(100, limit - totalFetched), // Fetch in chunks of 100
|
|
45
|
-
};
|
|
46
|
-
if (cursor) {
|
|
47
|
-
query.cursor = cursor;
|
|
48
|
-
}
|
|
49
|
-
if (startTime) {
|
|
50
|
-
query.start_time =
|
|
51
|
-
startTime instanceof Date ? startTime.toISOString() : startTime;
|
|
52
|
-
}
|
|
53
|
-
if (endTime) {
|
|
54
|
-
query.end_time =
|
|
55
|
-
endTime instanceof Date ? endTime.toISOString() : endTime;
|
|
56
|
-
}
|
|
57
|
-
const response = await client.GET("/v1/projects/{project_identifier}/spans", {
|
|
58
|
-
params: {
|
|
59
|
-
path: {
|
|
60
|
-
project_identifier: project,
|
|
61
|
-
},
|
|
62
|
-
query,
|
|
63
|
-
},
|
|
64
|
-
});
|
|
65
|
-
if (response.error)
|
|
66
|
-
throw response.error;
|
|
67
|
-
const data = response.data?.data ?? [];
|
|
68
|
-
newSpans.push(...data);
|
|
69
|
-
totalFetched += data.length;
|
|
70
|
-
cursor = response.data?.next_cursor ?? null;
|
|
71
|
-
// Stop if there's no more data
|
|
72
|
-
if (!cursor || data.length === 0) {
|
|
73
|
-
break;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
// Combine existing and new spans
|
|
77
|
-
const allSpans = [...existingSpans, ...newSpans];
|
|
78
|
-
// Write updated spans to JSONL file
|
|
79
|
-
const jsonlContent = allSpans
|
|
80
|
-
.map((span) => JSON.stringify(span))
|
|
81
|
-
.join("\n");
|
|
82
|
-
await mode.writeFile(`/phoenix/projects/${project}/spans/index.jsonl`, jsonlContent);
|
|
83
|
-
// Update metadata
|
|
84
|
-
const metadata = {
|
|
85
|
-
project,
|
|
86
|
-
spanCount: allSpans.length,
|
|
87
|
-
startTime: startTime instanceof Date
|
|
88
|
-
? startTime.toISOString()
|
|
89
|
-
: (startTime ?? null),
|
|
90
|
-
endTime: endTime instanceof Date ? endTime.toISOString() : (endTime ?? null),
|
|
91
|
-
snapshotTime: new Date().toISOString(),
|
|
92
|
-
lastCursor: cursor,
|
|
93
|
-
};
|
|
94
|
-
await mode.writeFile(`/phoenix/projects/${project}/spans/metadata.json`, JSON.stringify(metadata, null, 2));
|
|
95
|
-
console.log(`Fetched ${newSpans.length} additional spans for project "${project}"`);
|
|
96
|
-
console.log(`Total spans for project: ${allSpans.length}`);
|
|
97
|
-
}, `fetching more spans for project ${project}`);
|
|
98
|
-
}
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { withErrorHandling } from "../snapshot/client.js";
|
|
2
|
-
/**
|
|
3
|
-
* Fetches all spans for a specific trace ID
|
|
4
|
-
*
|
|
5
|
-
* @param client - Phoenix client instance
|
|
6
|
-
* @param mode - Execution mode for file operations
|
|
7
|
-
* @param options - Options for fetching trace
|
|
8
|
-
*/
|
|
9
|
-
export async function fetchMoreTrace(client, mode, options) {
|
|
10
|
-
const { traceId, project } = options;
|
|
11
|
-
await withErrorHandling(async () => {
|
|
12
|
-
// First, check if the project exists in our snapshot
|
|
13
|
-
const projectsResult = await mode.exec("cat /phoenix/projects/index.jsonl");
|
|
14
|
-
if (projectsResult.exitCode !== 0 || !projectsResult.stdout) {
|
|
15
|
-
console.error("No projects found in snapshot. Run a snapshot first.");
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
const projectNames = projectsResult.stdout
|
|
19
|
-
.trim()
|
|
20
|
-
.split("\n")
|
|
21
|
-
.filter((line) => line.length > 0)
|
|
22
|
-
.map((line) => JSON.parse(line).name);
|
|
23
|
-
if (!projectNames.includes(project)) {
|
|
24
|
-
console.error(`Project "${project}" not found. Available projects: ${projectNames.join(", ")}`);
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
// Fetch all spans that belong to this trace
|
|
28
|
-
const traceSpans = [];
|
|
29
|
-
let cursor = null;
|
|
30
|
-
let totalFetched = 0;
|
|
31
|
-
console.log(`Fetching trace ${traceId} from project "${project}"...`);
|
|
32
|
-
// We need to fetch spans in batches and filter by trace_id
|
|
33
|
-
// Since the API doesn't support direct trace_id filtering
|
|
34
|
-
while (true) {
|
|
35
|
-
const query = {
|
|
36
|
-
limit: 100, // Fetch in chunks
|
|
37
|
-
};
|
|
38
|
-
if (cursor) {
|
|
39
|
-
query.cursor = cursor;
|
|
40
|
-
}
|
|
41
|
-
const response = await client.GET("/v1/projects/{project_identifier}/spans", {
|
|
42
|
-
params: {
|
|
43
|
-
path: {
|
|
44
|
-
project_identifier: project,
|
|
45
|
-
},
|
|
46
|
-
query,
|
|
47
|
-
},
|
|
48
|
-
});
|
|
49
|
-
if (response.error)
|
|
50
|
-
throw response.error;
|
|
51
|
-
const data = response.data?.data ?? [];
|
|
52
|
-
totalFetched += data.length;
|
|
53
|
-
// Filter spans that belong to our trace
|
|
54
|
-
const matchingSpans = data.filter((span) => span.context.trace_id === traceId);
|
|
55
|
-
traceSpans.push(...matchingSpans);
|
|
56
|
-
cursor = response.data?.next_cursor ?? null;
|
|
57
|
-
// Stop if we found spans for the trace or no more data
|
|
58
|
-
if (traceSpans.length > 0 || !cursor || data.length === 0) {
|
|
59
|
-
// If we found some spans, continue until we have all spans from the trace
|
|
60
|
-
// This is because trace spans might be spread across multiple pages
|
|
61
|
-
if (traceSpans.length > 0 && cursor && data.length > 0) {
|
|
62
|
-
console.log(`Found ${traceSpans.length} spans so far, continuing search...`);
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
break;
|
|
66
|
-
}
|
|
67
|
-
// Show progress for large datasets
|
|
68
|
-
if (totalFetched % 1000 === 0) {
|
|
69
|
-
console.log(`Searched ${totalFetched} spans so far...`);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
if (traceSpans.length === 0) {
|
|
73
|
-
console.log(`No spans found for trace ${traceId} in project "${project}"`);
|
|
74
|
-
console.log(`Searched through ${totalFetched} spans. The trace might not exist or might be in a different project.`);
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
// Sort spans by start_time to show them in order
|
|
78
|
-
traceSpans.sort((a, b) => new Date(a.start_time).getTime() - new Date(b.start_time).getTime());
|
|
79
|
-
// Write trace spans to a dedicated file
|
|
80
|
-
const jsonlContent = traceSpans
|
|
81
|
-
.map((span) => JSON.stringify(span))
|
|
82
|
-
.join("\n");
|
|
83
|
-
const traceDir = `/phoenix/traces/${traceId}`;
|
|
84
|
-
await mode.writeFile(`${traceDir}/spans.jsonl`, jsonlContent);
|
|
85
|
-
// Create trace metadata
|
|
86
|
-
const rootSpan = traceSpans.find((span) => !span.parent_id);
|
|
87
|
-
const firstSpan = traceSpans[0];
|
|
88
|
-
const lastSpan = traceSpans[traceSpans.length - 1];
|
|
89
|
-
const metadata = {
|
|
90
|
-
traceId,
|
|
91
|
-
project,
|
|
92
|
-
spanCount: traceSpans.length,
|
|
93
|
-
rootSpan: rootSpan ? { id: rootSpan.id, name: rootSpan.name } : null,
|
|
94
|
-
startTime: firstSpan?.start_time || null,
|
|
95
|
-
endTime: lastSpan?.end_time || null,
|
|
96
|
-
duration: firstSpan && lastSpan
|
|
97
|
-
? new Date(lastSpan.end_time).getTime() -
|
|
98
|
-
new Date(firstSpan.start_time).getTime()
|
|
99
|
-
: 0,
|
|
100
|
-
snapshotTime: new Date().toISOString(),
|
|
101
|
-
};
|
|
102
|
-
await mode.writeFile(`${traceDir}/metadata.json`, JSON.stringify(metadata, null, 2));
|
|
103
|
-
console.log(`\nSuccessfully fetched trace ${traceId}:`);
|
|
104
|
-
console.log(`- Project: ${project}`);
|
|
105
|
-
console.log(`- Spans: ${traceSpans.length}`);
|
|
106
|
-
console.log(`- Root span: ${rootSpan?.name || "Unknown"}`);
|
|
107
|
-
console.log(`- Duration: ${(metadata.duration / 1000).toFixed(2)} seconds`);
|
|
108
|
-
console.log(`\nTrace data saved to: ${traceDir}/`);
|
|
109
|
-
}, `fetching trace ${traceId}`);
|
|
110
|
-
}
|