@@ -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 #
0 commit comments