@genkit-ai/google-cloud
Version:
Genkit AI framework plugin for Google Cloud Platform including Firestore trace/state store and deployment helpers for Cloud Functions for Firebase.
314 lines • 10.1 kB
JavaScript
import { ValueType } from "@opentelemetry/api";
import { createHash } from "crypto";
import {
GENKIT_VERSION
} from "genkit";
import { logger } from "genkit/logging";
import { toDisplayPath } from "genkit/tracing";
import {
MetricCounter,
MetricHistogram,
internalMetricNamespaceWrap
} from "../metrics.js";
import {
createCommonLogAttributes,
extractErrorName,
extractOuterFeatureNameFromPath,
truncate,
truncatePath
} from "../utils.js";
class GenerateTelemetry {
/**
* Wraps the declared metrics in a Genkit-specific, internal namespace.
*/
_N = internalMetricNamespaceWrap.bind(null, "ai");
actionCounter = new MetricCounter(this._N("generate/requests"), {
description: "Counts calls to genkit generate actions.",
valueType: ValueType.INT
});
latencies = new MetricHistogram(this._N("generate/latency"), {
description: "Latencies when interacting with a Genkit model.",
valueType: ValueType.DOUBLE,
unit: "ms"
});
inputCharacters = new MetricCounter(
this._N("generate/input/characters"),
{
description: "Counts input characters to any Genkit model.",
valueType: ValueType.INT
}
);
inputTokens = new MetricCounter(this._N("generate/input/tokens"), {
description: "Counts input tokens to a Genkit model.",
valueType: ValueType.INT
});
inputImages = new MetricCounter(this._N("generate/input/images"), {
description: "Counts input images to a Genkit model.",
valueType: ValueType.INT
});
outputCharacters = new MetricCounter(
this._N("generate/output/characters"),
{
description: "Counts output characters from a Genkit model.",
valueType: ValueType.INT
}
);
outputTokens = new MetricCounter(this._N("generate/output/tokens"), {
description: "Counts output tokens from a Genkit model.",
valueType: ValueType.INT
});
thinkingTokens = new MetricCounter(
this._N("generate/thinking/tokens"),
{
description: "Counts thinking tokens from a Genkit model.",
valueType: ValueType.INT
}
);
outputImages = new MetricCounter(this._N("generate/output/images"), {
description: "Count output images from a Genkit model.",
valueType: ValueType.INT
});
tick(span, logInputAndOutput, projectId) {
const attributes = span.attributes;
const modelName = truncate(attributes["genkit:name"], 1024);
const path = attributes["genkit:path"] || "";
const input = "genkit:input" in attributes ? JSON.parse(
attributes["genkit:input"]
) : void 0;
const output = "genkit:output" in attributes ? JSON.parse(
attributes["genkit:output"]
) : void 0;
const errName = extractErrorName(span.events);
let featureName = truncate(
attributes["genkit:metadata:flow:name"] || extractOuterFeatureNameFromPath(path)
);
if (!featureName || featureName === "<unknown>") {
featureName = "generate";
}
const sessionId = attributes["genkit:sessionId"];
const threadName = attributes["genkit:threadName"];
if (input) {
this.recordGenerateActionMetrics(modelName, featureName, path, {
response: output,
errName
});
this.recordGenerateActionConfigLogs(
span,
modelName,
featureName,
path,
input,
projectId,
sessionId,
threadName
);
if (logInputAndOutput) {
this.recordGenerateActionInputLogs(
span,
modelName,
featureName,
path,
input,
projectId,
sessionId,
threadName
);
}
}
if (output && logInputAndOutput) {
this.recordGenerateActionOutputLogs(
span,
modelName,
featureName,
path,
output,
projectId,
sessionId,
threadName
);
}
}
recordGenerateActionMetrics(modelName, featureName, path, opts) {
this.doRecordGenerateActionMetrics(modelName, opts.response?.usage || {}, {
featureName,
path,
latencyMs: opts.response?.latencyMs,
errName: opts.errName,
source: "ts",
sourceVersion: GENKIT_VERSION
});
}
recordGenerateActionConfigLogs(span, model, featureName, qualifiedPath, input, projectId, sessionId, threadName) {
const path = truncatePath(toDisplayPath(qualifiedPath));
const sharedMetadata = {
...createCommonLogAttributes(span, projectId),
model,
path,
qualifiedPath,
featureName,
sessionId,
threadName
};
logger.logStructured(`Config[${path}, ${model}]`, {
...sharedMetadata,
maxOutputTokens: input.config?.maxOutputTokens,
stopSequences: input.config?.stopSequences,
// array
source: "ts",
sourceVersion: GENKIT_VERSION
});
}
recordGenerateActionInputLogs(span, model, featureName, qualifiedPath, input, projectId, sessionId, threadName) {
const path = truncatePath(toDisplayPath(qualifiedPath));
const sharedMetadata = {
...createCommonLogAttributes(span, projectId),
model,
path,
qualifiedPath,
featureName,
sessionId,
threadName
};
const messages = input.messages.length;
input.messages.forEach((msg, msgIdx) => {
const parts = msg.content.length;
msg.content.forEach((part, partIdx) => {
const partCounts = this.toPartCounts(partIdx, parts, msgIdx, messages);
logger.logStructured(`Input[${path}, ${model}] ${partCounts}`, {
...sharedMetadata,
content: this.toPartLogContent(part),
role: msg.role,
partIndex: partIdx,
totalParts: parts,
messageIndex: msgIdx,
totalMessages: messages
});
});
});
}
recordGenerateActionOutputLogs(span, model, featureName, qualifiedPath, output, projectId, sessionId, threadName) {
const path = truncatePath(toDisplayPath(qualifiedPath));
const sharedMetadata = {
...createCommonLogAttributes(span, projectId),
model,
path,
qualifiedPath,
featureName,
sessionId,
threadName
};
const message = output.message || output.candidates?.[0]?.message;
if (message?.content) {
const parts = message.content.length;
message.content.forEach((part, partIdx) => {
const partCounts = this.toPartCounts(partIdx, parts, 0, 1);
const initial = output.finishMessage ? { finishMessage: truncate(output.finishMessage) } : {};
logger.logStructured(`Output[${path}, ${model}] ${partCounts}`, {
...initial,
...sharedMetadata,
content: this.toPartLogContent(part),
role: message.role,
partIndex: partIdx,
totalParts: parts,
candidateIndex: 0,
totalCandidates: 1,
messageIndex: 0,
finishReason: output.finishReason
});
});
}
}
toPartCounts(partOrdinal, parts, msgOrdinal, messages) {
if (parts > 1 && messages > 1) {
return `(part ${this.xOfY(partOrdinal, parts)} in message ${this.xOfY(
msgOrdinal,
messages
)})`;
}
if (parts > 1) {
return `(part ${this.xOfY(partOrdinal, parts)})`;
}
if (messages > 1) {
return `(message ${this.xOfY(msgOrdinal, messages)})`;
}
return "";
}
xOfY(x, y) {
return `${x + 1} of ${y}`;
}
toPartLogContent(part) {
if (part.text) {
return truncate(part.text);
}
if (part.data) {
return truncate(JSON.stringify(part.data));
}
if (part.media) {
return this.toPartLogMedia(part);
}
if (part.toolRequest) {
return this.toPartLogToolRequest(part);
}
if (part.toolResponse) {
return this.toPartLogToolResponse(part);
}
if (part.custom) {
return truncate(JSON.stringify(part.custom));
}
return "<unknown format>";
}
toPartLogMedia(part) {
if (part.media.url.startsWith("data:")) {
const splitIdx = part.media.url.indexOf("base64,");
if (splitIdx < 0) {
return "<unknown media format>";
}
const prefix = part.media.url.substring(0, splitIdx + 7);
const hashedContent = createHash("sha256").update(part.media.url.substring(splitIdx + 7)).digest("hex");
return `${prefix}<sha256(${hashedContent})>`;
}
return truncate(part.media.url);
}
toPartLogToolRequest(part) {
const inputText = typeof part.toolRequest.input === "string" ? part.toolRequest.input : JSON.stringify(part.toolRequest.input);
return truncate(
`Tool request: ${part.toolRequest.name}, ref: ${part.toolRequest.ref}, input: ${inputText}`
);
}
toPartLogToolResponse(part) {
const outputText = typeof part.toolResponse.output === "string" ? part.toolResponse.output : JSON.stringify(part.toolResponse.output);
return truncate(
`Tool response: ${part.toolResponse.name}, ref: ${part.toolResponse.ref}, output: ${outputText}`
);
}
/**
* Records all metrics associated with performing a GenerateAction.
*/
doRecordGenerateActionMetrics(modelName, usage, dimensions) {
const shared = {
modelName,
featureName: dimensions.featureName,
path: dimensions.path,
source: dimensions.source,
sourceVersion: dimensions.sourceVersion,
status: dimensions.errName ? "failure" : "success"
};
this.actionCounter.add(1, {
error: dimensions.errName,
...shared
});
this.latencies.record(dimensions.latencyMs, shared);
this.inputTokens.add(usage.inputTokens, shared);
this.inputCharacters.add(usage.inputCharacters, shared);
this.inputImages.add(usage.inputImages, shared);
this.outputTokens.add(usage.outputTokens, shared);
this.outputCharacters.add(usage.outputCharacters, shared);
this.outputImages.add(usage.outputImages, shared);
this.thinkingTokens.add(usage.thoughtsTokens, shared);
}
}
const generateTelemetry = new GenerateTelemetry();
export {
generateTelemetry
};
//# sourceMappingURL=generate.mjs.map