Skip to content

Commit

Permalink
add error handling to catch gemini api errors, change api key to fix …
Browse files Browse the repository at this point in the history
…rate limit issues
  • Loading branch information
faisalbhuiyan3038 committed Dec 8, 2024
1 parent 6d10d8e commit 20c9784
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 55 deletions.
2 changes: 1 addition & 1 deletion prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ model UserToProject {
model SourceCodeEmbedding {
id String @id @default(cuid())
summaryEmbedding Unsupported("Vector(768)")? //mainly because gemini provides 768 embeddings
summaryEmbedding Unsupported("Vector(768)")?
sourceCode String
fileName String
summary String
Expand Down
3 changes: 1 addition & 2 deletions src/app/(protected)/dashboard/ask-question-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,14 @@ import CodeReferences from './code-references'
import { api } from '@/trpc/react'
import { toast } from 'sonner'

const saveAnswer = api.project.saveAnswer.useMutation()

const AskQuestionCard = () => {
const {project} = useProject()
const [open, setOpen] = React.useState(false)
const [question, setQuestion] = React.useState('')
const [loading, setLoading] = React.useState(false)
const [fileReferences, setFileReferences] = React.useState<{fileName: string; sourceCode: string; summary: string}[]>([])
const [answer, setAnswer] = React.useState('')
const saveAnswer = api.project.saveAnswer.useMutation()

const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
setAnswer('')
Expand Down
106 changes: 73 additions & 33 deletions src/lib/gemini.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,80 @@ const model = genAI.getGenerativeModel({
model: 'gemini-1.5-flash',
})

export const aiSummariseCommit = async (diff:string) => {
//https://github.com/docker/gen-ai/commit/<commithash>.diff
const response = await model.generateContent([
`You are an expert programmer, and you are trying to summarise a git diff.
Reminders about the git diff format:
For every file, there are a few metadata lines, like (for example):
\`\`\`
diff --git a/<path> b/<path>
index <hash>..<hash>
--- a/lib/index.js
+++ b/lib/index.js
\`\`\`
This means that \`lib/index.js\` has changed. Note that this is only an example.
Then there is a specifier of the lines that were modified.
A line starting with \`+\` means it was added.
A line starting with \`-\` means it was deleted.
A line starting with neither \`+\` nor \`-\` is code given for context and better understanding.
It is not part of the diff.
[...]
EXAMPLE SUMMARY COMMENTS:
\`\`\`
- Raised the amount of returned recordings from \`10\` to \`15\` [packages/server/recordings.ts], [packages/server/constants.ts]
- Fixed a typo in the github action name [.github/workflows/ci.yml]
- Moved the \`octokit\` initialization to a separate file [packages/server/octokit.ts]
- Added an OpenAI API for completions [packages/server/openai.ts]
- Lowered numeric tolerance for text files
\`\`\`
Most commits will have less comments than this example list.
The last comment does not include any file names because there were more than two relevant files in the hypothetical commit.
Do not include parts of the example in your summary. It is given only as an example of appropirate comments.`,
`Please summarise the following git diff: \n\n${diff}`,
])
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));

return response.response.text();
export const aiSummariseCommit = async (diff: string, retries = 3) => {
try {
if (!diff || diff.trim().length === 0) {
console.error('Empty diff provided to AI summarization');
return '';
}

for (let attempt = 0; attempt < retries; attempt++) {
try {
// Add a small delay between requests to avoid rate limiting
if (attempt > 0) {
await sleep(1000 * (attempt + 1)); // Exponential backoff
}

const response = await model.generateContent([
`You are an expert programmer, and you are trying to summarise a git diff.
Reminders about the git diff format:
For every file, there are a few metadata lines, like (for example):
\`\`\`
diff --git a/<path> b/<path>
index <hash>..<hash>
--- a/lib/index.js
+++ b/lib/index.js
\`\`\`
This means that \`lib/index.js\` has changed. Note that this is only an example.
Then there is a specifier of the lines that were modified.
A line starting with \`+\` means it was added.
A line starting with \`-\` means it was deleted.
A line starting with neither \`+\` nor \`-\` is code given for context and better understanding.
It is not part of the diff.
[...]
EXAMPLE SUMMARY COMMENTS:
\`\`\`
- Raised the amount of returned recordings from \`10\` to \`15\` [packages/server/recordings.ts], [packages/server/constants.ts]
- Fixed a typo in the github action name [.github/workflows/ci.yml]
- Moved the \`octokit\` initialization to a separate file [packages/server/octokit.ts]
- Added an OpenAI API for completions [packages/server/openai.ts]
- Lowered numeric tolerance for text files
\`\`\`
Most commits will have less comments than this example list.
The last comment does not include any file names because there were more than two relevant files in the hypothetical commit.
Do not include parts of the example in your summary. It is given only as an example of appropirate comments.`,
`Please summarise the following git diff: \n\n${diff}`,
]);

if (!response?.response?.text) {
console.error('AI returned empty or invalid response');
continue;
}

const summary = response.response.text();
if (!summary || summary.trim().length === 0) {
console.error('AI returned empty summary');
continue;
}

return summary;
} catch (error: any) {
if (error?.status === 429 && attempt < retries - 1) {
console.log(`Rate limit hit, attempt ${attempt + 1}/${retries}, waiting before retry...`);
continue;
}
throw error;
}
}

console.error('All retry attempts failed');
return '';
} catch (error) {
console.error('Error in AI summarization:', error);
return '';
}
}

export async function summariseCode(doc: Document) {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/github-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const indexGithubRepo = async (projectId: string, githubUrl: string, gith
})

await db.$executeRaw`
UPDATE "sourceCodeEmbedding"
UPDATE "SourceCodeEmbedding"
SET "summaryEmbedding" = ${embedding.embedding}::vector
WHERE "id" = ${sourceCodeEmbedding.id}
`
Expand Down
65 changes: 47 additions & 18 deletions src/lib/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,25 +43,37 @@ export const pollCommits = async (projectId: string) => {
const { project, githubUrl } = await fetchProjectGithubUrl(projectId)
const commitHashes = await getCommitHashes(githubUrl!);
const unprocessedCommits = await filterUnprocessedCommits(projectId, commitHashes);

console.log(`Processing ${unprocessedCommits.length} unprocessed commits`);

const summaryResponses = await Promise.allSettled(unprocessedCommits.map(commit => {
return summariseCommit(githubUrl, commit.commitHash)
}))
const summaries = summaryResponses.map((response) => {

const summaries = summaryResponses.map((response, idx) => {
if (response.status === 'fulfilled') {
return response.value as string
if (!response.value) {
console.error(`Empty summary returned for commit ${unprocessedCommits[idx]?.commitHash}`);
}
return response.value as string;
}
return ''
})
console.error(`Failed to get summary for commit ${unprocessedCommits[idx]?.commitHash}:`, response.reason);
return '';
});

console.log(`Got ${summaries.filter(s => s).length} successful summaries out of ${summaries.length} total`);

const commits = await db.commit.createMany({
data: summaries.map((summary, index) => {
const commit = unprocessedCommits[index]!;
console.log(`Saving commit ${commit.commitHash} with summary length: ${summary?.length || 0}`);
return {
projectId:projectId,
commitHash: unprocessedCommits[index]!.commitHash,
commitMessage: unprocessedCommits[index]!.commitMessage,
commitAuthorName: unprocessedCommits[index]!.commitAuthorName,
commitAuthorAvatar: unprocessedCommits[index]!.commitAuthorAvatar,
commitDate: unprocessedCommits[index]!.commitDate,
projectId,
commitHash: commit.commitHash,
commitMessage: commit.commitMessage,
commitAuthorName: commit.commitAuthorName,
commitAuthorAvatar: commit.commitAuthorAvatar,
commitDate: commit.commitDate,
summary
}
})
Expand All @@ -70,17 +82,35 @@ export const pollCommits = async (projectId: string) => {
return commits
}

async function summariseCommit(githubUrl: string, commitHash: string) {
async function summariseCommit(githubUrl: string, commitHash: string) {
try {
// get the diff, then pass it to ai
const { data } = await axios.get(`${githubUrl}/commit/${commitHash}.diff`, {
headers: {
Accept: 'application/vnd.github.v3.diff'
}
})
return await aiSummariseCommit(data) || ""
});

if (!data) {
console.error(`No diff data received for commit ${commitHash}`);
return "";
}

console.log(`Got diff for commit ${commitHash}, length: ${data.length}`);
const summary = await aiSummariseCommit(data);

if (!summary) {
console.error(`AI returned empty summary for commit ${commitHash}`);
}

return summary || "";
} catch (error) {
console.error(`Error getting summary for commit ${commitHash}:`, error);
return "";
}
}

async function fetchProjectGithubUrl(projectId: string) {
async function fetchProjectGithubUrl(projectId: string) {
const project = await db.project.findUnique({
where: { id: projectId },
select: {
Expand All @@ -91,14 +121,13 @@ export const pollCommits = async (projectId: string) => {
throw new Error('Project has no github url')
}
return { project, githubUrl: project?.githubUrl }
}
}

async function filterUnprocessedCommits(projectId: string, commitHashes: Response[]) {
async function filterUnprocessedCommits(projectId: string, commitHashes: Response[]) {
const processedCommits = await db.commit.findMany({
where: { projectId }
})

const unprocessedCommits = commitHashes.filter((commit) => !processedCommits.some((processedCommit) => processedCommit.commitHash === commit.commitHash))
return unprocessedCommits
}

}

0 comments on commit 20c9784

Please sign in to comment.