Spaces:
Running
Running
| import OpenAI from 'openai' | |
| import { createLogger } from '../../utils/logger' | |
| import { cleanManimCode } from '../../utils/manim-code-cleaner' | |
| import { getClient } from './client' | |
| import type { CodeRetryContext } from './types' | |
| import { buildRetryPrompt, getCodeRetrySystemPrompt } from './prompt-builder' | |
| import { dedupeSharedBlocksInMessages } from '../prompt-dedup' | |
| import { createChatCompletionText } from '../openai-stream' | |
| import { buildTokenParams } from '../../utils/reasoning-model' | |
| import { applyPatchSetToCode, extractTargetLine, parsePatchResponse } from './utils' | |
| const logger = createLogger('CodeRetryCodeGen') | |
| const AI_TEMPERATURE = parseFloat(process.env.AI_TEMPERATURE || '0.7') | |
| const MAX_TOKENS = parseInt(process.env.AI_MAX_TOKENS || '12000', 10) | |
| const THINKING_TOKENS = parseInt(process.env.AI_THINKING_TOKENS || '20000', 10) | |
| function getModel(customApiConfig?: unknown): string { | |
| const model = (customApiConfig as { model?: string } | undefined)?.model | |
| const trimmed = model?.trim() || '' | |
| if (!trimmed) { | |
| throw new Error('No model available') | |
| } | |
| return trimmed | |
| } | |
| export async function retryCodeGeneration( | |
| context: CodeRetryContext, | |
| errorMessage: string, | |
| attempt: number, | |
| currentCode: string, | |
| codeSnippet: string | undefined, | |
| customApiConfig?: unknown | |
| ): Promise<string> { | |
| const client = getClient(customApiConfig as any) | |
| if (!client) { | |
| throw new Error('No upstream AI is configured for this request') | |
| } | |
| const retryPrompt = buildRetryPrompt(context, errorMessage, attempt, currentCode, codeSnippet) | |
| try { | |
| const requestMessages = dedupeSharedBlocksInMessages( | |
| [ | |
| { role: 'system', content: getCodeRetrySystemPrompt(context.promptOverrides) }, | |
| { role: 'user', content: retryPrompt } | |
| ], | |
| context.promptOverrides | |
| ) | |
| const { content, mode } = await createChatCompletionText( | |
| client, | |
| { | |
| model: getModel(customApiConfig), | |
| messages: requestMessages, | |
| temperature: AI_TEMPERATURE, | |
| ...buildTokenParams(THINKING_TOKENS, MAX_TOKENS) | |
| }, | |
| { fallbackToNonStream: true, usageLabel: `retry-${attempt}` } | |
| ) | |
| if (!content) { | |
| throw new Error('AI returned empty content') | |
| } | |
| logger.info('Code retry model response received', { | |
| concept: context.concept, | |
| attempt, | |
| mode, | |
| contentLength: content.length, | |
| contentPreview: content.trim().slice(0, 500) | |
| }) | |
| const patchSet = parsePatchResponse(content) | |
| const patchedCode = applyPatchSetToCode(currentCode, patchSet, extractTargetLine(errorMessage)) | |
| const cleaned = cleanManimCode(patchedCode) | |
| logger.info('Code retry patch applied', { | |
| concept: context.concept, | |
| attempt, | |
| mode, | |
| patchCount: patchSet.patches.length, | |
| codeLength: cleaned.code.length, | |
| patchLengths: patchSet.patches.map((patch) => ({ | |
| originalSnippetLength: patch.originalSnippet.length, | |
| replacementSnippetLength: patch.replacementSnippet.length | |
| })), | |
| codePreview: cleaned.code.slice(0, 500) | |
| }) | |
| return cleaned.code | |
| } catch (error) { | |
| if (error instanceof OpenAI.APIError) { | |
| logger.error('OpenAI API error during code retry', { | |
| attempt, | |
| status: error.status, | |
| message: error.message | |
| }) | |
| } | |
| throw error | |
| } | |
| } | |