Refactored gemini, openai and claude into one file and removed logic from the base class, also made helper class definable from command line
This commit is contained in:
@@ -4,8 +4,7 @@ import zipfile
|
||||
import io
|
||||
import re
|
||||
import logging
|
||||
from .base_tool import BaseTool # Added
|
||||
from .metrics import metrics # Added
|
||||
from .base_tool import BaseTool
|
||||
|
||||
# Configure logging for the tool - This will be handled by the logger instance now
|
||||
# logger = logging.getLogger(__name__) # Commented out or removed
|
||||
@@ -70,7 +69,8 @@ class GitHubCIHelper(BaseTool): # Inherits from BaseTool
|
||||
},
|
||||
"required": ["pull_request_number"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["read"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -85,7 +85,8 @@ class GitHubCIHelper(BaseTool): # Inherits from BaseTool
|
||||
},
|
||||
"required": ["pull_request_number"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["read"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -100,7 +101,8 @@ class GitHubCIHelper(BaseTool): # Inherits from BaseTool
|
||||
},
|
||||
"required": ["run_id"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["read"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -114,7 +116,8 @@ class GitHubCIHelper(BaseTool): # Inherits from BaseTool
|
||||
},
|
||||
"required": ["log_content"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["read"]
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
+72
-74
@@ -1,6 +1,5 @@
|
||||
# tools/github_tool.py
|
||||
from .base_tool import BaseTool
|
||||
from .metrics import metrics
|
||||
import requests
|
||||
import os
|
||||
import base64
|
||||
@@ -57,7 +56,8 @@ class GitHubTool(BaseTool):
|
||||
},
|
||||
"required": ["path"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["read"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -71,7 +71,8 @@ class GitHubTool(BaseTool):
|
||||
},
|
||||
"required": ["path"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["read"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -85,7 +86,8 @@ class GitHubTool(BaseTool):
|
||||
},
|
||||
"required": ["query"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["read"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -100,7 +102,8 @@ class GitHubTool(BaseTool):
|
||||
},
|
||||
"required": ["branch_name"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["write"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -116,7 +119,8 @@ class GitHubTool(BaseTool):
|
||||
},
|
||||
"required": ["file_path", "commit_message", "content"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["write"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -132,7 +136,8 @@ class GitHubTool(BaseTool):
|
||||
},
|
||||
"required": ["title", "body"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["write"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -147,7 +152,8 @@ class GitHubTool(BaseTool):
|
||||
},
|
||||
"required": ["file_path"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["read"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -162,7 +168,8 @@ class GitHubTool(BaseTool):
|
||||
},
|
||||
"required": ["file_path"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["read"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -176,7 +183,8 @@ class GitHubTool(BaseTool):
|
||||
},
|
||||
"required": ["branch"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["read"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -184,7 +192,8 @@ class GitHubTool(BaseTool):
|
||||
"name": "get_current_branch",
|
||||
"description": "Get the name of the current branch",
|
||||
"parameters": { "type": "object", "properties": {} }
|
||||
}
|
||||
},
|
||||
"_tags": ["read"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -198,7 +207,8 @@ class GitHubTool(BaseTool):
|
||||
},
|
||||
"required": ["branch_name"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["read", "write"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -213,7 +223,8 @@ class GitHubTool(BaseTool):
|
||||
},
|
||||
"required": ["file_path", "commit_sha"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["read"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -227,7 +238,8 @@ class GitHubTool(BaseTool):
|
||||
"all_pages": {"type": "boolean", "description": "Whether to fetch all pages of results", "default": True}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["read"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -241,7 +253,8 @@ class GitHubTool(BaseTool):
|
||||
},
|
||||
"required": ["pull_number"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["write"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -255,7 +268,8 @@ class GitHubTool(BaseTool):
|
||||
},
|
||||
"required": ["pull_number"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["write"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -277,7 +291,8 @@ class GitHubTool(BaseTool):
|
||||
},
|
||||
"required": ["pull_number"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["write"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -291,7 +306,8 @@ class GitHubTool(BaseTool):
|
||||
},
|
||||
"required": ["branch_name"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["write"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -305,7 +321,8 @@ class GitHubTool(BaseTool):
|
||||
},
|
||||
"required": ["issue_number"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["read"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -325,7 +342,8 @@ class GitHubTool(BaseTool):
|
||||
},
|
||||
"required": ["title", "body"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["communicate"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -340,7 +358,8 @@ class GitHubTool(BaseTool):
|
||||
"page": {"type": "integer", "default": 1, "description": "Page number of the results to fetch"}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["read"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -355,7 +374,8 @@ class GitHubTool(BaseTool):
|
||||
},
|
||||
"required": ["issue_number", "comment"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["communicate"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -369,7 +389,8 @@ class GitHubTool(BaseTool):
|
||||
},
|
||||
"required": ["issue_number"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["read"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -383,7 +404,8 @@ class GitHubTool(BaseTool):
|
||||
},
|
||||
"required": ["pull_number"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["read"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -398,7 +420,8 @@ class GitHubTool(BaseTool):
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["communicate"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -413,7 +436,8 @@ class GitHubTool(BaseTool):
|
||||
},
|
||||
"required": ["project_id", "column_name"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["communicate"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -428,7 +452,8 @@ class GitHubTool(BaseTool):
|
||||
},
|
||||
"required": ["column_id", "note"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["communicate"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -444,7 +469,8 @@ class GitHubTool(BaseTool):
|
||||
},
|
||||
"required": ["card_id", "position", "column_id"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["communicate"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -460,7 +486,8 @@ class GitHubTool(BaseTool):
|
||||
},
|
||||
"required": ["card_id", "content_id", "content_type"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["communicate"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -468,7 +495,8 @@ class GitHubTool(BaseTool):
|
||||
"name": "list_project_boards",
|
||||
"description": "List project boards associated with the repository",
|
||||
"parameters": { "type": "object", "properties": {} }
|
||||
}
|
||||
},
|
||||
"_tags": ["communicate"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -482,7 +510,8 @@ class GitHubTool(BaseTool):
|
||||
},
|
||||
"required": ["project_id"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["communicate"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -496,7 +525,8 @@ class GitHubTool(BaseTool):
|
||||
},
|
||||
"required": ["pull_number"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["read"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -510,7 +540,8 @@ class GitHubTool(BaseTool):
|
||||
},
|
||||
"required": ["pull_number"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["read"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -524,7 +555,8 @@ class GitHubTool(BaseTool):
|
||||
},
|
||||
"required": ["pull_number"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["read"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -545,7 +577,8 @@ class GitHubTool(BaseTool):
|
||||
},
|
||||
"required": ["pull_number", "body", "commit_id", "path", "position"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["communicate"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -559,7 +592,8 @@ class GitHubTool(BaseTool):
|
||||
},
|
||||
"required": ["pull_number"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["read"]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
@@ -575,11 +609,11 @@ class GitHubTool(BaseTool):
|
||||
},
|
||||
"required": ["pull_number", "event"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["communicate"]
|
||||
}
|
||||
]
|
||||
|
||||
@metrics.measure
|
||||
def execute(self, function_name, **kwargs):
|
||||
self.logger.info(f"Executing GitHub Tool function: {function_name} with args: {kwargs}")
|
||||
# Dispatch to the appropriate private method
|
||||
@@ -598,7 +632,6 @@ class GitHubTool(BaseTool):
|
||||
|
||||
# Private methods for each function, using self.session for HTTP requests
|
||||
|
||||
@metrics.measure
|
||||
def _read_file(self, path):
|
||||
self.logger.info(f"Reading file: {path} from branch: {self.current_branch}")
|
||||
url = f"{self.base_url}/repos/{self._repo}/contents/{path}"
|
||||
@@ -613,7 +646,6 @@ class GitHubTool(BaseTool):
|
||||
self.logger.error(error_message)
|
||||
return error_message
|
||||
|
||||
@metrics.measure
|
||||
def _create_branch(self, branch_name, base_branch="main"):
|
||||
self.logger.info(f"Creating branch: {branch_name} from base: {base_branch}")
|
||||
# Get SHA of base branch
|
||||
@@ -639,7 +671,6 @@ class GitHubTool(BaseTool):
|
||||
self.logger.error(error_message)
|
||||
return error_message
|
||||
|
||||
@metrics.measure
|
||||
def _commit_file(self, file_path, content, commit_message):
|
||||
self.logger.info(f"Committing file: {file_path} to branch: {self.current_branch} with message: '{commit_message}'")
|
||||
if self.current_branch == "main":
|
||||
@@ -679,7 +710,6 @@ class GitHubTool(BaseTool):
|
||||
self.logger.error(error_message)
|
||||
return error_message
|
||||
|
||||
@metrics.measure
|
||||
def _create_pull_request(self, title, body, base="main"):
|
||||
self.logger.info(f"Creating pull request: '{title}' from branch '{self.current_branch}' to '{base}'")
|
||||
if self.current_branch == base:
|
||||
@@ -701,7 +731,6 @@ class GitHubTool(BaseTool):
|
||||
self.logger.error(error_message)
|
||||
return error_message
|
||||
|
||||
@metrics.measure
|
||||
def _get_branch_sha(self, branch):
|
||||
self.logger.info(f"Getting SHA for branch: {branch}")
|
||||
url = f"{self.base_url}/repos/{self._repo}/git/refs/heads/{branch}"
|
||||
@@ -715,7 +744,6 @@ class GitHubTool(BaseTool):
|
||||
self.logger.error(error_message)
|
||||
return error_message
|
||||
|
||||
@metrics.measure
|
||||
def _list_files(self, path):
|
||||
self.logger.info(f"Listing files in path: '{path}' on branch: '{self.current_branch}'")
|
||||
url = f"{self.base_url}/repos/{self._repo}/contents/{path.strip('/')}" # Ensure no leading/trailing slashes for consistency
|
||||
@@ -738,7 +766,6 @@ class GitHubTool(BaseTool):
|
||||
self.logger.error(error_message)
|
||||
return error_message
|
||||
|
||||
@metrics.measure
|
||||
def _search_code(self, query):
|
||||
self.logger.info(f"Searching code with query: '{query}' in repo: '{self._repo}'")
|
||||
url = f"{self.base_url}/search/code"
|
||||
@@ -754,7 +781,6 @@ class GitHubTool(BaseTool):
|
||||
self.logger.error(error_message)
|
||||
return error_message
|
||||
|
||||
@metrics.measure
|
||||
def _get_commit_history(self, file_path, num_commits=10):
|
||||
self.logger.info(f"Getting last {num_commits} commit(s) for file: '{file_path}' on branch '{self.current_branch}'")
|
||||
url = f"{self.base_url}/repos/{self._repo}/commits"
|
||||
@@ -775,18 +801,15 @@ class GitHubTool(BaseTool):
|
||||
self.logger.error(error_message)
|
||||
return error_message
|
||||
|
||||
@metrics.measure
|
||||
def _view_commit_details_for_file(self, file_path, num_commits=10):
|
||||
# This function is essentially the same as get_commit_history based on its description.
|
||||
self.logger.info(f"Viewing commit details for file '{file_path}' (last {num_commits} commits) - using _get_commit_history.")
|
||||
return self._get_commit_history(file_path, num_commits)
|
||||
|
||||
@metrics.measure
|
||||
def _get_current_branch(self):
|
||||
self.logger.info(f"Current branch is: {self.current_branch}")
|
||||
return self.current_branch
|
||||
|
||||
@metrics.measure
|
||||
def _set_current_branch(self, branch_name):
|
||||
self.logger.info(f"Attempting to set current branch to: {branch_name}")
|
||||
# Check if branch exists by trying to get its SHA
|
||||
@@ -801,7 +824,6 @@ class GitHubTool(BaseTool):
|
||||
self.logger.info(success_message)
|
||||
return success_message
|
||||
|
||||
@metrics.measure
|
||||
def _get_file_at_commit(self, file_path, commit_sha):
|
||||
self.logger.info(f"Getting file '{file_path}' at commit SHA: {commit_sha}")
|
||||
url = f"{self.base_url}/repos/{self._repo}/contents/{file_path}"
|
||||
@@ -816,7 +838,6 @@ class GitHubTool(BaseTool):
|
||||
self.logger.error(error_message)
|
||||
return error_message
|
||||
|
||||
@metrics.measure
|
||||
def _list_branches(self, per_page=100, all_pages=True):
|
||||
self.logger.info(f"Listing branches for repo '{self._repo}'. Per_page={per_page}, All_pages={all_pages}")
|
||||
url = f"{self.base_url}/repos/{self._repo}/branches"
|
||||
@@ -844,7 +865,6 @@ class GitHubTool(BaseTool):
|
||||
self.logger.info(f"Successfully listed {len(branches_list)} branches.")
|
||||
return branches_list
|
||||
|
||||
@metrics.measure
|
||||
def _approve_pull_request(self, pull_number):
|
||||
self.logger.info(f"Approving pull request #{pull_number}")
|
||||
url = f"{self.base_url}/repos/{self._repo}/pulls/{pull_number}/reviews"
|
||||
@@ -859,7 +879,6 @@ class GitHubTool(BaseTool):
|
||||
self.logger.error(error_message)
|
||||
return error_message
|
||||
|
||||
@metrics.measure
|
||||
def _close_pull_request(self, pull_number):
|
||||
self.logger.info(f"Closing pull request #{pull_number}")
|
||||
url = f"{self.base_url}/repos/{self._repo}/pulls/{pull_number}"
|
||||
@@ -874,7 +893,6 @@ class GitHubTool(BaseTool):
|
||||
self.logger.error(error_message)
|
||||
return error_message
|
||||
|
||||
@metrics.measure
|
||||
def _merge_pull_request(self, pull_number, commit_title="Merge pull request", commit_message="", merge_method="merge"):
|
||||
self.logger.info(f"Merging pull request #{pull_number} using method '{merge_method}'")
|
||||
url = f"{self.base_url}/repos/{self._repo}/pulls/{pull_number}/merge"
|
||||
@@ -897,7 +915,6 @@ class GitHubTool(BaseTool):
|
||||
self.logger.error(error_message)
|
||||
return error_message
|
||||
|
||||
@metrics.measure
|
||||
def _delete_branch(self, branch_name):
|
||||
self.logger.info(f"Deleting branch: {branch_name}")
|
||||
if branch_name == "main" or (hasattr(self, 'default_branch') and branch_name == self.default_branch) :
|
||||
@@ -920,7 +937,6 @@ class GitHubTool(BaseTool):
|
||||
self.logger.error(error_message)
|
||||
return error_message
|
||||
|
||||
@metrics.measure
|
||||
def _get_issue_details(self, issue_number):
|
||||
self.logger.info(f"Getting details for issue #{issue_number}")
|
||||
url = f"{self.base_url}/repos/{self._repo}/issues/{issue_number}"
|
||||
@@ -933,7 +949,6 @@ class GitHubTool(BaseTool):
|
||||
self.logger.error(error_message)
|
||||
return error_message
|
||||
|
||||
@metrics.measure
|
||||
def _create_issue(self, title, body, labels=None):
|
||||
self.logger.info(f"Creating new issue with title: '{title}'")
|
||||
url = f"{self.base_url}/repos/{self._repo}/issues"
|
||||
@@ -953,7 +968,6 @@ class GitHubTool(BaseTool):
|
||||
self.logger.error(error_message)
|
||||
return error_message
|
||||
|
||||
@metrics.measure
|
||||
def _list_issues(self, state="open", per_page=30, page=1):
|
||||
self.logger.info(f"Listing issues with state: {state}, per_page: {per_page}, page: {page}")
|
||||
url = f"{self.base_url}/repos/{self._repo}/issues"
|
||||
@@ -969,7 +983,6 @@ class GitHubTool(BaseTool):
|
||||
self.logger.error(error_message)
|
||||
return error_message
|
||||
|
||||
@metrics.measure
|
||||
def _add_issue_comment(self, issue_number, comment):
|
||||
self.logger.info(f"Adding comment to issue #{issue_number}: '{comment[:50]}...'")
|
||||
url = f"{self.base_url}/repos/{self._repo}/issues/{issue_number}/comments"
|
||||
@@ -985,7 +998,6 @@ class GitHubTool(BaseTool):
|
||||
self.logger.error(error_message)
|
||||
return error_message
|
||||
|
||||
@metrics.measure
|
||||
def _get_issue_comments(self, issue_number):
|
||||
self.logger.info(f"Getting comments for issue #{issue_number}")
|
||||
url = f"{self.base_url}/repos/{self._repo}/issues/{issue_number}/comments"
|
||||
@@ -1000,14 +1012,12 @@ class GitHubTool(BaseTool):
|
||||
self.logger.error(error_message)
|
||||
return error_message
|
||||
|
||||
@metrics.measure
|
||||
def _get_pull_request_general_comments(self, pull_number):
|
||||
self.logger.info(f"Getting general comments for pull request #{pull_number}")
|
||||
# In GitHub API, PR comments (general, not review comments on lines) are issue comments.
|
||||
# The PR is also an issue, so use the issue comments endpoint.
|
||||
return self._get_issue_comments(issue_number=pull_number)
|
||||
|
||||
@metrics.measure
|
||||
def _create_project_board(self, name, body=None):
|
||||
self.logger.info(f"Creating project board: '{name}'")
|
||||
url = f"{self.base_url}/repos/{self._repo}/projects"
|
||||
@@ -1026,7 +1036,6 @@ class GitHubTool(BaseTool):
|
||||
self.logger.error(error_message)
|
||||
return error_message
|
||||
|
||||
@metrics.measure
|
||||
def _create_project_column(self, project_id, column_name):
|
||||
self.logger.info(f"Creating column '{column_name}' for project ID: {project_id}")
|
||||
url = f"{self.base_url}/projects/{project_id}/columns"
|
||||
@@ -1044,7 +1053,6 @@ class GitHubTool(BaseTool):
|
||||
self.logger.error(error_message)
|
||||
return error_message
|
||||
|
||||
@metrics.measure
|
||||
def _create_project_card(self, column_id, note=None, content_id=None, content_type=None):
|
||||
self.logger.info(f"Creating card in column ID: {column_id}")
|
||||
url = f"{self.base_url}/projects/columns/{column_id}/cards"
|
||||
@@ -1075,7 +1083,6 @@ class GitHubTool(BaseTool):
|
||||
self.logger.error(error_message)
|
||||
return error_message
|
||||
|
||||
@metrics.measure
|
||||
def _move_project_card(self, card_id, position, column_id=None):
|
||||
self.logger.info(f"Moving card ID: {card_id} to position: {position}" + (f" in column ID: {column_id}" if column_id else ""))
|
||||
url = f"{self.base_url}/projects/columns/cards/{card_id}/moves"
|
||||
@@ -1100,7 +1107,6 @@ class GitHubTool(BaseTool):
|
||||
# For updating an existing card to link an issue, one would PATCH the card's content_id/content_type.
|
||||
# Let's assume the function intends to update an existing card if it's a separate function.
|
||||
# However, the provided API spec for `link_issue_to_project_card` uses PATCH on card_id, so let's implement that.
|
||||
@metrics.measure
|
||||
def _link_issue_to_project_card(self, card_id, content_id, content_type):
|
||||
self.logger.info(f"Linking content_id {content_id} (type: {content_type}) to card_id {card_id}")
|
||||
url = f"{self.base_url}/projects/cards/{card_id}" # Note: API docs suggest /projects/columns/cards/{card_id} or /projects/cards/{card_id}
|
||||
@@ -1120,7 +1126,6 @@ class GitHubTool(BaseTool):
|
||||
self.logger.error(error_message)
|
||||
return error_message
|
||||
|
||||
@metrics.measure
|
||||
def _list_project_boards(self):
|
||||
self.logger.info(f"Listing project boards for repo: {self._repo}")
|
||||
url = f"{self.base_url}/repos/{self._repo}/projects"
|
||||
@@ -1136,7 +1141,6 @@ class GitHubTool(BaseTool):
|
||||
self.logger.error(error_message)
|
||||
return error_message
|
||||
|
||||
@metrics.measure
|
||||
def _view_project_board_items(self, project_id):
|
||||
self.logger.info(f"Viewing items for project ID: {project_id}")
|
||||
columns_url = f"{self.base_url}/projects/{project_id}/columns"
|
||||
@@ -1165,7 +1169,6 @@ class GitHubTool(BaseTool):
|
||||
self.logger.info(f"Successfully retrieved items for project ID: {project_id}.")
|
||||
return project_items
|
||||
|
||||
@metrics.measure
|
||||
def _get_pull_request_details(self, pull_number):
|
||||
self.logger.info(f"Getting details for PR #{pull_number}")
|
||||
url = f"{self.base_url}/repos/{self._repo}/pulls/{pull_number}"
|
||||
@@ -1178,7 +1181,6 @@ class GitHubTool(BaseTool):
|
||||
self.logger.error(error_message)
|
||||
return error_message
|
||||
|
||||
@metrics.measure
|
||||
def _get_pull_request_diff(self, pull_number):
|
||||
self.logger.info(f"Getting diff for PR #{pull_number}")
|
||||
url = f"{self.base_url}/repos/{self._repo}/pulls/{pull_number}"
|
||||
@@ -1193,7 +1195,6 @@ class GitHubTool(BaseTool):
|
||||
self.logger.error(error_message)
|
||||
return error_message
|
||||
|
||||
@metrics.measure
|
||||
def _get_pull_request_files(self, pull_number):
|
||||
self.logger.info(f"Getting files for PR #{pull_number}")
|
||||
url = f"{self.base_url}/repos/{self._repo}/pulls/{pull_number}/files"
|
||||
@@ -1206,7 +1207,6 @@ class GitHubTool(BaseTool):
|
||||
self.logger.error(error_message)
|
||||
return error_message
|
||||
|
||||
@metrics.measure
|
||||
def _create_pull_request_review_comment(self, pull_number, body, commit_id, path, position, side="RIGHT", start_line=None, start_side=None):
|
||||
self.logger.info(f"Creating review comment on PR #{pull_number}, file '{path}', position {position}")
|
||||
url = f"{self.base_url}/repos/{self._repo}/pulls/{pull_number}/comments"
|
||||
@@ -1225,7 +1225,6 @@ class GitHubTool(BaseTool):
|
||||
self.logger.error(error_message)
|
||||
return error_message
|
||||
|
||||
@metrics.measure
|
||||
def _list_pull_request_review_comments(self, pull_number):
|
||||
self.logger.info(f"Listing review comments for PR #{pull_number}")
|
||||
url = f"{self.base_url}/repos/{self._repo}/pulls/{pull_number}/comments"
|
||||
@@ -1238,7 +1237,6 @@ class GitHubTool(BaseTool):
|
||||
self.logger.error(error_message)
|
||||
return error_message
|
||||
|
||||
@metrics.measure
|
||||
def _submit_pull_request_review(self, pull_number, event, body=None):
|
||||
self.logger.info(f"Submitting '{event}' review for PR #{pull_number}")
|
||||
url = f"{self.base_url}/repos/{self._repo}/pulls/{pull_number}/reviews"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# tools/log_tool.py
|
||||
from .base_tool import BaseTool
|
||||
from .metrics import metrics
|
||||
import logging
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
@@ -44,7 +43,6 @@ class LogTool(BaseTool):
|
||||
}
|
||||
]
|
||||
|
||||
@metrics.measure
|
||||
def execute(self, function_name, **kwargs):
|
||||
self.logger.info(f"Executing LogTool function: {function_name} with args: {kwargs}")
|
||||
if function_name == "get_log_contents":
|
||||
@@ -55,7 +53,6 @@ class LogTool(BaseTool):
|
||||
self.logger.error(error_message)
|
||||
return error_message
|
||||
|
||||
@metrics.measure
|
||||
def _get_log_contents(self, line_count=None): # Default line_count is None to trigger 24h logic if not specified
|
||||
self.logger.info(f"Attempting to get log contents from: {self.configured_log_file_path}. Line count: {line_count if line_count is not None else 'Last 24 hours'}")
|
||||
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
# tools/metrics.py
|
||||
import cProfile
|
||||
import pstats
|
||||
import io
|
||||
from functools import wraps
|
||||
from collections import defaultdict
|
||||
import logging
|
||||
|
||||
class Metrics:
|
||||
def __init__(self, logger=None):
|
||||
self.call_count = defaultdict(int)
|
||||
self.total_time = defaultdict(float)
|
||||
self.logger = logger if logger else logging.getLogger(__name__)
|
||||
if not self.logger.handlers:
|
||||
self.logger.addHandler(logging.NullHandler())
|
||||
self.logger.debug("Metrics instance initialized.")
|
||||
|
||||
def measure(self, func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
self.call_count[func.__name__] += 1
|
||||
|
||||
pr = cProfile.Profile()
|
||||
pr.enable()
|
||||
result = func(*args, **kwargs)
|
||||
pr.disable()
|
||||
|
||||
ps = pstats.Stats(pr)
|
||||
|
||||
func_code = func.__code__
|
||||
func_key_tuple = (func_code.co_filename, func_code.co_firstlineno, func_code.co_name)
|
||||
|
||||
time_spent_for_func = 0.0
|
||||
if func_key_tuple in ps.stats:
|
||||
time_spent_for_func = ps.stats[func_key_tuple][3] # [3] is cumulative time (ct)
|
||||
else:
|
||||
# Fallback: try to find by function name if exact key fails (e.g. due to decorators changing code object details slightly)
|
||||
# This is less precise and might pick up other functions if names are not unique across files.
|
||||
found_by_name = False
|
||||
for key, stat in ps.stats.items():
|
||||
if key[2] == func.__name__: # key[2] is function name
|
||||
time_spent_for_func = stat[3] # cumulative time
|
||||
self.logger.debug(f"Found stats for {func.__name__} by name {key} after primary key failed.")
|
||||
found_by_name = True
|
||||
break
|
||||
if not found_by_name:
|
||||
self.logger.warning(
|
||||
f"Could not find exact cProfile stats for {func.__name__} with key {func_key_tuple} or by name. "
|
||||
f"Time for this call will be recorded as 0. This might occur for non-Python functions or due to complex decorators."
|
||||
)
|
||||
|
||||
self.total_time[func.__name__] += time_spent_for_func
|
||||
self.logger.debug(f"Measured cumulative time for {func.__name__}: {time_spent_for_func:.6f}s")
|
||||
|
||||
return result
|
||||
return wrapper
|
||||
|
||||
def get_metrics(self):
|
||||
metrics_data = {}
|
||||
for func_name in self.call_count:
|
||||
count = self.call_count[func_name]
|
||||
total_t = self.total_time[func_name]
|
||||
metrics_data[func_name] = {
|
||||
'call_count': count,
|
||||
'total_time': round(total_t, 6),
|
||||
'average_time': round(total_t / count, 6) if count > 0 else 0
|
||||
}
|
||||
return metrics_data
|
||||
|
||||
def clear_metrics(self):
|
||||
self.call_count.clear()
|
||||
self.total_time.clear()
|
||||
self.logger.info("Metrics cleared.")
|
||||
|
||||
# Global instance for convenience
|
||||
_metrics_instance_logger = logging.getLogger(__name__ + ".global_instance")
|
||||
if not _metrics_instance_logger.handlers:
|
||||
_metrics_instance_logger.addHandler(logging.NullHandler())
|
||||
metrics = Metrics(logger=_metrics_instance_logger)
|
||||
@@ -1,128 +0,0 @@
|
||||
# tools/metrics_tool.py
|
||||
from .base_tool import BaseTool
|
||||
from .metrics import metrics as global_metrics_instance # For default and measuring execute
|
||||
from .metrics import Metrics # For type hinting and potentially creating a new one if needed
|
||||
import logging
|
||||
|
||||
class MetricsTool(BaseTool):
|
||||
def __init__(self, metrics_provider: Metrics | None = None, logger: logging.Logger | None = None):
|
||||
self.metrics_provider = metrics_provider if metrics_provider is not None else global_metrics_instance
|
||||
self.logger = logger if logger else logging.getLogger(__name__)
|
||||
if not self.logger.handlers:
|
||||
self.logger.addHandler(logging.NullHandler())
|
||||
self.logger.debug(f"MetricsTool initialized. Using metrics provider: {self.metrics_provider}")
|
||||
|
||||
def clear(self):
|
||||
# This tool itself doesn't hold state that needs clearing beyond what its metrics_provider might do.
|
||||
# If this tool were responsible for clearing the metrics it reports on, it would call:
|
||||
# self.metrics_provider.clear_metrics()
|
||||
self.logger.debug("MetricsTool clear method called. No local state to clear.")
|
||||
pass
|
||||
|
||||
def get_functions(self):
|
||||
return [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_function_metrics",
|
||||
"description": "Get metrics for all measured functions.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_specific_function_metrics",
|
||||
"description": "Get metrics for a specific function.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"function_name": {
|
||||
"type": "string",
|
||||
"description": "Name of the function to get metrics for"
|
||||
}
|
||||
},
|
||||
"required": ["function_name"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_top_n_functions",
|
||||
"description": "Get the top N functions by total execution time.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"n": {
|
||||
"type": "integer",
|
||||
"description": "Number of top functions to retrieve"
|
||||
}
|
||||
},
|
||||
"required": ["n"]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@global_metrics_instance.measure # The execute method can be measured by the global instance
|
||||
def execute(self, function_name, **kwargs):
|
||||
self.logger.info(f"Executing MetricsTool function: {function_name} with args: {kwargs}")
|
||||
if function_name == "get_function_metrics":
|
||||
return self._get_function_metrics()
|
||||
elif function_name == "get_specific_function_metrics":
|
||||
func_name_arg = kwargs.get("function_name")
|
||||
if func_name_arg is None: # Check if None, as empty string could be a valid (though unlikely) func name
|
||||
self.logger.warning("'function_name' argument is missing for get_specific_function_metrics.")
|
||||
return "Error: Missing required argument 'function_name'."
|
||||
return self._get_specific_function_metrics(str(func_name_arg)) # Ensure string
|
||||
elif function_name == "get_top_n_functions":
|
||||
n_arg = kwargs.get("n")
|
||||
if n_arg is None:
|
||||
self.logger.warning("'n' argument is missing for get_top_n_functions.")
|
||||
return "Error: Missing required argument 'n'."
|
||||
try:
|
||||
n_val = int(n_arg)
|
||||
if n_val <= 0:
|
||||
self.logger.warning(f"'n' argument must be a positive integer, got {n_val}.")
|
||||
return "Error: Argument 'n' must be a positive integer."
|
||||
return self._get_top_n_functions(n_val)
|
||||
except ValueError:
|
||||
self.logger.warning(f"'n' argument must be an integer, got '{n_arg}'.")
|
||||
return "Error: Argument 'n' must be an integer."
|
||||
else:
|
||||
error_message = f"Unknown function: {function_name}"
|
||||
self.logger.error(error_message)
|
||||
return error_message
|
||||
|
||||
def _get_function_metrics(self):
|
||||
self.logger.debug("Calling metrics_provider.get_metrics() for all functions.")
|
||||
return self.metrics_provider.get_metrics()
|
||||
|
||||
def _get_specific_function_metrics(self, function_to_get):
|
||||
self.logger.debug(f"Getting metrics for specific function: {function_to_get}")
|
||||
all_metrics = self.metrics_provider.get_metrics()
|
||||
return all_metrics.get(function_to_get, f"No metrics found for function: {function_to_get}")
|
||||
|
||||
def _get_top_n_functions(self, n):
|
||||
self.logger.debug(f"Getting top {n} functions by total execution time.")
|
||||
all_metrics = self.metrics_provider.get_metrics()
|
||||
# Ensure that the items are actual metric dicts before trying to access 'total_time'
|
||||
valid_metrics_items = []
|
||||
for name, metric_values in all_metrics.items():
|
||||
if isinstance(metric_values, dict) and 'total_time' in metric_values:
|
||||
valid_metrics_items.append((name, metric_values))
|
||||
else:
|
||||
self.logger.warning(f"Metric item for '{name}' is not in expected format: {metric_values}")
|
||||
|
||||
# Sort items by total_time. items() gives list of (func_name, metrics_dict)
|
||||
try:
|
||||
sorted_metrics = sorted(valid_metrics_items, key=lambda item: item[1]['total_time'], reverse=True)
|
||||
return dict(sorted_metrics[:n])
|
||||
except TypeError as e:
|
||||
self.logger.error(f"Error sorting metrics, possibly due to unexpected data types: {e}", exc_info=True)
|
||||
return "Error: Could not sort metrics due to unexpected data."
|
||||
@@ -28,7 +28,7 @@ class StandaloneLLMTool(BaseTool):
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "The model to use for generating the detailed instructions. Use mini for most coding tasks, preview when needing sophisticated reasoning",
|
||||
"enum": ["o1-mini", "o1-preview"],
|
||||
"enum": ["mini", "max"],
|
||||
"default": "o1-mini"
|
||||
},
|
||||
"max_tokens": {
|
||||
@@ -38,7 +38,8 @@ class StandaloneLLMTool(BaseTool):
|
||||
},
|
||||
"required": ["prompt"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"_tags": ["llm", "external"]
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user