@evalview/node 0.1.0
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/LICENSE +21 -0
- package/README.md +97 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +10 -0
- package/dist/middleware.d.ts +65 -0
- package/dist/middleware.js +183 -0
- package/package.json +65 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 EvalView Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# @evalview/node
|
|
2
|
+
|
|
3
|
+
Drop-in Node.js/Next.js middleware for [EvalView](https://github.com/hidai25/eval-view) testing framework.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @evalview/node
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
### Next.js App Router
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
// app/api/evalview/route.ts
|
|
17
|
+
import { createEvalViewMiddleware } from '@evalview/node';
|
|
18
|
+
|
|
19
|
+
export const POST = createEvalViewMiddleware({
|
|
20
|
+
targetEndpoint: '/api/unifiedchat', // Your agent's endpoint
|
|
21
|
+
});
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Express.js
|
|
25
|
+
|
|
26
|
+
```javascript
|
|
27
|
+
const { createEvalViewMiddleware } = require('@evalview/node');
|
|
28
|
+
|
|
29
|
+
app.post('/api/evalview', createEvalViewMiddleware({
|
|
30
|
+
targetEndpoint: '/api/your-agent',
|
|
31
|
+
}));
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Configuration
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
createEvalViewMiddleware({
|
|
38
|
+
// Required: Endpoint to forward requests to
|
|
39
|
+
targetEndpoint: '/api/unifiedchat',
|
|
40
|
+
|
|
41
|
+
// Optional: Default user ID for test requests (defaults to 'evalview-test-user')
|
|
42
|
+
// Use an existing user ID from your database
|
|
43
|
+
defaultUserId: 'your-dev-user-id',
|
|
44
|
+
|
|
45
|
+
// Optional: Dynamic user ID resolution
|
|
46
|
+
// Useful for creating users on-the-fly or looking up existing ones
|
|
47
|
+
getUserId: async (req) => {
|
|
48
|
+
// Example: Look up or create user
|
|
49
|
+
const user = await findOrCreateUser('test@example.com');
|
|
50
|
+
return user.id;
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
// Optional: Transform EvalView request to your API format
|
|
54
|
+
transformRequest: (req) => ({
|
|
55
|
+
message: req.query,
|
|
56
|
+
userId: req.context?.userId, // Automatically set by middleware
|
|
57
|
+
// ... your custom mapping
|
|
58
|
+
}),
|
|
59
|
+
|
|
60
|
+
// Optional: Parse your API response to EvalView format
|
|
61
|
+
parseResponse: (responseText, startTime) => ({
|
|
62
|
+
session_id: `session-${startTime}`,
|
|
63
|
+
output: '...',
|
|
64
|
+
steps: [...],
|
|
65
|
+
cost: 0.05,
|
|
66
|
+
latency: Date.now() - startTime,
|
|
67
|
+
}),
|
|
68
|
+
|
|
69
|
+
// Optional: Base URL for requests
|
|
70
|
+
baseUrl: process.env.API_BASE_URL,
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Default Behavior
|
|
75
|
+
|
|
76
|
+
Works out-of-the-box with Tapescope-style APIs that:
|
|
77
|
+
- Accept: `{ message, userId, conversationId, route, history }`
|
|
78
|
+
- Return: NDJSON stream with `tool_call` and `message_complete` events
|
|
79
|
+
|
|
80
|
+
## Testing
|
|
81
|
+
|
|
82
|
+
Point EvalView CLI to your endpoint:
|
|
83
|
+
|
|
84
|
+
```yaml
|
|
85
|
+
# .evalview/config.yaml
|
|
86
|
+
adapter: http
|
|
87
|
+
endpoint: http://localhost:3000/api/evalview
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Then run tests:
|
|
91
|
+
```bash
|
|
92
|
+
evalview run
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## License
|
|
96
|
+
|
|
97
|
+
MIT
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @evalview/node
|
|
4
|
+
*
|
|
5
|
+
* Drop-in Node.js middleware for EvalView testing framework
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.createEvalViewMiddleware = void 0;
|
|
9
|
+
var middleware_1 = require("./middleware");
|
|
10
|
+
Object.defineProperty(exports, "createEvalViewMiddleware", { enumerable: true, get: function () { return middleware_1.createEvalViewMiddleware; } });
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EvalView Middleware for Node.js/Next.js
|
|
3
|
+
*
|
|
4
|
+
* Drop-in middleware that translates EvalView standard format
|
|
5
|
+
* to your agent's API format and back.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import { createEvalViewMiddleware } from '@evalview/node'
|
|
9
|
+
*
|
|
10
|
+
* app.post('/api/evalview', createEvalViewMiddleware({
|
|
11
|
+
* targetEndpoint: '/api/unifiedchat',
|
|
12
|
+
* parseResponse: (ndjson) => ({ output, steps, cost, latency })
|
|
13
|
+
* }))
|
|
14
|
+
*/
|
|
15
|
+
export interface EvalViewRequest {
|
|
16
|
+
query: string;
|
|
17
|
+
context?: {
|
|
18
|
+
route?: string;
|
|
19
|
+
userId?: string;
|
|
20
|
+
conversationId?: string;
|
|
21
|
+
history?: any[];
|
|
22
|
+
[key: string]: any;
|
|
23
|
+
};
|
|
24
|
+
enable_tracing?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface EvalViewStep {
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
tool: string;
|
|
30
|
+
parameters: any;
|
|
31
|
+
output?: any;
|
|
32
|
+
success: boolean;
|
|
33
|
+
latency?: number;
|
|
34
|
+
cost?: number;
|
|
35
|
+
tokens?: number;
|
|
36
|
+
}
|
|
37
|
+
export interface EvalViewResponse {
|
|
38
|
+
session_id: string;
|
|
39
|
+
output: string;
|
|
40
|
+
steps: EvalViewStep[];
|
|
41
|
+
cost: number;
|
|
42
|
+
latency: number;
|
|
43
|
+
tokens?: number;
|
|
44
|
+
}
|
|
45
|
+
export interface MiddlewareConfig {
|
|
46
|
+
/** Endpoint to forward requests to (e.g., '/api/unifiedchat') */
|
|
47
|
+
targetEndpoint: string;
|
|
48
|
+
/** Optional: Transform EvalView request to your API format */
|
|
49
|
+
transformRequest?: (req: EvalViewRequest) => any;
|
|
50
|
+
/** Optional: Parse your API response to EvalView format */
|
|
51
|
+
parseResponse?: (responseText: string, startTime: number) => EvalViewResponse;
|
|
52
|
+
/** Optional: Base URL for requests (defaults to current host) */
|
|
53
|
+
baseUrl?: string;
|
|
54
|
+
/** Optional: Default user ID for test requests (defaults to 'evalview-test-user') */
|
|
55
|
+
defaultUserId?: string;
|
|
56
|
+
/** Optional: Function to get/create user ID dynamically */
|
|
57
|
+
getUserId?: (req: EvalViewRequest) => string | Promise<string>;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Create EvalView middleware handler
|
|
61
|
+
*
|
|
62
|
+
* @param config - Middleware configuration
|
|
63
|
+
* @returns Request handler function
|
|
64
|
+
*/
|
|
65
|
+
export declare function createEvalViewMiddleware(config: MiddlewareConfig): (req: any) => Promise<import("undici-types").Response>;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* EvalView Middleware for Node.js/Next.js
|
|
4
|
+
*
|
|
5
|
+
* Drop-in middleware that translates EvalView standard format
|
|
6
|
+
* to your agent's API format and back.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* import { createEvalViewMiddleware } from '@evalview/node'
|
|
10
|
+
*
|
|
11
|
+
* app.post('/api/evalview', createEvalViewMiddleware({
|
|
12
|
+
* targetEndpoint: '/api/unifiedchat',
|
|
13
|
+
* parseResponse: (ndjson) => ({ output, steps, cost, latency })
|
|
14
|
+
* }))
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.createEvalViewMiddleware = createEvalViewMiddleware;
|
|
18
|
+
const DEBUG = process.env.EVALVIEW_DEBUG === 'true';
|
|
19
|
+
/**
|
|
20
|
+
* Default Tapescope request transformer
|
|
21
|
+
*/
|
|
22
|
+
function defaultTransformRequest(req) {
|
|
23
|
+
const timestamp = Date.now();
|
|
24
|
+
return {
|
|
25
|
+
message: req.query,
|
|
26
|
+
userId: req.context?.userId, // Set by middleware
|
|
27
|
+
conversationId: req.context?.conversationId || `eval-${timestamp}`,
|
|
28
|
+
route: req.context?.route || 'orchestrator',
|
|
29
|
+
history: req.context?.history || [],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Default Tapescope NDJSON response parser
|
|
34
|
+
* Handles both pure NDJSON and SSE format (with "data: " prefix)
|
|
35
|
+
*/
|
|
36
|
+
function defaultParseResponse(ndjsonText, startTime) {
|
|
37
|
+
const lines = ndjsonText.trim().split('\n');
|
|
38
|
+
const events = lines
|
|
39
|
+
.filter(line => line.trim())
|
|
40
|
+
.map(line => {
|
|
41
|
+
try {
|
|
42
|
+
// Strip SSE "data: " prefix if present
|
|
43
|
+
const jsonLine = line.startsWith('data: ') ? line.slice(6) : line;
|
|
44
|
+
return JSON.parse(jsonLine);
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
.filter(Boolean);
|
|
51
|
+
const steps = [];
|
|
52
|
+
let finalOutput = '';
|
|
53
|
+
let totalCost = 0;
|
|
54
|
+
let totalTokens = 0;
|
|
55
|
+
const sessionId = `session-${startTime}`;
|
|
56
|
+
for (const event of events) {
|
|
57
|
+
const eventType = event.type;
|
|
58
|
+
// Capture tool calls as steps
|
|
59
|
+
if (eventType === 'tool_call') {
|
|
60
|
+
steps.push({
|
|
61
|
+
id: `step-${steps.length}`,
|
|
62
|
+
name: event.tool || 'unknown',
|
|
63
|
+
tool: event.tool || 'unknown',
|
|
64
|
+
parameters: event.params || {},
|
|
65
|
+
output: event.result,
|
|
66
|
+
success: true,
|
|
67
|
+
latency: 0,
|
|
68
|
+
cost: 0,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
// Capture final message
|
|
72
|
+
if (eventType === 'message_complete') {
|
|
73
|
+
finalOutput = event.final?.text || '';
|
|
74
|
+
totalCost = event.data?.metadata?.cost || 0;
|
|
75
|
+
// Add synthesis step if present
|
|
76
|
+
if (event.tool) {
|
|
77
|
+
steps.push({
|
|
78
|
+
id: `step-${steps.length}`,
|
|
79
|
+
name: event.tool,
|
|
80
|
+
tool: event.tool,
|
|
81
|
+
parameters: {},
|
|
82
|
+
success: event.ok || false,
|
|
83
|
+
latency: 0,
|
|
84
|
+
cost: totalCost,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Accumulate streaming text (fallback if final not set)
|
|
89
|
+
if (eventType === 'text_delta' && !finalOutput) {
|
|
90
|
+
finalOutput += event.delta || '';
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const endTime = Date.now();
|
|
94
|
+
const latency = endTime - startTime;
|
|
95
|
+
return {
|
|
96
|
+
session_id: sessionId,
|
|
97
|
+
output: finalOutput,
|
|
98
|
+
steps,
|
|
99
|
+
cost: totalCost,
|
|
100
|
+
latency,
|
|
101
|
+
tokens: totalTokens,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Create EvalView middleware handler
|
|
106
|
+
*
|
|
107
|
+
* @param config - Middleware configuration
|
|
108
|
+
* @returns Request handler function
|
|
109
|
+
*/
|
|
110
|
+
function createEvalViewMiddleware(config) {
|
|
111
|
+
const { targetEndpoint, transformRequest = defaultTransformRequest, parseResponse = defaultParseResponse, baseUrl, defaultUserId = 'evalview-test-user', getUserId, } = config;
|
|
112
|
+
return async function evalViewHandler(req) {
|
|
113
|
+
const startTime = Date.now();
|
|
114
|
+
try {
|
|
115
|
+
// Parse EvalView request (Next.js App Router)
|
|
116
|
+
const evalViewReq = await req.json();
|
|
117
|
+
// Resolve user ID (priority: context.userId > getUserId() > defaultUserId)
|
|
118
|
+
if (!evalViewReq.context) {
|
|
119
|
+
evalViewReq.context = {};
|
|
120
|
+
}
|
|
121
|
+
if (!evalViewReq.context.userId) {
|
|
122
|
+
evalViewReq.context.userId = getUserId
|
|
123
|
+
? await getUserId(evalViewReq)
|
|
124
|
+
: defaultUserId;
|
|
125
|
+
}
|
|
126
|
+
if (DEBUG)
|
|
127
|
+
console.log('[EvalView] Received request:', {
|
|
128
|
+
query: evalViewReq.query,
|
|
129
|
+
route: evalViewReq.context?.route,
|
|
130
|
+
});
|
|
131
|
+
// Transform to target API format
|
|
132
|
+
const targetReq = transformRequest(evalViewReq);
|
|
133
|
+
if (DEBUG)
|
|
134
|
+
console.log('[EvalView] Calling target endpoint:', targetEndpoint);
|
|
135
|
+
// Determine base URL
|
|
136
|
+
const host = req.headers?.get?.('host') || req.headers?.host || 'localhost:3000';
|
|
137
|
+
const requestBaseUrl = baseUrl || `http://${host}`;
|
|
138
|
+
// Call target endpoint
|
|
139
|
+
const response = await fetch(`${requestBaseUrl}${targetEndpoint}`, {
|
|
140
|
+
method: 'POST',
|
|
141
|
+
headers: {
|
|
142
|
+
'Content-Type': 'application/json',
|
|
143
|
+
},
|
|
144
|
+
body: JSON.stringify(targetReq),
|
|
145
|
+
});
|
|
146
|
+
if (!response.ok) {
|
|
147
|
+
throw new Error(`Target endpoint failed: ${response.status} ${response.statusText}`);
|
|
148
|
+
}
|
|
149
|
+
// Get response text
|
|
150
|
+
const responseText = await response.text();
|
|
151
|
+
if (DEBUG)
|
|
152
|
+
console.log('[EvalView] Received response, parsing...');
|
|
153
|
+
// Parse and translate response
|
|
154
|
+
const evalViewRes = parseResponse(responseText, startTime);
|
|
155
|
+
if (DEBUG)
|
|
156
|
+
console.log('[EvalView] Translated response:', {
|
|
157
|
+
steps: evalViewRes.steps.length,
|
|
158
|
+
outputLength: evalViewRes.output.length,
|
|
159
|
+
cost: evalViewRes.cost,
|
|
160
|
+
latency: evalViewRes.latency,
|
|
161
|
+
});
|
|
162
|
+
// Return JSON response (Next.js App Router format)
|
|
163
|
+
return new Response(JSON.stringify(evalViewRes), {
|
|
164
|
+
status: 200,
|
|
165
|
+
headers: { 'Content-Type': 'application/json' },
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
console.error('[EvalView] Error:', error);
|
|
170
|
+
return new Response(JSON.stringify({
|
|
171
|
+
error: error.message || 'EvalView middleware failed',
|
|
172
|
+
session_id: `error-${startTime}`,
|
|
173
|
+
output: '',
|
|
174
|
+
steps: [],
|
|
175
|
+
cost: 0,
|
|
176
|
+
latency: Date.now() - startTime,
|
|
177
|
+
}), {
|
|
178
|
+
status: 500,
|
|
179
|
+
headers: { 'Content-Type': 'application/json' },
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@evalview/node",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Drop-in Node.js middleware for EvalView testing framework",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./middleware": {
|
|
15
|
+
"types": "./dist/middleware.d.ts",
|
|
16
|
+
"import": "./dist/middleware.js",
|
|
17
|
+
"require": "./dist/middleware.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"README.md",
|
|
23
|
+
"LICENSE"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc",
|
|
27
|
+
"dev": "tsc --watch",
|
|
28
|
+
"clean": "rm -rf dist",
|
|
29
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"evalview",
|
|
33
|
+
"testing",
|
|
34
|
+
"ai-agents",
|
|
35
|
+
"middleware",
|
|
36
|
+
"llm",
|
|
37
|
+
"evaluation"
|
|
38
|
+
],
|
|
39
|
+
"author": "EvalView",
|
|
40
|
+
"license": "Apache-2.0",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git+https://github.com/hidai25/eval-view.git",
|
|
44
|
+
"directory": "sdks/node"
|
|
45
|
+
},
|
|
46
|
+
"bugs": {
|
|
47
|
+
"url": "https://github.com/hidai25/eval-view/issues"
|
|
48
|
+
},
|
|
49
|
+
"homepage": "https://github.com/hidai25/eval-view/tree/main/sdks/node#readme",
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=16.0.0"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"next": ">=13.0.0"
|
|
55
|
+
},
|
|
56
|
+
"peerDependenciesMeta": {
|
|
57
|
+
"next": {
|
|
58
|
+
"optional": true
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@types/node": "^20.0.0",
|
|
63
|
+
"typescript": "^5.0.0"
|
|
64
|
+
}
|
|
65
|
+
}
|