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:
2025-06-03 13:04:42 -05:00
parent bd0ce3e340
commit f15228fa58
36 changed files with 487 additions and 3847 deletions
+9 -6
View File
@@ -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
View File
@@ -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"
-3
View File
@@ -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'}")
-79
View File
@@ -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)
-128
View File
@@ -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"]
}
]