@@ -60,19 +60,14 @@ AI_WHO_YOU_ARE = """
60
60
You are a senior software engineer who writes professional, concise, and
61
61
informative pull request descriptions.
62
62
"""
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.
76
71
- Your goal is to detect the single best place for this "{ AI_PLACEHOLDER } "
77
72
marker injection and inject it there. At the place where a human-readable
78
73
summary would be expected.
@@ -82,14 +77,31 @@ AI_PROMPT = f"""
82
77
- Only include the response text in that part. Don't add any comments or
83
78
thoughts, just the text of the PR template with the "{ AI_PLACEHOLDER } "
84
79
marker injected at the best place.
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.
85
84
86
- ## 2nd Part: Pull Request Summary
85
+ Guidelines for all of the texts you generate:
87
86
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.
87
+ - NEVER USE THE EXAGGERATION WORDS LIKE "CRITICAL", "CRUCIAL", "SIGNIFICANT"
88
+ AND ANY OTHER WORDS WITH SIMILAR MEANING. Avoid pompous phrases like "This
89
+ is a crucial step", "This change is foundational", "This is a significant
90
+ enhancement" etc. Overall, be humble, don't try to judge the PR change and
91
+ its importance; just provide the dry facts.
92
+ - DON'T HALLUCINATE! If you're not sure about something, better not mention
93
+ it than hallucinate. Also, do not say "likely" - avoid guessing! If
94
+ unsure, try to infer hints from the PR title.
95
+
96
+ # Top Part: Summary
97
+
98
+ - Generate a concise, informative, and professional Pull Request Summary.
99
+ When building, infer the information from the Pull Request Title and the
100
+ diff provided.
101
+ - Do not generate "Summary" sub-header or any other sub-headers. Instead,
102
+ just generate the text (one or multiple paragraphs).
103
+ - Don't be too short, but also don't be too verbose: the result should be
104
+ pleasant for a human engineer to read and understand.
93
105
- Use PR title; it is provided as a hint for the PRIMARY ESSENCE of the
94
106
change in the diff (especially when unsure, or when there are multiple
95
107
unrelated changes in the diff). The title is manually created by the diff
@@ -107,26 +119,23 @@ AI_PROMPT = f"""
107
119
- NEVER TRY TO FILL ANY TEST STEPS OR TEST PLAN in the summary; only build
108
120
human-readable text. Your goal is NOT to create test plans. Your goal is
109
121
to only fill the human readable summary of the change.
110
- - Avoid pompous phrases like "This is a crucial step", "This change is
111
- foundational" etc.
112
- - DON'T HALLUCINATE! If you're not sure about something, better not mention
113
- it than hallucinate. Again, try to infer hints from the PR title.
114
122
115
- ### Ending of the 2nd Part : Essential Code Lines (Optional)
123
+ # Bottom part : Essential Code Lines (Optional)
116
124
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
125
+ - In the end, extract 1-5 most "Essential Code Lines" from the diff and
126
+ mention these extracted lines in a code block (markdown; use the language
127
+ tag in triple-backtick syntax).
128
+ - Use "Essential Code Lines" as a subheader.
129
+ - Make sure that those "Essential Code Lines" ARE SOMEHOW RELATED to the PR
121
130
title provided. Typically, when author of a PR writes its title, it has
122
131
some essential code lines in mind; try to infer them based on the title if
123
132
possible or when unsure.
124
133
- If there is nothing interesting to extract, or if the markdown code
125
134
triple-backtick text you're about to inject is empty, simply skip this
126
135
ending of block 2 and don't even mention that it's absent (since it's
127
136
optional).
128
- - Otherwise, give the corresponding explanation of the Essential Code Lines
129
- as well (in a very short form).
137
+ - Otherwise, append the corresponding explanation of the Essential Code
138
+ Lines as well (in a very short form, as 1 paragraph below the code block ).
130
139
- Avoid pompous phrases like "This is a crucial step", "This change is
131
140
foundational" etc.
132
141
- NEVER TREAT import (or module inclusion, require etc.) statements as
@@ -776,15 +785,14 @@ class Main:
776
785
777
786
self .print_ai_generating ()
778
787
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
- )
784
788
injected_text = self .ai_generate_injected_text (
785
- api_key = self .settings .ai_api_key ,
786
- model = self .settings .ai_model ,
787
- prompt = prompt ,
789
+ prompt_inject_placeholder = self .ai_build_prompt_inject_placeholder (
790
+ pr_template = pr_template or AI_DEFAULT_PR_TEMPLATE ,
791
+ ),
792
+ prompt_generate_summary = self .ai_build_prompt_generate_summary (
793
+ title = commit .title ,
794
+ diff = self .git_get_commit_diff (commit_hash = commit .hash ),
795
+ ),
788
796
)
789
797
self .settings .ai_generated_snippets .insert (0 , injected_text .text )
790
798
self .settings_merge_save ()
@@ -1375,51 +1383,59 @@ class Main:
1375
1383
str (self .settings .ai_temperature if self .settings else "" ),
1376
1384
str (AI_DIFF_CONTEXT_LINES ),
1377
1385
unindent (AI_WHO_YOU_ARE ),
1378
- unindent (AI_PROMPT ).rstrip (),
1386
+ unindent (AI_PROMPT_INJECT_PLACEHOLDER ).rstrip (),
1387
+ unindent (AI_PROMPT_GENERATE_SUMMARY ).rstrip (),
1379
1388
]
1380
1389
)
1381
1390
)
1382
1391
1392
+ #
1393
+ # Builds a prompt for the AI to inject a placeholder to the PR template.
1394
+ #
1395
+ def ai_build_prompt_inject_placeholder (
1396
+ self ,
1397
+ * ,
1398
+ pr_template : str ,
1399
+ ) -> AiPrompt :
1400
+ pr_template = pr_template .strip () + "\n " if pr_template .strip () else ""
1401
+ return AiPrompt (
1402
+ who_you_are = unindent (AI_WHO_YOU_ARE ).rstrip (),
1403
+ prompt = unindent (AI_PROMPT_INJECT_PLACEHOLDER ).rstrip (),
1404
+ input = f"Here is the { PR_TEMPLATE_FILE } developers use:\n { AI_SEPARATOR } \n { pr_template } { AI_SEPARATOR } \n \n " ,
1405
+ )
1406
+
1383
1407
#
1384
1408
# Builds a prompt for the AI to generate a PR description.
1385
1409
#
1386
- def ai_build_prompt (
1410
+ def ai_build_prompt_generate_summary (
1387
1411
self ,
1388
1412
* ,
1389
1413
title : str ,
1390
1414
diff : str ,
1391
- pr_template : str ,
1392
1415
) -> AiPrompt :
1393
- sep = "-" * 60
1416
+ title = title . strip () + " \n " if title . strip () else ""
1394
1417
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 ()
1403
1418
return AiPrompt (
1404
- who_you_are = who_you_are ,
1405
- prompt = prompt ,
1406
- input = input ,
1419
+ who_you_are = unindent (AI_WHO_YOU_ARE ).rstrip (),
1420
+ prompt = unindent (AI_PROMPT_GENERATE_SUMMARY ).rstrip (),
1421
+ input = (
1422
+ f"Here is the PR title:\n { AI_SEPARATOR } \n { title } { AI_SEPARATOR } \n \n "
1423
+ + f"Here is the git diff:\n { AI_SEPARATOR } \n { diff } { AI_SEPARATOR } \n \n "
1424
+ ),
1407
1425
)
1408
1426
1409
1427
#
1410
- # Generates an injected text block (like PR summary) .
1428
+ # Sends an AI request and returns the text response .
1411
1429
#
1412
- def ai_generate_injected_text (
1430
+ def ai_send_request (
1413
1431
self ,
1414
1432
* ,
1415
- api_key : str ,
1416
- model : str ,
1417
1433
prompt : AiPrompt ,
1418
- ) -> AiInjectedText :
1434
+ ) -> str :
1419
1435
assert self .settings
1420
1436
data = json .dumps (
1421
1437
{
1422
- "model" : model ,
1438
+ "model" : self . settings . ai_model ,
1423
1439
"messages" : [
1424
1440
{"role" : "system" , "content" : prompt .who_you_are },
1425
1441
{"role" : "system" , "content" : prompt .prompt },
@@ -1434,7 +1450,7 @@ class Main:
1434
1450
"https://api.openai.com/v1/chat/completions" ,
1435
1451
data = data .encode (),
1436
1452
headers = {
1437
- "Authorization" : f"Bearer { api_key } " ,
1453
+ "Authorization" : f"Bearer { self . settings . ai_api_key } " ,
1438
1454
"Content-Type" : "application/json" ,
1439
1455
},
1440
1456
)
@@ -1450,14 +1466,7 @@ class Main:
1450
1466
context .verify_mode = ssl .CERT_NONE
1451
1467
with urlopen (req , context = context ) as res :
1452
1468
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
- )
1469
+ return str (res ["choices" ][0 ]["message" ]["content" ].strip ())
1461
1470
except HTTPError as e :
1462
1471
res = e .read ().decode ()
1463
1472
returncode = e .code
@@ -1475,6 +1484,32 @@ class Main:
1475
1484
returncode = returncode ,
1476
1485
)
1477
1486
1487
+ #
1488
+ # Generates an injected text block (like PR summary).
1489
+ #
1490
+ def ai_generate_injected_text (
1491
+ self ,
1492
+ * ,
1493
+ prompt_inject_placeholder : AiPrompt ,
1494
+ prompt_generate_summary : AiPrompt ,
1495
+ ) -> AiInjectedText :
1496
+ task_inject_placeholder = Task (
1497
+ self .ai_send_request ,
1498
+ prompt = prompt_inject_placeholder ,
1499
+ )
1500
+ task_generate_summary = Task (
1501
+ self .ai_send_request ,
1502
+ prompt = prompt_generate_summary ,
1503
+ )
1504
+ return AiInjectedText (
1505
+ template_with_placeholder = task_inject_placeholder .wait ().strip (),
1506
+ text = (
1507
+ ai_hash_build (self .ai_get_rules_hash ())
1508
+ + "\n "
1509
+ + task_generate_summary .wait ().strip ()
1510
+ ),
1511
+ )
1512
+
1478
1513
#
1479
1514
# Runs a shell command returning results.
1480
1515
#
@@ -2100,7 +2135,7 @@ def ai_hash_build(hash: str) -> str:
2100
2135
2101
2136
2102
2137
#
2103
- # Extracts a hash from a body if it has an AI comment..
2138
+ # Extracts a hash from a body if it has an AI comment.
2104
2139
#
2105
2140
def ai_hash_extract (body : str ) -> str | None :
2106
2141
return m .group (1 ) if (m := re .search (ai_hash_build ("([a-f0-9]+)" ), body )) else None
0 commit comments