feat: ✨ Working tool calls
o new file: llm_interface.py
This commit is contained in:
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
.continue/*
|
.continue/*
|
||||||
.ropeproject/*
|
.ropeproject/*
|
||||||
random_*
|
|
||||||
|
|
||||||
# Python-generated files
|
# Python-generated files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# App config
|
||||||
|
LM_STUDIO_URL = "http://127.0.0.1:1234/v1/chat/completions"
|
||||||
|
EXIT_STRINGS = ['exit','goodbye','go away','fuck off', 'bye']
|
||||||
|
|
||||||
|
# LLM Config
|
||||||
|
SYSTEM_MESSAGE = '''You have the following tools available,
|
||||||
|
if you cant use a tool, you dont need to tell me, just answer normally.
|
||||||
|
if you are using a tool reply only with the exact JSON format shown in examples with NO SPACES and NO OTHER TEXT.
|
||||||
|
|
||||||
|
CRITICAL: When calling tools, use COMPACT JSON with NO SPACES:
|
||||||
|
- Correct: {"tool":"get_weather","parameters":{"city":"New York"}}
|
||||||
|
- Wrong: { "tool": "get_weather", "parameters": { "city": "New York" } }
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "get_weather",
|
||||||
|
"description": "Get current weather for a location",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"input": {"tool":"get_weather","parameters":{"city":"New York"}},
|
||||||
|
"output": {"temperature": 22, "condition": "partly cloudy", "humidity": 65}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": {"tool":"get_weather","parameters":{"city":"London"}},
|
||||||
|
"output": {"temperature": 18, "condition": "rainy", "humidity": 80}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "find_folder",
|
||||||
|
"description": "Find any folder that matches the name provided on your machine or an optional directory",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"input": {"tool":"find_folder","parameters":{"folder_name":"devin"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": {"tool":"find_folder","parameters":{"folder_name":"winutils"}},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}'''
|
||||||
|
|
||||||
|
# Tool config
|
||||||
|
# WMO Weather interpretation codes mapping
|
||||||
|
WEATHER_CODE_MAP = {
|
||||||
|
0: "Clear sky",
|
||||||
|
1: "Mainly clear",
|
||||||
|
2: "Partly cloudy",
|
||||||
|
3: "Overcast",
|
||||||
|
45: "Fog",
|
||||||
|
48: "Depositing rime fog",
|
||||||
|
51: "Light drizzle",
|
||||||
|
53: "Moderate drizzle",
|
||||||
|
55: "Dense drizzle",
|
||||||
|
56: "Light freezing drizzle",
|
||||||
|
57: "Dense freezing drizzle",
|
||||||
|
61: "Slight rain",
|
||||||
|
63: "Moderate rain",
|
||||||
|
65: "Heavy rain",
|
||||||
|
66: "Light freezing rain",
|
||||||
|
67: "Heavy freezing rain",
|
||||||
|
71: "Slight snow",
|
||||||
|
73: "Moderate snow",
|
||||||
|
75: "Heavy snow",
|
||||||
|
77: "Snow grains",
|
||||||
|
80: "Slight rain showers",
|
||||||
|
81: "Moderate rain showers",
|
||||||
|
82: "Violent rain showers",
|
||||||
|
85: "Slight snow showers",
|
||||||
|
86: "Heavy snow showers",
|
||||||
|
95: "Thunderstorm",
|
||||||
|
96: "Thunderstorm with slight hail",
|
||||||
|
99: "Thunderstorm with heavy hail"
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import config
|
||||||
|
from tools import ToolExecutor
|
||||||
|
import requests
|
||||||
|
from rich import print
|
||||||
|
from rich.prompt import Prompt
|
||||||
|
|
||||||
|
#TODO: context squish when it gets big
|
||||||
|
|
||||||
|
class llm():
|
||||||
|
def __init__(self, logger) -> None:
|
||||||
|
self.logger = logger
|
||||||
|
self.first_prompt = 1
|
||||||
|
self.context = {
|
||||||
|
"history": [],
|
||||||
|
"summary": None,
|
||||||
|
"metadata": {
|
||||||
|
"created_at": None,
|
||||||
|
"token_budget": 8000,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def summarise_context(self):
|
||||||
|
# add ai summary & created at time
|
||||||
|
pass
|
||||||
|
|
||||||
|
def add_to_history(self, history_object):
|
||||||
|
self.context['history'].append(history_object)
|
||||||
|
self.logger.debug(self.context['history'])
|
||||||
|
|
||||||
|
def ask_model(self, prompt: str, url=config.LM_STUDIO_URL) -> str:
|
||||||
|
# prompt = config.PRE_PROMPT + prompt
|
||||||
|
payload = {
|
||||||
|
"model": 'qwen/qwen3-coder-30b',
|
||||||
|
"messages": [],
|
||||||
|
"temperature": 0.7,
|
||||||
|
"max_tokens": 2048,
|
||||||
|
}
|
||||||
|
self.add_to_history({"role": "user", "content": prompt})
|
||||||
|
|
||||||
|
payload["messages"] = [
|
||||||
|
{"role": "system", "content": config.SYSTEM_MESSAGE}
|
||||||
|
] + self.context['history']
|
||||||
|
self.logger.debug(f'json payload: {payload}')
|
||||||
|
resp = requests.post(url, json=payload)
|
||||||
|
resp.raise_for_status()
|
||||||
|
self.logger.debug(resp.json())
|
||||||
|
self.add_to_history(resp.json()["choices"][0]["message"])
|
||||||
|
return resp.json()["choices"][0]["message"]["content"].strip()
|
||||||
|
|
||||||
|
def tool_response(self, prompt: str, url=config.LM_STUDIO_URL) -> str:
|
||||||
|
payload = {
|
||||||
|
"model": 'qwen/qwen3-coder-30b',
|
||||||
|
"messages": [],
|
||||||
|
"temperature": 0.7,
|
||||||
|
"max_tokens": 2048,
|
||||||
|
}
|
||||||
|
payload["messages"] = [{"role": "tool", "content": prompt}]
|
||||||
|
resp = requests.post(url, json=payload)
|
||||||
|
resp.raise_for_status()
|
||||||
|
self.logger.debug(resp.json())
|
||||||
|
return resp.json()["choices"][0]["message"]["content"].strip()
|
||||||
|
|
||||||
|
def get_llm_prompt(self):
|
||||||
|
while True:
|
||||||
|
llm_prompt = Prompt.ask("[bold cyan]Ask your local llm[/]")
|
||||||
|
self.logger.debug(f'prompt is: {llm_prompt}')
|
||||||
|
if llm_prompt == '':
|
||||||
|
self.logger.error("Cannot be empty!")
|
||||||
|
return llm_prompt
|
||||||
|
|
||||||
|
def handle_llm_reply(self, llm_reply:str):
|
||||||
|
architect = ToolExecutor(self.logger)
|
||||||
|
return architect.process_llm_response(llm_output=llm_reply)
|
||||||
@@ -1,91 +1,51 @@
|
|||||||
from rich import print
|
from rich import print
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.table import Table
|
|
||||||
from rich.panel import Panel
|
from rich.panel import Panel
|
||||||
from rich.progress import Progress, SpinnerColumn, BarColumn, track
|
|
||||||
from rich.markdown import Markdown
|
from rich.markdown import Markdown
|
||||||
from rich.syntax import Syntax
|
|
||||||
from rich.live import Live
|
|
||||||
from rich.prompt import Prompt
|
|
||||||
import requests
|
|
||||||
import sys
|
|
||||||
from tools import ToolExecutor
|
|
||||||
import random
|
|
||||||
import time
|
|
||||||
|
|
||||||
LM_STUDIO_URL = "http://127.0.0.1:1234/v1/chat/completions"
|
import sys
|
||||||
EXIT_STRINGS = ['exit','goodbye','go away','fuck off']
|
|
||||||
|
import time
|
||||||
|
import config
|
||||||
|
from llm_interface import llm
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
#TODO: add context for llm calls so we have history and can chain messages
|
||||||
|
|
||||||
console = Console()
|
console = Console()
|
||||||
|
|
||||||
PRE_PROMPT = '''You have the following tools available, only use the tools if you need to.
|
class RichConsoleHandler(logging.StreamHandler):
|
||||||
please reply with only the tool call if you need to
|
def emit(self, record):
|
||||||
{
|
if record.levelno >= logging.CRITICAL:
|
||||||
"name": "get_weather",
|
console.print(f"[bold magenta]CRITICAL:[/bold magenta] {record.getMessage()}")
|
||||||
"description": "Get current weather for a location",
|
elif record.levelno >= logging.ERROR:
|
||||||
"examples": [
|
console.print(f"[bold red]ERROR:[/bold red] {record.getMessage()}")
|
||||||
{
|
elif record.levelno >= logging.WARNING:
|
||||||
"input": {"tool": "get_weather", "parameters":{"city":"New York"}},
|
console.print(f"[bold yellow]WARNING:[/bold yellow] {record.getMessage()}")
|
||||||
"output": {"temperature": 22, "condition": "partly cloudy", "humidity": 65}
|
# elif record.levelno >= logging.INFO:
|
||||||
},
|
# console.print(f"[bold blue]INFO:[/bold blue] {record.getMessage()}")
|
||||||
{
|
# elif record.levelno >= logging.DEBUG:
|
||||||
"input": {"tool": "get_weather", "parameters":{"city":"London"}},
|
# console.print(f"[bold green]DEBUG:[/bold green] {record.getMessage()}")
|
||||||
"output": {"temperature": 18, "condition": "rainy", "humidity": 80}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}'''
|
|
||||||
|
|
||||||
def ask_model(prompt: str, url=LM_STUDIO_URL) -> str:
|
|
||||||
payload = {
|
|
||||||
"model": 'qwen/qwen3-coder-30b',
|
|
||||||
"messages": [{"role": "user", "content": PRE_PROMPT+prompt}],
|
|
||||||
"temperature": 0.7,
|
|
||||||
"max_tokens": 2048,
|
|
||||||
}
|
|
||||||
resp = requests.post(url, json=payload)
|
|
||||||
resp.raise_for_status()
|
|
||||||
return resp.json()["choices"][0]["message"]["content"].strip()
|
|
||||||
|
|
||||||
def ask_model_no_pre(prompt: str, url=LM_STUDIO_URL) -> str:
|
def main(logger):
|
||||||
payload = {
|
logger.info('Application Started')
|
||||||
#"model": 'qwen/qwen3-coder-30b',
|
language_model = llm(logger)
|
||||||
"model": 'openai/gpt-oss-20b',
|
|
||||||
"messages": [{"role": "user", "content": prompt}],
|
|
||||||
"temperature": 0.7,
|
|
||||||
"max_tokens": 2048,
|
|
||||||
}
|
|
||||||
resp = requests.post(url, json=payload)
|
|
||||||
resp.raise_for_status()
|
|
||||||
return resp.json()["choices"][0]["message"]["content"].strip()
|
|
||||||
|
|
||||||
def get_llm_prompt():
|
|
||||||
while True:
|
while True:
|
||||||
llm_prompt = Prompt.ask("[bold cyan]Ask your local llm[/]")
|
llm_prompt = language_model.get_llm_prompt()
|
||||||
if llm_prompt.strip():
|
if llm_prompt.lower() in config.EXIT_STRINGS:
|
||||||
return llm_prompt.strip()
|
|
||||||
console.print("[red]Cannot be empty![/]", style="bold")
|
|
||||||
return llm_prompt
|
|
||||||
|
|
||||||
def handle_llm_reply(llm_reply:str):
|
|
||||||
architect = ToolExecutor()
|
|
||||||
return architect.process_llm_response(llm_output=llm_reply)
|
|
||||||
|
|
||||||
def main():
|
|
||||||
while True:
|
|
||||||
llm_prompt = get_llm_prompt()
|
|
||||||
if llm_prompt.lower() in EXIT_STRINGS:
|
|
||||||
sys.exit()
|
sys.exit()
|
||||||
start = time.time()
|
start = time.time()
|
||||||
|
|
||||||
llm_reply = ask_model(llm_prompt)
|
llm_reply = language_model.ask_model(llm_prompt)
|
||||||
print(llm_reply)
|
logger.info(f"LLM Reply: {llm_reply}")
|
||||||
handled_response = handle_llm_reply(llm_reply)
|
handled_response = language_model.handle_llm_reply(llm_reply)
|
||||||
print(handled_response)
|
logger.info(f"Handled Response: {handled_response}")
|
||||||
print(handled_response == llm_reply)
|
logger.info(f"Response equals original: {handled_response == llm_reply}")
|
||||||
|
|
||||||
if handled_response != llm_reply:
|
if handled_response != llm_reply:
|
||||||
output = ask_model_no_pre(prompt=f'Make this lovely markdown, use fun emojis {handled_response}')
|
output = language_model.tool_response(prompt=f'Make this lovely markdown, use fun emojis {handled_response}')
|
||||||
|
|
||||||
elif handled_response == llm_reply:
|
elif handled_response == llm_reply:
|
||||||
output = handled_response
|
output = handled_response
|
||||||
|
|
||||||
@@ -99,5 +59,21 @@ def main():
|
|||||||
console.print(panel_narrow)
|
console.print(panel_narrow)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
# Setup logging with custom handler for warnings and errors
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.DEBUG,
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||||
|
handlers=[
|
||||||
|
logging.FileHandler('app.log')
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
logger.addHandler(RichConsoleHandler())
|
||||||
|
logger.info("Logging Instantiated")
|
||||||
|
|
||||||
|
main(logger)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
# def ask_name() -> str:
|
|
||||||
# """Ask for a name and keep asking until it's not empty."""
|
|
||||||
# while True:
|
|
||||||
# name = Prompt.ask("[bold cyan]What is your name?[/]")
|
|
||||||
# if name.strip():
|
|
||||||
# return name.strip()
|
|
||||||
# console.print("[red]Name cannot be empty![/]", style="bold")
|
|
||||||
|
|
||||||
# def ask_age() -> int:
|
|
||||||
# """Ask for age and validate it's a positive integer."""
|
|
||||||
# while True:
|
|
||||||
# age_str = Prompt.ask("[bold cyan]How old are you?[/]")
|
|
||||||
# try:
|
|
||||||
# age = int(age_str)
|
|
||||||
# if age <= 0:
|
|
||||||
# raise ValueError
|
|
||||||
# return age
|
|
||||||
# except ValueError:
|
|
||||||
# console.print("[red]Please enter a valid positive integer.[/]", style="bold")
|
|
||||||
|
|
||||||
pre_prompt = '''{
|
|
||||||
"name": "get_weather",
|
|
||||||
"description": "Get current weather for a location",
|
|
||||||
"parameters": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"city": {"type": "string", "description": "City name"},
|
|
||||||
"units": {"type": "string", "enum": ["celsius", "fahrenheit"], "description": "Temperature units"}
|
|
||||||
},
|
|
||||||
"required": ["city"]
|
|
||||||
},
|
|
||||||
"examples": [
|
|
||||||
{
|
|
||||||
"input": {"city": "New York", "units": "celsius"},
|
|
||||||
"output": {"temperature": 22, "condition": "partly cloudy", "humidity": 65}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"input": {"city": "London"},
|
|
||||||
"output": {"temperature": 18, "condition": "rainy", "humidity": 80}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}'''
|
|
||||||
|
|
||||||
# def get_weather(city, units="celsius"):
|
|
||||||
# # Your logic here (API calls, calculations, etc.)
|
|
||||||
# return {"temperature": 25, "condition": "sunny"}
|
|
||||||
@@ -3,56 +3,28 @@ import openmeteo_requests
|
|||||||
import requests_cache
|
import requests_cache
|
||||||
from retry_requests import retry
|
from retry_requests import retry
|
||||||
import json
|
import json
|
||||||
|
import config
|
||||||
# WMO Weather interpretation codes mapping
|
import logging
|
||||||
WEATHER_CODE_MAP = {
|
import os
|
||||||
0: "Clear sky",
|
import subprocess
|
||||||
1: "Mainly clear",
|
|
||||||
2: "Partly cloudy",
|
|
||||||
3: "Overcast",
|
|
||||||
45: "Fog",
|
|
||||||
48: "Depositing rime fog",
|
|
||||||
51: "Light drizzle",
|
|
||||||
53: "Moderate drizzle",
|
|
||||||
55: "Dense drizzle",
|
|
||||||
56: "Light freezing drizzle",
|
|
||||||
57: "Dense freezing drizzle",
|
|
||||||
61: "Slight rain",
|
|
||||||
63: "Moderate rain",
|
|
||||||
65: "Heavy rain",
|
|
||||||
66: "Light freezing rain",
|
|
||||||
67: "Heavy freezing rain",
|
|
||||||
71: "Slight snow",
|
|
||||||
73: "Moderate snow",
|
|
||||||
75: "Heavy snow",
|
|
||||||
77: "Snow grains",
|
|
||||||
80: "Slight rain showers",
|
|
||||||
81: "Moderate rain showers",
|
|
||||||
82: "Violent rain showers",
|
|
||||||
85: "Slight snow showers",
|
|
||||||
86: "Heavy snow showers",
|
|
||||||
95: "Thunderstorm",
|
|
||||||
96: "Thunderstorm with slight hail",
|
|
||||||
99: "Thunderstorm with heavy hail"
|
|
||||||
}
|
|
||||||
|
|
||||||
class ToolExecutor:
|
class ToolExecutor:
|
||||||
def __init__(self):
|
def __init__(self, logger):
|
||||||
self.tools = {
|
self.tools = {
|
||||||
"get_weather": self.get_weather
|
"get_weather": self.get_weather,
|
||||||
|
"find_folder": self.find_folder
|
||||||
}
|
}
|
||||||
|
self.logger = logger
|
||||||
|
|
||||||
def process_llm_response(self, llm_output):
|
def process_llm_response(self, llm_output):
|
||||||
|
|
||||||
# Parse the tool call from LLM response
|
|
||||||
if llm_output.startswith('{"tool":'):
|
if llm_output.startswith('{"tool":'):
|
||||||
|
# Parse the tool call from LLM response
|
||||||
llm_output = json.loads(llm_output)
|
llm_output = json.loads(llm_output)
|
||||||
|
|
||||||
print(llm_output)
|
self.logger.info(f"Parsed LLM output: {llm_output}")
|
||||||
tool_name = llm_output.get("tool",'no tool')
|
tool_name = llm_output.get("tool",'no tool')
|
||||||
parameters = llm_output.get("parameters", 'no parameters')
|
parameters = llm_output.get("parameters", 'no parameters')
|
||||||
print(f'parsed tool: {tool_name}, parsed parameters: {parameters}')
|
self.logger.info(f'parsed tool: {tool_name}, parsed parameters: {parameters}')
|
||||||
|
|
||||||
# Execute the actual function
|
# Execute the actual function
|
||||||
result = self.tools[tool_name](**parameters)
|
result = self.tools[tool_name](**parameters)
|
||||||
@@ -64,7 +36,7 @@ class ToolExecutor:
|
|||||||
def get_weather(self, city: str, units: str = "celsius"):
|
def get_weather(self, city: str, units: str = "celsius"):
|
||||||
"""Get current weather for a location using Open-Meteo API with openmeteo-requests library."""
|
"""Get current weather for a location using Open-Meteo API with openmeteo-requests library."""
|
||||||
|
|
||||||
print(f'get weather called, city = {city}')
|
self.logger.info(f'get weather called, city = {city}')
|
||||||
if not city:
|
if not city:
|
||||||
raise ValueError("City parameter is required")
|
raise ValueError("City parameter is required")
|
||||||
|
|
||||||
@@ -86,7 +58,7 @@ class ToolExecutor:
|
|||||||
geo_response = requests.get(geocoding_url, params=geocoding_params, timeout=10)
|
geo_response = requests.get(geocoding_url, params=geocoding_params, timeout=10)
|
||||||
geo_response.raise_for_status()
|
geo_response.raise_for_status()
|
||||||
geo_data = geo_response.json()
|
geo_data = geo_response.json()
|
||||||
print(f'Geo data for city: {geo_data}')
|
self.logger.info(f'Geo data for city: {geo_data}')
|
||||||
|
|
||||||
if not geo_data.get("results"):
|
if not geo_data.get("results"):
|
||||||
raise Exception(f"City '{city}' not found")
|
raise Exception(f"City '{city}' not found")
|
||||||
@@ -119,11 +91,11 @@ class ToolExecutor:
|
|||||||
|
|
||||||
responses = openmeteo.weather_api(url, params=params)
|
responses = openmeteo.weather_api(url, params=params)
|
||||||
response = responses[0]
|
response = responses[0]
|
||||||
print(f'Weather API response: {response}')
|
self.logger.info(f'Weather API response: {response}')
|
||||||
|
|
||||||
# Get current weather data
|
# Get current weather data
|
||||||
current = response.Current()
|
current = response.Current()
|
||||||
print(f'Current weather data: {current}')
|
self.logger.info(f'Current weather data: {current}')
|
||||||
|
|
||||||
# Extract values using the library's methods
|
# Extract values using the library's methods
|
||||||
temperature = current.Variables(0).Value() # temperature_2m
|
temperature = current.Variables(0).Value() # temperature_2m
|
||||||
@@ -132,7 +104,7 @@ class ToolExecutor:
|
|||||||
pressure = current.Variables(3).Value() # surface_pressure
|
pressure = current.Variables(3).Value() # surface_pressure
|
||||||
wind_speed = current.Variables(4).Value() # wind_speed_10m
|
wind_speed = current.Variables(4).Value() # wind_speed_10m
|
||||||
|
|
||||||
condition = WEATHER_CODE_MAP.get(weather_code, "Unknown")
|
condition = config.WEATHER_CODE_MAP.get(weather_code, "Unknown")
|
||||||
|
|
||||||
|
|
||||||
json_reply = {
|
json_reply = {
|
||||||
@@ -146,8 +118,43 @@ class ToolExecutor:
|
|||||||
"country": country,
|
"country": country,
|
||||||
"coordinates": {"latitude": latitude, "longitude": longitude}
|
"coordinates": {"latitude": latitude, "longitude": longitude}
|
||||||
}
|
}
|
||||||
print(json_reply)
|
self.logger.info(f"Weather data result: {json_reply}")
|
||||||
return json_reply
|
return json_reply
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception(f"Weather lookup failed: {str(e)}")
|
raise Exception(f"Weather lookup failed: {str(e)}")
|
||||||
|
|
||||||
|
def find_folder(self, folder_name: str, search_path: str = "/home/"):
|
||||||
|
"""Search for folders with a specific name on the PC."""
|
||||||
|
|
||||||
|
self.logger.info(f'find_folder called, folder_name = {folder_name}, search_path = {search_path}')
|
||||||
|
|
||||||
|
if not folder_name:
|
||||||
|
raise ValueError("folder_name parameter is required")
|
||||||
|
|
||||||
|
try:
|
||||||
|
found_folders = []
|
||||||
|
# Use find command to search for directories
|
||||||
|
cmd = ['find', search_path, '-type', 'd', '-name', folder_name]
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
||||||
|
self.logger.warning(result)
|
||||||
|
if result.stdout:
|
||||||
|
found_folders = [path.strip() for path in result.stdout.split('\n') if path.strip()]
|
||||||
|
|
||||||
|
# Limit results to first 50 to avoid overwhelming output
|
||||||
|
found_folders = found_folders[:50]
|
||||||
|
|
||||||
|
json_reply = {
|
||||||
|
"folder_name": folder_name,
|
||||||
|
"search_path": search_path,
|
||||||
|
"found_folders": found_folders,
|
||||||
|
"count": len(found_folders)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.logger.info(f"Folder search result: {json_reply}")
|
||||||
|
return json_reply
|
||||||
|
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
raise Exception("Folder search timed out after 30 seconds")
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"Folder search failed: {str(e)}")
|
||||||
Reference in New Issue
Block a user