pr-agent 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. pr_agent/__init__.py +1 -0
  2. pr_agent/agent/__init__.py +0 -0
  3. pr_agent/agent/pr_agent.py +94 -0
  4. pr_agent/algo/__init__.py +31 -0
  5. pr_agent/algo/ai_handlers/base_ai_handler.py +28 -0
  6. pr_agent/algo/ai_handlers/langchain_ai_handler.py +67 -0
  7. pr_agent/algo/ai_handlers/litellm_ai_handler.py +153 -0
  8. pr_agent/algo/ai_handlers/openai_ai_handler.py +67 -0
  9. pr_agent/algo/file_filter.py +36 -0
  10. pr_agent/algo/git_patch_processing.py +303 -0
  11. pr_agent/algo/language_handler.py +67 -0
  12. pr_agent/algo/pr_processing.py +385 -0
  13. pr_agent/algo/token_handler.py +69 -0
  14. pr_agent/algo/types.py +23 -0
  15. pr_agent/algo/utils.py +663 -0
  16. pr_agent/cli.py +73 -0
  17. pr_agent/cli_pip.py +30 -0
  18. pr_agent/config_loader.py +70 -0
  19. pr_agent/git_providers/__init__.py +30 -0
  20. pr_agent/git_providers/azuredevops_provider.py +566 -0
  21. pr_agent/git_providers/bitbucket_provider.py +408 -0
  22. pr_agent/git_providers/bitbucket_server_provider.py +354 -0
  23. pr_agent/git_providers/codecommit_client.py +277 -0
  24. pr_agent/git_providers/codecommit_provider.py +495 -0
  25. pr_agent/git_providers/gerrit_provider.py +399 -0
  26. pr_agent/git_providers/git_provider.py +289 -0
  27. pr_agent/git_providers/github_provider.py +766 -0
  28. pr_agent/git_providers/gitlab_provider.py +484 -0
  29. pr_agent/git_providers/local_git_provider.py +180 -0
  30. pr_agent/git_providers/utils.py +49 -0
  31. pr_agent/identity_providers/__init__.py +13 -0
  32. pr_agent/identity_providers/default_identity_provider.py +9 -0
  33. pr_agent/identity_providers/identity_provider.py +18 -0
  34. pr_agent/log/__init__.py +65 -0
  35. pr_agent/secret_providers/__init__.py +19 -0
  36. pr_agent/secret_providers/google_cloud_storage_secret_provider.py +34 -0
  37. pr_agent/secret_providers/secret_provider.py +12 -0
  38. pr_agent/servers/__init__.py +0 -0
  39. pr_agent/servers/azuredevops_server_webhook.py +139 -0
  40. pr_agent/servers/bitbucket_app.py +210 -0
  41. pr_agent/servers/bitbucket_server_webhook.py +80 -0
  42. pr_agent/servers/gerrit_server.py +77 -0
  43. pr_agent/servers/github_action_runner.py +143 -0
  44. pr_agent/servers/github_app.py +354 -0
  45. pr_agent/servers/github_polling.py +115 -0
  46. pr_agent/servers/gitlab_webhook.py +140 -0
  47. pr_agent/servers/help.py +354 -0
  48. pr_agent/servers/serverless.py +17 -0
  49. pr_agent/servers/utils.py +86 -0
  50. pr_agent/settings/.secrets_template.toml +89 -0
  51. pr_agent/settings/configuration.toml +212 -0
  52. pr_agent/settings/custom_labels.toml +16 -0
  53. pr_agent/settings/ignore.toml +11 -0
  54. pr_agent/settings/language_extensions.toml +438 -0
  55. pr_agent/settings/pr_add_docs.toml +126 -0
  56. pr_agent/settings/pr_code_suggestions_prompts.toml +121 -0
  57. pr_agent/settings/pr_custom_labels.toml +86 -0
  58. pr_agent/settings/pr_description_prompts.toml +130 -0
  59. pr_agent/settings/pr_information_from_user_prompts.toml +53 -0
  60. pr_agent/settings/pr_line_questions_prompts.toml +53 -0
  61. pr_agent/settings/pr_questions_prompts.toml +42 -0
  62. pr_agent/settings/pr_reviewer_prompts.toml +174 -0
  63. pr_agent/settings/pr_sort_code_suggestions_prompts.toml +46 -0
  64. pr_agent/settings/pr_update_changelog_prompts.toml +62 -0
  65. pr_agent/tools/__init__.py +0 -0
  66. pr_agent/tools/pr_add_docs.py +184 -0
  67. pr_agent/tools/pr_code_suggestions.py +464 -0
  68. pr_agent/tools/pr_config.py +47 -0
  69. pr_agent/tools/pr_description.py +507 -0
  70. pr_agent/tools/pr_generate_labels.py +178 -0
  71. pr_agent/tools/pr_help_message.py +99 -0
  72. pr_agent/tools/pr_information_from_user.py +79 -0
  73. pr_agent/tools/pr_line_questions.py +107 -0
  74. pr_agent/tools/pr_questions.py +96 -0
  75. pr_agent/tools/pr_reviewer.py +420 -0
  76. pr_agent/tools/pr_similar_issue.py +486 -0
  77. pr_agent/tools/pr_update_changelog.py +174 -0
  78. pr_agent-0.2.0.dist-info/LICENSE +202 -0
  79. pr_agent-0.2.0.dist-info/METADATA +549 -0
  80. pr_agent-0.2.0.dist-info/RECORD +83 -0
  81. pr_agent-0.2.0.dist-info/WHEEL +5 -0
  82. pr_agent-0.2.0.dist-info/entry_points.txt +2 -0
  83. pr_agent-0.2.0.dist-info/top_level.txt +1 -0
pr_agent/__init__.py ADDED
@@ -0,0 +1 @@
1
+
File without changes
@@ -0,0 +1,94 @@
1
+ import shlex
2
+ from functools import partial
3
+
4
+ from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
5
+ from pr_agent.algo.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
6
+
7
+ from pr_agent.algo.utils import update_settings_from_args
8
+ from pr_agent.config_loader import get_settings
9
+ from pr_agent.git_providers.utils import apply_repo_settings
10
+ from pr_agent.log import get_logger
11
+ from pr_agent.tools.pr_add_docs import PRAddDocs
12
+ from pr_agent.tools.pr_code_suggestions import PRCodeSuggestions
13
+ from pr_agent.tools.pr_config import PRConfig
14
+ from pr_agent.tools.pr_description import PRDescription
15
+ from pr_agent.tools.pr_generate_labels import PRGenerateLabels
16
+ from pr_agent.tools.pr_help_message import PRHelpMessage
17
+ from pr_agent.tools.pr_information_from_user import PRInformationFromUser
18
+ from pr_agent.tools.pr_line_questions import PR_LineQuestions
19
+ from pr_agent.tools.pr_questions import PRQuestions
20
+ from pr_agent.tools.pr_reviewer import PRReviewer
21
+ from pr_agent.tools.pr_similar_issue import PRSimilarIssue
22
+ from pr_agent.tools.pr_update_changelog import PRUpdateChangelog
23
+
24
+ command2class = {
25
+ "auto_review": PRReviewer,
26
+ "answer": PRReviewer,
27
+ "review": PRReviewer,
28
+ "review_pr": PRReviewer,
29
+ "reflect": PRInformationFromUser,
30
+ "reflect_and_review": PRInformationFromUser,
31
+ "describe": PRDescription,
32
+ "describe_pr": PRDescription,
33
+ "improve": PRCodeSuggestions,
34
+ "improve_code": PRCodeSuggestions,
35
+ "ask": PRQuestions,
36
+ "ask_question": PRQuestions,
37
+ "ask_line": PR_LineQuestions,
38
+ "update_changelog": PRUpdateChangelog,
39
+ "config": PRConfig,
40
+ "settings": PRConfig,
41
+ "help": PRHelpMessage,
42
+ "similar_issue": PRSimilarIssue,
43
+ "add_docs": PRAddDocs,
44
+ "generate_labels": PRGenerateLabels,
45
+ }
46
+
47
+ commands = list(command2class.keys())
48
+
49
+ class PRAgent:
50
+ def __init__(self, ai_handler: partial[BaseAiHandler,] = LiteLLMAIHandler):
51
+ self.ai_handler = ai_handler # will be initialized in run_action
52
+ self.forbidden_cli_args = ['enable_auto_approval']
53
+
54
+ async def handle_request(self, pr_url, request, notify=None) -> bool:
55
+ # First, apply repo specific settings if exists
56
+ apply_repo_settings(pr_url)
57
+
58
+ # Then, apply user specific settings if exists
59
+ if isinstance(request, str):
60
+ request = request.replace("'", "\\'")
61
+ lexer = shlex.shlex(request, posix=True)
62
+ lexer.whitespace_split = True
63
+ action, *args = list(lexer)
64
+ else:
65
+ action, *args = request
66
+
67
+ if args:
68
+ for forbidden_arg in self.forbidden_cli_args:
69
+ for arg in args:
70
+ if forbidden_arg in arg:
71
+ get_logger().error(f"CLI argument for param '{forbidden_arg}' is forbidden. Use instead a configuration file.")
72
+ return False
73
+ args = update_settings_from_args(args)
74
+
75
+ action = action.lstrip("/").lower()
76
+ with get_logger().contextualize(command=action):
77
+ get_logger().info("PR-Agent request handler started", analytics=True)
78
+ if action == "reflect_and_review":
79
+ get_settings().pr_reviewer.ask_and_reflect = True
80
+ if action == "answer":
81
+ if notify:
82
+ notify()
83
+ await PRReviewer(pr_url, is_answer=True, args=args, ai_handler=self.ai_handler).run()
84
+ elif action == "auto_review":
85
+ await PRReviewer(pr_url, is_auto=True, args=args, ai_handler=self.ai_handler).run()
86
+ elif action in command2class:
87
+ if notify:
88
+ notify()
89
+
90
+ await command2class[action](pr_url, ai_handler=self.ai_handler, args=args).run()
91
+ else:
92
+ return False
93
+ return True
94
+
@@ -0,0 +1,31 @@
1
+ MAX_TOKENS = {
2
+ 'text-embedding-ada-002': 8000,
3
+ 'gpt-3.5-turbo': 4000,
4
+ 'gpt-3.5-turbo-0613': 4000,
5
+ 'gpt-3.5-turbo-0301': 4000,
6
+ 'gpt-3.5-turbo-16k': 16000,
7
+ 'gpt-3.5-turbo-16k-0613': 16000,
8
+ 'gpt-4': 8000,
9
+ 'gpt-4-0613': 8000,
10
+ 'gpt-4-32k': 32000,
11
+ 'gpt-4-1106-preview': 128000, # 128K, but may be limited by config.max_model_tokens
12
+ 'gpt-4-0125-preview': 128000, # 128K, but may be limited by config.max_model_tokens
13
+ 'claude-instant-1': 100000,
14
+ 'claude-2': 100000,
15
+ 'command-nightly': 4096,
16
+ 'replicate/llama-2-70b-chat:2c1608e18606fad2812020dc541930f2d0495ce32eee50074220b87300bc16e1': 4096,
17
+ 'meta-llama/Llama-2-7b-chat-hf': 4096,
18
+ 'vertex_ai/codechat-bison': 6144,
19
+ 'vertex_ai/codechat-bison-32k': 32000,
20
+ 'codechat-bison': 6144,
21
+ 'codechat-bison-32k': 32000,
22
+ 'anthropic.claude-instant-v1': 100000,
23
+ 'anthropic.claude-v1': 100000,
24
+ 'anthropic.claude-v2': 100000,
25
+ 'anthropic/claude-3-opus-20240229': 100000,
26
+ 'bedrock/anthropic.claude-instant-v1': 100000,
27
+ 'bedrock/anthropic.claude-v2': 100000,
28
+ 'bedrock/anthropic.claude-v2:1': 100000,
29
+ 'bedrock/anthropic.claude-3-sonnet-20240229-v1:0': 100000,
30
+ 'bedrock/anthropic.claude-3-haiku-20240307-v1:0': 100000,
31
+ }
@@ -0,0 +1,28 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ class BaseAiHandler(ABC):
4
+ """
5
+ This class defines the interface for an AI handler to be used by the PR Agents.
6
+ """
7
+
8
+ @abstractmethod
9
+ def __init__(self):
10
+ pass
11
+
12
+ @property
13
+ @abstractmethod
14
+ def deployment_id(self):
15
+ pass
16
+
17
+ @abstractmethod
18
+ async def chat_completion(self, model: str, system: str, user: str, temperature: float = 0.2):
19
+ """
20
+ This method should be implemented to return a chat completion from the AI model.
21
+ Args:
22
+ model (str): the name of the model to use for the chat completion
23
+ system (str): the system message string to use for the chat completion
24
+ user (str): the user message string to use for the chat completion
25
+ temperature (float): the temperature to use for the chat completion
26
+ """
27
+ pass
28
+
@@ -0,0 +1,67 @@
1
+ try:
2
+ from langchain.chat_models import ChatOpenAI, AzureChatOpenAI
3
+ from langchain.schema import SystemMessage, HumanMessage
4
+ except: # we don't enforce langchain as a dependency, so if it's not installed, just move on
5
+ pass
6
+
7
+ from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
8
+ from pr_agent.config_loader import get_settings
9
+ from pr_agent.log import get_logger
10
+
11
+ from openai.error import APIError, RateLimitError, Timeout, TryAgain
12
+ from retry import retry
13
+ import functools
14
+
15
+ OPENAI_RETRIES = 5
16
+
17
+ class LangChainOpenAIHandler(BaseAiHandler):
18
+ def __init__(self):
19
+ # Initialize OpenAIHandler specific attributes here
20
+ super().__init__()
21
+ self.azure = get_settings().get("OPENAI.API_TYPE", "").lower() == "azure"
22
+ try:
23
+ if self.azure:
24
+ # using a partial function so we can set the deployment_id later to support fallback_deployments
25
+ # but still need to access the other settings now so we can raise a proper exception if they're missing
26
+ self._chat = functools.partial(
27
+ lambda **kwargs: AzureChatOpenAI(**kwargs),
28
+ openai_api_key=get_settings().openai.key,
29
+ openai_api_base=get_settings().openai.api_base,
30
+ openai_api_version=get_settings().openai.api_version,
31
+ )
32
+ else:
33
+ self._chat = ChatOpenAI(openai_api_key=get_settings().openai.key)
34
+ except AttributeError as e:
35
+ if getattr(e, "name"):
36
+ raise ValueError(f"OpenAI {e.name} is required") from e
37
+ else:
38
+ raise e
39
+
40
+ @property
41
+ def chat(self):
42
+ if self.azure:
43
+ # we must set the deployment_id only here (instead of the __init__ method) to support fallback_deployments
44
+ return self._chat(deployment_name=self.deployment_id)
45
+ else:
46
+ return self._chat
47
+
48
+ @property
49
+ def deployment_id(self):
50
+ """
51
+ Returns the deployment ID for the OpenAI API.
52
+ """
53
+ return get_settings().get("OPENAI.DEPLOYMENT_ID", None)
54
+ @retry(exceptions=(APIError, Timeout, TryAgain, AttributeError, RateLimitError),
55
+ tries=OPENAI_RETRIES, delay=2, backoff=2, jitter=(1, 3))
56
+ async def chat_completion(self, model: str, system: str, user: str, temperature: float = 0.2):
57
+ try:
58
+ messages=[SystemMessage(content=system), HumanMessage(content=user)]
59
+
60
+ # get a chat completion from the formatted messages
61
+ resp = self.chat(messages, model=model, temperature=temperature)
62
+ finish_reason="completed"
63
+ return resp.content, finish_reason
64
+
65
+ except (Exception) as e:
66
+ get_logger().error("Unknown error during OpenAI inference: ", e)
67
+ raise e
@@ -0,0 +1,153 @@
1
+ import os
2
+
3
+ import boto3
4
+ import litellm
5
+ import openai
6
+ from litellm import acompletion
7
+ from tenacity import retry, retry_if_exception_type, stop_after_attempt
8
+ from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
9
+ from pr_agent.config_loader import get_settings
10
+ from pr_agent.log import get_logger
11
+
12
+ OPENAI_RETRIES = 5
13
+
14
+
15
+ class LiteLLMAIHandler(BaseAiHandler):
16
+ """
17
+ This class handles interactions with the OpenAI API for chat completions.
18
+ It initializes the API key and other settings from a configuration file,
19
+ and provides a method for performing chat completions using the OpenAI ChatCompletion API.
20
+ """
21
+
22
+ def __init__(self):
23
+ """
24
+ Initializes the OpenAI API key and other settings from a configuration file.
25
+ Raises a ValueError if the OpenAI key is missing.
26
+ """
27
+ self.azure = False
28
+ self.aws_bedrock_client = None
29
+ self.api_base = None
30
+ self.repetition_penalty = None
31
+ if get_settings().get("OPENAI.KEY", None):
32
+ openai.api_key = get_settings().openai.key
33
+ litellm.openai_key = get_settings().openai.key
34
+ if get_settings().get("litellm.use_client"):
35
+ litellm_token = get_settings().get("litellm.LITELLM_TOKEN")
36
+ assert litellm_token, "LITELLM_TOKEN is required"
37
+ os.environ["LITELLM_TOKEN"] = litellm_token
38
+ litellm.use_client = True
39
+ if get_settings().get("LITELLM.DROP_PARAMS", None):
40
+ litellm.drop_params = get_settings().litellm.drop_params
41
+ if get_settings().get("OPENAI.ORG", None):
42
+ litellm.organization = get_settings().openai.org
43
+ if get_settings().get("OPENAI.API_TYPE", None):
44
+ if get_settings().openai.api_type == "azure":
45
+ self.azure = True
46
+ litellm.azure_key = get_settings().openai.key
47
+ if get_settings().get("OPENAI.API_VERSION", None):
48
+ litellm.api_version = get_settings().openai.api_version
49
+ if get_settings().get("OPENAI.API_BASE", None):
50
+ litellm.api_base = get_settings().openai.api_base
51
+ if get_settings().get("ANTHROPIC.KEY", None):
52
+ litellm.anthropic_key = get_settings().anthropic.key
53
+ if get_settings().get("COHERE.KEY", None):
54
+ litellm.cohere_key = get_settings().cohere.key
55
+ if get_settings().get("REPLICATE.KEY", None):
56
+ litellm.replicate_key = get_settings().replicate.key
57
+ if get_settings().get("REPLICATE.KEY", None):
58
+ litellm.replicate_key = get_settings().replicate.key
59
+ if get_settings().get("HUGGINGFACE.KEY", None):
60
+ litellm.huggingface_key = get_settings().huggingface.key
61
+ if get_settings().get("HUGGINGFACE.API_BASE", None) and 'huggingface' in get_settings().config.model:
62
+ litellm.api_base = get_settings().huggingface.api_base
63
+ self.api_base = get_settings().huggingface.api_base
64
+ if get_settings().get("HUGGINGFACE.REPITITION_PENALTY", None):
65
+ self.repetition_penalty = float(get_settings().huggingface.repetition_penalty)
66
+ if get_settings().get("VERTEXAI.VERTEX_PROJECT", None):
67
+ litellm.vertex_project = get_settings().vertexai.vertex_project
68
+ litellm.vertex_location = get_settings().get(
69
+ "VERTEXAI.VERTEX_LOCATION", None
70
+ )
71
+ if get_settings().get("AWS.BEDROCK_REGION", None):
72
+ litellm.AmazonAnthropicConfig.max_tokens_to_sample = 2000
73
+ litellm.AmazonAnthropicClaude3Config.max_tokens = 2000
74
+ self.aws_bedrock_client = boto3.client(
75
+ service_name="bedrock-runtime",
76
+ region_name=get_settings().aws.bedrock_region,
77
+ )
78
+
79
+ def prepare_logs(self, response, system, user, resp, finish_reason):
80
+ response_log = response.dict().copy()
81
+ response_log['system'] = system
82
+ response_log['user'] = user
83
+ response_log['output'] = resp
84
+ response_log['finish_reason'] = finish_reason
85
+ if hasattr(self, 'main_pr_language'):
86
+ response_log['main_pr_language'] = self.main_pr_language
87
+ else:
88
+ response_log['main_pr_language'] = 'unknown'
89
+ return response_log
90
+
91
+ @property
92
+ def deployment_id(self):
93
+ """
94
+ Returns the deployment ID for the OpenAI API.
95
+ """
96
+ return get_settings().get("OPENAI.DEPLOYMENT_ID", None)
97
+
98
+ @retry(
99
+ retry=retry_if_exception_type((openai.APIError, openai.APIConnectionError, openai.Timeout)), # No retry on RateLimitError
100
+ stop=stop_after_attempt(OPENAI_RETRIES)
101
+ )
102
+ async def chat_completion(self, model: str, system: str, user: str, temperature: float = 0.2):
103
+ try:
104
+ resp, finish_reason = None, None
105
+ deployment_id = self.deployment_id
106
+ if self.azure:
107
+ model = 'azure/' + model
108
+ messages = [{"role": "system", "content": system}, {"role": "user", "content": user}]
109
+ kwargs = {
110
+ "model": model,
111
+ "deployment_id": deployment_id,
112
+ "messages": messages,
113
+ "temperature": temperature,
114
+ "force_timeout": get_settings().config.ai_timeout,
115
+ "api_base" : self.api_base,
116
+ }
117
+ if self.aws_bedrock_client:
118
+ kwargs["aws_bedrock_client"] = self.aws_bedrock_client
119
+ if self.repetition_penalty:
120
+ kwargs["repetition_penalty"] = self.repetition_penalty
121
+
122
+ get_logger().debug("Prompts", artifact={"system": system, "user": user})
123
+
124
+ if get_settings().config.verbosity_level >= 2:
125
+ get_logger().info(f"\nSystem prompt:\n{system}")
126
+ get_logger().info(f"\nUser prompt:\n{user}")
127
+
128
+ response = await acompletion(**kwargs)
129
+ except (openai.APIError, openai.Timeout) as e:
130
+ get_logger().error("Error during OpenAI inference: ", e)
131
+ raise
132
+ except (openai.RateLimitError) as e:
133
+ get_logger().error("Rate limit error during OpenAI inference: ", e)
134
+ raise
135
+ except (Exception) as e:
136
+ get_logger().error("Unknown error during OpenAI inference: ", e)
137
+ raise openai.APIError from e
138
+ if response is None or len(response["choices"]) == 0:
139
+ raise openai.APIError
140
+ else:
141
+ resp = response["choices"][0]['message']['content']
142
+ finish_reason = response["choices"][0]["finish_reason"]
143
+ get_logger().debug(f"\nAI response:\n{resp}")
144
+
145
+ # log the full response for debugging
146
+ response_log = self.prepare_logs(response, system, user, resp, finish_reason)
147
+ get_logger().debug("Full_response", artifact=response_log)
148
+
149
+ # for CLI debugging
150
+ if get_settings().config.verbosity_level >= 2:
151
+ get_logger().info(f"\nAI response:\n{resp}")
152
+
153
+ return resp, finish_reason
@@ -0,0 +1,67 @@
1
+ from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
2
+ import openai
3
+ from openai.error import APIError, RateLimitError, Timeout, TryAgain
4
+ from retry import retry
5
+
6
+ from pr_agent.config_loader import get_settings
7
+ from pr_agent.log import get_logger
8
+
9
+ OPENAI_RETRIES = 5
10
+
11
+
12
+ class OpenAIHandler(BaseAiHandler):
13
+ def __init__(self):
14
+ # Initialize OpenAIHandler specific attributes here
15
+ try:
16
+ super().__init__()
17
+ openai.api_key = get_settings().openai.key
18
+ if get_settings().get("OPENAI.ORG", None):
19
+ openai.organization = get_settings().openai.org
20
+ if get_settings().get("OPENAI.API_TYPE", None):
21
+ if get_settings().openai.api_type == "azure":
22
+ self.azure = True
23
+ openai.azure_key = get_settings().openai.key
24
+ if get_settings().get("OPENAI.API_VERSION", None):
25
+ openai.api_version = get_settings().openai.api_version
26
+ if get_settings().get("OPENAI.API_BASE", None):
27
+ openai.api_base = get_settings().openai.api_base
28
+
29
+ except AttributeError as e:
30
+ raise ValueError("OpenAI key is required") from e
31
+ @property
32
+ def deployment_id(self):
33
+ """
34
+ Returns the deployment ID for the OpenAI API.
35
+ """
36
+ return get_settings().get("OPENAI.DEPLOYMENT_ID", None)
37
+
38
+ @retry(exceptions=(APIError, Timeout, TryAgain, AttributeError, RateLimitError),
39
+ tries=OPENAI_RETRIES, delay=2, backoff=2, jitter=(1, 3))
40
+ async def chat_completion(self, model: str, system: str, user: str, temperature: float = 0.2):
41
+ try:
42
+ deployment_id = self.deployment_id
43
+ get_logger().info("System: ", system)
44
+ get_logger().info("User: ", user)
45
+ messages = [{"role": "system", "content": system}, {"role": "user", "content": user}]
46
+
47
+ chat_completion = await openai.ChatCompletion.acreate(
48
+ model=model,
49
+ deployment_id=deployment_id,
50
+ messages=messages,
51
+ temperature=temperature,
52
+ )
53
+ resp = chat_completion["choices"][0]['message']['content']
54
+ finish_reason = chat_completion["choices"][0]["finish_reason"]
55
+ usage = chat_completion.get("usage")
56
+ get_logger().info("AI response", response=resp, messages=messages, finish_reason=finish_reason,
57
+ model=model, usage=usage)
58
+ return resp, finish_reason
59
+ except (APIError, Timeout, TryAgain) as e:
60
+ get_logger().error("Error during OpenAI inference: ", e)
61
+ raise
62
+ except (RateLimitError) as e:
63
+ get_logger().error("Rate limit error during OpenAI inference: ", e)
64
+ raise
65
+ except (Exception) as e:
66
+ get_logger().error("Unknown error during OpenAI inference: ", e)
67
+ raise TryAgain from e
@@ -0,0 +1,36 @@
1
+ import fnmatch
2
+ import re
3
+
4
+ from pr_agent.config_loader import get_settings
5
+
6
+ def filter_ignored(files):
7
+ """
8
+ Filter out files that match the ignore patterns.
9
+ """
10
+
11
+ try:
12
+ # load regex patterns, and translate glob patterns to regex
13
+ patterns = get_settings().ignore.regex
14
+ if isinstance(patterns, str):
15
+ patterns = [patterns]
16
+ glob_setting = get_settings().ignore.glob
17
+ if isinstance(glob_setting, str): # --ignore.glob=[.*utils.py], --ignore.glob=.*utils.py
18
+ glob_setting = glob_setting.strip('[]').split(",")
19
+ patterns += [fnmatch.translate(glob) for glob in glob_setting]
20
+
21
+ # compile all valid patterns
22
+ compiled_patterns = []
23
+ for r in patterns:
24
+ try:
25
+ compiled_patterns.append(re.compile(r))
26
+ except re.error:
27
+ pass
28
+
29
+ # keep filenames that _don't_ match the ignore regex
30
+ for r in compiled_patterns:
31
+ files = [f for f in files if (f.filename and not r.match(f.filename))]
32
+
33
+ except Exception as e:
34
+ print(f"Could not filter file list: {e}")
35
+
36
+ return files