Compare commits

..

1 Commits

Author SHA1 Message Date
Tal
b9e3e5603b Update setup.py 2024-10-27 17:03:34 +02:00
14 changed files with 37 additions and 92 deletions

View File

@ -3,7 +3,7 @@
You can use the Bitbucket Pipeline system to run Qodo Merge on every pull request open or update. You can use the Bitbucket Pipeline system to run Qodo Merge on every pull request open or update.
1. Add the following file in your repository bitbucket-pipelines.yml 1. Add the following file in your repository bitbucket_pipelines.yml
```yaml ```yaml
pipelines: pipelines:

View File

@ -42,36 +42,21 @@ Note that if your base branches are not protected, don't set the variables as `p
## Run a GitLab webhook server ## Run a GitLab webhook server
1. From the GitLab workspace or group, create an access token with "Reporter" role ("Developer" if using Pro version of the agent) and "api" scope. 1. From the GitLab workspace or group, create an access token. Enable the "api" scope only.
2. Generate a random secret for your app, and save it for later. For example, you can use: 2. Generate a random secret for your app, and save it for later. For example, you can use:
``` ```
WEBHOOK_SECRET=$(python -c "import secrets; print(secrets.token_hex(10))") WEBHOOK_SECRET=$(python -c "import secrets; print(secrets.token_hex(10))")
``` ```
3. Follow the instructions to build the Docker image, setup a secrets file and deploy on your own server from [here](https://qodo-merge-docs.qodo.ai/installation/github/#run-as-a-github-app) steps 4-7.
3. Clone this repository: 4. In the secrets file, fill in the following:
- Your OpenAI key.
- In the [gitlab] section, fill in personal_access_token and shared_secret. The access token can be a personal access token, or a group or project access token.
- Set deployment_type to 'gitlab' in [configuration.toml](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml)
``` 5. Create a webhook in GitLab. Set the URL to ```http[s]://<PR_AGENT_HOSTNAME>/webhook```. Set the secret token to the generated secret from step 2.
git clone https://github.com/Codium-ai/pr-agent.git In the "Trigger" section, check the comments and merge request events boxes.
```
4. Prepare variables and secrets. Skip this step if you plan on settings these as environment variables when running the agent: 6. Test your installation by opening a merge request or commenting or a merge request using one of CodiumAI's commands.
1. In the configuration file/variables:
- Set `deployment_type` to "gitlab"
2. In the secrets file/variables:
- Set your AI model key in the respective section
- In the [gitlab] section, set `personal_access_token` (with token from step 1) and `shared_secret` (with secret from step 2)
5. Build a Docker image for the app and optionally push it to a Docker repository. We'll use Dockerhub as an example:
```
docker build . -t gitlab_pr_agent --target gitlab_webhook -f docker/Dockerfile
docker push codiumai/pr-agent:gitlab_webhook # Push to your Docker repository
```
6. Create a webhook in GitLab. Set the URL to ```http[s]://<PR_AGENT_HOSTNAME>/webhook```, the secret token to the generated secret from step 2, and enable the triggers `push`, `comments` and `merge request events`.
7. Test your installation by opening a merge request or commenting on a merge request using one of CodiumAI's commands.
boxes

View File

@ -133,26 +133,9 @@ Your [application default credentials](https://cloud.google.com/docs/authenticat
If you do want to set explicit credentials, then you can use the `GOOGLE_APPLICATION_CREDENTIALS` environment variable set to a path to a json credentials file. If you do want to set explicit credentials, then you can use the `GOOGLE_APPLICATION_CREDENTIALS` environment variable set to a path to a json credentials file.
### Google AI Studio
To use [Google AI Studio](https://aistudio.google.com/) models, set the relevant models in the configuration section of the configuration file:
```toml
[config] # in configuration.toml
model="google_ai_studio/gemini-1.5-flash"
model_turbo="google_ai_studio/gemini-1.5-flash"
fallback_models=["google_ai_studio/gemini-1.5-flash"]
[google_ai_studio] # in .secrets.toml
gemini_api_key = "..."
```
If you don't want to set the API key in the .secrets.toml file, you can set the `GOOGLE_AI_STUDIO.GEMINI_API_KEY` environment variable.
### Anthropic ### Anthropic
To use Anthropic models, set the relevant models in the configuration section of the configuration file: To use Anthropic models, set the relevant models in the configuration section of the configuration file:
``` ```
[config] [config]
model="anthropic/claude-3-opus-20240229" model="anthropic/claude-3-opus-20240229"

View File

@ -38,8 +38,6 @@ MAX_TOKENS = {
'vertex_ai/gemini-1.5-pro': 1048576, 'vertex_ai/gemini-1.5-pro': 1048576,
'vertex_ai/gemini-1.5-flash': 1048576, 'vertex_ai/gemini-1.5-flash': 1048576,
'vertex_ai/gemma2': 8200, 'vertex_ai/gemma2': 8200,
'gemini/gemini-1.5-pro': 1048576,
'gemini/gemini-1.5-flash': 1048576,
'codechat-bison': 6144, 'codechat-bison': 6144,
'codechat-bison-32k': 32000, 'codechat-bison-32k': 32000,
'anthropic.claude-instant-v1': 100000, 'anthropic.claude-instant-v1': 100000,

View File

@ -83,11 +83,6 @@ class LiteLLMAIHandler(BaseAiHandler):
litellm.vertex_location = get_settings().get( litellm.vertex_location = get_settings().get(
"VERTEXAI.VERTEX_LOCATION", None "VERTEXAI.VERTEX_LOCATION", None
) )
# Google AI Studio
# SEE https://docs.litellm.ai/docs/providers/gemini
if get_settings().get("GOOGLE_AI_STUDIO.GEMINI_API_KEY", None):
os.environ["GEMINI_API_KEY"] = get_settings().google_ai_studio.gemini_api_key
def prepare_logs(self, response, system, user, resp, finish_reason): def prepare_logs(self, response, system, user, resp, finish_reason):
response_log = response.dict().copy() response_log = response.dict().copy()
response_log['system'] = system response_log['system'] = system

View File

@ -1,7 +1,6 @@
from os import environ
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
import openai import openai
from openai import APIError, AsyncOpenAI, RateLimitError, Timeout from openai.error import APIError, RateLimitError, Timeout, TryAgain
from retry import retry from retry import retry
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings
@ -15,7 +14,7 @@ class OpenAIHandler(BaseAiHandler):
# Initialize OpenAIHandler specific attributes here # Initialize OpenAIHandler specific attributes here
try: try:
super().__init__() super().__init__()
environ["OPENAI_API_KEY"] = get_settings().openai.key openai.api_key = get_settings().openai.key
if get_settings().get("OPENAI.ORG", None): if get_settings().get("OPENAI.ORG", None):
openai.organization = get_settings().openai.org openai.organization = get_settings().openai.org
if get_settings().get("OPENAI.API_TYPE", None): if get_settings().get("OPENAI.API_TYPE", None):
@ -25,7 +24,7 @@ class OpenAIHandler(BaseAiHandler):
if get_settings().get("OPENAI.API_VERSION", None): if get_settings().get("OPENAI.API_VERSION", None):
openai.api_version = get_settings().openai.api_version openai.api_version = get_settings().openai.api_version
if get_settings().get("OPENAI.API_BASE", None): if get_settings().get("OPENAI.API_BASE", None):
environ["OPENAI_BASE_URL"] = get_settings().openai.api_base openai.api_base = get_settings().openai.api_base
except AttributeError as e: except AttributeError as e:
raise ValueError("OpenAI key is required") from e raise ValueError("OpenAI key is required") from e
@ -37,7 +36,7 @@ class OpenAIHandler(BaseAiHandler):
""" """
return get_settings().get("OPENAI.DEPLOYMENT_ID", None) return get_settings().get("OPENAI.DEPLOYMENT_ID", None)
@retry(exceptions=(APIError, Timeout, AttributeError, RateLimitError), @retry(exceptions=(APIError, Timeout, TryAgain, AttributeError, RateLimitError),
tries=OPENAI_RETRIES, delay=2, backoff=2, jitter=(1, 3)) tries=OPENAI_RETRIES, delay=2, backoff=2, jitter=(1, 3))
async def chat_completion(self, model: str, system: str, user: str, temperature: float = 0.2): async def chat_completion(self, model: str, system: str, user: str, temperature: float = 0.2):
try: try:
@ -45,19 +44,20 @@ class OpenAIHandler(BaseAiHandler):
get_logger().info("System: ", system) get_logger().info("System: ", system)
get_logger().info("User: ", user) get_logger().info("User: ", user)
messages = [{"role": "system", "content": system}, {"role": "user", "content": user}] messages = [{"role": "system", "content": system}, {"role": "user", "content": user}]
client = AsyncOpenAI()
chat_completion = await client.chat.completions.create( chat_completion = await openai.ChatCompletion.acreate(
model=model, model=model,
deployment_id=deployment_id,
messages=messages, messages=messages,
temperature=temperature, temperature=temperature,
) )
resp = chat_completion.choices[0].message.content resp = chat_completion["choices"][0]['message']['content']
finish_reason = chat_completion.choices[0].finish_reason finish_reason = chat_completion["choices"][0]["finish_reason"]
usage = chat_completion.usage usage = chat_completion.get("usage")
get_logger().info("AI response", response=resp, messages=messages, finish_reason=finish_reason, get_logger().info("AI response", response=resp, messages=messages, finish_reason=finish_reason,
model=model, usage=usage) model=model, usage=usage)
return resp, finish_reason return resp, finish_reason
except (APIError, Timeout) as e: except (APIError, Timeout, TryAgain) as e:
get_logger().error("Error during OpenAI inference: ", e) get_logger().error("Error during OpenAI inference: ", e)
raise raise
except (RateLimitError) as e: except (RateLimitError) as e:
@ -65,4 +65,4 @@ class OpenAIHandler(BaseAiHandler):
raise raise
except (Exception) as e: except (Exception) as e:
get_logger().error("Unknown error during OpenAI inference: ", e) get_logger().error("Unknown error during OpenAI inference: ", e)
raise raise TryAgain from e

View File

@ -43,10 +43,6 @@ class PRReviewHeader(str, Enum):
INCREMENTAL = "## Incremental PR Reviewer Guide" INCREMENTAL = "## Incremental PR Reviewer Guide"
class PRDescriptionHeader(str, Enum):
CHANGES_WALKTHROUGH = "### **Changes walkthrough** 📝"
def get_setting(key: str) -> Any: def get_setting(key: str) -> Any:
try: try:
key = key.upper() key = key.upper()
@ -1028,7 +1024,8 @@ def process_description(description_full: str) -> Tuple[str, List]:
if not description_full: if not description_full:
return "", [] return "", []
description_split = description_full.split(PRDescriptionHeader.CHANGES_WALKTHROUGH.value) split_str = "### **Changes walkthrough** 📝"
description_split = description_full.split(split_str)
base_description_str = description_split[0] base_description_str = description_split[0]
changes_walkthrough_str = "" changes_walkthrough_str = ""
files = [] files = []
@ -1063,9 +1060,6 @@ def process_description(description_full: str) -> Tuple[str, List]:
if not res or res.lastindex != 4: if not res or res.lastindex != 4:
pattern_back = r'<details>\s*<summary><strong>(.*?)</strong><dd><code>(.*?)</code>.*?</summary>\s*<hr>\s*(.*?)\n\n\s*(.*?)</details>' pattern_back = r'<details>\s*<summary><strong>(.*?)</strong><dd><code>(.*?)</code>.*?</summary>\s*<hr>\s*(.*?)\n\n\s*(.*?)</details>'
res = re.search(pattern_back, file_data, re.DOTALL) res = re.search(pattern_back, file_data, re.DOTALL)
if not res or res.lastindex != 4:
pattern_back = r'<details>\s*<summary><strong>(.*?)</strong>\s*<dd><code>(.*?)</code>.*?</summary>\s*<hr>\s*(.*?)\s*-\s*(.*?)\s*</details>' # looking for hypen ('- ')
res = re.search(pattern_back, file_data, re.DOTALL)
if res and res.lastindex == 4: if res and res.lastindex == 4:
short_filename = res.group(1).strip() short_filename = res.group(1).strip()
short_summary = res.group(2).strip() short_summary = res.group(2).strip()

View File

@ -5,7 +5,7 @@ from urllib.parse import urlparse
from ..algo.file_filter import filter_ignored from ..algo.file_filter import filter_ignored
from ..log import get_logger from ..log import get_logger
from ..algo.language_handler import is_valid_file from ..algo.language_handler import is_valid_file
from ..algo.utils import clip_tokens, find_line_number_of_relevant_line_in_file, load_large_diff, PRDescriptionHeader from ..algo.utils import clip_tokens, find_line_number_of_relevant_line_in_file, load_large_diff
from ..config_loader import get_settings from ..config_loader import get_settings
from .git_provider import GitProvider from .git_provider import GitProvider
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
@ -404,7 +404,7 @@ class AzureDevopsProvider(GitProvider):
pr_body = pr_body[:ind] pr_body = pr_body[:ind]
if len(pr_body) > MAX_PR_DESCRIPTION_AZURE_LENGTH: if len(pr_body) > MAX_PR_DESCRIPTION_AZURE_LENGTH:
changes_walkthrough_text = PRDescriptionHeader.CHANGES_WALKTHROUGH.value changes_walkthrough_text = '## **Changes walkthrough**'
ind = pr_body.find(changes_walkthrough_text) ind = pr_body.find(changes_walkthrough_text)
if ind != -1: if ind != -1:
pr_body = pr_body[:ind] pr_body = pr_body[:ind]

View File

@ -43,9 +43,6 @@ api_base = "" # the base url for your local Llama 2, Code Llama, and other model
vertex_project = "" # the google cloud platform project name for your vertexai deployment vertex_project = "" # the google cloud platform project name for your vertexai deployment
vertex_location = "" # the google cloud platform location for your vertexai deployment vertex_location = "" # the google cloud platform location for your vertexai deployment
[google_ai_studio]
gemini_api_key = "" # the google AI Studio API key
[github] [github]
# ---- Set the following only for deployment type == "user" # ---- Set the following only for deployment type == "user"
user_token = "" # A GitHub personal access token with 'repo' scope. user_token = "" # A GitHub personal access token with 'repo' scope.
@ -63,7 +60,6 @@ webhook_secret = "<WEBHOOK SECRET>" # Optional, may be commented out.
[gitlab] [gitlab]
# Gitlab personal access token # Gitlab personal access token
personal_access_token = "" personal_access_token = ""
shared_secret = "" # webhook secret
[bitbucket] [bitbucket]
# For Bitbucket personal/repository bearer token # For Bitbucket personal/repository bearer token

View File

@ -367,18 +367,6 @@ class PRCodeSuggestions:
"code_suggestions_feedback": code_suggestions_feedback[i]}) "code_suggestions_feedback": code_suggestions_feedback[i]})
suggestion["score"] = 7 suggestion["score"] = 7
suggestion["score_why"] = "" suggestion["score_why"] = ""
# if the before and after code is the same, clear one of them
try:
if suggestion['existing_code'] == suggestion['improved_code']:
get_logger().debug(
f"edited improved suggestion {i + 1}, because equal to existing code: {suggestion['existing_code']}")
if get_settings().pr_code_suggestions.commitable_code_suggestions:
suggestion['improved_code'] = "" # we need 'existing_code' to locate the code in the PR
else:
suggestion['existing_code'] = ""
except Exception as e:
get_logger().error(f"Error processing suggestion {i + 1}, error: {e}")
else: else:
# get_logger().error(f"Could not self-reflect on suggestions. using default score 7") # get_logger().error(f"Could not self-reflect on suggestions. using default score 7")
for i, suggestion in enumerate(data["code_suggestions"]): for i, suggestion in enumerate(data["code_suggestions"]):
@ -434,6 +422,13 @@ class PRCodeSuggestions:
continue continue
if ('existing_code' in suggestion) and ('improved_code' in suggestion): if ('existing_code' in suggestion) and ('improved_code' in suggestion):
if suggestion['existing_code'] == suggestion['improved_code']:
get_logger().debug(
f"edited improved suggestion {i + 1}, because equal to existing code: {suggestion['existing_code']}")
if get_settings().pr_code_suggestions.commitable_code_suggestions:
suggestion['improved_code'] = "" # we need 'existing_code' to locate the code in the PR
else:
suggestion['existing_code'] = ""
suggestion = self._truncate_if_needed(suggestion) suggestion = self._truncate_if_needed(suggestion)
one_sentence_summary_list.append(suggestion['one_sentence_summary']) one_sentence_summary_list.append(suggestion['one_sentence_summary'])
suggestion_list.append(suggestion) suggestion_list.append(suggestion)

View File

@ -12,7 +12,7 @@ from pr_agent.algo.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
from pr_agent.algo.pr_processing import get_pr_diff, retry_with_fallback_models, get_pr_diff_multiple_patchs, \ from pr_agent.algo.pr_processing import get_pr_diff, retry_with_fallback_models, get_pr_diff_multiple_patchs, \
OUTPUT_BUFFER_TOKENS_HARD_THRESHOLD OUTPUT_BUFFER_TOKENS_HARD_THRESHOLD
from pr_agent.algo.token_handler import TokenHandler from pr_agent.algo.token_handler import TokenHandler
from pr_agent.algo.utils import set_custom_labels, PRDescriptionHeader from pr_agent.algo.utils import set_custom_labels
from pr_agent.algo.utils import load_yaml, get_user_labels, ModelType, show_relevant_configurations, get_max_tokens, \ from pr_agent.algo.utils import load_yaml, get_user_labels, ModelType, show_relevant_configurations, get_max_tokens, \
clip_tokens clip_tokens
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings
@ -501,7 +501,7 @@ extra_file_yaml =
pr_body += "</details>\n" pr_body += "</details>\n"
elif 'pr_files' in key.lower() and get_settings().pr_description.enable_semantic_files_types: elif 'pr_files' in key.lower() and get_settings().pr_description.enable_semantic_files_types:
changes_walkthrough, pr_file_changes = self.process_pr_files_prediction(changes_walkthrough, value) changes_walkthrough, pr_file_changes = self.process_pr_files_prediction(changes_walkthrough, value)
changes_walkthrough = f"{PRDescriptionHeader.CHANGES_WALKTHROUGH.value}\n{changes_walkthrough}" changes_walkthrough = f"### **Changes walkthrough** 📝\n{changes_walkthrough}"
else: else:
# if the value is a list, join its items by comma # if the value is a list, join its items by comma
if isinstance(value, list): if isinstance(value, list):

View File

@ -4,12 +4,10 @@ atlassian-python-api==3.41.4
azure-devops==7.1.0b3 azure-devops==7.1.0b3
azure-identity==1.15.0 azure-identity==1.15.0
boto3==1.33.6 boto3==1.33.6
certifi==2024.8.30
dynaconf==3.2.4 dynaconf==3.2.4
fastapi==0.111.0 fastapi==0.111.0
GitPython==3.1.41 GitPython==3.1.41
google-cloud-aiplatform==1.38.0 google-cloud-aiplatform==1.38.0
google-generativeai==0.8.3
google-cloud-storage==2.10.0 google-cloud-storage==2.10.0
Jinja2==3.1.2 Jinja2==3.1.2
litellm==1.50.2 litellm==1.50.2

View File

@ -3,3 +3,4 @@
from setuptools import setup from setuptools import setup
setup() setup()
print("aaa")

View File

@ -5,7 +5,7 @@ REVIEW_START_WITH = '## PR Reviewer Guide 🔍\n\n<table>\n<tr><td>⏱️&nbsp;<
IMPROVE_START_WITH_REGEX_PATTERN = r'^## PR Code Suggestions ✨\n\n<!-- [a-z0-9]+ -->\n\n<table><thead><tr><td>Category</td>' IMPROVE_START_WITH_REGEX_PATTERN = r'^## PR Code Suggestions ✨\n\n<!-- [a-z0-9]+ -->\n\n<table><thead><tr><td>Category</td>'
NUM_MINUTES = 5 NUM_MINUTES = 5
print("aaa")
NEW_FILE_CONTENT = """\ NEW_FILE_CONTENT = """\
from pr_agent import cli from pr_agent import cli
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings