fix(ai): log raw OpenAI API response objects, not cooked usage
- Add rawUsage field to OpenAiChatIterationResult to carry the
original OpenAI SDK usage object through the call chain
- All downstream chat() logs now dump rawUsage via
JSON.parse(JSON.stringify()) instead of our cooked
{ promptTokens, completionTokens }
- This exposes completion_tokens_details.reasoning_tokens and
any other fields OpenAI returns that we weren't capturing
This commit is contained in:
parent
51b3013659
commit
73b0b3149d
@ -85,6 +85,8 @@ interface OpenAiChatIterationResult {
|
|||||||
promptTokens: number;
|
promptTokens: number;
|
||||||
completionTokens: number;
|
completionTokens: number;
|
||||||
};
|
};
|
||||||
|
// Raw usage object from OpenAI API response (for logging/observability)
|
||||||
|
rawUsage?: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class OpenAiApi extends AiApi {
|
export class OpenAiApi extends AiApi {
|
||||||
@ -230,6 +232,7 @@ export class OpenAiApi extends AiApi {
|
|||||||
let accumulatedResponse = "";
|
let accumulatedResponse = "";
|
||||||
let accumulatedThinking = "";
|
let accumulatedThinking = "";
|
||||||
let usage: { promptTokens: number; completionTokens: number } | undefined;
|
let usage: { promptTokens: number; completionTokens: number } | undefined;
|
||||||
|
let rawUsage: unknown;
|
||||||
|
|
||||||
for await (const chunk of response) {
|
for await (const chunk of response) {
|
||||||
if (options.signal?.aborted) {
|
if (options.signal?.aborted) {
|
||||||
@ -242,6 +245,7 @@ export class OpenAiApi extends AiApi {
|
|||||||
promptTokens: chunk.usage.prompt_tokens,
|
promptTokens: chunk.usage.prompt_tokens,
|
||||||
completionTokens: chunk.usage.completion_tokens,
|
completionTokens: chunk.usage.completion_tokens,
|
||||||
};
|
};
|
||||||
|
rawUsage = chunk.usage;
|
||||||
// LOG: dump entire usage chunk from OpenAI generate streaming response
|
// LOG: dump entire usage chunk from OpenAI generate streaming response
|
||||||
this.log.info("OpenAI generate: streaming chunk with usage", {
|
this.log.info("OpenAI generate: streaming chunk with usage", {
|
||||||
usage: JSON.parse(JSON.stringify(chunk.usage)),
|
usage: JSON.parse(JSON.stringify(chunk.usage)),
|
||||||
@ -277,7 +281,7 @@ export class OpenAiApi extends AiApi {
|
|||||||
|
|
||||||
// LOG: dump final usage and accumulated stats from generate()
|
// LOG: dump final usage and accumulated stats from generate()
|
||||||
this.log.info("OpenAI generate: complete", {
|
this.log.info("OpenAI generate: complete", {
|
||||||
usage,
|
rawUsage: rawUsage ? JSON.parse(JSON.stringify(rawUsage)) : undefined,
|
||||||
responseLength: accumulatedResponse.length,
|
responseLength: accumulatedResponse.length,
|
||||||
thinkingLength: accumulatedThinking.length,
|
thinkingLength: accumulatedThinking.length,
|
||||||
durationMs,
|
durationMs,
|
||||||
@ -335,7 +339,7 @@ export class OpenAiApi extends AiApi {
|
|||||||
|
|
||||||
// LOG: dump usage from streaming chat iteration
|
// LOG: dump usage from streaming chat iteration
|
||||||
this.log.info("OpenAI chat: streaming iteration usage", {
|
this.log.info("OpenAI chat: streaming iteration usage", {
|
||||||
usage: iteration.usage,
|
rawUsage: iteration.rawUsage ? JSON.parse(JSON.stringify(iteration.rawUsage)) : undefined,
|
||||||
finishReason: iteration.finishReason,
|
finishReason: iteration.finishReason,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -352,7 +356,7 @@ export class OpenAiApi extends AiApi {
|
|||||||
});
|
});
|
||||||
// LOG: dump usage from non-streaming fallback
|
// LOG: dump usage from non-streaming fallback
|
||||||
this.log.info("OpenAI chat: non-streaming fallback usage", {
|
this.log.info("OpenAI chat: non-streaming fallback usage", {
|
||||||
usage: iteration.usage,
|
rawUsage: iteration.rawUsage ? JSON.parse(JSON.stringify(iteration.rawUsage)) : undefined,
|
||||||
finishReason: iteration.finishReason,
|
finishReason: iteration.finishReason,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -377,7 +381,7 @@ export class OpenAiApi extends AiApi {
|
|||||||
};
|
};
|
||||||
// LOG: dump final chat response stats
|
// LOG: dump final chat response stats
|
||||||
this.log.info("OpenAI chat: final response", {
|
this.log.info("OpenAI chat: final response", {
|
||||||
usage: iteration.usage,
|
rawUsage: iteration.rawUsage ? JSON.parse(JSON.stringify(iteration.rawUsage)) : undefined,
|
||||||
stats: finalResponse.stats,
|
stats: finalResponse.stats,
|
||||||
finishReason: iteration.finishReason,
|
finishReason: iteration.finishReason,
|
||||||
});
|
});
|
||||||
@ -470,6 +474,7 @@ export class OpenAiApi extends AiApi {
|
|||||||
let finishReason: string | null | undefined;
|
let finishReason: string | null | undefined;
|
||||||
const toolCallMap = new Map<number, StreamingToolCallAccumulator>();
|
const toolCallMap = new Map<number, StreamingToolCallAccumulator>();
|
||||||
let usage: { promptTokens: number; completionTokens: number } | undefined;
|
let usage: { promptTokens: number; completionTokens: number } | undefined;
|
||||||
|
let rawUsage: unknown;
|
||||||
|
|
||||||
for await (const chunk of response) {
|
for await (const chunk of response) {
|
||||||
if (signal?.aborted) {
|
if (signal?.aborted) {
|
||||||
@ -485,6 +490,7 @@ export class OpenAiApi extends AiApi {
|
|||||||
promptTokens: chunk.usage.prompt_tokens,
|
promptTokens: chunk.usage.prompt_tokens,
|
||||||
completionTokens: chunk.usage.completion_tokens,
|
completionTokens: chunk.usage.completion_tokens,
|
||||||
};
|
};
|
||||||
|
rawUsage = chunk.usage;
|
||||||
// LOG: dump entire usage chunk from OpenAI chat streaming response
|
// LOG: dump entire usage chunk from OpenAI chat streaming response
|
||||||
this.log.info("OpenAI chat stream: streaming chunk with usage", {
|
this.log.info("OpenAI chat stream: streaming chunk with usage", {
|
||||||
usage: JSON.parse(JSON.stringify(chunk.usage)),
|
usage: JSON.parse(JSON.stringify(chunk.usage)),
|
||||||
@ -522,7 +528,7 @@ export class OpenAiApi extends AiApi {
|
|||||||
contentDeltaCount,
|
contentDeltaCount,
|
||||||
toolDeltaCount,
|
toolDeltaCount,
|
||||||
finishReason,
|
finishReason,
|
||||||
usage,
|
rawUsage: rawUsage ? JSON.parse(JSON.stringify(rawUsage)) : undefined,
|
||||||
responseLength: content.length,
|
responseLength: content.length,
|
||||||
thinkingLength: thinking.length,
|
thinkingLength: thinking.length,
|
||||||
toolCallCount: toolCallMap.size,
|
toolCallCount: toolCallMap.size,
|
||||||
@ -537,6 +543,7 @@ export class OpenAiApi extends AiApi {
|
|||||||
contentDeltaCount,
|
contentDeltaCount,
|
||||||
toolDeltaCount,
|
toolDeltaCount,
|
||||||
usage,
|
usage,
|
||||||
|
rawUsage,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -580,6 +587,7 @@ export class OpenAiApi extends AiApi {
|
|||||||
promptTokens: response.usage.prompt_tokens,
|
promptTokens: response.usage.prompt_tokens,
|
||||||
completionTokens: response.usage.completion_tokens,
|
completionTokens: response.usage.completion_tokens,
|
||||||
} : undefined;
|
} : undefined;
|
||||||
|
const rawUsage = response.usage;
|
||||||
const assistantToolCalls = (message?.tool_calls || [])
|
const assistantToolCalls = (message?.tool_calls || [])
|
||||||
.filter((tc) => tc.type === "function")
|
.filter((tc) => tc.type === "function")
|
||||||
.map((tc) => ({
|
.map((tc) => ({
|
||||||
@ -607,6 +615,7 @@ export class OpenAiApi extends AiApi {
|
|||||||
contentDeltaCount: content ? 1 : 0,
|
contentDeltaCount: content ? 1 : 0,
|
||||||
toolDeltaCount: assistantToolCalls.length,
|
toolDeltaCount: assistantToolCalls.length,
|
||||||
usage,
|
usage,
|
||||||
|
rawUsage,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user