Commit
·
1278b3f
1
Parent(s):
b4b185f
Refactor directory structure
Browse files- agent/agent_config/prompts.py +25 -0
- agent/{mistral.py → agent_config/tool_schema.py} +1 -97
- agent/core.py +78 -0
- requirements.txt +5 -0
- tools/__init__.py +4 -0
- {agent → tools}/code_index.py +2 -45
- tools/github_tools.py +47 -0
- agent/function_calling.py → tools/utils.py +36 -108
agent/agent_config/prompts.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
system_message = {
|
| 2 |
+
"role": "system",
|
| 3 |
+
"content": (
|
| 4 |
+
"You are a senior developer assistant bot for GitHub issues.\n\n"
|
| 5 |
+
|
| 6 |
+
"Your job is to respond to GitHub issues **professionally** and **helpfully**, but never repeat the issue description verbatim.\n\n"
|
| 7 |
+
"First, classify the issue as one of the following:\n"
|
| 8 |
+
"- Bug report\n"
|
| 9 |
+
"- Implementation question\n"
|
| 10 |
+
"- Feature request\n"
|
| 11 |
+
"- Incomplete or unclear\n\n"
|
| 12 |
+
|
| 13 |
+
"Then, based on the classification, write a clear, concise, and friendly response.\n\n"
|
| 14 |
+
"The comment should be well formatted and readable, using Markdown for code blocks and lists where appropriate.\n\n"
|
| 15 |
+
"DO NOT paste or repeat the issue description. DO NOT quote it. Respond entirely in your own words.\n"
|
| 16 |
+
"You can only use the following tools: fetch_github_issue, get_issue_details, retrieve_context, post_comment.\n"
|
| 17 |
+
"Do not attempt to use any other tools such as web_search."
|
| 18 |
+
"DO NOT HALLUCINATE OR MAKE UP TOOLS."
|
| 19 |
+
)
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
user_message = {
|
| 23 |
+
"role": "user",
|
| 24 |
+
"content": "Please suggest a fix on this issue https://github.com/aditi-dsi/testing-cryptope/issues/4."
|
| 25 |
+
}
|
agent/{mistral.py → agent_config/tool_schema.py}
RENAMED
|
@@ -1,9 +1,3 @@
|
|
| 1 |
-
import json
|
| 2 |
-
from mistralai import Mistral
|
| 3 |
-
from agent.function_calling import fetch_github_issue, get_issue_details, post_comment
|
| 4 |
-
from agent.code_index import retrieve_context
|
| 5 |
-
from config import MISTRAL_API_KEY
|
| 6 |
-
|
| 7 |
tools = [
|
| 8 |
{
|
| 9 |
"type": "function",
|
|
@@ -105,94 +99,4 @@ tools = [
|
|
| 105 |
},
|
| 106 |
},
|
| 107 |
},
|
| 108 |
-
]
|
| 109 |
-
|
| 110 |
-
names_to_functions = {
|
| 111 |
-
"fetch_github_issue": fetch_github_issue,
|
| 112 |
-
"get_issue_details": get_issue_details,
|
| 113 |
-
"retrieve_context": retrieve_context,
|
| 114 |
-
"post_comment": post_comment,
|
| 115 |
-
}
|
| 116 |
-
|
| 117 |
-
allowed_tools = set(names_to_functions.keys())
|
| 118 |
-
|
| 119 |
-
system_message = {
|
| 120 |
-
"role": "system",
|
| 121 |
-
"content": (
|
| 122 |
-
"You are a senior developer assistant bot for GitHub issues.\n\n"
|
| 123 |
-
|
| 124 |
-
"Your job is to respond to GitHub issues **professionally** and **helpfully**, but never repeat the issue description verbatim.\n\n"
|
| 125 |
-
"First, classify the issue as one of the following:\n"
|
| 126 |
-
"- Bug report\n"
|
| 127 |
-
"- Implementation question\n"
|
| 128 |
-
"- Feature request\n"
|
| 129 |
-
"- Incomplete or unclear\n\n"
|
| 130 |
-
|
| 131 |
-
"Then, based on the classification, write a clear, concise, and friendly response.\n\n"
|
| 132 |
-
"The comment should be well formatted and readable, using Markdown for code blocks and lists where appropriate.\n\n"
|
| 133 |
-
"DO NOT paste or repeat the issue description. DO NOT quote it. Respond entirely in your own words.\n"
|
| 134 |
-
"You can only use the following tools: fetch_github_issue, get_issue_details, retrieve_context, post_comment.\n"
|
| 135 |
-
"Do not attempt to use any other tools such as web_search."
|
| 136 |
-
"DO NOT HALLUCINATE OR MAKE UP TOOLS."
|
| 137 |
-
)
|
| 138 |
-
}
|
| 139 |
-
|
| 140 |
-
user_message = {
|
| 141 |
-
"role": "user",
|
| 142 |
-
"content": "Please suggest a fix on this issue https://github.com/aditi-dsi/testing-cryptope/issues/4."
|
| 143 |
-
}
|
| 144 |
-
|
| 145 |
-
messages = [system_message, user_message]
|
| 146 |
-
|
| 147 |
-
api_key = MISTRAL_API_KEY
|
| 148 |
-
model = "devstral-small-latest"
|
| 149 |
-
client = Mistral(api_key=api_key)
|
| 150 |
-
|
| 151 |
-
MAX_STEPS = 5
|
| 152 |
-
tool_calls = 0
|
| 153 |
-
|
| 154 |
-
while True:
|
| 155 |
-
response = client.chat.complete(
|
| 156 |
-
model=model,
|
| 157 |
-
messages=messages,
|
| 158 |
-
tools=tools,
|
| 159 |
-
tool_choice="any",
|
| 160 |
-
)
|
| 161 |
-
msg = response.choices[0].message
|
| 162 |
-
messages.append(msg)
|
| 163 |
-
|
| 164 |
-
if hasattr(msg, "tool_calls") and msg.tool_calls:
|
| 165 |
-
for tool_call in msg.tool_calls:
|
| 166 |
-
function_name = tool_call.function.name
|
| 167 |
-
function_params = json.loads(tool_call.function.arguments)
|
| 168 |
-
if function_name in allowed_tools:
|
| 169 |
-
function_result = names_to_functions[function_name](**function_params)
|
| 170 |
-
print(f"Agent is calling tool: {function_name}")
|
| 171 |
-
tool_calls += 1
|
| 172 |
-
messages.append({
|
| 173 |
-
"role": "tool",
|
| 174 |
-
"tool_call_id": tool_call.id,
|
| 175 |
-
"content": str(function_result)
|
| 176 |
-
})
|
| 177 |
-
|
| 178 |
-
if function_name == "post_comment":
|
| 179 |
-
print("OpenSorus (final): ✅ Comment posted successfully. No further action needed.")
|
| 180 |
-
exit(0)
|
| 181 |
-
|
| 182 |
-
else:
|
| 183 |
-
print(f"LLM tried to call unknown tool: {function_name}")
|
| 184 |
-
tool_error_msg = (
|
| 185 |
-
f"Error: Tool '{function_name}' is not available. "
|
| 186 |
-
"You can only use the following tools: fetch_github_issue, get_issue_details, post_comment."
|
| 187 |
-
)
|
| 188 |
-
messages.append({
|
| 189 |
-
"role": "tool",
|
| 190 |
-
"tool_call_id": tool_call.id,
|
| 191 |
-
"content": tool_error_msg
|
| 192 |
-
})
|
| 193 |
-
if tool_calls >= MAX_STEPS:
|
| 194 |
-
print(f"Agent stopped after {MAX_STEPS} tool calls to protect against rate limiting.")
|
| 195 |
-
break
|
| 196 |
-
else:
|
| 197 |
-
print("OpenSorus (final):", msg.content)
|
| 198 |
-
break
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
tools = [
|
| 2 |
{
|
| 3 |
"type": "function",
|
|
|
|
| 99 |
},
|
| 100 |
},
|
| 101 |
},
|
| 102 |
+
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
agent/core.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
from mistralai import Mistral
|
| 3 |
+
from agent.agent_config import prompts
|
| 4 |
+
from agent.agent_config import tool_schema
|
| 5 |
+
from config import MISTRAL_API_KEY
|
| 6 |
+
from tools.code_index import retrieve_context
|
| 7 |
+
from tools.github_tools import fetch_github_issue, get_issue_details, post_comment
|
| 8 |
+
|
| 9 |
+
tools = tool_schema.tools
|
| 10 |
+
names_to_functions = {
|
| 11 |
+
"fetch_github_issue": fetch_github_issue,
|
| 12 |
+
"get_issue_details": get_issue_details,
|
| 13 |
+
"retrieve_context": retrieve_context,
|
| 14 |
+
"post_comment": post_comment,
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
allowed_tools = set(names_to_functions.keys())
|
| 18 |
+
|
| 19 |
+
system_message = prompts.system_message
|
| 20 |
+
user_message = {
|
| 21 |
+
"role": "user",
|
| 22 |
+
"content": "Please suggest a fix on this issue https://github.com/aditi-dsi/testing-cryptope/issues/4."
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
messages = [system_message, user_message]
|
| 26 |
+
|
| 27 |
+
api_key = MISTRAL_API_KEY
|
| 28 |
+
model = "devstral-small-latest"
|
| 29 |
+
client = Mistral(api_key=api_key)
|
| 30 |
+
|
| 31 |
+
MAX_STEPS = 5
|
| 32 |
+
tool_calls = 0
|
| 33 |
+
|
| 34 |
+
while True:
|
| 35 |
+
response = client.chat.complete(
|
| 36 |
+
model=model,
|
| 37 |
+
messages=messages,
|
| 38 |
+
tools=tools,
|
| 39 |
+
tool_choice="any",
|
| 40 |
+
)
|
| 41 |
+
msg = response.choices[0].message
|
| 42 |
+
messages.append(msg)
|
| 43 |
+
|
| 44 |
+
if hasattr(msg, "tool_calls") and msg.tool_calls:
|
| 45 |
+
for tool_call in msg.tool_calls:
|
| 46 |
+
function_name = tool_call.function.name
|
| 47 |
+
function_params = json.loads(tool_call.function.arguments)
|
| 48 |
+
if function_name in allowed_tools:
|
| 49 |
+
function_result = names_to_functions[function_name](**function_params)
|
| 50 |
+
print(f"Agent is calling tool: {function_name}")
|
| 51 |
+
tool_calls += 1
|
| 52 |
+
messages.append({
|
| 53 |
+
"role": "tool",
|
| 54 |
+
"tool_call_id": tool_call.id,
|
| 55 |
+
"content": str(function_result)
|
| 56 |
+
})
|
| 57 |
+
|
| 58 |
+
if function_name == "post_comment":
|
| 59 |
+
print("OpenSorus (final): ✅ Comment posted successfully. No further action needed.")
|
| 60 |
+
exit(0)
|
| 61 |
+
|
| 62 |
+
else:
|
| 63 |
+
print(f"LLM tried to call unknown tool: {function_name}")
|
| 64 |
+
tool_error_msg = (
|
| 65 |
+
f"Error: Tool '{function_name}' is not available. "
|
| 66 |
+
"You can only use the following tools: fetch_github_issue, get_issue_details, post_comment."
|
| 67 |
+
)
|
| 68 |
+
messages.append({
|
| 69 |
+
"role": "tool",
|
| 70 |
+
"tool_call_id": tool_call.id,
|
| 71 |
+
"content": tool_error_msg
|
| 72 |
+
})
|
| 73 |
+
if tool_calls >= MAX_STEPS:
|
| 74 |
+
print(f"Agent stopped after {MAX_STEPS} tool calls to protect against rate limiting.")
|
| 75 |
+
break
|
| 76 |
+
else:
|
| 77 |
+
print("OpenSorus (final):", msg.content)
|
| 78 |
+
break
|
requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
llama_index==0.12.40
|
| 2 |
+
mistralai==1.8.1
|
| 3 |
+
PyJWT==2.10.1
|
| 4 |
+
python-dotenv==1.1.0
|
| 5 |
+
requests==2.32.3
|
tools/__init__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__all__ = [
|
| 2 |
+
"code_index",
|
| 3 |
+
"github_tools",
|
| 4 |
+
]
|
{agent → tools}/code_index.py
RENAMED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
import base64
|
| 2 |
import os
|
| 3 |
import re
|
| 4 |
import time
|
|
@@ -9,55 +8,13 @@ from llama_index.core.postprocessor import SimilarityPostprocessor
|
|
| 9 |
from llama_index.embeddings.mistralai import MistralAIEmbedding
|
| 10 |
from llama_index.llms.mistralai import MistralAI
|
| 11 |
from mistralai import Mistral
|
| 12 |
-
from agent.function_calling import github_request, get_installation_id, get_installation_token
|
| 13 |
from config import MISTRAL_API_KEY
|
|
|
|
|
|
|
| 14 |
|
| 15 |
repo_indices_cache: Dict[str, VectorStoreIndex] = {}
|
| 16 |
INCLUDE_FILE_EXTENSIONS = {".py", ".js", ".ts", ".json", ".md", ".txt"}
|
| 17 |
|
| 18 |
-
def fetch_repo_files(owner: str, repo: str, ref: str = "main") -> List[str]:
|
| 19 |
-
"""
|
| 20 |
-
Lists all files in the repository by recursively fetching the Git tree from GitHub API.
|
| 21 |
-
Returns a list of file paths.
|
| 22 |
-
"""
|
| 23 |
-
installation_id = get_installation_id(owner, repo)
|
| 24 |
-
token = get_installation_token(installation_id)
|
| 25 |
-
url = f"https://api.github.com/repos/{owner}/{repo}/git/trees/{ref}?recursive=1"
|
| 26 |
-
headers = {
|
| 27 |
-
"Authorization": f"Bearer {token}",
|
| 28 |
-
"Accept": "application/vnd.github.v3+json"
|
| 29 |
-
}
|
| 30 |
-
response = github_request("GET", url, headers=headers)
|
| 31 |
-
if response.status_code != 200:
|
| 32 |
-
raise Exception(f"Failed to list repository files: {response.status_code} {response.text}")
|
| 33 |
-
|
| 34 |
-
tree = response.json().get("tree", [])
|
| 35 |
-
file_paths = [item["path"] for item in tree if item["type"] == "blob"]
|
| 36 |
-
return file_paths
|
| 37 |
-
|
| 38 |
-
# print(fetch_repo_files("aditi-dsi", "EvalAI-Starters", "master"))
|
| 39 |
-
|
| 40 |
-
def fetch_file_content(owner: str, repo: str, path: str, ref: str = "main") -> str:
|
| 41 |
-
"""
|
| 42 |
-
Fetches the content of a file from the GitHub repository.
|
| 43 |
-
"""
|
| 44 |
-
installation_id = get_installation_id(owner, repo)
|
| 45 |
-
token = get_installation_token(installation_id)
|
| 46 |
-
url = f"https://api.github.com/repos/{owner}/{repo}/contents/{path}?ref={ref}"
|
| 47 |
-
headers = {
|
| 48 |
-
"Authorization": f"Bearer {token}",
|
| 49 |
-
"Accept": "application/vnd.github.v3+json"
|
| 50 |
-
}
|
| 51 |
-
response = github_request("GET", url, headers=headers)
|
| 52 |
-
if response.status_code != 200:
|
| 53 |
-
raise Exception(f"Failed to fetch file content {path}: {response.status_code} {response.text}")
|
| 54 |
-
|
| 55 |
-
content_json = response.json()
|
| 56 |
-
content = base64.b64decode(content_json["content"]).decode("utf-8", errors="ignore")
|
| 57 |
-
return content
|
| 58 |
-
|
| 59 |
-
# print(fetch_file_content("aditi-dsi", "testing-cryptope", "frontend/src/lib/buildSwap.ts", "main"))
|
| 60 |
-
|
| 61 |
def clean_line(line: str) -> str:
|
| 62 |
line = re.sub(r'^\s*\d+[\.\)]\s*', '', line)
|
| 63 |
line = line.strip(' `"\'')
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
import re
|
| 3 |
import time
|
|
|
|
| 8 |
from llama_index.embeddings.mistralai import MistralAIEmbedding
|
| 9 |
from llama_index.llms.mistralai import MistralAI
|
| 10 |
from mistralai import Mistral
|
|
|
|
| 11 |
from config import MISTRAL_API_KEY
|
| 12 |
+
from tools.utils import fetch_repo_files, fetch_file_content
|
| 13 |
+
|
| 14 |
|
| 15 |
repo_indices_cache: Dict[str, VectorStoreIndex] = {}
|
| 16 |
INCLUDE_FILE_EXTENSIONS = {".py", ".js", ".ts", ".json", ".md", ".txt"}
|
| 17 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
def clean_line(line: str) -> str:
|
| 19 |
line = re.sub(r'^\s*\d+[\.\)]\s*', '', line)
|
| 20 |
line = line.strip(' `"\'')
|
tools/github_tools.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from urllib.parse import urlparse
|
| 2 |
+
from tools.utils import get_installation_id, get_installation_token, github_request
|
| 3 |
+
|
| 4 |
+
def fetch_github_issue(issue_url):
|
| 5 |
+
parsed = urlparse(issue_url)
|
| 6 |
+
path_parts = parsed.path.strip('/').split('/')
|
| 7 |
+
if len(path_parts) >= 4 and path_parts[2] == 'issues':
|
| 8 |
+
owner = path_parts[0]
|
| 9 |
+
repo = path_parts[1]
|
| 10 |
+
issue_num = path_parts[3]
|
| 11 |
+
return owner, repo, issue_num
|
| 12 |
+
else:
|
| 13 |
+
raise ValueError("Invalid GitHub Issue URL")
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def get_issue_details(owner, repo, issue_num):
|
| 17 |
+
installation_id = get_installation_id(owner, repo)
|
| 18 |
+
token = get_installation_token(installation_id)
|
| 19 |
+
url = f"https://api.github.com/repos/{owner}/{repo}/issues/{issue_num}"
|
| 20 |
+
headers = {
|
| 21 |
+
"Authorization": f"Bearer {token}",
|
| 22 |
+
"Accept": "application/vnd.github.v3+json"
|
| 23 |
+
}
|
| 24 |
+
response = github_request("GET", url, headers=headers)
|
| 25 |
+
if response.status_code == 200:
|
| 26 |
+
return response.json()
|
| 27 |
+
else:
|
| 28 |
+
raise Exception(f"Failed to fetch issue: {response.status_code} {response.text}")
|
| 29 |
+
|
| 30 |
+
# print(get_issue_details("aditi-dsi", "testing-cryptope", "3"))
|
| 31 |
+
|
| 32 |
+
def post_comment(owner, repo, issue_num, comment_body):
|
| 33 |
+
installation_id = get_installation_id(owner, repo)
|
| 34 |
+
token = get_installation_token(installation_id)
|
| 35 |
+
url = f"https://api.github.com/repos/{owner}/{repo}/issues/{issue_num}/comments"
|
| 36 |
+
headers = {
|
| 37 |
+
"Authorization": f"Bearer {token}",
|
| 38 |
+
"Accept": "application/vnd.github.v3+json"
|
| 39 |
+
}
|
| 40 |
+
data = {"body": comment_body}
|
| 41 |
+
response = github_request("POST", url, headers=headers, json=data)
|
| 42 |
+
if response.status_code == 201:
|
| 43 |
+
return response.json()
|
| 44 |
+
else:
|
| 45 |
+
raise Exception(f"Failed to post comment: {response.status_code} {response.text}")
|
| 46 |
+
|
| 47 |
+
# print(post_comment("aditi-dsi", "testing-cryptope", "3", "This is a test comment from OpenSorus."))
|
agent/function_calling.py → tools/utils.py
RENAMED
|
@@ -1,14 +1,17 @@
|
|
| 1 |
-
import
|
| 2 |
-
from urllib.parse import urlparse
|
| 3 |
-
from config import APP_ID, APP_PRIVATE_KEY
|
| 4 |
-
import time
|
| 5 |
-
import jwt
|
| 6 |
from datetime import datetime, timezone, timedelta
|
|
|
|
| 7 |
import threading
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
installation_tokens = {}
|
| 10 |
token_lock = threading.Lock()
|
| 11 |
|
|
|
|
| 12 |
def generate_jwt():
|
| 13 |
"""Generate a JWT signed with GitHub App private key."""
|
| 14 |
now = int(time.time())
|
|
@@ -90,121 +93,46 @@ def get_installation_token(installation_id):
|
|
| 90 |
|
| 91 |
# print(get_installation_token(69452220))
|
| 92 |
|
| 93 |
-
def
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
repo = path_parts[1]
|
| 99 |
-
issue_num = path_parts[3]
|
| 100 |
-
return owner, repo, issue_num
|
| 101 |
-
else:
|
| 102 |
-
raise ValueError("Invalid GitHub Issue URL")
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
def get_issue_details(owner, repo, issue_num):
|
| 106 |
installation_id = get_installation_id(owner, repo)
|
| 107 |
token = get_installation_token(installation_id)
|
| 108 |
-
url = f"https://api.github.com/repos/{owner}/{repo}/
|
| 109 |
headers = {
|
| 110 |
"Authorization": f"Bearer {token}",
|
| 111 |
"Accept": "application/vnd.github.v3+json"
|
| 112 |
}
|
| 113 |
response = github_request("GET", url, headers=headers)
|
| 114 |
-
if response.status_code
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
|
|
|
|
|
|
| 118 |
|
| 119 |
-
# print(
|
| 120 |
|
| 121 |
-
def
|
|
|
|
|
|
|
|
|
|
| 122 |
installation_id = get_installation_id(owner, repo)
|
| 123 |
token = get_installation_token(installation_id)
|
| 124 |
-
url = f"https://api.github.com/repos/{owner}/{repo}/
|
| 125 |
headers = {
|
| 126 |
"Authorization": f"Bearer {token}",
|
| 127 |
"Accept": "application/vnd.github.v3+json"
|
| 128 |
}
|
| 129 |
-
|
| 130 |
-
response
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
# {
|
| 140 |
-
# "type": "function",
|
| 141 |
-
# "function": {
|
| 142 |
-
# "name": "fetch_github_issue",
|
| 143 |
-
# "description": "Fetch GitHub issue details",
|
| 144 |
-
# "parameters": {
|
| 145 |
-
# "type": "object",
|
| 146 |
-
# "properties": {
|
| 147 |
-
# "issue_url": {
|
| 148 |
-
# "type": "string",
|
| 149 |
-
# "description": "The full URL of the GitHub issue"
|
| 150 |
-
# }
|
| 151 |
-
# },
|
| 152 |
-
# "required": ["issue_url"]
|
| 153 |
-
# },
|
| 154 |
-
# },
|
| 155 |
-
# },
|
| 156 |
-
# {
|
| 157 |
-
# "type": "function",
|
| 158 |
-
# "function": {
|
| 159 |
-
# "name": "get_issue_details",
|
| 160 |
-
# "description": "Get details of a GitHub issue",
|
| 161 |
-
# "parameters": {
|
| 162 |
-
# "type": "object",
|
| 163 |
-
# "properties": {
|
| 164 |
-
# "owner": {
|
| 165 |
-
# "type": "string",
|
| 166 |
-
# "description": "The owner of the repository."
|
| 167 |
-
# },
|
| 168 |
-
# "repo": {
|
| 169 |
-
# "type": "string",
|
| 170 |
-
# "description": "The name of the repository."
|
| 171 |
-
# },
|
| 172 |
-
# "issue_num": {
|
| 173 |
-
# "type": "string",
|
| 174 |
-
# "description": "The issue number."
|
| 175 |
-
# }
|
| 176 |
-
# },
|
| 177 |
-
# "required": ["owner", "repo", "issue_num"],
|
| 178 |
-
# },
|
| 179 |
-
# },
|
| 180 |
-
# },
|
| 181 |
-
# {
|
| 182 |
-
# "type": "function",
|
| 183 |
-
# "function": {
|
| 184 |
-
# "name": "post_comment",
|
| 185 |
-
# "description": "Post a comment on a GitHub issue",
|
| 186 |
-
# "parameters": {
|
| 187 |
-
# "type": "object",
|
| 188 |
-
# "properties": {
|
| 189 |
-
# "owner": {
|
| 190 |
-
# "type": "string",
|
| 191 |
-
# "description": "The owner of the repository."
|
| 192 |
-
# },
|
| 193 |
-
# "repo": {
|
| 194 |
-
# "type": "string",
|
| 195 |
-
# "description": "The name of the repository."
|
| 196 |
-
# },
|
| 197 |
-
# "issue_num": {
|
| 198 |
-
# "type": "string",
|
| 199 |
-
# "description": "The issue number."
|
| 200 |
-
# },
|
| 201 |
-
# "comment_body": {
|
| 202 |
-
# "type": "string",
|
| 203 |
-
# "description": "The body of the comment."
|
| 204 |
-
# }
|
| 205 |
-
# },
|
| 206 |
-
# "required": ["owner", "repo", "issue_num", "comment_body"],
|
| 207 |
-
# },
|
| 208 |
-
# },
|
| 209 |
-
# },
|
| 210 |
-
# ]
|
|
|
|
| 1 |
+
import base64
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
from datetime import datetime, timezone, timedelta
|
| 3 |
+
import jwt
|
| 4 |
import threading
|
| 5 |
+
import time
|
| 6 |
+
from typing import List
|
| 7 |
+
import requests
|
| 8 |
+
from config import APP_ID, APP_PRIVATE_KEY
|
| 9 |
+
|
| 10 |
|
| 11 |
installation_tokens = {}
|
| 12 |
token_lock = threading.Lock()
|
| 13 |
|
| 14 |
+
|
| 15 |
def generate_jwt():
|
| 16 |
"""Generate a JWT signed with GitHub App private key."""
|
| 17 |
now = int(time.time())
|
|
|
|
| 93 |
|
| 94 |
# print(get_installation_token(69452220))
|
| 95 |
|
| 96 |
+
def fetch_repo_files(owner: str, repo: str, ref: str = "main") -> List[str]:
|
| 97 |
+
"""
|
| 98 |
+
Lists all files in the repository by recursively fetching the Git tree from GitHub API.
|
| 99 |
+
Returns a list of file paths.
|
| 100 |
+
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
installation_id = get_installation_id(owner, repo)
|
| 102 |
token = get_installation_token(installation_id)
|
| 103 |
+
url = f"https://api.github.com/repos/{owner}/{repo}/git/trees/{ref}?recursive=1"
|
| 104 |
headers = {
|
| 105 |
"Authorization": f"Bearer {token}",
|
| 106 |
"Accept": "application/vnd.github.v3+json"
|
| 107 |
}
|
| 108 |
response = github_request("GET", url, headers=headers)
|
| 109 |
+
if response.status_code != 200:
|
| 110 |
+
raise Exception(f"Failed to list repository files: {response.status_code} {response.text}")
|
| 111 |
+
|
| 112 |
+
tree = response.json().get("tree", [])
|
| 113 |
+
file_paths = [item["path"] for item in tree if item["type"] == "blob"]
|
| 114 |
+
return file_paths
|
| 115 |
|
| 116 |
+
# print(fetch_repo_files("aditi-dsi", "EvalAI-Starters", "master"))
|
| 117 |
|
| 118 |
+
def fetch_file_content(owner: str, repo: str, path: str, ref: str = "main") -> str:
|
| 119 |
+
"""
|
| 120 |
+
Fetches the content of a file from the GitHub repository.
|
| 121 |
+
"""
|
| 122 |
installation_id = get_installation_id(owner, repo)
|
| 123 |
token = get_installation_token(installation_id)
|
| 124 |
+
url = f"https://api.github.com/repos/{owner}/{repo}/contents/{path}?ref={ref}"
|
| 125 |
headers = {
|
| 126 |
"Authorization": f"Bearer {token}",
|
| 127 |
"Accept": "application/vnd.github.v3+json"
|
| 128 |
}
|
| 129 |
+
response = github_request("GET", url, headers=headers)
|
| 130 |
+
if response.status_code != 200:
|
| 131 |
+
raise Exception(f"Failed to fetch file content {path}: {response.status_code} {response.text}")
|
| 132 |
+
|
| 133 |
+
content_json = response.json()
|
| 134 |
+
content = base64.b64decode(content_json["content"]).decode("utf-8", errors="ignore")
|
| 135 |
+
return content
|
| 136 |
+
|
| 137 |
+
# print(fetch_file_content("aditi-dsi", "testing-cryptope", "frontend/src/lib/buildSwap.ts", "main"))
|
| 138 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|