Adding telemetry and testharness code

This commit is contained in:
Peter Morton 2025-04-23 16:53:52 -05:00
parent f68804b66c
commit 671ec85997
7 changed files with 296 additions and 0 deletions

View File

@ -0,0 +1,21 @@
(async () => {
webdis().publish(recognizedObject.workspaceId, {
conversationId: recognizedObject.conversationId,
input: recognizedObject.input,
answers: recognizedObject.answers,
});
})()
.catch((error) => {
console.log(error.message);
recognizedObject.answers.push("");
recognizedObject.errorInfo = {
...recognizedObject.errorInfo,
label: {
data: error.toJSON ? error.toJSON() : {},
message: error.message,
},
};
})
.finally(() => {
next();
});

View File

@ -0,0 +1,111 @@
const traces = axios.create({
baseURL: opentelemetry_settings.baseUrl,
timeout: 16000,
});
return {
endSpan(conversationId) {
return new Promise(async (resolve, reject) => {
try {
const latencySpanKey = `latency-span-${conversationId}`;
const span = JSON.parse(await redis.get(latencySpanKey));
if (!span) {
resolve();
return;
}
redis.del(latencySpanKey);
span.resourceSpans[0].scopeSpans[0].spans[0].endTimeUnixNano =
"" + Date.now() * 1000000;
console.log("latencySpan span:");
console.log(span);
const response = await traces.post(`/v1/traces`, span, {
headers: {
"Content-Type": "application/json",
},
});
resolve(span);
} catch (error) {
reject(error);
}
});
},
startSpan(conversationId, startTime) {
return new Promise(async (resolve, reject) => {
try {
const latencySpanKey = `latency-span-${conversationId}`;
const traceId = conversationId.replace(/-/gi, "");
const span = {
resourceSpans: [
{
resource: {
attributes: [
{
key: "service.name",
value: {
stringValue: "end-of-utterance-latency",
},
},
],
},
scopeSpans: [
{
scope: {
name: "scope-name",
version: "1.0.0",
attributes: [
{
key: "my.scope.attribute",
value: {
stringValue: "some scope attribute",
},
},
],
},
spans: [
{
traceId: traceId,
spanId: getIdGenerator(8)(),
name: "end-of-utterance-latency",
startTimeUnixNano: startTime,
kind: 2,
},
],
},
],
},
],
};
await redis.set(latencySpanKey, JSON.stringify(span));
await redis.expire(latencySpanKey, 300);
resolve(span);
} catch (error) {
reject(error);
}
});
},
};
function getIdGenerator(bytes) {
return function generateId() {
const SHARED_BUFFER = buffer.Buffer.allocUnsafe(16);
for (let i = 0; i < bytes / 4; i++) {
// unsigned right shift drops decimal part of the number
// it is required because if a number between 2**32 and 2**32 - 1 is generated, an out of range error is thrown by writeUInt32BE
SHARED_BUFFER.writeUInt32BE((Math.random() * 2 ** 32) >>> 0, i * 4);
}
// If buffer is all 0, set the last byte to 1 to guarantee a valid w3c id is generated
for (let i = 0; i < bytes; i++) {
if (SHARED_BUFFER[i] > 0) {
break;
} else if (i === bytes - 1) {
SHARED_BUFFER[bytes - 1] = 1;
}
}
return SHARED_BUFFER.toString("hex", 0, bytes);
};
}

View File

@ -0,0 +1,105 @@
const traces = axios.create({
baseURL: opentelemetry_settings.baseUrl,
timeout: 16000,
})
return {
async startSpan(name) {
try {
if (!conversationData.spans) {
conversationData.spans = []
}
const spanName = name ? name : 'default'
const span = {
resourceSpans: [
{
resource: {
attributes: [
{
key: 'service.name',
value: {
stringValue: 'ivastudio.verint.live',
},
},
],
},
scopeSpans: [
{
scope: {
name: recognizedObject.conversationId,
version: '1.0.0',
attributes: [
{
key: 'my.scope.attribute',
value: {
stringValue: 'some scope attribute',
},
},
],
},
spans: [
{
// traceId: recognizedObject.req.headers['x-b3-traceid'],
traceId: recognizedObject.conversationId.replace(/-/gi, ''),
spanId: recognizedObject.req.headers['x-b3-spanid'],
// parentSpanId: recognizedObject.req.headers['x-b3-parentspanid'],
name: spanName,
startTimeUnixNano: '' + Date.now() * 1000000,
kind: 2,
attributes: [
{
key: 'conversationId',
value: {
stringValue: recognizedObject.conversationId,
},
},
{
key: 'workspaceId',
value: {
stringValue: recognizedObject.workspaceId,
},
},
{
key: 'input',
value: {
stringValue: recognizedObject.input,
},
},
],
},
],
},
],
},
],
}
conversationData.spans.push(span)
return span
} catch (e) {
throw e
}
},
async endSpan() {
try {
const data = conversationData.spans.pop()
data.resourceSpans[0].scopeSpans[0].spans[0].endTimeUnixNano = '' + Date.now() * 1000000
data.resourceSpans[0].scopeSpans[0].spans[0].attributes.push({
key: 'answers',
value: {
stringValue: JSON.stringify(recognizedObject.answers),
},
})
const response = await traces.post(`/v1/traces`, data, {
headers: {
'Content-Type': 'application/json',
},
})
return response.data
} catch (e) {
throw e
}
},
}

View File

@ -0,0 +1,3 @@
{
"baseUrl": "https://iva.mortons.site/tracing"
}

View File

@ -0,0 +1,17 @@
const webdisBaseURL = "https://iva.mortons.site/webdis/";
return {
async publish(channel, message) {
const str = JSON.stringify(message);
const encodedStr = buffer.Buffer.from(str).toString("base64");
let escapedStr = encodedStr.replace(/\//g, "%2f");
escapedStr = escapedStr.replace(/\+/g, "%2B");
console.log(`webdis:PUBLISH/${channel}/${escapedStr}`);
axios
.post(webdisBaseURL, `PUBLISH/${channel}/${escapedStr}`)
.then(function (response) {
console.log(response.data);
});
},
};

View File

@ -0,0 +1,15 @@
const conversationId = req.body.conversationId;
const startTime = req.body.startTime;
(async () => {
try {
const span = await latencySpan().startSpan(conversationId, startTime);
res.send(span);
} catch (error) {
console.log(error.message);
res.send({
error: error.message,
});
}
})();

View File

@ -0,0 +1,24 @@
(async () => {
const caseId = "003abc000123xyzabc";
try {
console.log({ URL: req.url, body: req.body });
if (req.url.endsWith("token")) {
res.json({ access_token: "TOKEN" });
} else if (req.url.endsWith(caseId)) {
res.json({
id: caseId,
CaseNumber: 1024,
});
} else {
// create a case
res.json({ id: caseId });
}
} catch (error) {
res.json({
error: {
message: error.message,
},
});
}
})();