Skip to content

Commit cf866fb

Browse files
committed
Get rid of multi-part prompt and instead use 2 requests with 2 independent prompts sent in parallel
Pull Request: #41 (main)
1 parent 9275b36 commit cf866fb

File tree

2 files changed

+104
-67
lines changed

2 files changed

+104
-67
lines changed

git-grok

Lines changed: 94 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -60,19 +60,14 @@ AI_WHO_YOU_ARE = """
6060
You are a senior software engineer who writes professional, concise, and
6161
informative pull request descriptions.
6262
"""
63-
AI_PROMPT = f"""
64-
# Response format
65-
66-
The response MUST consist of EXACTLY 2 parts separated by the EXACT
67-
"{AI_SEPARATOR}" marker located on a separate line. Those 2 parts are:
68-
Modified PR Template and Pull Request Summary.
69-
70-
## 1st Part: Modified PR Template
71-
72-
- The 1st part of the response is the exact text of the PR template that I
73-
passed in the input, BUT with "{AI_PLACEHOLDER}" marker injected on a
74-
separate line at the place where it will make sense to insert the Pull
75-
Request Summary later.
63+
AI_PROMPT_INJECT_PLACEHOLDER = f"""
64+
I am passing you the Pull Request Template text. Modify it according to the
65+
instructions below and return the text back to me.
66+
67+
- It must be the exact text of the Pull Request Template that I passed in
68+
the input, BUT with "{AI_PLACEHOLDER}" marker injected on a separate line
69+
at the place where it will make sense for me to later insert the Pull
70+
Request Summary.
7671
- Your goal is to detect the single best place for this "{AI_PLACEHOLDER}"
7772
marker injection and inject it there. At the place where a human-readable
7873
summary would be expected.
@@ -82,14 +77,20 @@ AI_PROMPT = f"""
8277
- Only include the response text in that part. Don't add any comments or
8378
thoughts, just the text of the PR template with the "{AI_PLACEHOLDER}"
8479
marker injected at the best place.
85-
86-
## 2nd Part: Pull Request Summary
87-
88-
- The 2nd part of the response must be a concise, informative, and
89-
professional pull request summary. When building, infer the information
90-
from the PR title and the diff provided. Don't be too short, but also
91-
don't be too verbose: the result should be pleasant for a human engineer
92-
to read and understand.
80+
"""
81+
AI_PROMPT_GENERATE_SUMMARY = f"""
82+
I am passing you the Pull Request Title and the code diff. Generate the Pull
83+
Request Summary according to the following instructions.
84+
85+
# Top Part: Summary
86+
87+
- Generate a concise, informative, and professional Pull Request Summary.
88+
When building, infer the information from the Pull Request Title and the
89+
diff provided.
90+
- Do not generate "Summary" sub-header or any other sub-headers. Instead,
91+
just generate the text (one or multiple paragraphs).
92+
- Don't be too short, but also don't be too verbose: the result should be
93+
pleasant for a human engineer to read and understand.
9394
- Use PR title; it is provided as a hint for the PRIMARY ESSENCE of the
9495
change in the diff (especially when unsure, or when there are multiple
9596
unrelated changes in the diff). The title is manually created by the diff
@@ -112,21 +113,22 @@ AI_PROMPT = f"""
112113
- DON'T HALLUCINATE! If you're not sure about something, better not mention
113114
it than hallucinate. Again, try to infer hints from the PR title.
114115
115-
### Ending of the 2nd Part: Essential Code Lines (Optional)
116+
# Bottom part: Essential Code Lines (Optional)
116117
117-
- In the end of the 2nd part text, extract 1-5 most "essential code lines"
118-
from the diff and mention these extracted lines in a code block (markdown;
119-
use the language tag in triple-backtick syntax).
120-
- Make sure that those "essential code lines" ARE SOMEHOW RELATED to the PR
118+
- In the end, extract 1-5 most "Essential Code Lines" from the diff and
119+
mention these extracted lines in a code block (markdown; use the language
120+
tag in triple-backtick syntax).
121+
- Use "Essential Code Lines" as a subheader.
122+
- Make sure that those "Essential Code Lines" ARE SOMEHOW RELATED to the PR
121123
title provided. Typically, when author of a PR writes its title, it has
122124
some essential code lines in mind; try to infer them based on the title if
123125
possible or when unsure.
124126
- If there is nothing interesting to extract, or if the markdown code
125127
triple-backtick text you're about to inject is empty, simply skip this
126128
ending of block 2 and don't even mention that it's absent (since it's
127129
optional).
128-
- Otherwise, give the corresponding explanation of the Essential Code Lines
129-
as well (in a very short form).
130+
- Otherwise, append the corresponding explanation of the Essential Code
131+
Lines as well (in a very short form, as 1 paragraph below the code block).
130132
- Avoid pompous phrases like "This is a crucial step", "This change is
131133
foundational" etc.
132134
- NEVER TREAT import (or module inclusion, require etc.) statements as
@@ -776,15 +778,14 @@ class Main:
776778

777779
self.print_ai_generating()
778780

779-
prompt = self.ai_build_prompt(
780-
title=commit.title,
781-
diff=self.git_get_commit_diff(commit_hash=commit.hash),
782-
pr_template=pr_template or AI_DEFAULT_PR_TEMPLATE,
783-
)
784781
injected_text = self.ai_generate_injected_text(
785-
api_key=self.settings.ai_api_key,
786-
model=self.settings.ai_model,
787-
prompt=prompt,
782+
prompt_inject_placeholder=self.ai_build_prompt_inject_placeholder(
783+
pr_template=pr_template or AI_DEFAULT_PR_TEMPLATE,
784+
),
785+
prompt_generate_summary=self.ai_build_prompt_generate_summary(
786+
title=commit.title,
787+
diff=self.git_get_commit_diff(commit_hash=commit.hash),
788+
),
788789
)
789790
self.settings.ai_generated_snippets.insert(0, injected_text.text)
790791
self.settings_merge_save()
@@ -1375,51 +1376,59 @@ class Main:
13751376
str(self.settings.ai_temperature if self.settings else ""),
13761377
str(AI_DIFF_CONTEXT_LINES),
13771378
unindent(AI_WHO_YOU_ARE),
1378-
unindent(AI_PROMPT).rstrip(),
1379+
unindent(AI_PROMPT_INJECT_PLACEHOLDER).rstrip(),
1380+
unindent(AI_PROMPT_GENERATE_SUMMARY).rstrip(),
13791381
]
13801382
)
13811383
)
13821384

1385+
#
1386+
# Builds a prompt for the AI to inject a placeholder to the PR template.
1387+
#
1388+
def ai_build_prompt_inject_placeholder(
1389+
self,
1390+
*,
1391+
pr_template: str,
1392+
) -> AiPrompt:
1393+
pr_template = pr_template.strip() + "\n" if pr_template.strip() else ""
1394+
return AiPrompt(
1395+
who_you_are=unindent(AI_WHO_YOU_ARE).rstrip(),
1396+
prompt=unindent(AI_PROMPT_INJECT_PLACEHOLDER).rstrip(),
1397+
input=f"Here is the {PR_TEMPLATE_FILE} developers use:\n{AI_SEPARATOR}\n{pr_template}{AI_SEPARATOR}\n\n",
1398+
)
1399+
13831400
#
13841401
# Builds a prompt for the AI to generate a PR description.
13851402
#
1386-
def ai_build_prompt(
1403+
def ai_build_prompt_generate_summary(
13871404
self,
13881405
*,
13891406
title: str,
13901407
diff: str,
1391-
pr_template: str,
13921408
) -> AiPrompt:
1393-
sep = "-" * 60
1409+
title = title.strip() + "\n" if title.strip() else ""
13941410
diff = diff.strip() + "\n" if diff.strip() else ""
1395-
pr_template = pr_template.strip() + "\n" if pr_template.strip() else ""
1396-
who_you_are = unindent(AI_WHO_YOU_ARE).rstrip()
1397-
prompt = unindent(AI_PROMPT).rstrip()
1398-
input = (
1399-
f"Here is the PR title:\n{sep}\n{title.strip()}{sep}\n\n"
1400-
+ f"Here is the {PR_TEMPLATE_FILE} developers use:\n{sep}\n{pr_template}{sep}\n\n"
1401-
+ f"Here is the git diff:\n{sep}\n{diff}{sep}\n\n"
1402-
).rstrip()
14031411
return AiPrompt(
1404-
who_you_are=who_you_are,
1405-
prompt=prompt,
1406-
input=input,
1412+
who_you_are=unindent(AI_WHO_YOU_ARE).rstrip(),
1413+
prompt=unindent(AI_PROMPT_GENERATE_SUMMARY).rstrip(),
1414+
input=(
1415+
f"Here is the PR title:\n{AI_SEPARATOR}\n{title}{AI_SEPARATOR}\n\n"
1416+
+ f"Here is the git diff:\n{AI_SEPARATOR}\n{diff}{AI_SEPARATOR}\n\n"
1417+
),
14071418
)
14081419

14091420
#
1410-
# Generates an injected text block (like PR summary).
1421+
# Sends an AI request and returns the text response.
14111422
#
1412-
def ai_generate_injected_text(
1423+
def ai_send_request(
14131424
self,
14141425
*,
1415-
api_key: str,
1416-
model: str,
14171426
prompt: AiPrompt,
1418-
) -> AiInjectedText:
1427+
) -> str:
14191428
assert self.settings
14201429
data = json.dumps(
14211430
{
1422-
"model": model,
1431+
"model": self.settings.ai_model,
14231432
"messages": [
14241433
{"role": "system", "content": prompt.who_you_are},
14251434
{"role": "system", "content": prompt.prompt},
@@ -1434,7 +1443,7 @@ class Main:
14341443
"https://api.openai.com/v1/chat/completions",
14351444
data=data.encode(),
14361445
headers={
1437-
"Authorization": f"Bearer {api_key}",
1446+
"Authorization": f"Bearer {self.settings.ai_api_key}",
14381447
"Content-Type": "application/json",
14391448
},
14401449
)
@@ -1450,14 +1459,7 @@ class Main:
14501459
context.verify_mode = ssl.CERT_NONE
14511460
with urlopen(req, context=context) as res:
14521461
res = json.load(res)
1453-
res = str(res["choices"][0]["message"]["content"].strip())
1454-
parts = [part.strip() for part in res.split(AI_SEPARATOR)]
1455-
if len(parts) != 2:
1456-
raise UserException(f"Invalid response from AI:\n[[[\n{res}\n]]]")
1457-
return AiInjectedText(
1458-
template_with_placeholder=parts[0],
1459-
text=ai_hash_build(self.ai_get_rules_hash()) + "\n" + parts[1],
1460-
)
1462+
return str(res["choices"][0]["message"]["content"].strip())
14611463
except HTTPError as e:
14621464
res = e.read().decode()
14631465
returncode = e.code
@@ -1475,6 +1477,32 @@ class Main:
14751477
returncode=returncode,
14761478
)
14771479

1480+
#
1481+
# Generates an injected text block (like PR summary).
1482+
#
1483+
def ai_generate_injected_text(
1484+
self,
1485+
*,
1486+
prompt_inject_placeholder: AiPrompt,
1487+
prompt_generate_summary: AiPrompt,
1488+
) -> AiInjectedText:
1489+
task_inject_placeholder = Task(
1490+
self.ai_send_request,
1491+
prompt=prompt_inject_placeholder,
1492+
)
1493+
task_generate_summary = Task(
1494+
self.ai_send_request,
1495+
prompt=prompt_generate_summary,
1496+
)
1497+
return AiInjectedText(
1498+
template_with_placeholder=task_inject_placeholder.wait().strip(),
1499+
text=(
1500+
ai_hash_build(self.ai_get_rules_hash())
1501+
+ "\n"
1502+
+ task_generate_summary.wait().strip()
1503+
),
1504+
)
1505+
14781506
#
14791507
# Runs a shell command returning results.
14801508
#

tests/helpers.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ def git_init_and_cd_to_test_dir(
175175
tasks = [
176176
GarbageTask(
177177
kind=pr.kind,
178-
task=git_grok.Task(check_output_x, "gh", "pr", "close", pr.name),
178+
task=git_grok.Task(gh_pr_close_if_open, pr.name),
179179
)
180180
for pr in prs
181181
] + [
@@ -197,6 +197,15 @@ def git_init_and_cd_to_test_dir(
197197
# branch has already been deleted by a parallel test run or so).
198198

199199

200+
def gh_pr_close_if_open(name: str):
201+
try:
202+
check_output_x("gh", "pr", "close", name)
203+
except CalledProcessError as e:
204+
if "Could not close the pull request" in e.stderr:
205+
return
206+
raise
207+
208+
200209
def git_touch(file: str, content: str | None = None):
201210
with open(file, "w") as f:
202211
f.write(content or "")

0 commit comments

Comments
 (0)