PikoAi 0.1.15__py3-none-any.whl → 0.1.17__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- Agents/Executor/executor.py +1 -16
- Agents/Executor/prompts.py +5 -7
- Tools/system_details.py +46 -30
- Tools/tool_dir.json +1 -1
- Tools/tool_manager.py +1 -1
- Tools/userinp.py +1 -9
- Tools/web_loader.py +3 -21
- Tools/web_search.py +38 -47
- cli.py +155 -252
- {pikoai-0.1.15.dist-info → pikoai-0.1.17.dist-info}/METADATA +1 -1
- {pikoai-0.1.15.dist-info → pikoai-0.1.17.dist-info}/RECORD +15 -15
- {pikoai-0.1.15.dist-info → pikoai-0.1.17.dist-info}/WHEEL +0 -0
- {pikoai-0.1.15.dist-info → pikoai-0.1.17.dist-info}/entry_points.txt +0 -0
- {pikoai-0.1.15.dist-info → pikoai-0.1.17.dist-info}/licenses/LICENSE +0 -0
- {pikoai-0.1.15.dist-info → pikoai-0.1.17.dist-info}/top_level.txt +0 -0
Agents/Executor/executor.py
CHANGED
@@ -9,16 +9,7 @@ from Utils.ter_interface import TerminalInterface
|
|
9
9
|
from Utils.executor_utils import parse_tool_call
|
10
10
|
from Agents.Executor.prompts import get_executor_prompt # Import prompts
|
11
11
|
|
12
|
-
from typing import Optional
|
13
|
-
from mistralai.models.sdkerror import SDKError # This might be an issue if LiteLLM doesn't use SDKError
|
14
|
-
# LiteLLM maps exceptions to OpenAI exceptions.
|
15
|
-
# We'll keep it for now and see if errors arise during testing.
|
16
|
-
# from Env import python_executor # Will be replaced by BaseEnv
|
17
|
-
# from Env.shell import ShellExecutor # Will be replaced by BaseEnv
|
18
|
-
from Env.base_env import create_environment, BaseEnv # Added
|
19
|
-
from Env import python_executor # Keep for type hint in the old execute method if needed, or remove if execute is fully removed
|
20
12
|
from llm_interface.llm import LiteLLMInterface # Import LiteLLMInterface
|
21
|
-
|
22
13
|
from Tools import tool_manager
|
23
14
|
|
24
15
|
class RateLimiter:
|
@@ -93,12 +84,6 @@ class executor:
|
|
93
84
|
retries += 1
|
94
85
|
print(f"\nRate limit error detected. Waiting {self.rate_limiter.wait_time} seconds before retry {retries}/{self.rate_limiter.max_retries}")
|
95
86
|
time.sleep(self.rate_limiter.wait_time)
|
96
|
-
# Check if the error is an SDKError (though less likely with LiteLLM directly)
|
97
|
-
# or if it's any other exception that we should retry or raise.
|
98
|
-
elif isinstance(e, SDKError) and "429" in str(e) and retries < self.rate_limiter.max_retries: # Added SDKError check just in case
|
99
|
-
retries += 1
|
100
|
-
print(f"\nRate limit exceeded (SDKError). Waiting {self.rate_limiter.wait_time} seconds before retry {retries}/{self.rate_limiter.max_retries}")
|
101
|
-
time.sleep(self.rate_limiter.wait_time)
|
102
87
|
else:
|
103
88
|
print(f"\nError occurred during inference: {str(e)}")
|
104
89
|
# You might want to log the full traceback here for debugging
|
@@ -130,7 +115,7 @@ class executor:
|
|
130
115
|
tool_output_result = tool_manager.call_tool(tool_name, tool_input)
|
131
116
|
if tool_name not in ['execute_python_code', 'execute_shell_command']:
|
132
117
|
self.terminal.tool_output_log(tool_output_result, tool_name)
|
133
|
-
self.message.append({"role": "user", "content": tool_output_result})
|
118
|
+
self.message.append({"role": "user", "content": "Tool Output: " + str(tool_output_result)})
|
134
119
|
except ValueError as e:
|
135
120
|
error_msg = str(e)
|
136
121
|
print(f"Tool Error: {error_msg}")
|
Agents/Executor/prompts.py
CHANGED
@@ -33,9 +33,7 @@ You must break down the user's goal into smaller steps and perform one action at
|
|
33
33
|
}}
|
34
34
|
}}
|
35
35
|
<<END_TOOL_CALL>>
|
36
|
-
- **
|
37
|
-
the code written will be executed immediately and not saved.
|
38
|
-
- **Direct Response**: Provide a direct answer if the task doesn't require tools or code.
|
36
|
+
- **Direct Response**: Provide a direct answer if the task doesn't require tool calling
|
39
37
|
|
40
38
|
|
41
39
|
These are the things that you learned from the mistakes you made earlier :
|
@@ -46,13 +44,13 @@ These are the things that you learned from the mistakes you made earlier :
|
|
46
44
|
- Don't execute dangerous commands like rm -rf * or access sensitive files
|
47
45
|
- If you are stuck, have tried to fix an issue (e.g., a linter error) multiple times (e.g., 3 times) without success, or need clarification, ask the USER for input. Explain the situation clearly.
|
48
46
|
- Upon creating anything (like a new project, website, data analysis png) always show the output.You can do this by executing shell commands.
|
49
|
-
- the python/shell code execution
|
47
|
+
- the python/shell code execution through tool call will be executed immediately and output will be shown. it wont be saved.
|
50
48
|
|
51
49
|
|
52
50
|
** Important **
|
53
|
-
-
|
54
|
-
- Always evaluate the output of
|
51
|
+
- You can only perform one tool call at a time.
|
52
|
+
- Always evaluate the output of the tool call before deciding the next step.
|
55
53
|
- Continue performing actions until the user's goal is fully achieved. Only then, include 'TASK_DONE' in your response if that is the required signal for completion.
|
56
|
-
- Do not end the task immediately after a tool call
|
54
|
+
- Do not end the task immediately after a tool call without evaluating its output.
|
57
55
|
|
58
56
|
"""
|
Tools/system_details.py
CHANGED
@@ -1,49 +1,64 @@
|
|
1
1
|
import platform
|
2
2
|
import psutil
|
3
3
|
import datetime
|
4
|
-
import json
|
5
4
|
|
6
5
|
def get_os_details():
|
7
6
|
"""Get operating system details"""
|
8
|
-
|
7
|
+
os_info = {
|
9
8
|
"system": platform.system(),
|
10
9
|
"release": platform.release(),
|
11
10
|
"version": platform.version(),
|
12
11
|
"machine": platform.machine(),
|
13
12
|
"processor": platform.processor()
|
14
13
|
}
|
14
|
+
|
15
|
+
return f"""Operating System Details:
|
16
|
+
System: {os_info['system']}
|
17
|
+
Release: {os_info['release']}
|
18
|
+
Version: {os_info['version']}
|
19
|
+
Machine: {os_info['machine']}
|
20
|
+
Processor: {os_info['processor']}"""
|
15
21
|
|
16
22
|
def get_datetime():
|
17
23
|
"""Get current date and time"""
|
18
24
|
now = datetime.datetime.now()
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
25
|
+
date = now.strftime("%Y-%m-%d")
|
26
|
+
time = now.strftime("%H:%M:%S")
|
27
|
+
timezone = datetime.datetime.now().astimezone().tzname()
|
28
|
+
|
29
|
+
return f"""Date and Time:
|
30
|
+
Date: {date}
|
31
|
+
Time: {time}
|
32
|
+
Timezone: {timezone}"""
|
24
33
|
|
25
34
|
def get_memory_usage():
|
26
35
|
"""Get memory usage details"""
|
27
36
|
memory = psutil.virtual_memory()
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
37
|
+
total = f"{memory.total / (1024**3):.2f} GB"
|
38
|
+
available = f"{memory.available / (1024**3):.2f} GB"
|
39
|
+
used = f"{memory.used / (1024**3):.2f} GB"
|
40
|
+
percent = f"{memory.percent}%"
|
41
|
+
|
42
|
+
return f"""Memory Usage:
|
43
|
+
Total: {total}
|
44
|
+
Available: {available}
|
45
|
+
Used: {used}
|
46
|
+
Usage Percent: {percent}"""
|
34
47
|
|
35
48
|
def get_cpu_info():
|
36
49
|
"""Get CPU information"""
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
50
|
+
cpu_freq = psutil.cpu_freq()
|
51
|
+
current_freq = f"{cpu_freq.current:.2f} MHz" if cpu_freq else "N/A"
|
52
|
+
min_freq = f"{cpu_freq.min:.2f} MHz" if cpu_freq and cpu_freq.min else "N/A"
|
53
|
+
max_freq = f"{cpu_freq.max:.2f} MHz" if cpu_freq and cpu_freq.max else "N/A"
|
54
|
+
|
55
|
+
return f"""CPU Information:
|
56
|
+
Physical Cores: {psutil.cpu_count(logical=False)}
|
57
|
+
Total Cores: {psutil.cpu_count(logical=True)}
|
58
|
+
Current Frequency: {current_freq}
|
59
|
+
Min Frequency: {min_freq}
|
60
|
+
Max Frequency: {max_freq}
|
61
|
+
CPU Usage: {psutil.cpu_percent()}%"""
|
47
62
|
|
48
63
|
def system_details(detail_type="all"):
|
49
64
|
"""
|
@@ -53,17 +68,18 @@ def system_details(detail_type="all"):
|
|
53
68
|
detail_type (str): Type of system detail to retrieve (os, datetime, memory, cpu, all)
|
54
69
|
|
55
70
|
Returns:
|
56
|
-
|
71
|
+
str: Requested system details as formatted string
|
57
72
|
"""
|
58
73
|
detail_type = detail_type.lower()
|
59
74
|
|
60
75
|
if detail_type == "all":
|
61
|
-
return {
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
76
|
+
return f"""{get_os_details()}
|
77
|
+
|
78
|
+
{get_datetime()}
|
79
|
+
|
80
|
+
{get_memory_usage()}
|
81
|
+
|
82
|
+
{get_cpu_info()}"""
|
67
83
|
elif detail_type == "os":
|
68
84
|
return get_os_details()
|
69
85
|
elif detail_type == "datetime":
|
Tools/tool_dir.json
CHANGED
@@ -68,7 +68,7 @@
|
|
68
68
|
"summary": "Prompts the user for input and returns their response",
|
69
69
|
"arguments": {
|
70
70
|
"prompt": "The message to display to the user",
|
71
|
-
"input_type": "Type of input to validate (text, number,
|
71
|
+
"input_type": "Type of input to validate (text, number, optional, defaults to text)"
|
72
72
|
}
|
73
73
|
},
|
74
74
|
{
|
Tools/tool_manager.py
CHANGED
@@ -77,7 +77,7 @@ def call_tool(tool_name, tool_input):
|
|
77
77
|
# Pass the tool_input dictionary as kwargs to the tool function
|
78
78
|
return tools_function_map[tool_name](**tool_input)
|
79
79
|
else:
|
80
|
-
|
80
|
+
return f"This tool is invalid. Please check the tools available in the tool directory"
|
81
81
|
|
82
82
|
tools_function_map = {
|
83
83
|
"web_loader": load_data,
|
Tools/userinp.py
CHANGED
@@ -17,15 +17,7 @@ def get_user_input(prompt="Enter input: ", input_type="text"):
|
|
17
17
|
return user_input
|
18
18
|
elif input_type == "number":
|
19
19
|
return float(user_input)
|
20
|
-
|
21
|
-
user_input = user_input.lower()
|
22
|
-
if user_input in ["true", "yes", "y", "1"]:
|
23
|
-
return True
|
24
|
-
elif user_input in ["false", "no", "n", "0"]:
|
25
|
-
return False
|
26
|
-
else:
|
27
|
-
print("Please enter 'yes' or 'no'")
|
28
|
-
continue
|
20
|
+
|
29
21
|
else:
|
30
22
|
raise ValueError(f"Invalid input type: {input_type}")
|
31
23
|
|
Tools/web_loader.py
CHANGED
@@ -123,7 +123,6 @@ def load_data(**kwargs):
|
|
123
123
|
headers = {
|
124
124
|
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML like Gecko) Chrome/52.0.2743.116 Safari/537.36"
|
125
125
|
}
|
126
|
-
web_data = {}
|
127
126
|
content = ""
|
128
127
|
try:
|
129
128
|
response = session.get(url, headers=headers, timeout=30)
|
@@ -139,28 +138,11 @@ def load_data(**kwargs):
|
|
139
138
|
# Extract text from each page and combine it
|
140
139
|
content = "\n".join([page.extract_text() for page in pdf.pages if page.extract_text()])
|
141
140
|
|
142
|
-
meta_data = {"url": url}
|
143
|
-
doc_id = hashlib.sha256((content + url).encode()).hexdigest()
|
144
|
-
web_data = {
|
145
|
-
"doc_id": doc_id,
|
146
|
-
"data": [
|
147
|
-
{
|
148
|
-
"content": content,
|
149
|
-
"meta_data": meta_data,
|
150
|
-
}
|
151
|
-
],
|
152
|
-
}
|
153
141
|
except Exception as e:
|
154
142
|
logging.error(f"Error loading data from {url}: {e}")
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
"content": "",
|
159
|
-
"meta_data": "",
|
160
|
-
}
|
161
|
-
],
|
162
|
-
}
|
163
|
-
return web_data
|
143
|
+
content = ""
|
144
|
+
|
145
|
+
return content
|
164
146
|
|
165
147
|
|
166
148
|
def close_session(session):
|
Tools/web_search.py
CHANGED
@@ -1,11 +1,14 @@
|
|
1
1
|
import os
|
2
|
+
from dotenv import load_dotenv
|
2
3
|
from duckduckgo_search import DDGS
|
3
|
-
|
4
|
+
import requests
|
5
|
+
|
6
|
+
load_dotenv()
|
4
7
|
|
5
8
|
def web_search(max_results: int = 10, **kwargs) -> str:
|
6
9
|
"""
|
7
10
|
Performs a DuckDuckGo web search based on your query (think a Google search) then returns the top search results.
|
8
|
-
|
11
|
+
Falls back to SerpAPI (Google) if DuckDuckGo fails or returns no results.
|
9
12
|
|
10
13
|
Args:
|
11
14
|
query (str): The search query to perform.
|
@@ -16,52 +19,40 @@ def web_search(max_results: int = 10, **kwargs) -> str:
|
|
16
19
|
str: Formatted string containing search results.
|
17
20
|
|
18
21
|
Raises:
|
19
|
-
ImportError: If
|
20
|
-
Exception: If no results are found for the given query
|
21
|
-
|
22
|
-
Note:
|
23
|
-
For SerpAPI fallback, the SERPAPI_API_KEY environment variable must be set.
|
22
|
+
ImportError: If neither duckduckgo_search nor SerpAPI is available.
|
23
|
+
Exception: If no results are found for the given query.
|
24
24
|
"""
|
25
|
+
query = kwargs['query']
|
26
|
+
# Try DuckDuckGo first
|
25
27
|
try:
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
28
|
+
ddgs = DDGS()
|
29
|
+
results = ddgs.text(query, max_results=max_results)
|
30
|
+
if results and len(results) > 0:
|
31
|
+
postprocessed_results = [f"[{result['title']}]({result['href']})\n{result['body']}" for result in results]
|
32
|
+
return "## Search Results (DuckDuckGo)\n\n" + "\n\n".join(postprocessed_results)
|
33
|
+
except Exception:
|
34
|
+
pass # Will try SerpAPI fallback
|
35
|
+
|
36
|
+
# Fallback to SerpAPI (Google)
|
37
|
+
SERP_API_KEY = os.getenv("SERP_API_KEY")
|
38
|
+
if not SERP_API_KEY:
|
39
|
+
raise ImportError("DuckDuckGo search failed and SERP_API_KEY is not set for SerpAPI fallback.")
|
40
|
+
url = "https://serpapi.com/search"
|
41
|
+
params = {
|
42
|
+
"q": query,
|
43
|
+
"api_key": SERP_API_KEY,
|
44
|
+
"engine": "google"
|
45
|
+
}
|
30
46
|
try:
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
47
|
+
response = requests.get(url, params=params)
|
48
|
+
data = response.json()
|
49
|
+
results = data.get("organic_results", [])
|
50
|
+
if not results:
|
51
|
+
raise Exception("No results found from SerpAPI either!")
|
52
|
+
simplified_results = [
|
53
|
+
f"[{result.get('title')}]({result.get('link')})\n{result.get('snippet', '')}"
|
54
|
+
for result in results[:max_results]
|
55
|
+
]
|
56
|
+
return "## Search Results (Google via SerpAPI)\n\n" + "\n\n".join(simplified_results)
|
40
57
|
except Exception as e:
|
41
|
-
|
42
|
-
# If the exception was the specific DDGS ImportError, we re-raise it directly if it wasn't caught above.
|
43
|
-
# However, the structure above should prevent it from reaching here.
|
44
|
-
# The primary purpose of this block is to catch runtime errors from ddgs.text or the "No results" exception.
|
45
|
-
|
46
|
-
api_key = os.environ.get("SERPAPI_API_KEY")
|
47
|
-
if not api_key:
|
48
|
-
raise Exception("SerpAPI key not found. Please set the SERPAPI_API_KEY environment variable.")
|
49
|
-
|
50
|
-
try:
|
51
|
-
client = SerpApiClient({"api_key": api_key})
|
52
|
-
except ImportError as serp_e:
|
53
|
-
raise ImportError("You must install package `serpapi` to run this function: for instance run `pip install google-search-results`.") from serp_e
|
54
|
-
|
55
|
-
search_params = {
|
56
|
-
"engine": "google",
|
57
|
-
"q": query,
|
58
|
-
"num": max_results # SerpAPI uses 'num' for number of results
|
59
|
-
}
|
60
|
-
serp_results = client.search(search_params)
|
61
|
-
|
62
|
-
if "organic_results" in serp_results and serp_results["organic_results"]:
|
63
|
-
organic_results = serp_results["organic_results"]
|
64
|
-
postprocessed_results = [f"[{result['title']}]({result['link']})\n{result.get('snippet', '')}" for result in organic_results]
|
65
|
-
return "## Search Results (via SerpAPI)\n\n" + "\n\n".join(postprocessed_results)
|
66
|
-
else:
|
67
|
-
raise Exception(f"No results found via DuckDuckGo or SerpAPI! Original error: {e}")
|
58
|
+
raise Exception(f"No results found from DuckDuckGo or SerpAPI. Last error: {e}")
|
cli.py
CHANGED
@@ -43,320 +43,223 @@ API_KEYS = {
|
|
43
43
|
"anthropic": "ANTHROPIC_API_KEY"
|
44
44
|
}
|
45
45
|
|
46
|
+
# --- Utility Functions ---
|
47
|
+
|
48
|
+
def clear_terminal():
|
49
|
+
"""Clear the terminal screen."""
|
50
|
+
os.system('cls' if os.name == 'nt' else 'clear')
|
51
|
+
|
46
52
|
def get_provider_from_model_name(model_name: str) -> str:
|
47
53
|
"""Extracts the provider from a litellm model string (e.g., 'openai/gpt-4o' -> 'openai')."""
|
48
54
|
if not model_name or '/' not in model_name:
|
49
|
-
# Fallback or error handling if model_name is not in expected format
|
50
|
-
# For now, try to return the model_name itself if it doesn't contain '/',
|
51
|
-
# as it might be a provider name already or an old format.
|
52
|
-
# This case should ideally be handled based on how robust the system needs to be.
|
53
55
|
print(f"Warning: Model name '{model_name}' may not be in 'provider/model' format. Attempting to use as provider.")
|
54
56
|
return model_name
|
55
57
|
return model_name.split('/')[0]
|
56
58
|
|
57
|
-
|
58
|
-
"""Clear the terminal screen"""
|
59
|
-
os.system('cls' if os.name == 'nt' else 'clear')
|
59
|
+
# --- Configuration Management ---
|
60
60
|
|
61
|
-
def
|
62
|
-
"""
|
63
|
-
env_path = os.path.join(os.path.dirname(__file__), '../.env')
|
64
|
-
env_var = API_KEYS.get(provider)
|
65
|
-
if not env_var:
|
66
|
-
raise ValueError(f"Unknown provider: {provider}")
|
67
|
-
|
68
|
-
# Force reload of .env file
|
69
|
-
if os.path.exists(env_path):
|
70
|
-
load_dotenv(env_path, override=True)
|
71
|
-
|
72
|
-
# Check if API key exists in environment
|
73
|
-
api_key = os.getenv(env_var)
|
74
|
-
|
75
|
-
if not api_key:
|
76
|
-
# Ask for API key
|
77
|
-
questions = [
|
78
|
-
inquirer.Text('api_key',
|
79
|
-
message=f"Enter your {provider.upper()} API key",
|
80
|
-
validate=lambda _, x: len(x.strip()) > 0
|
81
|
-
)
|
82
|
-
]
|
83
|
-
api_key = inquirer.prompt(questions)['api_key']
|
84
|
-
clear_terminal()
|
85
|
-
|
86
|
-
# Save to .env file
|
87
|
-
if not os.path.exists(env_path):
|
88
|
-
with open(env_path, 'w') as f:
|
89
|
-
f.write(f"{env_var}={api_key}\n")
|
90
|
-
else:
|
91
|
-
# Read existing .env file
|
92
|
-
with open(env_path, 'r') as f:
|
93
|
-
lines = f.readlines()
|
94
|
-
|
95
|
-
# Check if key already exists
|
96
|
-
key_exists = False
|
97
|
-
for i, line in enumerate(lines):
|
98
|
-
if line.strip().startswith(f"{env_var}=") or line.strip().startswith(f"#{env_var}="):
|
99
|
-
lines[i] = f"{env_var}={api_key}\n"
|
100
|
-
key_exists = True
|
101
|
-
break
|
102
|
-
|
103
|
-
# If key doesn't exist, append it
|
104
|
-
if not key_exists:
|
105
|
-
lines.append(f"{env_var}={api_key}\n")
|
106
|
-
|
107
|
-
# Write back to .env file
|
108
|
-
with open(env_path, 'w') as f:
|
109
|
-
f.writelines(lines)
|
110
|
-
|
111
|
-
# Reload environment with new key
|
112
|
-
load_dotenv(env_path, override=True)
|
113
|
-
|
114
|
-
return api_key
|
115
|
-
|
116
|
-
def ensure_config_exists():
|
117
|
-
"""Ensure config.json exists and has required fields"""
|
118
|
-
config_path = os.path.join(os.path.dirname(__file__), '../config.json')
|
119
|
-
config = None
|
120
|
-
|
61
|
+
def load_config(config_path: str) -> dict:
|
62
|
+
"""Load (or create) config.json and return its contents as a dict. If config.json does not exist, copy from config.example.json (or create a default) and update working_directory to os.getcwd()."""
|
121
63
|
if not os.path.exists(config_path):
|
122
|
-
# Copy config from example if it doesn't exist
|
123
64
|
example_path = os.path.join(os.path.dirname(__file__), '../config.example.json')
|
124
65
|
if os.path.exists(example_path):
|
125
66
|
shutil.copy2(example_path, config_path)
|
126
67
|
with open(config_path, 'r') as f:
|
127
68
|
config = json.load(f)
|
128
69
|
else:
|
129
|
-
|
130
|
-
config = {
|
131
|
-
"working_directory": os.getcwd(),
|
132
|
-
"llm_provider": None, # Will store the provider part, e.g., "openai"
|
133
|
-
"model_name": None # Will store the full litellm string, e.g., "openai/gpt-4o"
|
134
|
-
}
|
70
|
+
config = { "working_directory": os.getcwd(), "llm_provider": None, "model_name": None }
|
135
71
|
else:
|
136
|
-
# Read existing config
|
137
72
|
with open(config_path, 'r') as f:
|
138
73
|
try:
|
139
74
|
config = json.load(f)
|
75
|
+
config["working_directory"] = os.getcwd()
|
140
76
|
except json.JSONDecodeError:
|
141
77
|
print("Error reading config.json. File might be corrupted. Re-creating default.")
|
142
|
-
config = {
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
}
|
78
|
+
config = { "working_directory": os.getcwd(), "llm_provider": None, "model_name": None }
|
79
|
+
# Always update working_directory to current directory
|
80
|
+
config["working_directory"] = os.getcwd()
|
81
|
+
return config
|
147
82
|
|
148
|
-
|
149
|
-
|
150
|
-
|
83
|
+
def save_config(config_path: str, config: dict) -> None:
|
84
|
+
"""Save config dict (with updated working_directory) to config_path."""
|
85
|
+
with open(config_path, 'w') as f:
|
86
|
+
json.dump(config, f, indent=4)
|
151
87
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
questions = [
|
168
|
-
|
169
|
-
message=f"Select {selected_provider_key} Model",
|
170
|
-
choices=AVAILABLE_MODELS[selected_provider_key] # Shows "openai/gpt-3.5-turbo", etc.
|
171
|
-
)
|
172
|
-
]
|
173
|
-
selected_model_name_full = inquirer.prompt(questions)['model_name_full']
|
88
|
+
# --- API Key Management ---
|
89
|
+
|
90
|
+
def ensure_api_key(provider: str):
|
91
|
+
"""Ensure that an API key for the given provider (e.g. "openai") is available (via .env or prompt) and return it. Raise an error if unknown provider."""
|
92
|
+
env_path = os.path.join(os.path.dirname(__file__), '../.env')
|
93
|
+
env_var = API_KEYS.get(provider)
|
94
|
+
if not env_var:
|
95
|
+
raise ValueError(f"Unknown provider: {provider}")
|
96
|
+
|
97
|
+
# Force reload .env (if it exists) so that any new key is picked up.
|
98
|
+
if os.path.exists(env_path):
|
99
|
+
load_dotenv(env_path, override=True)
|
100
|
+
|
101
|
+
api_key = os.getenv(env_var)
|
102
|
+
if not api_key:
|
103
|
+
questions = [ inquirer.Text("api_key", message=f"Enter your {provider.upper()} API key", validate=lambda _, x: len(x.strip()) > 0) ]
|
104
|
+
api_key = inquirer.prompt(questions)["api_key"]
|
174
105
|
clear_terminal()
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
106
|
+
# Save (or update) the key in .env
|
107
|
+
lines = []
|
108
|
+
if os.path.exists(env_path):
|
109
|
+
with open(env_path, 'r') as f:
|
110
|
+
lines = f.readlines()
|
111
|
+
key_line = f"{env_var}={api_key}\n"
|
112
|
+
key_exists = False
|
113
|
+
for (i, line) in enumerate(lines):
|
114
|
+
if (line.strip().startswith(f"{env_var}=") or line.strip().startswith(f"#{env_var}=")):
|
115
|
+
lines[i] = key_line
|
116
|
+
key_exists = True
|
117
|
+
break
|
118
|
+
if not key_exists:
|
119
|
+
lines.append(key_line)
|
120
|
+
with open(env_path, 'w') as f:
|
121
|
+
f.writelines(lines)
|
122
|
+
# Reload .env (override) so that the new key is available.
|
123
|
+
load_dotenv(env_path, override=True)
|
124
|
+
|
182
125
|
|
183
|
-
|
184
|
-
# Config exists, ensure API key for the stored provider
|
185
|
-
# llm_provider should already be the provider part, e.g., "openai"
|
186
|
-
# If old config only had model_name, try to parse provider from it
|
187
|
-
provider_to_check = config.get("llm_provider")
|
188
|
-
if not provider_to_check and config.get("model_name"):
|
189
|
-
provider_to_check = get_provider_from_model_name(config["model_name"])
|
190
|
-
# Optionally, update config if llm_provider was missing
|
191
|
-
if provider_to_check != config.get("llm_provider"): # Check if it's different or was None
|
192
|
-
config["llm_provider"] = provider_to_check
|
193
|
-
with open(config_path, 'w') as f:
|
194
|
-
json.dump(config, f, indent=4)
|
126
|
+
# --- Model / Provider Management ---
|
195
127
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
128
|
+
def prompt_model_selection() -> tuple:
|
129
|
+
"""Prompt the user (via inquirer) to select a provider (from AVAILABLE_MODELS) and then a model (from that provider's list). Return (provider, model_name_full)."""
|
130
|
+
questions = [ inquirer.List("provider_key", message="Select LLM Provider", choices=list(AVAILABLE_MODELS.keys())) ]
|
131
|
+
selected_provider_key = inquirer.prompt(questions)["provider_key"]
|
132
|
+
clear_terminal()
|
133
|
+
# (Ensure API key for the selected provider.)
|
134
|
+
ensure_api_key(selected_provider_key)
|
135
|
+
questions = [ inquirer.List("model_name_full", message=f"Select {selected_provider_key} Model", choices=AVAILABLE_MODELS[selected_provider_key]) ]
|
136
|
+
selected_model_name_full = inquirer.prompt(questions)["model_name_full"]
|
137
|
+
clear_terminal()
|
138
|
+
return (selected_provider_key, selected_model_name_full)
|
201
139
|
|
140
|
+
def update_model_config(config_path: str, provider_key: str, model_name_full: str) -> None:
|
141
|
+
"""Update config (at config_path) so that "llm_provider" is provider_key and "model_name" is model_name_full. (Also update "working_directory" to os.getcwd() if missing.)"""
|
142
|
+
config = load_config(config_path)
|
143
|
+
config["llm_provider"] = provider_key
|
144
|
+
config["model_name"] = model_name_full
|
145
|
+
if "working_directory" not in config or not config["working_directory"]:
|
146
|
+
config["working_directory"] = os.getcwd()
|
147
|
+
save_config(config_path, config)
|
202
148
|
|
203
|
-
|
204
|
-
if not os.path.exists(config_path):
|
205
|
-
with open(config_path, 'w') as f:
|
206
|
-
json.dump(config, f, indent=4)
|
207
|
-
|
208
|
-
return config_path
|
149
|
+
# --- CLI Commands ---
|
209
150
|
|
210
151
|
@click.group(invoke_without_command=True)
|
211
|
-
@click.option(
|
212
|
-
@click.option(
|
213
|
-
@click.option(
|
152
|
+
@click.option("--task", "-t", help="The task to automate")
|
153
|
+
@click.option("--max-iter", "-m", default=10, help="Maximum number of iterations for the task")
|
154
|
+
@click.option("--change-model", is_flag=True, help="Change the LLM provider and model")
|
214
155
|
@click.pass_context
|
215
156
|
def cli(ctx, task, max_iter, change_model):
|
216
|
-
"""TaskAutomator
|
217
|
-
|
218
|
-
|
219
|
-
|
157
|
+
"""TaskAutomator – Your AI Task Automation Tool"""
|
158
|
+
config_path = os.path.join(os.path.dirname(__file__), '../config.json')
|
159
|
+
config = load_config(config_path)
|
160
|
+
save_config(config_path, config)
|
220
161
|
|
221
|
-
|
222
|
-
if change_model:
|
223
|
-
with open(config_path, 'r') as f:
|
224
|
-
config = json.load(f)
|
225
|
-
|
226
|
-
print("Current configuration: Provider: {}, Model: {}".format(config.get("llm_provider"), config.get("model_name")))
|
227
|
-
questions = [
|
228
|
-
inquirer.List('provider_key',
|
229
|
-
message="Select LLM Provider",
|
230
|
-
choices=list(AVAILABLE_MODELS.keys()) # User selects "openai", "mistral", etc.
|
231
|
-
)
|
232
|
-
]
|
233
|
-
selected_provider_key = inquirer.prompt(questions)['provider_key']
|
234
|
-
clear_terminal()
|
235
|
-
|
236
|
-
# Ensure API key exists for the selected provider
|
237
|
-
ensure_api_key(selected_provider_key)
|
238
|
-
|
239
|
-
questions = [
|
240
|
-
inquirer.List('model_name_full',
|
241
|
-
message=f"Select {selected_provider_key} Model",
|
242
|
-
choices=AVAILABLE_MODELS[selected_provider_key] # Shows "openai/gpt-3.5-turbo", etc.
|
243
|
-
)
|
244
|
-
]
|
245
|
-
selected_model_name_full = inquirer.prompt(questions)['model_name_full']
|
246
|
-
clear_terminal()
|
247
|
-
|
248
|
-
config["llm_provider"] = selected_provider_key # Store "openai"
|
249
|
-
config["model_name"] = selected_model_name_full # Store "openai/gpt-4o"
|
250
|
-
|
251
|
-
# Ensure working_directory is preserved or set
|
252
|
-
if "working_directory" not in config or not config["working_directory"]:
|
253
|
-
config["working_directory"] = os.getcwd()
|
162
|
+
clear_terminal()
|
254
163
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
#
|
263
|
-
with open(config_path, 'r') as f:
|
264
|
-
config = json.load(f)
|
265
|
-
|
164
|
+
if change_model or not config.get("model_name"):
|
165
|
+
(provider, model) = prompt_model_selection()
|
166
|
+
update_model_config(config_path, provider, model)
|
167
|
+
click.echo(f"Model changed to {model}")
|
168
|
+
if not change_model: # Only return if this was triggered by missing model
|
169
|
+
return
|
170
|
+
|
171
|
+
# Ensure API key for the configured model (or derive provider from model_name if missing) before running OpenCopilot.
|
266
172
|
current_provider = config.get("llm_provider")
|
267
|
-
if not current_provider and config.get("model_name"):
|
268
|
-
|
269
|
-
|
173
|
+
if not current_provider and config.get("model_name"):
|
174
|
+
current_provider = get_provider_from_model_name(config["model_name"])
|
270
175
|
if current_provider:
|
271
|
-
|
176
|
+
ensure_api_key(current_provider)
|
272
177
|
else:
|
273
|
-
|
274
|
-
|
178
|
+
click.echo("Error: LLM provider not configured. Please run with --change-model to set it up.", err=True)
|
179
|
+
return
|
275
180
|
|
276
181
|
copilot = OpenCopilot()
|
277
182
|
if ctx.invoked_subcommand is None:
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
183
|
+
if task:
|
184
|
+
copilot.run_task(user_prompt=task, max_iter=max_iter)
|
185
|
+
else:
|
186
|
+
copilot.run()
|
282
187
|
|
283
|
-
@cli.command(
|
188
|
+
@cli.command("list-tools")
|
284
189
|
def list_tools():
|
285
|
-
"""List all available automation tools"""
|
190
|
+
"""List all available automation tools."""
|
286
191
|
tools = OpenCopilot.list_available_tools()
|
287
192
|
click.echo("Available Tools:")
|
288
193
|
for tool in tools:
|
289
|
-
|
290
|
-
|
291
|
-
|
194
|
+
click.echo(f"- {tool['name']}: {tool['summary']}")
|
195
|
+
if tool.get("arguments"):
|
196
|
+
click.echo(f" Arguments: {tool['arguments']}")
|
292
197
|
|
293
|
-
@cli.command(
|
198
|
+
@cli.command("list-models")
|
294
199
|
def list_models():
|
295
|
-
"""List all available LLM providers and their models (litellm compatible)"""
|
200
|
+
"""List all available LLM providers and their models (litellm compatible)."""
|
296
201
|
click.echo("Available LLM Providers and Models (litellm compatible):")
|
297
|
-
for provider_key, model_list in AVAILABLE_MODELS.items():
|
298
|
-
|
299
|
-
|
300
|
-
|
202
|
+
for (provider_key, model_list) in AVAILABLE_MODELS.items():
|
203
|
+
click.echo(f"\n{provider_key.upper()}:")
|
204
|
+
for model_name_full in model_list:
|
205
|
+
click.echo(f" - {model_name_full}")
|
301
206
|
|
302
|
-
@cli.command(
|
303
|
-
@click.option(
|
304
|
-
|
305
|
-
@click.option('--key', '-k', help='The API key to set (if not provided, will prompt for it)')
|
207
|
+
@cli.command("set-api-key")
|
208
|
+
@click.option("--provider", "-p", type=click.Choice(list(AVAILABLE_MODELS.keys())), help="The LLM provider to set API key for")
|
209
|
+
@click.option("--key", "-k", help="The API key to set (if not provided, will prompt for it)")
|
306
210
|
def set_api_key(provider, key):
|
307
|
-
"""Set or update API key for a specific LLM provider"""
|
211
|
+
"""Set or update API key for a specific LLM provider."""
|
308
212
|
if not provider:
|
309
|
-
|
310
|
-
|
311
|
-
inquirer.List('provider_key',
|
312
|
-
message="Select LLM Provider to update API key",
|
313
|
-
choices=list(AVAILABLE_MODELS.keys())
|
314
|
-
)
|
315
|
-
]
|
316
|
-
provider = inquirer.prompt(questions)['provider_key']
|
317
|
-
|
318
|
-
# Get the environment variable name for this provider
|
213
|
+
questions = [ inquirer.List("provider_key", message="Select LLM Provider to update API key", choices=list(AVAILABLE_MODELS.keys())) ]
|
214
|
+
provider = inquirer.prompt(questions)["provider_key"]
|
319
215
|
env_var = API_KEYS.get(provider)
|
320
216
|
if not env_var:
|
321
|
-
|
322
|
-
|
323
|
-
# Get the API key (either from command line or prompt)
|
217
|
+
raise ValueError(f"Unknown provider: {provider}")
|
324
218
|
if not key:
|
325
|
-
|
326
|
-
|
327
|
-
message=f"Enter your {provider.upper()} API key",
|
328
|
-
validate=lambda _, x: len(x.strip()) > 0
|
329
|
-
)
|
330
|
-
]
|
331
|
-
key = inquirer.prompt(questions)['api_key']
|
332
|
-
|
333
|
-
# Get the path to .env file
|
219
|
+
questions = [ inquirer.Text("api_key", message=f"Enter your {provider.upper()} API key", validate=lambda _, x: len(x.strip()) > 0) ]
|
220
|
+
key = inquirer.prompt(questions)["api_key"]
|
334
221
|
env_path = os.path.join(os.path.dirname(__file__), '../.env')
|
335
|
-
|
336
|
-
# Read existing .env file if it exists
|
337
222
|
lines = []
|
338
223
|
if os.path.exists(env_path):
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
# Update or add the API key
|
224
|
+
with open(env_path, 'r') as f:
|
225
|
+
lines = f.readlines()
|
343
226
|
key_line = f"{env_var}={key}\n"
|
344
227
|
key_exists = False
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
break
|
351
|
-
|
228
|
+
for (i, line) in enumerate(lines):
|
229
|
+
if (line.strip().startswith(f"{env_var}=") or line.strip().startswith(f"#{env_var}=")):
|
230
|
+
lines[i] = key_line
|
231
|
+
key_exists = True
|
232
|
+
break
|
352
233
|
if not key_exists:
|
353
|
-
|
354
|
-
|
355
|
-
# Write back to .env file
|
234
|
+
lines.append(key_line)
|
356
235
|
with open(env_path, 'w') as f:
|
357
|
-
|
358
|
-
|
236
|
+
f.writelines(lines)
|
359
237
|
click.echo(f"API key for {provider.upper()} has been updated successfully in {env_path}")
|
360
238
|
|
239
|
+
@cli.command("set-serp-key")
|
240
|
+
@click.option("--key", "-k", help="The SERP API key to set (if not provided, will prompt for it)")
|
241
|
+
def set_serp_key(key):
|
242
|
+
"""Set or update the SERP API key used for web search functionality."""
|
243
|
+
if not key:
|
244
|
+
questions = [ inquirer.Text("api_key", message="Enter your SERP API key", validate=lambda _, x: len(x.strip()) > 0) ]
|
245
|
+
key = inquirer.prompt(questions)["api_key"]
|
246
|
+
env_path = os.path.join(os.path.dirname(__file__), '../.env')
|
247
|
+
lines = []
|
248
|
+
if os.path.exists(env_path):
|
249
|
+
with open(env_path, 'r') as f:
|
250
|
+
lines = f.readlines()
|
251
|
+
key_line = f"SERP_API_KEY={key}\n"
|
252
|
+
key_exists = False
|
253
|
+
for (i, line) in enumerate(lines):
|
254
|
+
if (line.strip().startswith("SERP_API_KEY=") or line.strip().startswith("#SERP_API_KEY=")):
|
255
|
+
lines[i] = key_line
|
256
|
+
key_exists = True
|
257
|
+
break
|
258
|
+
if not key_exists:
|
259
|
+
lines.append(key_line)
|
260
|
+
with open(env_path, 'w') as f:
|
261
|
+
f.writelines(lines)
|
262
|
+
click.echo(f"SERP API key has been updated successfully in {env_path}")
|
263
|
+
|
361
264
|
if __name__ == '__main__':
|
362
265
|
cli()
|
@@ -1,9 +1,9 @@
|
|
1
1
|
OpenCopilot.py,sha256=kPTs0-ly84h4dM7AmBlK4uwst5Sj2AM6UAlE3okkD8U,12157
|
2
|
-
cli.py,sha256=
|
2
|
+
cli.py,sha256=MJSassshPDTHrvdw3kRDob7CK1CeEir_TgJvMv4cxyw,11229
|
3
3
|
Agents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
4
|
Agents/Executor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
-
Agents/Executor/executor.py,sha256=
|
6
|
-
Agents/Executor/prompts.py,sha256=
|
5
|
+
Agents/Executor/executor.py,sha256=kk5TpfTUrunIl1gv5ZZO2UIm5PC4ZygeiB4z0YAJ_PY,7018
|
6
|
+
Agents/Executor/prompts.py,sha256=pGY4uXNGYiw_TnTUsRjrVsWc9CV657q3916eui0oulU,2688
|
7
7
|
Env/__init__.py,sha256=KLe7UcNV5L395SxhMwbYGyu7KPrSNaoV_9QJo3mLop0,196
|
8
8
|
Env/base_env.py,sha256=K4PoWwPXn3pKeu7_-JOlUuyNbyYQ9itMhQybFOm-3K4,1563
|
9
9
|
Env/base_executor.py,sha256=awTwJ44CKWV4JO2KUHfHDX0p1Ujw55hlaL5oNYTEW9M,893
|
@@ -16,20 +16,20 @@ Env/tests/test_python_executor.py,sha256=5kfs0cOf-RgWTOers_1wB0yvYSF-HrHPsraJ-Px
|
|
16
16
|
Env/tests/test_shell_executor.py,sha256=-RcCdSUMoRAXHISIh0IR5MCutco80fX2S3sQBcinc_Q,1034
|
17
17
|
Tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
18
18
|
Tools/file_task.py,sha256=VUhWq_G-SWvGahQo8PG7TOpElHUW3BGLUabrTdJS89o,12151
|
19
|
-
Tools/system_details.py,sha256=
|
20
|
-
Tools/tool_dir.json,sha256=
|
21
|
-
Tools/tool_manager.py,sha256=
|
22
|
-
Tools/userinp.py,sha256=
|
23
|
-
Tools/web_loader.py,sha256=
|
24
|
-
Tools/web_search.py,sha256=
|
19
|
+
Tools/system_details.py,sha256=RScVnhTpUOlNG0g5bGnwmtNr5nSJzOec8HJSFpbicds,2651
|
20
|
+
Tools/tool_dir.json,sha256=RTawcanxIkJaUys6Y3yftXAT5uxMH0xPZYTtD1ilJl0,3119
|
21
|
+
Tools/tool_manager.py,sha256=xrjrGmLI1hXqCfALBRBuMFCgXwc7pb5TpKMBH_4tCzs,4106
|
22
|
+
Tools/userinp.py,sha256=SK69fMEdUvNQor9V3BVckeDMJcq71g6H6EHPmNfsZD4,834
|
23
|
+
Tools/web_loader.py,sha256=_oP48uwveTaCKU7G5ju2zsJGTcZd1ScXTKOvHDFtZJU,4564
|
24
|
+
Tools/web_search.py,sha256=12_VhwJGXmn3oUNhTbQ5ENFG964t9DWkfCz3UtlxrbM,2261
|
25
25
|
Utils/__init__.py,sha256=oukU0ufroPRd8_N8d2xiFes9CTxSaw4NA6p2nS1kkSg,16
|
26
26
|
Utils/executor_utils.py,sha256=WwK3TKgw_hG_crg7ijRaqfidYnnNXYbbs37vKZRYK-0,491
|
27
27
|
Utils/ter_interface.py,sha256=2k32kVxcxVBewXnjSGSPbx25ZLhKindEMtL6wSC_wL4,3829
|
28
28
|
llm_interface/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
29
29
|
llm_interface/llm.py,sha256=tI_KDOW14QLWowA7bB3GPe2qjlk0sjS5fBavs9XD1fo,5185
|
30
|
-
pikoai-0.1.
|
31
|
-
pikoai-0.1.
|
32
|
-
pikoai-0.1.
|
33
|
-
pikoai-0.1.
|
34
|
-
pikoai-0.1.
|
35
|
-
pikoai-0.1.
|
30
|
+
pikoai-0.1.17.dist-info/licenses/LICENSE,sha256=cELUVOboOAderKFp8bdtcM5VyJi61YH1oDbRhOuoQZw,1067
|
31
|
+
pikoai-0.1.17.dist-info/METADATA,sha256=6RNL3fe8b177XNA4YfqQKC9o8YFe--RQ5DKBafQfhpg,2962
|
32
|
+
pikoai-0.1.17.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
33
|
+
pikoai-0.1.17.dist-info/entry_points.txt,sha256=xjZnheDymNDnQ0o84R0jZKEITrhNbzQWN-AhqfA_d6s,50
|
34
|
+
pikoai-0.1.17.dist-info/top_level.txt,sha256=hWzBNE7UQsuNcENIOksGcJED08k3ZGRRn2X5jnStICU,53
|
35
|
+
pikoai-0.1.17.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|