Skip to content

Commit d36f824

Browse files
committed
feat: 增强调试日志记录功能,添加对 GitHub Copilot API 响应和工具调用的日志记录
1 parent 141466b commit d36f824

3 files changed

Lines changed: 249 additions & 31 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,5 @@ debug-logs/debug-gemini-2025-09-24T16-05-20-120Z-m6ebiq-compressed.log
4646
debug-logs/debug-gemini-2025-09-24T16-05-20-120Z-m6ebiq.log
4747
debug-logs/debug-gemini-2025-09-24T16-05-24-000Z-nuggbx-compressed.log
4848
debug-logs/debug-gemini-2025-09-24T16-05-24-000Z-nuggbx.log
49+
generate-content-review-gpt5.md
50+
generate-content-test-review.md

src/lib/debug-logger.ts

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import { writeFile } from "node:fs/promises"
33
import { join } from "node:path"
44

55
import type { GeminiRequest } from "~/routes/generate-content/types"
6-
import type { ChatCompletionsPayload } from "~/services/copilot/create-chat-completions"
6+
import type {
7+
ChatCompletionsPayload,
8+
ChatCompletionResponse,
9+
} from "~/services/copilot/create-chat-completions"
710

811
interface DebugLogData {
912
timestamp: string
@@ -77,4 +80,100 @@ export class DebugLogger {
7780
const requestId = Math.random().toString(36).slice(2, 8)
7881
await logger.logRequest({ requestId, geminiPayload, openAIPayload, error })
7982
}
83+
84+
// Log GitHub Copilot API Response
85+
static async logCopilotResponse(
86+
response: ChatCompletionResponse,
87+
context?: string,
88+
): Promise<void> {
89+
const logger = DebugLogger.getInstance()
90+
const requestId = Math.random().toString(36).slice(2, 8)
91+
const timestamp = new Date().toISOString().replaceAll(/[:.]/g, "-")
92+
const logPath = join(
93+
logger.logDir,
94+
`debug-copilot-response-${timestamp}-${requestId}.log`,
95+
)
96+
97+
const logData = {
98+
timestamp: new Date().toISOString(),
99+
context: context || "GitHub Copilot API Response",
100+
response,
101+
}
102+
103+
try {
104+
await writeFile(logPath, JSON.stringify(logData, null, 2), "utf8")
105+
console.log(`[DEBUG] Logged Copilot response to: ${logPath}`)
106+
} catch (writeError) {
107+
console.error(
108+
`[DEBUG] Failed to write Copilot response log file ${logPath}:`,
109+
writeError,
110+
)
111+
}
112+
}
113+
114+
// Log any object for debugging purposes
115+
static async logDebugData(
116+
data: unknown,
117+
context: string,
118+
filePrefix = "debug-data",
119+
): Promise<void> {
120+
const logger = DebugLogger.getInstance()
121+
const requestId = Math.random().toString(36).slice(2, 8)
122+
const timestamp = new Date().toISOString().replaceAll(/[:.]/g, "-")
123+
const logPath = join(
124+
logger.logDir,
125+
`${filePrefix}-${timestamp}-${requestId}.log`,
126+
)
127+
128+
const logData = {
129+
timestamp: new Date().toISOString(),
130+
context,
131+
data,
132+
}
133+
134+
try {
135+
await writeFile(logPath, JSON.stringify(logData, null, 2), "utf8")
136+
console.log(`[DEBUG] Logged ${context} to: ${logPath}`)
137+
} catch (writeError) {
138+
console.error(
139+
`[DEBUG] Failed to write debug log file ${logPath}:`,
140+
writeError,
141+
)
142+
}
143+
}
144+
145+
// Log original and translated response comparison
146+
static async logResponseComparison(
147+
originalResponse: unknown,
148+
translatedResponse: unknown,
149+
options: { context: string; filePrefix?: string } = {
150+
context: "Response Comparison",
151+
},
152+
): Promise<void> {
153+
const { context, filePrefix = "debug-comparison" } = options
154+
const logger = DebugLogger.getInstance()
155+
const requestId = Math.random().toString(36).slice(2, 8)
156+
const timestamp = new Date().toISOString().replaceAll(/[:.]/g, "-")
157+
const logPath = join(
158+
logger.logDir,
159+
`${filePrefix}-${timestamp}-${requestId}.log`,
160+
)
161+
162+
const logData = {
163+
timestamp: new Date().toISOString(),
164+
context,
165+
originalResponse,
166+
translatedResponse,
167+
}
168+
169+
try {
170+
await writeFile(logPath, JSON.stringify(logData, null, 2), "utf8")
171+
console.log(`[DEBUG] Logged ${context} comparison to: ${logPath}`)
172+
} catch (writeError) {
173+
console.error(
174+
`[DEBUG] Failed to write comparison log file ${logPath}:`,
175+
writeError,
176+
)
177+
}
178+
}
80179
}

src/routes/generate-content/translation.ts

Lines changed: 147 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { DebugLogger } from "~/lib/debug-logger"
12
import {
23
type ChatCompletionResponse,
34
type ChatCompletionChunk,
@@ -518,22 +519,33 @@ function ensureToolCallResponseMatch(messages: Array<Message>): Array<Message> {
518519
export function translateOpenAIToGemini(
519520
response: ChatCompletionResponse,
520521
): GeminiResponse {
521-
const candidates: Array<GeminiCandidate> = response.choices.map(
522-
(choice, index) => ({
522+
const result = {
523+
candidates: response.choices.map((choice, index) => ({
523524
content: translateOpenAIMessageToGeminiContent(choice.message),
524525
finishReason: mapOpenAIFinishReasonToGemini(choice.finish_reason),
525526
index,
526-
}),
527-
)
528-
529-
return {
530-
candidates,
527+
})),
531528
usageMetadata: {
532529
promptTokenCount: response.usage?.prompt_tokens || 0,
533530
candidatesTokenCount: response.usage?.completion_tokens || 0,
534531
totalTokenCount: response.usage?.total_tokens || 0,
535532
},
536533
}
534+
535+
// Debug: Log original GitHub Copilot response and translated Gemini response for comparison
536+
if (process.env.DEBUG_GEMINI_REQUESTS === "true") {
537+
DebugLogger.logResponseComparison(response, result, {
538+
context: "Non-Stream Response Translation",
539+
filePrefix: "debug-nonstream-comparison",
540+
}).catch((error: unknown) => {
541+
console.error(
542+
"[DEBUG] Failed to log non-stream response comparison:",
543+
error,
544+
)
545+
})
546+
}
547+
548+
return result
537549
}
538550

539551
function translateOpenAIMessageToGeminiContent(
@@ -568,6 +580,13 @@ function translateOpenAIMessageToGeminiContent(
568580
// Handle tool calls
569581
if (message.tool_calls) {
570582
for (const toolCall of message.tool_calls) {
583+
// Debug: Log tool call arguments to verify what GitHub Copilot returns
584+
if (process.env.DEBUG_GEMINI_REQUESTS === "true") {
585+
console.log(
586+
`[DEBUG] Tool call - name: ${toolCall.function.name}, arguments: "${toolCall.function.arguments}", type: ${typeof toolCall.function.arguments}, truthy: ${Boolean(toolCall.function.arguments)}`,
587+
)
588+
}
589+
571590
parts.push({
572591
functionCall: {
573592
name: toolCall.function.name,
@@ -595,7 +614,94 @@ function generateToolCallId(functionName: string): string {
595614
return `call_${functionName}_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`
596615
}
597616

598-
// Helper function to process tool calls in streaming chunks
617+
// Global accumulator for streaming tool call arguments
618+
const streamingToolCallAccumulator = new Map<
619+
number,
620+
{
621+
name: string
622+
arguments: string
623+
id?: string
624+
}
625+
>()
626+
627+
// Helper function to try parsing and creating a function call
628+
function tryCreateFunctionCall(
629+
name: string,
630+
argumentsStr: string,
631+
): GeminiPart | null {
632+
try {
633+
const args = JSON.parse(argumentsStr) as Record<string, unknown>
634+
return {
635+
functionCall: {
636+
name,
637+
args,
638+
},
639+
}
640+
} catch {
641+
return null
642+
}
643+
}
644+
645+
// Helper function to handle tool call with function name
646+
function handleToolCallWithName(toolCall: {
647+
index: number
648+
id?: string
649+
function: {
650+
name: string
651+
arguments?: string
652+
}
653+
}): GeminiPart | null {
654+
const accumulatedArgs = toolCall.function.arguments || ""
655+
656+
streamingToolCallAccumulator.set(toolCall.index, {
657+
name: toolCall.function.name,
658+
arguments: accumulatedArgs,
659+
id: toolCall.id,
660+
})
661+
662+
// If we already have arguments, try to process immediately (for non-streaming models like Gemini)
663+
if (accumulatedArgs) {
664+
const functionCall = tryCreateFunctionCall(
665+
toolCall.function.name,
666+
accumulatedArgs,
667+
)
668+
if (functionCall) {
669+
// Clear the accumulator for this index since we've successfully processed it
670+
streamingToolCallAccumulator.delete(toolCall.index)
671+
return functionCall
672+
}
673+
}
674+
675+
return null
676+
}
677+
678+
// Helper function to handle tool call argument accumulation
679+
function handleToolCallAccumulation(toolCall: {
680+
index: number
681+
function?: {
682+
arguments?: string
683+
}
684+
}): GeminiPart | null {
685+
const existingAccumulated = streamingToolCallAccumulator.get(toolCall.index)
686+
687+
if (existingAccumulated && toolCall.function?.arguments) {
688+
existingAccumulated.arguments += toolCall.function.arguments
689+
690+
const functionCall = tryCreateFunctionCall(
691+
existingAccumulated.name,
692+
existingAccumulated.arguments,
693+
)
694+
if (functionCall) {
695+
// Clear the accumulator for this index since we've successfully processed it
696+
streamingToolCallAccumulator.delete(toolCall.index)
697+
return functionCall
698+
}
699+
}
700+
701+
return null
702+
}
703+
704+
// Helper function to process tool calls in streaming chunks with argument accumulation
599705
function processToolCalls(
600706
toolCalls: Array<{
601707
index: number
@@ -610,33 +716,34 @@ function processToolCalls(
610716
const parts: Array<GeminiPart> = []
611717

612718
for (const toolCall of toolCalls) {
613-
// Enhanced validation: check for empty/whitespace-only names
614-
if (
615-
!toolCall.function?.name
616-
|| typeof toolCall.function.name !== "string"
617-
|| toolCall.function.name.trim() === ""
618-
) {
619-
continue
719+
// Debug: Log streaming tool call arguments to verify what GitHub Copilot returns
720+
if (process.env.DEBUG_GEMINI_REQUESTS === "true") {
721+
console.log(
722+
`[DEBUG STREAM] Tool call - name: ${toolCall.function?.name}, arguments: "${toolCall.function?.arguments}", type: ${typeof toolCall.function?.arguments}, truthy: ${Boolean(toolCall.function?.arguments)}`,
723+
)
620724
}
621725

622-
let args: Record<string, unknown>
623-
try {
624-
args = JSON.parse(toolCall.function.arguments || "{}") as Record<
625-
string,
626-
unknown
627-
>
628-
} catch {
629-
// In streaming, arguments might be incomplete JSON
630-
// Skip this chunk and wait for complete arguments
726+
// If this chunk has a function name, it's the start of a new tool call
727+
if (toolCall.function?.name && toolCall.function.name.trim() !== "") {
728+
const functionCall = handleToolCallWithName({
729+
index: toolCall.index,
730+
id: toolCall.id,
731+
function: {
732+
name: toolCall.function.name,
733+
arguments: toolCall.function.arguments,
734+
},
735+
})
736+
if (functionCall) {
737+
parts.push(functionCall)
738+
}
631739
continue
632740
}
633741

634-
parts.push({
635-
functionCall: {
636-
name: toolCall.function.name,
637-
args,
638-
},
639-
})
742+
// If we have existing accumulated data and this chunk has arguments, append them
743+
const functionCall = handleToolCallAccumulation(toolCall)
744+
if (functionCall) {
745+
parts.push(functionCall)
746+
}
640747
}
641748

642749
return parts
@@ -806,6 +913,16 @@ export function translateOpenAIChunkToGemini(chunk: ChatCompletionChunk): {
806913
)
807914
const response = buildGeminiResponse(candidate, shouldInclude, chunk)
808915

916+
// Debug: Log original GitHub Copilot chunk and translated Gemini chunk for comparison
917+
if (process.env.DEBUG_GEMINI_REQUESTS === "true") {
918+
DebugLogger.logResponseComparison(chunk, response, {
919+
context: "Streaming Chunk Translation",
920+
filePrefix: "debug-stream-comparison",
921+
}).catch((error: unknown) => {
922+
console.error("[DEBUG] Failed to log streaming chunk comparison:", error)
923+
})
924+
}
925+
809926
return response
810927
}
811928

0 commit comments

Comments
 (0)