Skip to content

Commit f9c6ed1

Browse files
authored
Merge pull request #82 from devverseio/fix/summary
fix: fix summary with structuredOutputs
2 parents 89ecb40 + 8bee38d commit f9c6ed1

13 files changed

+69
-130
lines changed

src/common/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ export * from './diff-line.type';
66
export * from './diff-position.type';
77
export * from './file-content.type';
88
export * from './file-context.type';
9+
export * from './summary-object.type';
910
export * from './template-replacement.type';
1011
export * from './walkthrough-object.type';
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { z } from 'zod';
2+
import { summaryObjectSchema } from '~schemas';
3+
4+
export type SummaryObject = z.infer<typeof summaryObjectSchema>;

src/handlers/summary.handler.ts

Lines changed: 35 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,15 @@
11
import { RestEndpointMethodTypes } from '@octokit/plugin-rest-endpoint-methods';
2-
import { generateObject, generateText } from 'ai';
2+
import { generateObject } from 'ai';
33
import { Context } from 'probot';
4-
import { z } from 'zod';
54
import { toneDetails } from '~common/consts';
65
import { Severity } from '~common/enums';
76
import { Handler } from '~common/interfaces';
8-
import { Config, FileContent, FileContext } from '~common/types';
7+
import { Config, FileContent, SummaryObject } from '~common/types';
98
import { aiConfig, appConfig } from '~configs';
109
import { SystemError } from '~errors';
11-
import { calculateContentTokens, isTextContent } from '~helpers';
10+
import { summaryObjectSchema } from '~schemas';
1211
import { AiService, CacheService, GitHubService, QueueService, TemplateService } from '~services';
13-
import {
14-
summaryCommentTemplate,
15-
summaryCreatePromptTemplate,
16-
summarySystemTemplate,
17-
summaryUpdatePromptTemplate
18-
} from '~templates';
12+
import { summaryCommentTemplate, summaryPromptTemplate, summarySystemTemplate } from '~templates';
1913
import { SummarySkipConditions } from '~utils';
2014

2115
export class SummaryHandler implements Handler<'pull_request'> {
@@ -33,14 +27,9 @@ export class SummaryHandler implements Handler<'pull_request'> {
3327
}
3428

3529
const fileContents = await this.queueService.schedule(() => GitHubService.getPullRequestFilesContent(context));
36-
const fileContexts = await this.generateFileContexts(apiKey, fileContents);
30+
const summaryObject = await this.generateSummaryObject(apiKey, config, fileContents);
31+
const summary = this.generateMarkdown(summaryObject.data);
3732
const existingComment = await this.findExistingComment(context);
38-
const summary = await this.generateSummary(
39-
apiKey,
40-
config,
41-
fileContexts,
42-
existingComment?.body?.split('<!-- heading title summary -->')[1] // FIX: This is a temporary fix
43-
);
4433

4534
if (existingComment?.id) {
4635
await this.queueService.schedule(() =>
@@ -55,7 +44,7 @@ export class SummaryHandler implements Handler<'pull_request'> {
5544
);
5645
} else {
5746
const commentBody = TemplateService.render(summaryCommentTemplate, {
58-
summary: summary,
47+
summary,
5948
commentType: appConfig.metadata.summary
6049
});
6150

@@ -71,89 +60,51 @@ export class SummaryHandler implements Handler<'pull_request'> {
7160
return comments.find((comment) => comment.body?.includes(appConfig.metadata.summary));
7261
}
7362

74-
private async generateFileContexts(apiKey: string, fileContents: FileContent[]): Promise<FileContext[]> {
75-
try {
76-
const filesContexts = Promise.all(
77-
fileContents
78-
.filter((file) => isTextContent(file.content))
79-
.map(async (file) => {
80-
const contentTpm = calculateContentTokens(file.content ?? '', aiConfig.model.summary);
63+
private generateMarkdown(data: SummaryObject['data']): string {
64+
const groupedData = data.reduce((acc: Record<string, string[]>, item) => {
65+
acc[item.type] = acc[item.type] || [];
66+
acc[item.type].push(item.description);
8167

82-
if (contentTpm > aiConfig.maxTpm) {
83-
return this.makeSkippedLargeFileContext(file);
84-
}
68+
return acc;
69+
}, {});
8570

86-
return this.queueService.schedule(async () => {
87-
try {
88-
const result = await this.queueService.schedule(() =>
89-
generateObject<FileContext>({
90-
topP: aiConfig.topP.summary,
91-
model: this.aiService.getModel(aiConfig.model.summary, apiKey),
92-
system: `This is a Context Generator AI system which reads the content of the file and generates the context for the given file.`,
93-
prompt: `create a proper and clean context using these:\nFilename: ${file.name}\nPath: ${file.path}\nContent: ${file.content}`,
94-
schema: z.object({
95-
name: z.string().describe('The name of the file'),
96-
path: z.string().describe('The path of the file'),
97-
context: z.string().describe('The context of the file')
98-
})
99-
})
100-
);
101-
return result.object;
102-
} catch {
103-
// TODO I expect we don't need this line
104-
return this.makeSkippedLargeFileContext(file);
105-
}
106-
});
107-
})
108-
);
71+
return Object.entries(groupedData)
72+
.map(([type, descriptions]) => {
73+
const description = descriptions.map((description) => ` - ${description}`).join('\n');
10974

110-
return filesContexts;
111-
} catch (error) {
112-
throw new SystemError('Failed to generate summary', Severity.ERROR, error);
113-
}
75+
return `- **${type}**\n${description}`;
76+
})
77+
.join('\n');
11478
}
11579

116-
private async generateSummary(
80+
private async generateSummaryObject(
11781
apiKey: string,
11882
config: Config,
119-
summaryContexts: FileContext[],
120-
existingCommentBody: string | undefined
121-
): Promise<string> {
83+
fileContents: FileContent[]
84+
): Promise<SummaryObject> {
85+
const filesContext = JSON.stringify(fileContents);
86+
12287
try {
123-
const filesContext = summaryContexts.map((file) => file.path + ': ' + file.context).join('\n');
12488
const result = await this.queueService.schedule(() =>
125-
generateText({
89+
generateObject<SummaryObject>({
12690
topP: aiConfig.topP.summary,
127-
model: this.aiService.getModel(aiConfig.model.summary, apiKey),
91+
model: this.aiService.getModel(aiConfig.model.summary, apiKey, true),
12892
system: TemplateService.render(summarySystemTemplate, {
12993
tone: toneDetails[config.summary.tone],
13094
language: config.language
13195
}),
132-
prompt: existingCommentBody
133-
? TemplateService.render(summaryUpdatePromptTemplate, {
134-
existingSummary: existingCommentBody,
135-
filesContext
136-
})
137-
: TemplateService.render(summaryCreatePromptTemplate, {
138-
filesContext
139-
})
96+
prompt: TemplateService.render(summaryPromptTemplate, {
97+
tone: toneDetails[config.summary.tone],
98+
language: config.language,
99+
filesContext
100+
}),
101+
schema: summaryObjectSchema
140102
})
141103
);
142104

143-
return result.text;
144-
} catch {
145-
// TODO I expect we should throw an exception here
146-
return 'No summary available';
105+
return result.object;
106+
} catch (error) {
107+
throw new SystemError('Failed to generate summary object', Severity.ERROR, error);
147108
}
148109
}
149-
150-
private makeSkippedLargeFileContext(fileContent: FileContent): FileContext {
151-
const { name, path } = fileContent;
152-
153-
return {
154-
name,
155-
path,
156-
context: "Skipped due to the API's rate limitation."
157-
};
158-
}
159110
}

src/handlers/walkthrough.handler.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { aiConfig, appConfig } from '~configs';
99
import { SystemError } from '~errors';
1010
import { walkthroughObjectSchema } from '~schemas';
1111
import { AiService, CacheService, GitHubService, QueueService, TemplateService } from '~services';
12-
import { walkthroughCommentTemplate, walkthroughCreatePromptTemplate, walkthroughSystemTemplate } from '~templates';
12+
import { walkthroughCommentTemplate, walkthroughPromptTemplate, walkthroughSystemTemplate } from '~templates';
1313
import { WalkthroughSkipConditions } from '~utils';
1414

1515
export class WalkthroughHandler implements Handler<'pull_request'> {
@@ -27,7 +27,7 @@ export class WalkthroughHandler implements Handler<'pull_request'> {
2727
}
2828

2929
const fileContents = await this.queueService.schedule(() => GitHubService.getPullRequestFilesContent(context));
30-
const walkthroughObject = await this.generateWalkthrough(apiKey, config, fileContents);
30+
const walkthroughObject = await this.generateWalkthroughObject(apiKey, config, fileContents);
3131
const existingComment = await this.findExistingComment(context);
3232
const changes = this.generateMarkdown(walkthroughObject.changes);
3333

@@ -69,11 +69,13 @@ export class WalkthroughHandler implements Handler<'pull_request'> {
6969
return header + rows;
7070
}
7171

72-
private async generateWalkthrough(
72+
private async generateWalkthroughObject(
7373
apiKey: string,
7474
config: Config,
7575
fileContents: FileContent[]
7676
): Promise<WalkthroughObject> {
77+
const filesContext = JSON.stringify(fileContents);
78+
7779
try {
7880
const result = await this.queueService.schedule(() =>
7981
generateObject<WalkthroughObject>({
@@ -83,16 +85,16 @@ export class WalkthroughHandler implements Handler<'pull_request'> {
8385
tone: toneDetails[config.walkthrough.tone],
8486
language: config.language
8587
}),
86-
prompt: TemplateService.render(walkthroughCreatePromptTemplate, {
87-
filesContext: JSON.stringify(fileContents)
88+
prompt: TemplateService.render(walkthroughPromptTemplate, {
89+
filesContext
8890
}),
8991
schema: walkthroughObjectSchema
9092
})
9193
);
9294

9395
return result.object;
9496
} catch (error) {
95-
throw new SystemError('Failed to generate walkthrough', Severity.ERROR, error);
97+
throw new SystemError('Failed to generate walkthrough object', Severity.ERROR, error);
9698
}
9799
}
98100
}

src/schemas/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './config.schema';
2+
export * from './summary-object.schema';
23
export * from './walkthrough-object.schema';
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { z } from 'zod';
2+
3+
export const summaryObjectSchema = z.object({
4+
data: z.array(
5+
z.object({
6+
type: z
7+
.string()
8+
.describe(
9+
'The type of change made across the files, such as fix, refactor, feature, chore, test, documentation, etc.'
10+
),
11+
description: z.string().describe('A summary description of the changes made for this type')
12+
})
13+
)
14+
});

src/templates/index.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,8 @@ export { default as walkthroughCommentTemplate } from './comments/walkthrough.co
55
export { default as chatPromptTemplate } from './prompts/chat.prompt.template.md';
66
export { default as diagramPromptTemplate } from './prompts/diagram.prompt.template.md';
77
export { default as reviewPromptTemplate } from './prompts/review.prompt.template.md';
8-
export { default as summaryCreatePromptTemplate } from './prompts/summary-create.prompt.template.md';
9-
export { default as summaryUpdatePromptTemplate } from './prompts/summary-update.prompt.template.md';
10-
export { default as walkthroughCreatePromptTemplate } from './prompts/walkthrough-create.prompt.template.md';
11-
export { default as walkthroughUpdatePromptTemplate } from './prompts/walkthrough-update.prompt.template.md';
8+
export { default as summaryPromptTemplate } from './prompts/summary.prompt.template.md';
9+
export { default as walkthroughPromptTemplate } from './prompts/walkthrough.prompt.template.md';
1210
export { default as chatSystemTemplate } from './systems/chat.system.template.md';
1311
export { default as diagramSystemTemplate } from './systems/diagram.system.template.md';
1412
export { default as reviewSystemTemplate } from './systems/review.system.template.md';

src/templates/prompts/summary-create.prompt.template.md

Lines changed: 0 additions & 3 deletions
This file was deleted.

src/templates/prompts/summary-update.prompt.template.md

Lines changed: 0 additions & 9 deletions
This file was deleted.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Review the following changes across all files: {{filesContext}}. Determine the overall types of changes made (e.g., 'fix', 'refactor', 'feature', etc.), and for each type, provide a brief description of the changes. Organize the response by the change types, with a summary of changes for each type. If a type is not present, exclude it from the response.
2+
Respond in {{language}} with a {{tone}}

0 commit comments

Comments
 (0)