bugfixing #3
This commit is contained in:
parent
1c4dcdd108
commit
e89e3b7a70
6 changed files with 137 additions and 30 deletions
|
|
@ -118,6 +118,7 @@ async function main() {
|
||||||
totalTokens: result.metrics.totalTokens,
|
totalTokens: result.metrics.totalTokens,
|
||||||
promptTimeMs: result.metrics.promptTimeMs,
|
promptTimeMs: result.metrics.promptTimeMs,
|
||||||
completionTimeMs: result.metrics.completionTimeMs,
|
completionTimeMs: result.metrics.completionTimeMs,
|
||||||
|
totalTimeMs: result.metrics.totalTimeMs,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,9 @@ interface LlmResponse {
|
||||||
promptTokens: number;
|
promptTokens: number;
|
||||||
completionTokens: number;
|
completionTokens: number;
|
||||||
totalTokens: number;
|
totalTokens: number;
|
||||||
promptTimeMs: number;
|
promptTimeMs: number | null;
|
||||||
completionTimeMs: number;
|
completionTimeMs: number | null;
|
||||||
|
totalTimeMs: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EnrichmentResult {
|
export interface EnrichmentResult {
|
||||||
|
|
@ -19,8 +20,9 @@ export interface EnrichmentResult {
|
||||||
promptTokens: number;
|
promptTokens: number;
|
||||||
completionTokens: number;
|
completionTokens: number;
|
||||||
totalTokens: number;
|
totalTokens: number;
|
||||||
promptTimeMs: number;
|
promptTimeMs: number | null;
|
||||||
completionTimeMs: number;
|
completionTimeMs: number | null;
|
||||||
|
totalTimeMs: number;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,6 +56,60 @@ function sanitizeLlmOutput(raw: string): string {
|
||||||
return cleaned.slice(start, end + 1);
|
return cleaned.slice(start, end + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function validateSense(item: unknown, word: string, index: number): void {
|
||||||
|
if (typeof item !== "object" || item === null || Array.isArray(item)) {
|
||||||
|
throw new Error(`Sense ${index} for "${word}" is not an object`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sense = item as Record<string, unknown>;
|
||||||
|
|
||||||
|
if (typeof sense["sense"] !== "string" || !sense["sense"]) {
|
||||||
|
throw new Error(`Sense ${index} for "${word}": missing or invalid "sense"`);
|
||||||
|
}
|
||||||
|
if (typeof sense["example"] !== "string" || !sense["example"]) {
|
||||||
|
throw new Error(
|
||||||
|
`Sense ${index} for "${word}": missing or invalid "example"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!["easy", "medium", "hard"].includes(sense["difficulty_level"] as string)
|
||||||
|
) {
|
||||||
|
throw new Error(`Sense ${index} for "${word}": invalid "difficulty_level"`);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
typeof sense["translations"] !== "object" ||
|
||||||
|
sense["translations"] === null
|
||||||
|
) {
|
||||||
|
throw new Error(`Sense ${index} for "${word}": missing "translations"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const trans = sense["translations"] as Record<string, unknown>;
|
||||||
|
for (const lang of ["de", "it", "es", "fr"]) {
|
||||||
|
if (!Array.isArray(trans[lang])) {
|
||||||
|
throw new Error(
|
||||||
|
`Sense ${index} for "${word}": missing or invalid "${lang}" translations`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (let j = 0; j < (trans[lang] as unknown[]).length; j++) {
|
||||||
|
const t = (trans[lang] as unknown[])[j] as Record<string, unknown>;
|
||||||
|
if (typeof t["word"] !== "string" || !t["word"]) {
|
||||||
|
throw new Error(
|
||||||
|
`Sense ${index} for "${word}": ${lang}[${j}] missing "word"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!["masculine", "feminine", "neuter", null].includes(
|
||||||
|
t["gender"] as string | null,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`Sense ${index} for "${word}": ${lang}[${j}] invalid "gender"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the LLM response string into a JavaScript object.
|
* Parses the LLM response string into a JavaScript object.
|
||||||
* Throws if the response is not valid JSON or not an object with expected keys.
|
* Throws if the response is not valid JSON or not an object with expected keys.
|
||||||
|
|
@ -86,6 +142,12 @@ export function parseLlmResponse(
|
||||||
if (!Array.isArray(obj[word]) || (obj[word] as unknown[]).length === 0) {
|
if (!Array.isArray(obj[word]) || (obj[word] as unknown[]).length === 0) {
|
||||||
throw new Error(`LLM output for "${word}" is not a non-empty array`);
|
throw new Error(`LLM output for "${word}" is not a non-empty array`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate each sense in the array
|
||||||
|
const senses = obj[word] as unknown[];
|
||||||
|
for (let i = 0; i < senses.length; i++) {
|
||||||
|
validateSense(senses[i], word, i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
|
|
@ -145,6 +207,7 @@ export async function enrichWord(
|
||||||
totalTokens: llmResponse.totalTokens,
|
totalTokens: llmResponse.totalTokens,
|
||||||
promptTimeMs: llmResponse.promptTimeMs,
|
promptTimeMs: llmResponse.promptTimeMs,
|
||||||
completionTimeMs: llmResponse.completionTimeMs,
|
completionTimeMs: llmResponse.completionTimeMs,
|
||||||
|
totalTimeMs: llmResponse.totalTimeMs,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -207,10 +270,13 @@ export async function enrichWordWithRetry(
|
||||||
totalTokens:
|
totalTokens:
|
||||||
leftResult.metrics.totalTokens + rightResult.metrics.totalTokens,
|
leftResult.metrics.totalTokens + rightResult.metrics.totalTokens,
|
||||||
promptTimeMs:
|
promptTimeMs:
|
||||||
leftResult.metrics.promptTimeMs + rightResult.metrics.promptTimeMs,
|
(leftResult.metrics.promptTimeMs ?? 0) +
|
||||||
|
(rightResult.metrics.promptTimeMs ?? 0),
|
||||||
completionTimeMs:
|
completionTimeMs:
|
||||||
leftResult.metrics.completionTimeMs +
|
(leftResult.metrics.completionTimeMs ?? 0) +
|
||||||
rightResult.metrics.completionTimeMs,
|
(rightResult.metrics.completionTimeMs ?? 0),
|
||||||
|
totalTimeMs:
|
||||||
|
leftResult.metrics.totalTimeMs + rightResult.metrics.totalTimeMs,
|
||||||
};
|
};
|
||||||
|
|
||||||
return { results: merged, metrics: mergedMetrics };
|
return { results: merged, metrics: mergedMetrics };
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,9 @@ export class GeminiAdapter implements LlmAdapter {
|
||||||
promptTokens: number;
|
promptTokens: number;
|
||||||
completionTokens: number;
|
completionTokens: number;
|
||||||
totalTokens: number;
|
totalTokens: number;
|
||||||
promptTimeMs: number;
|
promptTimeMs: number | null;
|
||||||
completionTimeMs: number;
|
completionTimeMs: number | null;
|
||||||
|
totalTimeMs: number;
|
||||||
}> {
|
}> {
|
||||||
const url = `https://generativelanguage.googleapis.com/v1beta/models/${this.model}:generateContent?key=${this.apiKey}`;
|
const url = `https://generativelanguage.googleapis.com/v1beta/models/${this.model}:generateContent?key=${this.apiKey}`;
|
||||||
|
|
||||||
|
|
@ -72,8 +73,9 @@ export class GeminiAdapter implements LlmAdapter {
|
||||||
promptTokens,
|
promptTokens,
|
||||||
completionTokens,
|
completionTokens,
|
||||||
totalTokens: json.usageMetadata.totalTokenCount,
|
totalTokens: json.usageMetadata.totalTokenCount,
|
||||||
promptTimeMs: totalTimeMs * 0.3,
|
promptTimeMs: null,
|
||||||
completionTimeMs: totalTimeMs * 0.7,
|
completionTimeMs: null,
|
||||||
|
totalTimeMs,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,9 @@ export class OpenAiCompatibleAdapter implements LlmAdapter {
|
||||||
promptTokens: number;
|
promptTokens: number;
|
||||||
completionTokens: number;
|
completionTokens: number;
|
||||||
totalTokens: number;
|
totalTokens: number;
|
||||||
promptTimeMs: number;
|
promptTimeMs: number | null;
|
||||||
completionTimeMs: number;
|
completionTimeMs: number | null;
|
||||||
|
totalTimeMs: number;
|
||||||
}> {
|
}> {
|
||||||
const payload: Record<string, unknown> = {
|
const payload: Record<string, unknown> = {
|
||||||
messages: [
|
messages: [
|
||||||
|
|
@ -78,16 +79,14 @@ export class OpenAiCompatibleAdapter implements LlmAdapter {
|
||||||
const promptTokens = json.usage.prompt_tokens;
|
const promptTokens = json.usage.prompt_tokens;
|
||||||
const completionTokens = json.usage.completion_tokens;
|
const completionTokens = json.usage.completion_tokens;
|
||||||
|
|
||||||
const promptTimeMs = json.timings?.prompt_ms ?? totalTimeMs * 0.3;
|
|
||||||
const completionTimeMs = json.timings?.predicted_ms ?? totalTimeMs * 0.7;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content,
|
content,
|
||||||
promptTokens,
|
promptTokens,
|
||||||
completionTokens,
|
completionTokens,
|
||||||
totalTokens: json.usage.total_tokens,
|
totalTokens: json.usage.total_tokens,
|
||||||
promptTimeMs,
|
promptTimeMs: json.timings?.prompt_ms ?? null,
|
||||||
completionTimeMs,
|
completionTimeMs: json.timings?.predicted_ms ?? null,
|
||||||
|
totalTimeMs,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ export interface LlmAdapter {
|
||||||
promptTokens: number;
|
promptTokens: number;
|
||||||
completionTokens: number;
|
completionTokens: number;
|
||||||
totalTokens: number;
|
totalTokens: number;
|
||||||
promptTimeMs: number;
|
promptTimeMs: number | null;
|
||||||
completionTimeMs: number;
|
completionTimeMs: number | null;
|
||||||
|
totalTimeMs: number;
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@ interface LlmMetrics {
|
||||||
promptTokens: number;
|
promptTokens: number;
|
||||||
completionTokens: number;
|
completionTokens: number;
|
||||||
totalTokens: number;
|
totalTokens: number;
|
||||||
promptTimeMs: number;
|
promptTimeMs: number | null;
|
||||||
completionTimeMs: number;
|
completionTimeMs: number | null;
|
||||||
|
totalTimeMs: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PipelineMetrics {
|
interface PipelineMetrics {
|
||||||
|
|
@ -18,6 +19,7 @@ interface PipelineMetrics {
|
||||||
totalTokens: number;
|
totalTokens: number;
|
||||||
totalPromptTimeMs: number;
|
totalPromptTimeMs: number;
|
||||||
totalCompletionTimeMs: number;
|
totalCompletionTimeMs: number;
|
||||||
|
totalTimeMs: number;
|
||||||
currentWordStartTime?: Date;
|
currentWordStartTime?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,6 +42,7 @@ export class PipelineTimer {
|
||||||
totalTokens: 0,
|
totalTokens: 0,
|
||||||
totalPromptTimeMs: 0,
|
totalPromptTimeMs: 0,
|
||||||
totalCompletionTimeMs: 0,
|
totalCompletionTimeMs: 0,
|
||||||
|
totalTimeMs: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,9 +62,14 @@ export class PipelineTimer {
|
||||||
this.metrics.totalPromptTokens += llmMetrics.promptTokens;
|
this.metrics.totalPromptTokens += llmMetrics.promptTokens;
|
||||||
this.metrics.totalCompletionTokens += llmMetrics.completionTokens;
|
this.metrics.totalCompletionTokens += llmMetrics.completionTokens;
|
||||||
this.metrics.totalTokens += llmMetrics.totalTokens;
|
this.metrics.totalTokens += llmMetrics.totalTokens;
|
||||||
|
if (llmMetrics.promptTimeMs !== null) {
|
||||||
this.metrics.totalPromptTimeMs += llmMetrics.promptTimeMs;
|
this.metrics.totalPromptTimeMs += llmMetrics.promptTimeMs;
|
||||||
|
}
|
||||||
|
if (llmMetrics.completionTimeMs !== null) {
|
||||||
this.metrics.totalCompletionTimeMs += llmMetrics.completionTimeMs;
|
this.metrics.totalCompletionTimeMs += llmMetrics.completionTimeMs;
|
||||||
}
|
}
|
||||||
|
this.metrics.totalTimeMs += llmMetrics.totalTimeMs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
recordSkipped(): void {
|
recordSkipped(): void {
|
||||||
|
|
@ -125,22 +133,40 @@ export class PipelineTimer {
|
||||||
0,
|
0,
|
||||||
)
|
)
|
||||||
: "0";
|
: "0";
|
||||||
|
const avgTotalTimeMs =
|
||||||
|
this.metrics.llmCalls > 0
|
||||||
|
? (this.metrics.totalTimeMs / this.metrics.llmCalls).toFixed(0)
|
||||||
|
: "0";
|
||||||
|
|
||||||
|
const unifiedThroughput =
|
||||||
|
this.metrics.totalTimeMs > 0
|
||||||
|
? (
|
||||||
|
this.metrics.totalTokens /
|
||||||
|
(this.metrics.totalTimeMs / 1000)
|
||||||
|
).toFixed(1)
|
||||||
|
: "N/A";
|
||||||
|
|
||||||
|
const hasDetailedTimings =
|
||||||
|
this.metrics.totalPromptTimeMs > 0 ||
|
||||||
|
this.metrics.totalCompletionTimeMs > 0;
|
||||||
|
|
||||||
const avgPromptSpeed =
|
const avgPromptSpeed =
|
||||||
this.metrics.totalPromptTimeMs > 0
|
this.metrics.totalPromptTimeMs > 0
|
||||||
? (
|
? (
|
||||||
this.metrics.totalPromptTokens /
|
this.metrics.totalPromptTokens /
|
||||||
(this.metrics.totalPromptTimeMs / 1000)
|
(this.metrics.totalPromptTimeMs / 1000)
|
||||||
).toFixed(1)
|
).toFixed(1)
|
||||||
: "0";
|
: "N/A";
|
||||||
|
|
||||||
const avgCompletionSpeed =
|
const avgCompletionSpeed =
|
||||||
this.metrics.totalCompletionTimeMs > 0
|
this.metrics.totalCompletionTimeMs > 0
|
||||||
? (
|
? (
|
||||||
this.metrics.totalCompletionTokens /
|
this.metrics.totalCompletionTokens /
|
||||||
(this.metrics.totalCompletionTimeMs / 1000)
|
(this.metrics.totalCompletionTimeMs / 1000)
|
||||||
).toFixed(1)
|
).toFixed(1)
|
||||||
: "0";
|
: "N/A";
|
||||||
|
|
||||||
return [
|
const lines = [
|
||||||
`⏱️ Pipeline Summary`,
|
`⏱️ Pipeline Summary`,
|
||||||
` Duration: ${durationSec}s`,
|
` Duration: ${durationSec}s`,
|
||||||
` Processed: ${this.metrics.wordsProcessed}`,
|
` Processed: ${this.metrics.wordsProcessed}`,
|
||||||
|
|
@ -153,8 +179,20 @@ export class PipelineTimer {
|
||||||
` Calls: ${this.metrics.llmCalls}`,
|
` Calls: ${this.metrics.llmCalls}`,
|
||||||
` Avg prompt tokens: ${avgPromptTokens}`,
|
` Avg prompt tokens: ${avgPromptTokens}`,
|
||||||
` Avg completion tokens: ${avgCompletionTokens}`,
|
` Avg completion tokens: ${avgCompletionTokens}`,
|
||||||
|
` Avg total tokens: ${avgPromptTokens + avgCompletionTokens}`,
|
||||||
|
` Avg total request time: ${avgTotalTimeMs}ms`,
|
||||||
|
` Avg throughput: ${unifiedThroughput} tok/s`,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (hasDetailedTimings) {
|
||||||
|
lines.push(
|
||||||
|
``,
|
||||||
|
` [Local breakdown]`,
|
||||||
` Avg prompt speed: ${avgPromptSpeed} tok/s`,
|
` Avg prompt speed: ${avgPromptSpeed} tok/s`,
|
||||||
` Avg completion speed: ${avgCompletionSpeed} tok/s`,
|
` Avg completion speed: ${avgCompletionSpeed} tok/s`,
|
||||||
].join("\n");
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.join("\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue