weflayr 0.14.4 → 0.15.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/index.d.ts +18 -0
- package/package.json +1 -1
- package/src/index.js +2 -1
- package/src/instrument.js +7 -1
- package/src/vercel-ai-middleware.js +124 -0
- package/tests/index.test.js +296 -0
package/index.d.ts
CHANGED
|
@@ -99,6 +99,24 @@ export function send_event(
|
|
|
99
99
|
data: Record<string, unknown>,
|
|
100
100
|
): Promise<void> | undefined;
|
|
101
101
|
|
|
102
|
+
/** Options for {@link weflayr_vercel_ai_middleware}. */
|
|
103
|
+
export interface WeflayrVercelAiMiddlewareOptions {
|
|
104
|
+
/** Static tags attached to every event emitted by this middleware instance. */
|
|
105
|
+
tags?: Tags;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Returns a Vercel AI SDK `LanguageModelV1Middleware` that emits Weflayr events.
|
|
110
|
+
* Requires `weflayr_setup` to have been called first.
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* const model = wrapLanguageModel({
|
|
114
|
+
* model: openai('gpt-4o-mini'),
|
|
115
|
+
* middleware: weflayr_vercel_ai_middleware({ tags: { feature: 'chat' } }),
|
|
116
|
+
* });
|
|
117
|
+
*/
|
|
118
|
+
export function weflayr_vercel_ai_middleware(options?: WeflayrVercelAiMiddlewareOptions): object;
|
|
119
|
+
|
|
102
120
|
/**
|
|
103
121
|
* Attaches weflayr metadata tags to any SDK call options object.
|
|
104
122
|
*
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { configure, weflayr_instrument, weflayr_propagate, send_event } = require('./instrument');
|
|
4
|
+
const { weflayr_vercel_ai_middleware } = require('./vercel-ai-middleware');
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
*
|
|
@@ -32,4 +33,4 @@ function flayred(options) {
|
|
|
32
33
|
return options;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
module.exports = { weflayr_setup, weflayr_instrument, weflayr_propagate, send_event, flayred };
|
|
36
|
+
module.exports = { weflayr_setup, weflayr_instrument, weflayr_propagate, send_event, flayred, weflayr_vercel_ai_middleware };
|
package/src/instrument.js
CHANGED
|
@@ -315,4 +315,10 @@ function send_event(eventType, data) {
|
|
|
315
315
|
return _sendEvent(eventType, data);
|
|
316
316
|
}
|
|
317
317
|
|
|
318
|
-
|
|
318
|
+
// Returns merged default_tags + propagated async-local tags. Used by the Vercel AI middleware.
|
|
319
|
+
function get_context_tags() {
|
|
320
|
+
if (!_config) return {};
|
|
321
|
+
return { ...(_config.settings.default_tags || {}), ..._asyncStore.getStore() };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
module.exports = { configure, weflayr_instrument, weflayr_propagate, send_event, get_context_tags };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { randomUUID } = require('crypto');
|
|
4
|
+
const { send_event, get_context_tags } = require('./instrument');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Returns a Vercel AI SDK LanguageModelV1Middleware that emits Weflayr events.
|
|
8
|
+
* Requires weflayr_setup() to have been called first.
|
|
9
|
+
*
|
|
10
|
+
* @param {{ tags?: Record<string, string | number | boolean> }} [options]
|
|
11
|
+
* @returns {object} Vercel AI SDK middleware
|
|
12
|
+
*/
|
|
13
|
+
function weflayr_vercel_ai_middleware(options) {
|
|
14
|
+
const callTags = options?.tags ?? {};
|
|
15
|
+
const modelId = options?.model ?? 'unknown';
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
wrapGenerate: async ({ doGenerate }) => {
|
|
19
|
+
const eventId = randomUUID();
|
|
20
|
+
const t0 = Date.now();
|
|
21
|
+
const tags = { ...get_context_tags(), ...callTags };
|
|
22
|
+
|
|
23
|
+
send_event('before', {
|
|
24
|
+
event_id: eventId,
|
|
25
|
+
method: 'vercel.generate',
|
|
26
|
+
tags,
|
|
27
|
+
args: { model: modelId },
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const result = await doGenerate();
|
|
32
|
+
send_event('after', {
|
|
33
|
+
event_id: eventId,
|
|
34
|
+
method: 'vercel.generate',
|
|
35
|
+
tags,
|
|
36
|
+
args: { model: modelId },
|
|
37
|
+
response: { usage: result.usage, finish_reason: result.finishReason },
|
|
38
|
+
elapsed_ms: Date.now() - t0,
|
|
39
|
+
});
|
|
40
|
+
return result;
|
|
41
|
+
} catch (err) {
|
|
42
|
+
send_event('after', {
|
|
43
|
+
event_id: eventId,
|
|
44
|
+
method: 'vercel.generate',
|
|
45
|
+
tags,
|
|
46
|
+
args: { model: modelId },
|
|
47
|
+
error: err.message ?? 'unknown',
|
|
48
|
+
elapsed_ms: Date.now() - t0,
|
|
49
|
+
});
|
|
50
|
+
throw err;
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
wrapStream: async ({ doStream }) => {
|
|
55
|
+
const eventId = randomUUID();
|
|
56
|
+
const t0 = Date.now();
|
|
57
|
+
const tags = { ...get_context_tags(), ...callTags };
|
|
58
|
+
|
|
59
|
+
send_event('before', {
|
|
60
|
+
event_id: eventId,
|
|
61
|
+
method: 'vercel.stream',
|
|
62
|
+
tags,
|
|
63
|
+
args: { model: modelId },
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const { stream, ...rest } = await doStream();
|
|
68
|
+
|
|
69
|
+
send_event('stream_start', {
|
|
70
|
+
event_id: eventId,
|
|
71
|
+
method: 'vercel.stream',
|
|
72
|
+
tags,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
let finishChunk = null;
|
|
76
|
+
let errorChunk = null;
|
|
77
|
+
const patched = new TransformStream({
|
|
78
|
+
transform(chunk, controller) {
|
|
79
|
+
if (chunk.type === 'finish') finishChunk = chunk;
|
|
80
|
+
if (chunk.type === 'error') errorChunk = chunk;
|
|
81
|
+
controller.enqueue(chunk);
|
|
82
|
+
},
|
|
83
|
+
flush() {
|
|
84
|
+
if (errorChunk) {
|
|
85
|
+
send_event('after', {
|
|
86
|
+
event_id: eventId,
|
|
87
|
+
method: 'vercel.stream',
|
|
88
|
+
tags,
|
|
89
|
+
args: { model: modelId },
|
|
90
|
+
error: String(errorChunk.error),
|
|
91
|
+
elapsed_ms: Date.now() - t0,
|
|
92
|
+
});
|
|
93
|
+
} else {
|
|
94
|
+
send_event('after', {
|
|
95
|
+
event_id: eventId,
|
|
96
|
+
method: 'vercel.stream',
|
|
97
|
+
tags,
|
|
98
|
+
args: { model: modelId },
|
|
99
|
+
response: finishChunk
|
|
100
|
+
? { usage: finishChunk.usage, finish_reason: finishChunk.finishReason }
|
|
101
|
+
: null,
|
|
102
|
+
elapsed_ms: Date.now() - t0,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
return { stream: stream.pipeThrough(patched), ...rest };
|
|
109
|
+
} catch (err) {
|
|
110
|
+
send_event('after', {
|
|
111
|
+
event_id: eventId,
|
|
112
|
+
method: 'vercel.stream',
|
|
113
|
+
tags,
|
|
114
|
+
args: { model: modelId },
|
|
115
|
+
error: err.message ?? 'unknown',
|
|
116
|
+
elapsed_ms: Date.now() - t0,
|
|
117
|
+
});
|
|
118
|
+
throw err;
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = { weflayr_vercel_ai_middleware };
|
package/tests/index.test.js
CHANGED
|
@@ -62,6 +62,72 @@
|
|
|
62
62
|
if (request === '@ai-sdk/openai') {
|
|
63
63
|
return { openai: (modelId) => ({ _provider: 'openai', modelId }) };
|
|
64
64
|
}
|
|
65
|
+
if (request === '@ai-sdk/openai-compatible') {
|
|
66
|
+
function createOpenAICompatible({ name }) {
|
|
67
|
+
return function(modelId) {
|
|
68
|
+
return {
|
|
69
|
+
specificationVersion: 'v1',
|
|
70
|
+
provider: name,
|
|
71
|
+
modelId,
|
|
72
|
+
defaultObjectGenerationMode: undefined,
|
|
73
|
+
doGenerate: async () => ({
|
|
74
|
+
text: 'Hello!',
|
|
75
|
+
usage: { promptTokens: 10, completionTokens: 5 },
|
|
76
|
+
finishReason: 'stop',
|
|
77
|
+
rawCall: { rawPrompt: [], rawSettings: {} },
|
|
78
|
+
rawResponse: { headers: {} },
|
|
79
|
+
warnings: [],
|
|
80
|
+
}),
|
|
81
|
+
doStream: async () => ({
|
|
82
|
+
stream: new ReadableStream({
|
|
83
|
+
start(ctrl) {
|
|
84
|
+
ctrl.enqueue({ type: 'text-delta', textDelta: 'Hello!' });
|
|
85
|
+
ctrl.enqueue({ type: 'finish', finishReason: 'stop', usage: { promptTokens: 10, completionTokens: 5 } });
|
|
86
|
+
ctrl.close();
|
|
87
|
+
},
|
|
88
|
+
}),
|
|
89
|
+
rawCall: { rawPrompt: [], rawSettings: {} },
|
|
90
|
+
rawResponse: { headers: {} },
|
|
91
|
+
warnings: [],
|
|
92
|
+
}),
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return { createOpenAICompatible };
|
|
97
|
+
}
|
|
98
|
+
if (request === 'ai') {
|
|
99
|
+
function wrapLanguageModel({ model, middleware }) {
|
|
100
|
+
return {
|
|
101
|
+
modelId: model.modelId,
|
|
102
|
+
doGenerate: async (params) =>
|
|
103
|
+
middleware.wrapGenerate({ doGenerate: () => model.doGenerate(params), params, model }),
|
|
104
|
+
doStream: async (params) =>
|
|
105
|
+
middleware.wrapStream({ doStream: () => model.doStream(params), params, model }),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
async function generateText({ model, prompt }) {
|
|
109
|
+
const params = { prompt: [{ role: 'user', content: [{ type: 'text', text: prompt }] }] };
|
|
110
|
+
return model.doGenerate(params);
|
|
111
|
+
}
|
|
112
|
+
function streamText({ model, prompt }) {
|
|
113
|
+
const params = { prompt: [{ role: 'user', content: [{ type: 'text', text: prompt }] }] };
|
|
114
|
+
const streamPromise = model.doStream(params);
|
|
115
|
+
return {
|
|
116
|
+
get textStream() {
|
|
117
|
+
return (async function* () {
|
|
118
|
+
const { stream } = await streamPromise;
|
|
119
|
+
const reader = stream.getReader();
|
|
120
|
+
while (true) {
|
|
121
|
+
const { done, value } = await reader.read();
|
|
122
|
+
if (done) break;
|
|
123
|
+
if (value && value.type === 'text-delta') yield value.textDelta;
|
|
124
|
+
}
|
|
125
|
+
})();
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return { wrapLanguageModel, generateText, streamText };
|
|
130
|
+
}
|
|
65
131
|
if (request === 'weflayr') {
|
|
66
132
|
return _origLoad.call(this, path.resolve(__dirname, '../src/index.js'), parent, isMain);
|
|
67
133
|
}
|
|
@@ -81,6 +147,7 @@ const TEST_CREDS = {
|
|
|
81
147
|
// Reset module cache between tests so _config doesn't leak
|
|
82
148
|
function freshSDK() {
|
|
83
149
|
delete require.cache[require.resolve('../src/instrument')];
|
|
150
|
+
delete require.cache[require.resolve('../src/vercel-ai-middleware')];
|
|
84
151
|
delete require.cache[require.resolve('../src/index')];
|
|
85
152
|
return require('../src/index');
|
|
86
153
|
}
|
|
@@ -804,3 +871,232 @@ test('snippet mastra_stream: stream_start + after emitted, model/usage in after
|
|
|
804
871
|
assert.equal(after.tags.customer_id, '42');
|
|
805
872
|
});
|
|
806
873
|
|
|
874
|
+
// ---------------------------------------------------------------------------
|
|
875
|
+
// Vercel AI middleware — behavioural
|
|
876
|
+
// ---------------------------------------------------------------------------
|
|
877
|
+
|
|
878
|
+
test('weflayr_vercel_ai_middleware: wrapGenerate sends before + after with model and tags', async () => {
|
|
879
|
+
const { weflayr_setup, weflayr_vercel_ai_middleware } = freshSDK();
|
|
880
|
+
|
|
881
|
+
weflayr_setup({ ...TEST_CREDS });
|
|
882
|
+
|
|
883
|
+
const middleware = weflayr_vercel_ai_middleware({
|
|
884
|
+
model: 'openai/gpt-4o-mini',
|
|
885
|
+
tags: { feature: 'chat' },
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
const sent = await captureHttp(async () => {
|
|
889
|
+
await middleware.wrapGenerate({
|
|
890
|
+
doGenerate: async () => ({
|
|
891
|
+
text: 'Hello!',
|
|
892
|
+
usage: { promptTokens: 10, completionTokens: 5 },
|
|
893
|
+
finishReason: 'stop',
|
|
894
|
+
rawCall: {}, rawResponse: {}, warnings: [],
|
|
895
|
+
}),
|
|
896
|
+
params: {},
|
|
897
|
+
model: {},
|
|
898
|
+
});
|
|
899
|
+
});
|
|
900
|
+
|
|
901
|
+
assert.ok(sent.some(e => e.event_type === 'before'), 'before event sent');
|
|
902
|
+
assert.ok(sent.some(e => e.event_type === 'after'), 'after event sent');
|
|
903
|
+
const before = sent.find(e => e.event_type === 'before');
|
|
904
|
+
assert.equal(before.method, 'vercel.generate');
|
|
905
|
+
assert.equal(before.args.model, 'openai/gpt-4o-mini');
|
|
906
|
+
assert.equal(before.tags.feature, 'chat');
|
|
907
|
+
const after = sent.find(e => e.event_type === 'after');
|
|
908
|
+
assert.deepEqual(after.response.usage, { promptTokens: 10, completionTokens: 5 });
|
|
909
|
+
assert.equal(after.response.finish_reason, 'stop');
|
|
910
|
+
assert.ok(after.elapsed_ms >= 0);
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
test('weflayr_vercel_ai_middleware: wrapStream sends before + stream_start + after with usage', async () => {
|
|
914
|
+
const { weflayr_setup, weflayr_vercel_ai_middleware } = freshSDK();
|
|
915
|
+
|
|
916
|
+
weflayr_setup({ ...TEST_CREDS });
|
|
917
|
+
|
|
918
|
+
const middleware = weflayr_vercel_ai_middleware({
|
|
919
|
+
model: 'openai/gpt-4o-mini',
|
|
920
|
+
tags: { feature: 'chat' },
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
const sent = await captureHttp(async () => {
|
|
924
|
+
const { stream } = await middleware.wrapStream({
|
|
925
|
+
doStream: async () => ({
|
|
926
|
+
stream: new ReadableStream({
|
|
927
|
+
start(ctrl) {
|
|
928
|
+
ctrl.enqueue({ type: 'text-delta', textDelta: 'Hello!' });
|
|
929
|
+
ctrl.enqueue({ type: 'finish', finishReason: 'stop', usage: { promptTokens: 10, completionTokens: 5 } });
|
|
930
|
+
ctrl.close();
|
|
931
|
+
},
|
|
932
|
+
}),
|
|
933
|
+
rawCall: {}, rawResponse: {}, warnings: [],
|
|
934
|
+
}),
|
|
935
|
+
params: {},
|
|
936
|
+
model: {},
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
const reader = stream.getReader();
|
|
940
|
+
while (true) {
|
|
941
|
+
const { done } = await reader.read();
|
|
942
|
+
if (done) break;
|
|
943
|
+
}
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
const types = sent.map(e => e.event_type);
|
|
947
|
+
assert.ok(types.includes('before'), 'before event sent');
|
|
948
|
+
assert.ok(types.includes('stream_start'), 'stream_start event sent');
|
|
949
|
+
assert.ok(types.includes('after'), 'after event sent');
|
|
950
|
+
const after = sent.find(e => e.event_type === 'after');
|
|
951
|
+
assert.equal(after.method, 'vercel.stream');
|
|
952
|
+
assert.deepEqual(after.response.usage, { promptTokens: 10, completionTokens: 5 });
|
|
953
|
+
assert.equal(after.response.finish_reason, 'stop');
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
test('weflayr_vercel_ai_middleware: weflayr_propagate tags appear in events', async () => {
|
|
957
|
+
const { weflayr_setup, weflayr_vercel_ai_middleware, weflayr_propagate } = freshSDK();
|
|
958
|
+
|
|
959
|
+
weflayr_setup({ ...TEST_CREDS });
|
|
960
|
+
weflayr_propagate('customer_id', 'acme-corp');
|
|
961
|
+
|
|
962
|
+
const middleware = weflayr_vercel_ai_middleware({ model: 'openai/gpt-4o-mini' });
|
|
963
|
+
|
|
964
|
+
const sent = await captureHttp(async () => {
|
|
965
|
+
await middleware.wrapGenerate({
|
|
966
|
+
doGenerate: async () => ({ text: 'ok', usage: { promptTokens: 1, completionTokens: 1 }, finishReason: 'stop', rawCall: {}, rawResponse: {}, warnings: [] }),
|
|
967
|
+
params: {},
|
|
968
|
+
model: {},
|
|
969
|
+
});
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
const before = sent.find(e => e.event_type === 'before');
|
|
973
|
+
assert.equal(before.tags.customer_id, 'acme-corp');
|
|
974
|
+
const after = sent.find(e => e.event_type === 'after');
|
|
975
|
+
assert.equal(after.tags.customer_id, 'acme-corp');
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
// ---------------------------------------------------------------------------
|
|
979
|
+
// Vercel AI middleware — documentation snippets
|
|
980
|
+
// @ai-sdk/openai-compatible and ai are faked by Module._load at top of file.
|
|
981
|
+
// ---------------------------------------------------------------------------
|
|
982
|
+
|
|
983
|
+
test('snippet vercel_ai_setup: before+after via generateText, method and args correct', async () => {
|
|
984
|
+
freshSDK();
|
|
985
|
+
setSnippetEnv();
|
|
986
|
+
|
|
987
|
+
const sent = await captureHttp(async () => {
|
|
988
|
+
// SNIPPET_START: vercel_ai_setup
|
|
989
|
+
const { weflayr_setup, weflayr_vercel_ai_middleware } = require('weflayr');
|
|
990
|
+
const { wrapLanguageModel, generateText } = require('ai');
|
|
991
|
+
const { createOpenAICompatible } = require('@ai-sdk/openai-compatible');
|
|
992
|
+
|
|
993
|
+
weflayr_setup({
|
|
994
|
+
intake_url: process.env.WEFLAYR_INTAKE_URL,
|
|
995
|
+
client_id: process.env.WEFLAYR_CLIENT_ID,
|
|
996
|
+
client_secret: process.env.WEFLAYR_CLIENT_SECRET,
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
const provider = createOpenAICompatible({
|
|
1000
|
+
name: 'openai',
|
|
1001
|
+
baseURL: 'https://api.openai.com/v1',
|
|
1002
|
+
headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}` },
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
const model = wrapLanguageModel({
|
|
1006
|
+
model: provider('gpt-4o-mini'),
|
|
1007
|
+
middleware: weflayr_vercel_ai_middleware({
|
|
1008
|
+
model: 'openai/gpt-4o-mini',
|
|
1009
|
+
tags: { env: 'production' },
|
|
1010
|
+
}),
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
await generateText({ model, prompt: 'Hello!' });
|
|
1014
|
+
// SNIPPET_END: vercel_ai_setup
|
|
1015
|
+
});
|
|
1016
|
+
|
|
1017
|
+
assert.ok(sent.some(e => e.event_type === 'before'), 'before event sent');
|
|
1018
|
+
assert.ok(sent.some(e => e.event_type === 'after'), 'after event sent');
|
|
1019
|
+
const before = sent.find(e => e.event_type === 'before');
|
|
1020
|
+
assert.equal(before.method, 'vercel.generate');
|
|
1021
|
+
assert.equal(before.args.model, 'openai/gpt-4o-mini');
|
|
1022
|
+
assert.equal(before.tags.env, 'production');
|
|
1023
|
+
const after = sent.find(e => e.event_type === 'after');
|
|
1024
|
+
assert.ok(after.response.usage, 'usage in after event');
|
|
1025
|
+
assert.ok(after.elapsed_ms >= 0);
|
|
1026
|
+
});
|
|
1027
|
+
|
|
1028
|
+
test('snippet vercel_ai_propagate: propagated customer_id in before+after events', async () => {
|
|
1029
|
+
const { weflayr_setup, weflayr_vercel_ai_middleware } = freshSDK();
|
|
1030
|
+
setSnippetEnv();
|
|
1031
|
+
|
|
1032
|
+
const { wrapLanguageModel, generateText } = require('ai');
|
|
1033
|
+
const { createOpenAICompatible } = require('@ai-sdk/openai-compatible');
|
|
1034
|
+
|
|
1035
|
+
weflayr_setup({ ...TEST_CREDS });
|
|
1036
|
+
|
|
1037
|
+
const provider = createOpenAICompatible({ name: 'openai', baseURL: 'https://api.openai.com/v1', headers: {} });
|
|
1038
|
+
const model = wrapLanguageModel({
|
|
1039
|
+
model: provider('gpt-4o-mini'),
|
|
1040
|
+
middleware: weflayr_vercel_ai_middleware({ model: 'openai/gpt-4o-mini' }),
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
const sent = await captureHttp(async () => {
|
|
1044
|
+
// SNIPPET_START: vercel_ai_propagate
|
|
1045
|
+
const { weflayr_propagate } = require('weflayr');
|
|
1046
|
+
|
|
1047
|
+
weflayr_propagate('customer_id', 'acme-corp');
|
|
1048
|
+
await generateText({ model, prompt: 'Hello!' });
|
|
1049
|
+
// SNIPPET_END: vercel_ai_propagate
|
|
1050
|
+
});
|
|
1051
|
+
|
|
1052
|
+
const before = sent.find(e => e.event_type === 'before');
|
|
1053
|
+
assert.ok(before, 'before event sent');
|
|
1054
|
+
assert.equal(before.tags.customer_id, 'acme-corp');
|
|
1055
|
+
const after = sent.find(e => e.event_type === 'after');
|
|
1056
|
+
assert.equal(after.tags.customer_id, 'acme-corp');
|
|
1057
|
+
});
|
|
1058
|
+
|
|
1059
|
+
test('snippet vercel_ai_stream: before + stream_start + after via streamText', async () => {
|
|
1060
|
+
freshSDK();
|
|
1061
|
+
setSnippetEnv();
|
|
1062
|
+
|
|
1063
|
+
const sent = await captureHttp(async () => {
|
|
1064
|
+
// SNIPPET_START: vercel_ai_stream
|
|
1065
|
+
const { weflayr_setup, weflayr_vercel_ai_middleware } = require('weflayr');
|
|
1066
|
+
const { wrapLanguageModel, streamText } = require('ai');
|
|
1067
|
+
const { createOpenAICompatible } = require('@ai-sdk/openai-compatible');
|
|
1068
|
+
|
|
1069
|
+
weflayr_setup({
|
|
1070
|
+
intake_url: process.env.WEFLAYR_INTAKE_URL,
|
|
1071
|
+
client_id: process.env.WEFLAYR_CLIENT_ID,
|
|
1072
|
+
client_secret: process.env.WEFLAYR_CLIENT_SECRET,
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
const provider = createOpenAICompatible({
|
|
1076
|
+
name: 'openai',
|
|
1077
|
+
baseURL: 'https://api.openai.com/v1',
|
|
1078
|
+
headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}` },
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
const model = wrapLanguageModel({
|
|
1082
|
+
model: provider('gpt-4o-mini'),
|
|
1083
|
+
middleware: weflayr_vercel_ai_middleware({
|
|
1084
|
+
model: 'openai/gpt-4o-mini',
|
|
1085
|
+
tags: { feature: 'chat' },
|
|
1086
|
+
}),
|
|
1087
|
+
});
|
|
1088
|
+
|
|
1089
|
+
const { textStream } = streamText({ model, prompt: 'Hello!' });
|
|
1090
|
+
for await (const _chunk of textStream) {}
|
|
1091
|
+
// SNIPPET_END: vercel_ai_stream
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
const types = sent.map(e => e.event_type);
|
|
1095
|
+
assert.ok(types.includes('before'), 'before event sent');
|
|
1096
|
+
assert.ok(types.includes('stream_start'), 'stream_start event sent');
|
|
1097
|
+
assert.ok(types.includes('after'), 'after event sent');
|
|
1098
|
+
const after = sent.find(e => e.event_type === 'after');
|
|
1099
|
+
assert.equal(after.method, 'vercel.stream');
|
|
1100
|
+
assert.equal(after.tags.feature, 'chat');
|
|
1101
|
+
assert.ok(after.response.usage, 'usage in after event');
|
|
1102
|
+
});
|