yet another attempt from yet another agent on trying to just capture some simple stats
This commit is contained in:
parent
241f3b5b69
commit
ef43fa2a00
@ -64,6 +64,12 @@ interface StreamingToolCallAccumulator {
|
||||
};
|
||||
}
|
||||
|
||||
interface OpenAiUsage {
|
||||
promptTokens: number;
|
||||
completionTokens: number;
|
||||
reasoningTokens: number;
|
||||
}
|
||||
|
||||
interface OpenAiChatIterationResult {
|
||||
response: string;
|
||||
thinking?: string;
|
||||
@ -80,13 +86,7 @@ interface OpenAiChatIterationResult {
|
||||
chunkCount: number;
|
||||
contentDeltaCount: number;
|
||||
toolDeltaCount: number;
|
||||
// Usage data from OpenAI API (prompt_tokens, completion_tokens)
|
||||
usage?: {
|
||||
promptTokens: number;
|
||||
completionTokens: number;
|
||||
};
|
||||
// Raw usage object from OpenAI API response (for logging/observability)
|
||||
rawUsage?: unknown;
|
||||
usage?: OpenAiUsage;
|
||||
}
|
||||
|
||||
export class OpenAiApi extends AiApi {
|
||||
@ -194,11 +194,6 @@ export class OpenAiApi extends AiApi {
|
||||
options: IAiGenerateOptions,
|
||||
streamCallback?: IAiResponseStreamFn,
|
||||
): Promise<IAiGenerateResponse> {
|
||||
await this.log.debug("OpenAiApi.generate called", {
|
||||
provider: model.provider.name,
|
||||
modelId: model.modelId,
|
||||
});
|
||||
|
||||
if (options.signal?.aborted) {
|
||||
throw new DOMException("The operation was aborted", "AbortError");
|
||||
}
|
||||
@ -229,12 +224,9 @@ export class OpenAiApi extends AiApi {
|
||||
: {}),
|
||||
}, options.signal ? { signal: options.signal } : undefined);
|
||||
|
||||
this.log.debug("OpenAI API response", { response: JSON.parse(JSON.stringify(response)) });
|
||||
|
||||
let accumulatedResponse = "";
|
||||
let accumulatedThinking = "";
|
||||
let usage: { promptTokens: number; completionTokens: number } | undefined;
|
||||
let rawUsage: unknown;
|
||||
let usage: OpenAiUsage | undefined;
|
||||
|
||||
for await (const chunk of response) {
|
||||
if (options.signal?.aborted) {
|
||||
@ -243,16 +235,7 @@ export class OpenAiApi extends AiApi {
|
||||
|
||||
// Capture usage from the final streaming chunk
|
||||
if (chunk.usage) {
|
||||
usage = {
|
||||
promptTokens: chunk.usage.prompt_tokens,
|
||||
completionTokens: chunk.usage.completion_tokens,
|
||||
};
|
||||
rawUsage = chunk.usage;
|
||||
// LOG: dump entire usage chunk from OpenAI generate streaming response
|
||||
this.log.info("OpenAI generate: streaming chunk with usage", {
|
||||
usage: JSON.parse(JSON.stringify(chunk.usage)),
|
||||
chunk: JSON.parse(JSON.stringify(chunk)),
|
||||
});
|
||||
usage = this.extractUsage(chunk.usage);
|
||||
}
|
||||
|
||||
const delta = chunk.choices[0]?.delta;
|
||||
@ -281,14 +264,6 @@ export class OpenAiApi extends AiApi {
|
||||
const endTime = Date.now();
|
||||
const durationMs = endTime - startTime;
|
||||
|
||||
// LOG: dump final usage and accumulated stats from generate()
|
||||
this.log.info("OpenAI generate: complete", {
|
||||
rawUsage: rawUsage ? JSON.parse(JSON.stringify(rawUsage)) : undefined,
|
||||
responseLength: accumulatedResponse.length,
|
||||
thinkingLength: accumulatedThinking.length,
|
||||
durationMs,
|
||||
});
|
||||
|
||||
return {
|
||||
done: true,
|
||||
response: accumulatedResponse,
|
||||
@ -301,7 +276,7 @@ export class OpenAiApi extends AiApi {
|
||||
tokenCounts: {
|
||||
input: usage?.promptTokens ?? 0,
|
||||
response: usage?.completionTokens ?? 0,
|
||||
thinking: 0,
|
||||
thinking: usage?.reasoningTokens ?? 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -312,11 +287,6 @@ export class OpenAiApi extends AiApi {
|
||||
options: IAiChatOptions,
|
||||
streamCallback?: IAiResponseStreamFn,
|
||||
): Promise<IAiChatResponse> {
|
||||
await this.log.debug("OpenAiApi.chat called", {
|
||||
provider: model.provider.name,
|
||||
modelId: model.modelId,
|
||||
});
|
||||
|
||||
const startTime = Date.now();
|
||||
const messages = this.buildMessages(options);
|
||||
const tools = this.buildTools(options);
|
||||
@ -329,38 +299,11 @@ export class OpenAiApi extends AiApi {
|
||||
options.signal,
|
||||
);
|
||||
|
||||
await this.log.debug("OpenAI chat stream iteration finished", {
|
||||
chunkCount: iteration.chunkCount,
|
||||
contentDeltaCount: iteration.contentDeltaCount,
|
||||
toolDeltaCount: iteration.toolDeltaCount,
|
||||
responseLength: iteration.response.length,
|
||||
thinkingLength: iteration.thinking?.length || 0,
|
||||
toolCallCount: iteration.toolCalls.length,
|
||||
finishReason: iteration.finishReason,
|
||||
});
|
||||
|
||||
// LOG: dump usage from streaming chat iteration
|
||||
this.log.info("OpenAI chat: streaming iteration usage", {
|
||||
rawUsage: iteration.rawUsage ? JSON.parse(JSON.stringify(iteration.rawUsage)) : undefined,
|
||||
finishReason: iteration.finishReason,
|
||||
});
|
||||
|
||||
if (this.isEmptyIteration(iteration)) {
|
||||
iteration = await this.readNonStreamingChatCompletion(model, messages, tools, options.signal);
|
||||
if (streamCallback && iteration.response) {
|
||||
await streamCallback({ type: "response", data: iteration.response });
|
||||
}
|
||||
await this.log.warn("OpenAI stream was empty; used non-streaming fallback", {
|
||||
responseLength: iteration.response.length,
|
||||
thinkingLength: iteration.thinking?.length || 0,
|
||||
toolCallCount: iteration.toolCalls.length,
|
||||
finishReason: iteration.finishReason,
|
||||
});
|
||||
// LOG: dump usage from non-streaming fallback
|
||||
this.log.info("OpenAI chat: non-streaming fallback usage", {
|
||||
rawUsage: iteration.rawUsage ? JSON.parse(JSON.stringify(iteration.rawUsage)) : undefined,
|
||||
finishReason: iteration.finishReason,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.isEmptyIteration(iteration)) {
|
||||
@ -381,16 +324,18 @@ export class OpenAiApi extends AiApi {
|
||||
toolCalls: iteration.toolCalls.length > 0 ? iteration.toolCalls : undefined,
|
||||
stats: this.buildStats(startTime, iteration.usage),
|
||||
};
|
||||
// LOG: dump final chat response stats
|
||||
this.log.info("OpenAI chat: final response", {
|
||||
rawUsage: iteration.rawUsage ? JSON.parse(JSON.stringify(iteration.rawUsage)) : undefined,
|
||||
stats: finalResponse.stats,
|
||||
finishReason: iteration.finishReason,
|
||||
});
|
||||
this.assertNonEmptyChatResponse(finalResponse);
|
||||
return finalResponse;
|
||||
}
|
||||
|
||||
private extractUsage(usage: { prompt_tokens: number; completion_tokens: number; completion_tokens_details?: { reasoning_tokens?: number } }): OpenAiUsage {
|
||||
return {
|
||||
promptTokens: usage.prompt_tokens,
|
||||
completionTokens: usage.completion_tokens,
|
||||
reasoningTokens: usage.completion_tokens_details?.reasoning_tokens ?? 0,
|
||||
};
|
||||
}
|
||||
|
||||
private buildMessages(options: IAiChatOptions): ChatCompletionMessageParam[] {
|
||||
const messages: ChatCompletionMessageParam[] = [];
|
||||
if (options.systemPrompt) {
|
||||
@ -468,8 +413,6 @@ export class OpenAiApi extends AiApi {
|
||||
: {}),
|
||||
}, signal ? { signal } : undefined);
|
||||
|
||||
this.log.debug("OpenAI API response", { response: JSON.parse(JSON.stringify(response)) });
|
||||
|
||||
let content = "";
|
||||
let thinking = "";
|
||||
let chunkCount = 0;
|
||||
@ -477,8 +420,7 @@ export class OpenAiApi extends AiApi {
|
||||
let toolDeltaCount = 0;
|
||||
let finishReason: string | null | undefined;
|
||||
const toolCallMap = new Map<number, StreamingToolCallAccumulator>();
|
||||
let usage: { promptTokens: number; completionTokens: number } | undefined;
|
||||
let rawUsage: unknown;
|
||||
let usage: OpenAiUsage | undefined;
|
||||
|
||||
for await (const chunk of response) {
|
||||
if (signal?.aborted) {
|
||||
@ -490,16 +432,7 @@ export class OpenAiApi extends AiApi {
|
||||
|
||||
// Capture usage from the final streaming chunk
|
||||
if (chunk.usage) {
|
||||
usage = {
|
||||
promptTokens: chunk.usage.prompt_tokens,
|
||||
completionTokens: chunk.usage.completion_tokens,
|
||||
};
|
||||
rawUsage = chunk.usage;
|
||||
// LOG: dump entire usage chunk from OpenAI chat streaming response
|
||||
this.log.info("OpenAI chat stream: streaming chunk with usage", {
|
||||
usage: JSON.parse(JSON.stringify(chunk.usage)),
|
||||
chunk: JSON.parse(JSON.stringify(chunk)),
|
||||
});
|
||||
usage = this.extractUsage(chunk.usage);
|
||||
}
|
||||
|
||||
const delta = chunk.choices[0]?.delta;
|
||||
@ -526,18 +459,6 @@ export class OpenAiApi extends AiApi {
|
||||
}
|
||||
}
|
||||
|
||||
// LOG: dump final streaming chat completion iteration result
|
||||
this.log.info("OpenAI chat stream: iteration complete", {
|
||||
chunkCount,
|
||||
contentDeltaCount,
|
||||
toolDeltaCount,
|
||||
finishReason,
|
||||
rawUsage: rawUsage ? JSON.parse(JSON.stringify(rawUsage)) : undefined,
|
||||
responseLength: content.length,
|
||||
thinkingLength: thinking.length,
|
||||
toolCallCount: toolCallMap.size,
|
||||
});
|
||||
|
||||
return {
|
||||
response: content,
|
||||
thinking: thinking || undefined,
|
||||
@ -547,7 +468,6 @@ export class OpenAiApi extends AiApi {
|
||||
contentDeltaCount,
|
||||
toolDeltaCount,
|
||||
usage,
|
||||
rawUsage,
|
||||
};
|
||||
}
|
||||
|
||||
@ -580,18 +500,11 @@ export class OpenAiApi extends AiApi {
|
||||
}
|
||||
: {}),
|
||||
}, signal ? { signal } : undefined);
|
||||
// LOG: dump entire non-streaming response from OpenAI
|
||||
this.log.info("OpenAI chat non-streaming: response", {
|
||||
response: JSON.parse(JSON.stringify(response)),
|
||||
});
|
||||
|
||||
const choice = response.choices[0];
|
||||
const message = choice?.message;
|
||||
const content = typeof message?.content === "string" ? message.content : "";
|
||||
const usage = response.usage ? {
|
||||
promptTokens: response.usage.prompt_tokens,
|
||||
completionTokens: response.usage.completion_tokens,
|
||||
} : undefined;
|
||||
const rawUsage = response.usage;
|
||||
const usage = response.usage ? this.extractUsage(response.usage) : undefined;
|
||||
const assistantToolCalls = (message?.tool_calls || [])
|
||||
.filter((tc) => tc.type === "function")
|
||||
.map((tc) => ({
|
||||
@ -619,7 +532,6 @@ export class OpenAiApi extends AiApi {
|
||||
contentDeltaCount: content ? 1 : 0,
|
||||
toolDeltaCount: assistantToolCalls.length,
|
||||
usage,
|
||||
rawUsage,
|
||||
};
|
||||
}
|
||||
|
||||
@ -690,7 +602,7 @@ export class OpenAiApi extends AiApi {
|
||||
|
||||
private buildStats(
|
||||
startTime: number,
|
||||
usage?: { promptTokens: number; completionTokens: number },
|
||||
usage?: OpenAiUsage,
|
||||
): IAiChatResponse["stats"] {
|
||||
const seconds = (Date.now() - startTime) / 1000;
|
||||
return {
|
||||
@ -701,7 +613,7 @@ export class OpenAiApi extends AiApi {
|
||||
tokenCounts: {
|
||||
input: usage?.promptTokens ?? 0,
|
||||
response: usage?.completionTokens ?? 0,
|
||||
thinking: 0,
|
||||
thinking: usage?.reasoningTokens ?? 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user