Skip to content

Commit dd1683d

Browse files
fix: use proper waiting techniques
1 parent 521a641 commit dd1683d

1 file changed

Lines changed: 121 additions & 70 deletions

File tree

.github/actions/auto-release-description/generate_pr_description.js

Lines changed: 121 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -52,46 +52,69 @@ function chunkDiffByFiles(diffContent) {
5252
const lines = diffContent.split('\n');
5353
let currentChunk = '';
5454
let currentFile = '';
55-
let tokenCount = 0;
55+
let currentChunkTokenCount = 0;
56+
const PROMPT_OVERHEAD = 2000; // Reserve tokens for prompt overhead
57+
const MAX_CHUNK_TOKENS = MAX_TOKENS_PER_REQUEST - PROMPT_OVERHEAD;
5658

57-
for (const line of lines) {
58-
// Check if this is a new file header
59-
//console.error(`Line is estimated at ${estimateTokens(line)} tokens`);
60-
tokenCount += estimateTokens(line);
61-
//console.error(`Total tokens for this chunk is ${tokenCount}`);
62-
if (line.startsWith('diff --git') || line.startsWith('+++') || line.startsWith('---')) {
63-
// If we have content and it's getting large, save current chunk
64-
if (currentChunk && tokenCount > MAX_TOKENS_PER_REQUEST) {
65-
fileChunks.push({
66-
content: currentChunk.trim(),
67-
file: currentFile,
68-
type: 'file-chunk'
69-
});
70-
currentChunk = '';
71-
tokenCount = 0;
59+
for (let i = 0; i < lines.length; i++) {
60+
const line = lines[i];
61+
const lineTokens = estimateTokens(line);
62+
const isNewFile = line.startsWith('diff --git');
63+
const isFileHeader = line.startsWith('+++') || line.startsWith('---');
64+
65+
// Check if we need to split current chunk before adding this line
66+
if (currentChunk && (currentChunkTokenCount + lineTokens) > MAX_CHUNK_TOKENS) {
67+
// Current chunk is getting too large, save it
68+
fileChunks.push({
69+
content: currentChunk.trim(),
70+
file: currentFile,
71+
type: 'file-chunk'
72+
});
73+
console.error(`Chunk ${fileChunks.length} saved: ${currentChunkTokenCount} tokens for ${currentFile || 'unknown'}`);
74+
currentChunk = '';
75+
currentChunkTokenCount = 0;
76+
}
77+
78+
// Handle new file boundaries
79+
if (isNewFile) {
80+
// Extract filename from next lines
81+
// Look ahead for +++ line
82+
for (let j = i + 1; j < Math.min(i + 10, lines.length); j++) {
83+
if (lines[j].startsWith('+++')) {
84+
currentFile = lines[j].replace('+++ b/', '').replace('+++ a/', '');
85+
break;
86+
}
7287
}
88+
}
89+
90+
// Add line to current chunk
91+
currentChunk += line + '\n';
92+
currentChunkTokenCount += lineTokens;
93+
94+
// If a single line is too large, split it
95+
if (lineTokens > MAX_CHUNK_TOKENS && currentChunk.length > 100) {
96+
// Remove the line from current chunk
97+
currentChunk = currentChunk.substring(0, currentChunk.length - line.length - 1);
98+
currentChunkTokenCount -= lineTokens;
7399

74-
// Start new chunk
75-
currentChunk = line + '\n';
76-
77-
78-
// Extract filename for reference
79-
if (line.startsWith('+++')) {
80-
currentFile = line.replace('+++ b/', '').replace('+++ a/', '');
81-
}
82-
if(tokenCount > MAX_TOKENS_PER_REQUEST){
83-
const split_chunk = splitStringByTokens(currentChunk, MAX_TOKENS_PER_REQUEST);
84-
currentChunk = split_chunk[split_chunk.length-1];
85-
for(let i = 0; i < split_chunk.length -1;i++){
86-
fileChunks.push({
87-
content: split_chunk[i].trim(),
88-
file: currentFile,
89-
type: 'file-chunk'
90-
});
100+
// Split the large line
101+
const splitChunks = splitStringByTokens(line, MAX_CHUNK_TOKENS);
102+
for (let j = 0; j < splitChunks.length; j++) {
103+
if (j > 0) {
104+
// Save previous chunk if it has content
105+
if (currentChunk.trim()) {
106+
fileChunks.push({
107+
content: currentChunk.trim(),
108+
file: currentFile,
109+
type: 'file-chunk'
110+
});
111+
currentChunk = '';
112+
currentChunkTokenCount = 0;
113+
}
91114
}
115+
currentChunk = splitChunks[j] + '\n';
116+
currentChunkTokenCount = estimateTokens(currentChunk);
92117
}
93-
} else {
94-
currentChunk += line + '\n';
95118
}
96119
}
97120

@@ -102,6 +125,7 @@ function chunkDiffByFiles(diffContent) {
102125
file: currentFile,
103126
type: 'file-chunk'
104127
});
128+
console.error(`Final chunk ${fileChunks.length} saved: ${currentChunkTokenCount} tokens for ${currentFile || 'unknown'}`);
105129
}
106130

107131
return fileChunks;
@@ -160,44 +184,67 @@ ${diffContent}`;
160184
}
161185

162186
/**
163-
* Call Gemini API with the given prompt
187+
* Call Gemini API with the given prompt (with retry logic for rate limits)
164188
*/
165-
async function callGeminiAPI(prompt, apiKey) {
189+
async function callGeminiAPI(prompt, apiKey, retryCount = 0) {
190+
const maxRetries = 3;
191+
const baseDelay = 1000; // 1 second base delay
192+
166193
console.error(`Sending prompt with an estimated ${estimateTokens(prompt)} tokens`);
167-
const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${apiKey}`, {
168-
method: 'POST',
169-
headers: { 'Content-Type': 'application/json' },
170-
body: JSON.stringify({
171-
contents: [{
172-
parts: [{
173-
text: prompt
174-
}]
175-
}],
176-
generationConfig: {
177-
temperature: 0.7,
178-
topK: 40,
179-
topP: 0.95,
180-
maxOutputTokens: 8192,
181-
}
182-
})
183-
});
194+
195+
try {
196+
const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${apiKey}`, {
197+
method: 'POST',
198+
headers: { 'Content-Type': 'application/json' },
199+
body: JSON.stringify({
200+
contents: [{
201+
parts: [{
202+
text: prompt
203+
}]
204+
}],
205+
generationConfig: {
206+
temperature: 0.7,
207+
topK: 40,
208+
topP: 0.95,
209+
maxOutputTokens: 8192,
210+
}
211+
})
212+
});
184213

185-
if (!response.ok) {
186-
const errorText = await response.text();
187-
throw new Error(`Gemini API request failed with status ${response.status}: ${errorText}`);
188-
}
214+
// Handle rate limiting (429) with exponential backoff
215+
if (response.status === 429 && retryCount < maxRetries) {
216+
const delay = baseDelay * Math.pow(2, retryCount); // Exponential backoff: 1s, 2s, 4s
217+
console.error(`Rate limit hit, retrying in ${delay}ms (attempt ${retryCount + 1}/${maxRetries})`);
218+
await sleep(delay);
219+
return await callGeminiAPI(prompt, apiKey, retryCount + 1);
220+
}
189221

190-
const json = await response.json();
191-
192-
if (!json.candidates || !json.candidates[0]) {
193-
throw new Error('Invalid response from Gemini API');
194-
}
222+
if (!response.ok) {
223+
const errorText = await response.text();
224+
throw new Error(`Gemini API request failed with status ${response.status}: ${errorText}`);
225+
}
195226

196-
if (!json.candidates[0].content || !json.candidates[0].content.parts || !json.candidates[0].content.parts[0] || !json.candidates[0].content.parts[0].text) {
197-
throw new Error('Invalid response structure from Gemini API - missing content');
198-
}
227+
const json = await response.json();
228+
229+
if (!json.candidates || !json.candidates[0]) {
230+
throw new Error('Invalid response from Gemini API');
231+
}
232+
233+
if (!json.candidates[0].content || !json.candidates[0].content.parts || !json.candidates[0].content.parts[0] || !json.candidates[0].content.parts[0].text) {
234+
throw new Error('Invalid response structure from Gemini API - missing content');
235+
}
199236

200-
return json.candidates[0].content.parts[0].text;
237+
return json.candidates[0].content.parts[0].text;
238+
} catch (error) {
239+
// If it's a network error and we have retries left, retry with exponential backoff
240+
if (retryCount < maxRetries && (error.message.includes('fetch') || error.message.includes('network'))) {
241+
const delay = baseDelay * Math.pow(2, retryCount);
242+
console.error(`Network error, retrying in ${delay}ms (attempt ${retryCount + 1}/${maxRetries}): ${error.message}`);
243+
await sleep(delay);
244+
return await callGeminiAPI(prompt, apiKey, retryCount + 1);
245+
}
246+
throw error;
247+
}
201248
}
202249

203250
/**
@@ -212,12 +259,13 @@ async function processChunks(chunks, apiKey) {
212259

213260
// Multiple chunks - process each and combine
214261
const chunkResults = [];
262+
const CHUNK_DELAY = 500; // 500ms delay between chunks (reduced from 5s)
215263

216264
for (let i = 0; i < Math.min(chunks.length, MAX_CHUNKS); i++) {
217265
const chunk = chunks[i];
218266
if (i > 0) {
219-
// sleep for 3 seconds
220-
sleep(5 * 1000);
267+
// Small delay between chunks to avoid rate limits (reduced from 5s to 500ms)
268+
await sleep(CHUNK_DELAY);
221269
}
222270
console.error(`Processing chunk ${i + 1}/${Math.min(chunks.length, MAX_CHUNKS)} (${chunk.file || 'unknown file'})`);
223271

@@ -236,7 +284,10 @@ async function processChunks(chunks, apiKey) {
236284
if (chunkResults.length === 0) {
237285
throw new Error('Failed to process any chunks');
238286
}
239-
sleep(5*1000);
287+
288+
// Small delay before combining (reduced from 5s to 500ms)
289+
await sleep(CHUNK_DELAY);
290+
240291
// Combine results from multiple chunks
241292
const combinedPrompt = `Combine these pull request descriptions into a single, coherent PR description. Use the same format:
242293

0 commit comments

Comments
 (0)