@vectorx/functions-framework 0.1.3 → 0.3.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/bin/rcb-ff.js +4 -0
- package/lib/config.js +1 -1
- package/lib/function-wrapper.js +3 -4
- package/lib/logger.js +44 -0
- package/lib/middlewares/middle-logs-request.js +72 -22
- package/lib/sse.js +83 -1
- package/lib/telemetry/langfuse.js +13 -0
- package/package.json +5 -4
- package/types/framework.d.ts +3 -0
- package/types/logger.d.ts +12 -0
- package/types/server.d.ts +1 -0
- package/types/telemetry/langfuse.d.ts +4 -0
package/bin/rcb-ff.js
CHANGED
|
@@ -20,6 +20,7 @@ program
|
|
|
20
20
|
.option('--enable-cors', '启用 CORS')
|
|
21
21
|
.option('--allowed-origins <origins>', 'CORS 允许的域名,用逗号分隔', 'localhost,127.0.0.1')
|
|
22
22
|
.option('--config <path>', '指定配置文件路径', 'agent-cloudbase-functions.json')
|
|
23
|
+
.option('--enableRedLangfuse', '启用 Red Langfuse 上报')
|
|
23
24
|
.option('-w, --watch', '启用文件监听模式,文件变化时自动重启服务');
|
|
24
25
|
|
|
25
26
|
program.parse();
|
|
@@ -45,6 +46,9 @@ async function startServer() {
|
|
|
45
46
|
functionsConfigFile: path.resolve(directory, options.config),
|
|
46
47
|
enableCors: options.enableCors,
|
|
47
48
|
allowedOrigins: options.allowedOrigins.split(','),
|
|
49
|
+
redLangfuseConfig: {
|
|
50
|
+
enable: Boolean(options.enableRedLangfuse),
|
|
51
|
+
},
|
|
48
52
|
kitInfo: {
|
|
49
53
|
agentRuntimeVersion: getDependencyVersion('@vectorx/agent-runtime', directory),
|
|
50
54
|
functionsFrameworkVersion: getDependencyVersion('@vectorx/functions-framework', directory)
|
package/lib/config.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getBaseUrl = getBaseUrl;
|
|
4
4
|
const ENV_BASE_URL = {
|
|
5
|
-
development: "https://agentbase-sandbox.xiaohongshu.com",
|
|
5
|
+
development: "https://agentbase-sandbox.beta.xiaohongshu.com",
|
|
6
6
|
production: "https://agentbase.xiaohongshu.com",
|
|
7
7
|
};
|
|
8
8
|
function getBaseUrl() {
|
package/lib/function-wrapper.js
CHANGED
|
@@ -79,11 +79,11 @@ const wrapEventFunction = (execute, projectConfig) => {
|
|
|
79
79
|
ctx.state.event = event;
|
|
80
80
|
ctx.state.contextInjected = frozenContext;
|
|
81
81
|
let result;
|
|
82
|
-
const start = Date.now();
|
|
83
82
|
try {
|
|
84
83
|
result = yield execute(event, context);
|
|
85
84
|
}
|
|
86
85
|
catch (e) {
|
|
86
|
+
console.log("============>>>>>>>>>> function wrapper error ", e);
|
|
87
87
|
ctx.state.error = e;
|
|
88
88
|
if (hasSwitchSSEMode) {
|
|
89
89
|
const sse = (_a = ctx.sse) === null || _a === void 0 ? void 0 : _a.call(ctx);
|
|
@@ -93,14 +93,13 @@ const wrapEventFunction = (execute, projectConfig) => {
|
|
|
93
93
|
data: e.message,
|
|
94
94
|
comment: "uncaught-exception-from-user-code",
|
|
95
95
|
};
|
|
96
|
+
console.log("============>>>>>>>>>> errorEvent ", errorEvent);
|
|
96
97
|
sse.emit("error", errorEvent);
|
|
98
|
+
console.log("============>>>>>>>>>> sse.end(errorEvent) ");
|
|
97
99
|
sse.end(errorEvent);
|
|
98
100
|
}
|
|
99
101
|
}
|
|
100
102
|
}
|
|
101
|
-
finally {
|
|
102
|
-
ctx.state.userCodeTimeCost = Date.now() - start;
|
|
103
|
-
}
|
|
104
103
|
executeDone = true;
|
|
105
104
|
if (hasSwitchSSEMode) {
|
|
106
105
|
return;
|
package/lib/logger.js
CHANGED
|
@@ -131,6 +131,50 @@ class FunctionsLogger {
|
|
|
131
131
|
return logs;
|
|
132
132
|
});
|
|
133
133
|
}
|
|
134
|
+
getLogsPaginated(options) {
|
|
135
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
136
|
+
const { type, pageNum = 1, pageSize = 50 } = options;
|
|
137
|
+
const allLogs = yield this.getAllLogs(type);
|
|
138
|
+
const total = allLogs.length;
|
|
139
|
+
const offset = (pageNum - 1) * pageSize;
|
|
140
|
+
const logs = allLogs.slice(offset, offset + pageSize);
|
|
141
|
+
const hasMore = pageNum * pageSize < total;
|
|
142
|
+
return {
|
|
143
|
+
logs,
|
|
144
|
+
pageNum,
|
|
145
|
+
pageSize: logs.length,
|
|
146
|
+
total,
|
|
147
|
+
hasMore,
|
|
148
|
+
};
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
getAllLogs(type) {
|
|
152
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
153
|
+
const logs = [];
|
|
154
|
+
const logDir = DEFAULT_LOG_DIRNAME;
|
|
155
|
+
const files = fs_1.default
|
|
156
|
+
.readdirSync(logDir)
|
|
157
|
+
.filter((file) => file.startsWith(type) && file.endsWith(".log"))
|
|
158
|
+
.sort()
|
|
159
|
+
.reverse();
|
|
160
|
+
let globalId = 0;
|
|
161
|
+
for (const file of files) {
|
|
162
|
+
const content = fs_1.default.readFileSync(path_1.default.join(logDir, file), "utf-8");
|
|
163
|
+
const lines = content.split("\n").filter(Boolean).reverse();
|
|
164
|
+
for (const line of lines) {
|
|
165
|
+
try {
|
|
166
|
+
const log = JSON.parse(line);
|
|
167
|
+
log.id = globalId++;
|
|
168
|
+
logs.push(log);
|
|
169
|
+
}
|
|
170
|
+
catch (e) {
|
|
171
|
+
console.error("Failed to parse log line:", line);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return logs;
|
|
176
|
+
});
|
|
177
|
+
}
|
|
134
178
|
createWinstonLogger(options) {
|
|
135
179
|
const transports = [
|
|
136
180
|
new winston_daily_rotate_file_1.default({
|
|
@@ -16,32 +16,82 @@ function logsQueryMiddleware() {
|
|
|
16
16
|
return (ctx, next) => __awaiter(this, void 0, void 0, function* () {
|
|
17
17
|
if (ctx.path.startsWith("/@logs")) {
|
|
18
18
|
const type = ctx.query.type || logger_1.LogType.ACCESS;
|
|
19
|
+
const pageNumParam = ctx.query.pageNum;
|
|
20
|
+
const pageSizeParam = ctx.query.pageSize;
|
|
19
21
|
const limitParam = ctx.query.limit;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
if (pageNumParam !== undefined || pageSizeParam !== undefined) {
|
|
23
|
+
const pageNum = pageNumParam ? Number.parseInt(pageNumParam) : 1;
|
|
24
|
+
const pageSize = pageSizeParam ? Number.parseInt(pageSizeParam) : 50;
|
|
25
|
+
if (Number.isNaN(pageNum) || pageNum <= 0) {
|
|
26
|
+
ctx.status = 400;
|
|
27
|
+
ctx.body = {
|
|
28
|
+
success: false,
|
|
29
|
+
error: "Invalid pageNum parameter. Must be a positive number.",
|
|
30
|
+
};
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (Number.isNaN(pageSize) || pageSize <= 0 || pageSize > 100) {
|
|
34
|
+
ctx.status = 400;
|
|
35
|
+
ctx.body = {
|
|
36
|
+
success: false,
|
|
37
|
+
error: "Invalid pageSize parameter. Must be a positive number <= 100",
|
|
38
|
+
};
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const result = yield logger_2.functionsLogger.getLogsPaginated({
|
|
43
|
+
type,
|
|
44
|
+
pageNum,
|
|
45
|
+
pageSize,
|
|
46
|
+
});
|
|
47
|
+
ctx.body = {
|
|
48
|
+
success: true,
|
|
49
|
+
data: result.logs,
|
|
50
|
+
pagination: {
|
|
51
|
+
pageNum: result.pageNum,
|
|
52
|
+
pageSize: result.pageSize,
|
|
53
|
+
total: result.total,
|
|
54
|
+
hasMore: result.hasMore,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
logger_2.functionsLogger.logError(error);
|
|
60
|
+
ctx.status = 500;
|
|
61
|
+
ctx.body = {
|
|
62
|
+
success: false,
|
|
63
|
+
error: error.message,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
27
66
|
return;
|
|
28
67
|
}
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
68
|
+
else {
|
|
69
|
+
const limit = limitParam ? Number.parseInt(limitParam) : 100;
|
|
70
|
+
if (Number.isNaN(limit) || limit <= 0 || limit > 1000) {
|
|
71
|
+
ctx.status = 400;
|
|
72
|
+
ctx.body = {
|
|
73
|
+
success: false,
|
|
74
|
+
error: "Invalid limit parameter. Must be a positive number <= 1000",
|
|
75
|
+
};
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const logs = yield logger_2.functionsLogger.getLogs(type, limit);
|
|
80
|
+
ctx.body = {
|
|
81
|
+
success: true,
|
|
82
|
+
data: logs,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
logger_2.functionsLogger.logError(error);
|
|
87
|
+
ctx.status = 500;
|
|
88
|
+
ctx.body = {
|
|
89
|
+
success: false,
|
|
90
|
+
error: error.message,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return;
|
|
43
94
|
}
|
|
44
|
-
return;
|
|
45
95
|
}
|
|
46
96
|
return yield next();
|
|
47
97
|
});
|
package/lib/sse.js
CHANGED
|
@@ -32,15 +32,20 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
35
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
39
|
exports.ServerSentEvent = void 0;
|
|
37
40
|
const events_1 = require("events");
|
|
38
41
|
const stream_1 = require("stream");
|
|
39
42
|
const util = __importStar(require("util"));
|
|
43
|
+
const langfuse_1 = __importDefault(require("./telemetry/langfuse"));
|
|
40
44
|
const helper_1 = require("./utils/helper");
|
|
41
45
|
const kDefaultEncoder = new util.TextEncoder();
|
|
42
46
|
class ServerSentEvent extends events_1.EventEmitter {
|
|
43
47
|
constructor(ctx) {
|
|
48
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
44
49
|
super();
|
|
45
50
|
this.closed = false;
|
|
46
51
|
this.encoder = kDefaultEncoder;
|
|
@@ -77,6 +82,36 @@ class ServerSentEvent extends events_1.EventEmitter {
|
|
|
77
82
|
writable: false,
|
|
78
83
|
});
|
|
79
84
|
}
|
|
85
|
+
try {
|
|
86
|
+
const enabled = ((_c = (_b = (_a = ctx.state) === null || _a === void 0 ? void 0 : _a.frameworkOptions) === null || _b === void 0 ? void 0 : _b.redLangfuseConfig) === null || _c === void 0 ? void 0 : _c.enable) === true;
|
|
87
|
+
if (!enabled)
|
|
88
|
+
return;
|
|
89
|
+
const stage = process.env.OPEN_PLATFORM_STAGE || "development";
|
|
90
|
+
const openId = ctx.headers["open-id"] || undefined;
|
|
91
|
+
const eventID = (_d = ctx.state) === null || _d === void 0 ? void 0 : _d.eventID;
|
|
92
|
+
const agentInfo = ((_e = ctx.state) === null || _e === void 0 ? void 0 : _e.agentInfo) || {};
|
|
93
|
+
const name = [(agentInfo === null || agentInfo === void 0 ? void 0 : agentInfo.agentName) || (agentInfo === null || agentInfo === void 0 ? void 0 : agentInfo.name), agentInfo === null || agentInfo === void 0 ? void 0 : agentInfo.version].filter(Boolean).join("@");
|
|
94
|
+
const metadata = {
|
|
95
|
+
http: { method: ctx.method, path: ctx.path },
|
|
96
|
+
eventID,
|
|
97
|
+
agent: {
|
|
98
|
+
id: agentInfo === null || agentInfo === void 0 ? void 0 : agentInfo.agentId,
|
|
99
|
+
name: (agentInfo === null || agentInfo === void 0 ? void 0 : agentInfo.agentName) || (agentInfo === null || agentInfo === void 0 ? void 0 : agentInfo.name),
|
|
100
|
+
version: agentInfo === null || agentInfo === void 0 ? void 0 : agentInfo.version,
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
const trace = (_f = langfuse_1.default === null || langfuse_1.default === void 0 ? void 0 : langfuse_1.default.trace) === null || _f === void 0 ? void 0 : _f.call(langfuse_1.default, {
|
|
104
|
+
name: name || "chat-app-session",
|
|
105
|
+
userId: openId,
|
|
106
|
+
tags: [stage],
|
|
107
|
+
metadata,
|
|
108
|
+
});
|
|
109
|
+
if (trace) {
|
|
110
|
+
ctx.state.langfuseTrace = trace;
|
|
111
|
+
(_g = trace.event) === null || _g === void 0 ? void 0 : _g.call(trace, { name: "sse:start", input: (_j = (_h = ctx.state) === null || _h === void 0 ? void 0 : _h.event) !== null && _j !== void 0 ? _j : null, metadata: { path: ctx.path } });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch (_k) { }
|
|
80
115
|
}
|
|
81
116
|
setEncoder(encoder) {
|
|
82
117
|
this.encoder = encoder;
|
|
@@ -94,6 +129,7 @@ class ServerSentEvent extends events_1.EventEmitter {
|
|
|
94
129
|
return super.emit(event, ...args);
|
|
95
130
|
}
|
|
96
131
|
send(event) {
|
|
132
|
+
var _a, _b, _c;
|
|
97
133
|
const encoder = this.encoder;
|
|
98
134
|
const stream = this.stream;
|
|
99
135
|
if (this.closed) {
|
|
@@ -122,17 +158,63 @@ class ServerSentEvent extends events_1.EventEmitter {
|
|
|
122
158
|
}
|
|
123
159
|
const buf = Buffer.concat(bufs);
|
|
124
160
|
if (Buffer.byteLength(buf) > 0) {
|
|
125
|
-
|
|
161
|
+
const ok = stream.write(Buffer.concat(bufs));
|
|
162
|
+
try {
|
|
163
|
+
const lfTrace = (_a = this.ctx.state) === null || _a === void 0 ? void 0 : _a.langfuseTrace;
|
|
164
|
+
if (lfTrace) {
|
|
165
|
+
let output = null;
|
|
166
|
+
try {
|
|
167
|
+
if (!Array.isArray(event)) {
|
|
168
|
+
if (typeof event === "string")
|
|
169
|
+
output = event;
|
|
170
|
+
else if (Buffer.isBuffer(event))
|
|
171
|
+
output = event.toString("utf-8");
|
|
172
|
+
else if ((0, helper_1.isPlainObject)(event))
|
|
173
|
+
output = (_b = event.data) !== null && _b !== void 0 ? _b : null;
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
const last = event[event.length - 1];
|
|
177
|
+
if (typeof last === "string")
|
|
178
|
+
output = last;
|
|
179
|
+
else if (Buffer.isBuffer(last))
|
|
180
|
+
output = last.toString("utf-8");
|
|
181
|
+
else if ((0, helper_1.isPlainObject)(last))
|
|
182
|
+
output = (_c = last.data) !== null && _c !== void 0 ? _c : null;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch (_d) { }
|
|
186
|
+
lfTrace.event({
|
|
187
|
+
name: "sse:send",
|
|
188
|
+
input: null,
|
|
189
|
+
output,
|
|
190
|
+
metadata: { size: Buffer.byteLength(buf) },
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
catch (_e) { }
|
|
195
|
+
return ok;
|
|
126
196
|
}
|
|
127
197
|
return false;
|
|
128
198
|
}
|
|
129
199
|
end(msg) {
|
|
200
|
+
var _a, _b, _c, _d, _e, _f;
|
|
130
201
|
if (msg) {
|
|
131
202
|
this.send(msg);
|
|
132
203
|
}
|
|
133
204
|
this.stream.end();
|
|
134
205
|
this.ctx.res.end();
|
|
135
206
|
this.closed = true;
|
|
207
|
+
try {
|
|
208
|
+
const lfTrace = (_a = this.ctx.state) === null || _a === void 0 ? void 0 : _a.langfuseTrace;
|
|
209
|
+
const enabled = ((_d = (_c = (_b = this.ctx.state) === null || _b === void 0 ? void 0 : _b.frameworkOptions) === null || _c === void 0 ? void 0 : _c.redLangfuseConfig) === null || _d === void 0 ? void 0 : _d.enable) === true;
|
|
210
|
+
if (lfTrace) {
|
|
211
|
+
lfTrace.event({ name: "sse:end", input: null, output: null });
|
|
212
|
+
}
|
|
213
|
+
if (enabled && (langfuse_1.default === null || langfuse_1.default === void 0 ? void 0 : langfuse_1.default.shutdownAsync)) {
|
|
214
|
+
(_f = (_e = langfuse_1.default).shutdownAsync) === null || _f === void 0 ? void 0 : _f.call(_e);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch (_g) { }
|
|
136
218
|
}
|
|
137
219
|
get isClosed() {
|
|
138
220
|
return this.closed;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.langfuse = void 0;
|
|
4
|
+
const langfuse_1 = require("langfuse");
|
|
5
|
+
const secretKey = process.env.LANGFUSE_SECRET_KEY;
|
|
6
|
+
const publicKey = process.env.LANGFUSE_PUBLIC_KEY;
|
|
7
|
+
const baseUrl = process.env.LANGFUSE_BASEURL;
|
|
8
|
+
exports.langfuse = new langfuse_1.Langfuse({
|
|
9
|
+
secretKey: "sk-lf-f389bb1e-24db-4b42-b506-8d6118f45044",
|
|
10
|
+
publicKey: "pk-lf-ee301553-7a64-4f0f-bf6b-a2ac1e5ca2df",
|
|
11
|
+
baseUrl: "https://xray-langfuse.devops.xiaohongshu.com",
|
|
12
|
+
});
|
|
13
|
+
exports.default = exports.langfuse;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vectorx/functions-framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "VectorX Functions Framework",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "types/index.d.ts",
|
|
@@ -25,17 +25,18 @@
|
|
|
25
25
|
"node": ">=18.0.0"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@vectorx/ai-types": "0.
|
|
29
|
-
"commander": "^12.1.0",
|
|
30
|
-
"nodemon": "^3.1.10",
|
|
28
|
+
"@vectorx/ai-types": "0.3.0",
|
|
31
29
|
"async_hooks": "^1.0.0",
|
|
32
30
|
"chalk": "4",
|
|
31
|
+
"commander": "^12.1.0",
|
|
33
32
|
"dotenv": "^16.5.0",
|
|
34
33
|
"koa": "^2.14.2",
|
|
35
34
|
"koa-body": "^6.0.1",
|
|
36
35
|
"koa-bodyparser": "^4.4.1",
|
|
37
36
|
"koa-router": "^12.0.1",
|
|
37
|
+
"langfuse": "^3.38.4",
|
|
38
38
|
"node-fetch": "v2",
|
|
39
|
+
"nodemon": "^3.1.10",
|
|
39
40
|
"openai": "^4.103.0",
|
|
40
41
|
"radix3": "^1.1.2",
|
|
41
42
|
"raw-body": "^2.5.2",
|
package/types/framework.d.ts
CHANGED
package/types/logger.d.ts
CHANGED
|
@@ -55,6 +55,18 @@ declare class FunctionsLogger {
|
|
|
55
55
|
logUserCodelog(): void;
|
|
56
56
|
log(type: LogType, level: LogLevel, message: string, metadata?: any): void;
|
|
57
57
|
getLogs(type: LogType, limit?: number): Promise<LogEntry[]>;
|
|
58
|
+
getLogsPaginated(options: {
|
|
59
|
+
type: LogType;
|
|
60
|
+
pageNum?: number;
|
|
61
|
+
pageSize?: number;
|
|
62
|
+
}): Promise<{
|
|
63
|
+
logs: LogEntry[];
|
|
64
|
+
pageNum: number;
|
|
65
|
+
pageSize: number;
|
|
66
|
+
total: number;
|
|
67
|
+
hasMore: boolean;
|
|
68
|
+
}>;
|
|
69
|
+
private getAllLogs;
|
|
58
70
|
private createWinstonLogger;
|
|
59
71
|
logError(error: Error | string, metadata?: any): void;
|
|
60
72
|
}
|
package/types/server.d.ts
CHANGED