janito 2.21.0__py3-none-any.whl → 2.22.0__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.
- janito/README.md +0 -0
- janito/agent/setup_agent.py +34 -4
- janito/agent/templates/profiles/system_prompt_template_Developer_with_Python_Tools.txt.j2 +0 -0
- janito/agent/templates/profiles/system_prompt_template_developer.txt.j2 +0 -0
- janito/agent/templates/profiles/system_prompt_template_market_analyst.txt.j2 +10 -0
- janito/agent/templates/profiles/system_prompt_template_model_conversation_without_tools_or_context.txt.j2 +0 -0
- janito/cli/chat_mode/session_profile_select.py +20 -3
- janito/cli/chat_mode/shell/commands.bak.zip +0 -0
- janito/cli/chat_mode/shell/session.bak.zip +0 -0
- janito/cli/cli_commands/list_profiles.py +29 -1
- janito/cli/cli_commands/show_system_prompt.py +45 -4
- janito/docs/GETTING_STARTED.md +0 -0
- janito/drivers/dashscope.bak.zip +0 -0
- janito/drivers/openai/README.md +0 -0
- janito/drivers/openai_responses.bak.zip +0 -0
- janito/llm/README.md +0 -0
- janito/mkdocs.yml +0 -0
- janito/providers/dashscope.bak.zip +0 -0
- janito/providers/ibm/README.md +0 -0
- janito/providers/ibm/model_info.py +9 -0
- janito/shell.bak.zip +0 -0
- janito/tools/DOCSTRING_STANDARD.txt +0 -0
- janito/tools/README.md +0 -0
- janito/tools/adapters/local/fetch_url.py +96 -8
- janito/tools/outline_file.bak.zip +0 -0
- {janito-2.21.0.dist-info → janito-2.22.0.dist-info}/METADATA +411 -411
- {janito-2.21.0.dist-info → janito-2.22.0.dist-info}/RECORD +13 -12
- {janito-2.21.0.dist-info → janito-2.22.0.dist-info}/entry_points.txt +0 -0
- {janito-2.21.0.dist-info → janito-2.22.0.dist-info}/licenses/LICENSE +0 -0
- {janito-2.21.0.dist-info → janito-2.22.0.dist-info}/top_level.txt +0 -0
- {janito-2.21.0.dist-info → janito-2.22.0.dist-info}/WHEEL +0 -0
janito/README.md
CHANGED
File without changes
|
janito/agent/setup_agent.py
CHANGED
@@ -52,10 +52,40 @@ def _load_template_content(profile, templates_dir):
|
|
52
52
|
with open(user_template_path, "r", encoding="utf-8") as file:
|
53
53
|
return file.read(), user_template_path
|
54
54
|
|
55
|
-
# If nothing matched, raise an informative error
|
56
|
-
|
57
|
-
|
58
|
-
)
|
55
|
+
# If nothing matched, list available profiles and raise an informative error
|
56
|
+
from janito.cli.cli_commands.list_profiles import _gather_default_profiles, _gather_user_profiles
|
57
|
+
|
58
|
+
default_profiles = _gather_default_profiles()
|
59
|
+
user_profiles = _gather_user_profiles()
|
60
|
+
|
61
|
+
available_profiles = []
|
62
|
+
if default_profiles:
|
63
|
+
available_profiles.extend([(p, "default") for p in default_profiles])
|
64
|
+
if user_profiles:
|
65
|
+
available_profiles.extend([(p, "user") for p in user_profiles])
|
66
|
+
|
67
|
+
# Normalize the input profile for better matching suggestions
|
68
|
+
normalized_input = re.sub(r"\s+", " ", profile.strip().lower())
|
69
|
+
|
70
|
+
if available_profiles:
|
71
|
+
profile_list = "\n".join([f" - {name} ({source})" for name, source in available_profiles])
|
72
|
+
|
73
|
+
# Find close matches
|
74
|
+
close_matches = []
|
75
|
+
for name, source in available_profiles:
|
76
|
+
normalized_name = name.lower()
|
77
|
+
if normalized_input in normalized_name or normalized_name in normalized_input:
|
78
|
+
close_matches.append(name)
|
79
|
+
|
80
|
+
suggestion = ""
|
81
|
+
if close_matches:
|
82
|
+
suggestion = f"\nDid you mean: {', '.join(close_matches)}?"
|
83
|
+
|
84
|
+
error_msg = f"[janito] Could not find profile '{profile}'. Available profiles:\n{profile_list}{suggestion}"
|
85
|
+
else:
|
86
|
+
error_msg = f"[janito] Could not find profile '{profile}'. No profiles available."
|
87
|
+
|
88
|
+
raise FileNotFoundError(error_msg)
|
59
89
|
# Replace spaces in profile name with underscores for filename resolution
|
60
90
|
sanitized_profile = re.sub(r"\\s+", "_", profile.strip()) if profile else profile
|
61
91
|
"""
|
File without changes
|
File without changes
|
@@ -0,0 +1,10 @@
|
|
1
|
+
You are: market analyst specializing in financial markets, business intelligence, and economic research
|
2
|
+
|
3
|
+
Focus on providing market analysis, financial insights, and business intelligence. When analyzing data:
|
4
|
+
|
5
|
+
• Identify market trends and patterns
|
6
|
+
• Provide quantitative analysis with clear metrics
|
7
|
+
• Consider economic indicators and market sentiment
|
8
|
+
• Offer actionable insights for business decisions
|
9
|
+
• Use appropriate financial terminology and frameworks
|
10
|
+
• Reference relevant market data, benchmarks, and industry standards
|
File without changes
|
@@ -111,6 +111,7 @@ def select_profile():
|
|
111
111
|
choices = [
|
112
112
|
"Developer with Python Tools",
|
113
113
|
"Developer",
|
114
|
+
"Market Analyst",
|
114
115
|
"Custom system prompt...",
|
115
116
|
"Raw Model Session (no tools, no context)",
|
116
117
|
]
|
@@ -146,9 +147,9 @@ def select_profile():
|
|
146
147
|
from jinja2 import Template
|
147
148
|
from janito.agent.setup_agent import _prepare_template_context
|
148
149
|
|
149
|
-
|
150
|
-
|
151
|
-
|
150
|
+
# Get the absolute path relative to the current script location
|
151
|
+
current_dir = Path(__file__).parent
|
152
|
+
template_path = current_dir / "../../agent/templates/profiles/system_prompt_template_developer.txt.j2"
|
152
153
|
with open(template_path, "r", encoding="utf-8") as f:
|
153
154
|
template_content = f.read()
|
154
155
|
|
@@ -156,4 +157,20 @@ def select_profile():
|
|
156
157
|
context = _prepare_template_context("developer", "Developer", None)
|
157
158
|
prompt = template.render(**context)
|
158
159
|
return {"profile": "Developer", "profile_system_prompt": prompt}
|
160
|
+
elif answer == "Market Analyst":
|
161
|
+
# Return the content of the built-in Market Analyst profile prompt
|
162
|
+
from pathlib import Path
|
163
|
+
from jinja2 import Template
|
164
|
+
from janito.agent.setup_agent import _prepare_template_context
|
165
|
+
|
166
|
+
# Get the absolute path relative to the current script location
|
167
|
+
current_dir = Path(__file__).parent
|
168
|
+
template_path = current_dir / "../../agent/templates/profiles/system_prompt_template_market_analyst.txt.j2"
|
169
|
+
with open(template_path, "r", encoding="utf-8") as f:
|
170
|
+
template_content = f.read()
|
171
|
+
|
172
|
+
template = Template(template_content)
|
173
|
+
context = _prepare_template_context("market_analyst", "Market Analyst", None)
|
174
|
+
prompt = template.render(**context)
|
175
|
+
return {"profile": "Market Analyst", "profile_system_prompt": prompt}
|
159
176
|
return answer
|
File without changes
|
File without changes
|
@@ -19,7 +19,35 @@ def _extract_profile_name(filename: str) -> str:
|
|
19
19
|
filename = filename[len(_PREFIX) :]
|
20
20
|
if filename.endswith(_SUFFIX):
|
21
21
|
filename = filename[: -len(_SUFFIX)]
|
22
|
-
|
22
|
+
|
23
|
+
# Convert to title case for consistent capitalization, but handle common acronyms
|
24
|
+
name = filename.replace("_", " ")
|
25
|
+
|
26
|
+
# Convert to proper title case with consistent capitalization
|
27
|
+
name = filename.replace("_", " ")
|
28
|
+
|
29
|
+
# Handle special cases and acronyms
|
30
|
+
special_cases = {
|
31
|
+
"python": "Python",
|
32
|
+
"tools": "Tools",
|
33
|
+
"model": "Model",
|
34
|
+
"context": "Context",
|
35
|
+
"developer": "Developer",
|
36
|
+
"analyst": "Analyst",
|
37
|
+
"conversation": "Conversation",
|
38
|
+
"without": "Without"
|
39
|
+
}
|
40
|
+
|
41
|
+
words = name.split()
|
42
|
+
capitalized_words = []
|
43
|
+
for word in words:
|
44
|
+
lower_word = word.lower()
|
45
|
+
if lower_word in special_cases:
|
46
|
+
capitalized_words.append(special_cases[lower_word])
|
47
|
+
else:
|
48
|
+
capitalized_words.append(word.capitalize())
|
49
|
+
|
50
|
+
return " ".join(capitalized_words)
|
23
51
|
|
24
52
|
|
25
53
|
def _gather_default_profiles():
|
@@ -60,7 +60,17 @@ def _load_template(profile, templates_dir):
|
|
60
60
|
).open("r", encoding="utf-8") as file:
|
61
61
|
template_content = file.read()
|
62
62
|
except (FileNotFoundError, ModuleNotFoundError, AttributeError):
|
63
|
-
|
63
|
+
# Also check user profiles directory
|
64
|
+
from pathlib import Path
|
65
|
+
import os
|
66
|
+
user_profiles_dir = Path(os.path.expanduser("~/.janito/profiles"))
|
67
|
+
user_template_path = user_profiles_dir / template_filename
|
68
|
+
if user_template_path.exists():
|
69
|
+
with open(user_template_path, "r", encoding="utf-8") as file:
|
70
|
+
template_content = file.read()
|
71
|
+
else:
|
72
|
+
template_content = None
|
73
|
+
return template_filename, template_content
|
64
74
|
return template_filename, template_content
|
65
75
|
|
66
76
|
|
@@ -117,9 +127,40 @@ def handle_show_system_prompt(args):
|
|
117
127
|
|
118
128
|
if not template_content:
|
119
129
|
if profile:
|
120
|
-
|
121
|
-
|
122
|
-
|
130
|
+
from janito.cli.cli_commands.list_profiles import _gather_default_profiles, _gather_user_profiles
|
131
|
+
import re
|
132
|
+
|
133
|
+
default_profiles = _gather_default_profiles()
|
134
|
+
user_profiles = _gather_user_profiles()
|
135
|
+
|
136
|
+
available_profiles = []
|
137
|
+
if default_profiles:
|
138
|
+
available_profiles.extend([(p, "default") for p in default_profiles])
|
139
|
+
if user_profiles:
|
140
|
+
available_profiles.extend([(p, "user") for p in user_profiles])
|
141
|
+
|
142
|
+
# Normalize the input profile for better matching suggestions
|
143
|
+
normalized_input = re.sub(r"\s+", " ", profile.strip().lower())
|
144
|
+
|
145
|
+
if available_profiles:
|
146
|
+
profile_list = "\n".join([f" - {name} ({source})" for name, source in available_profiles])
|
147
|
+
|
148
|
+
# Find close matches
|
149
|
+
close_matches = []
|
150
|
+
for name, source in available_profiles:
|
151
|
+
normalized_name = name.lower()
|
152
|
+
if normalized_input in normalized_name or normalized_name in normalized_input:
|
153
|
+
close_matches.append(name)
|
154
|
+
|
155
|
+
suggestion = ""
|
156
|
+
if close_matches:
|
157
|
+
suggestion = f"\nDid you mean: {', '.join(close_matches)}?"
|
158
|
+
|
159
|
+
error_msg = f"[janito] Could not find profile '{profile}'. Available profiles:\n{profile_list}{suggestion}"
|
160
|
+
else:
|
161
|
+
error_msg = f"[janito] Could not find profile '{profile}'. No profiles available."
|
162
|
+
|
163
|
+
raise FileNotFoundError(error_msg)
|
123
164
|
else:
|
124
165
|
print(
|
125
166
|
f"[janito] Could not find {template_filename} in {templates_dir / template_filename} nor in janito.agent.templates.profiles package."
|
janito/docs/GETTING_STARTED.md
CHANGED
File without changes
|
janito/drivers/dashscope.bak.zip
CHANGED
File without changes
|
janito/drivers/openai/README.md
CHANGED
File without changes
|
File without changes
|
janito/llm/README.md
CHANGED
File without changes
|
janito/mkdocs.yml
CHANGED
File without changes
|
File without changes
|
janito/providers/ibm/README.md
CHANGED
File without changes
|
@@ -3,6 +3,15 @@
|
|
3
3
|
from janito.llm.model import LLMModelInfo
|
4
4
|
|
5
5
|
MODEL_SPECS = {
|
6
|
+
"openai/gpt-oss-120b": LLMModelInfo(
|
7
|
+
name="openai/gpt-oss-120b",
|
8
|
+
context=128000,
|
9
|
+
max_input=128000,
|
10
|
+
max_response=4096,
|
11
|
+
max_cot=4096,
|
12
|
+
thinking_supported=True,
|
13
|
+
category="IBM WatsonX",
|
14
|
+
),
|
6
15
|
"ibm/granite-3-8b-instruct": LLMModelInfo(
|
7
16
|
name="ibm/granite-3-8b-instruct",
|
8
17
|
context=128000,
|
janito/shell.bak.zip
CHANGED
File without changes
|
File without changes
|
janito/tools/README.md
CHANGED
File without changes
|
@@ -1,4 +1,8 @@
|
|
1
1
|
import requests
|
2
|
+
import time
|
3
|
+
import os
|
4
|
+
import json
|
5
|
+
from pathlib import Path
|
2
6
|
from bs4 import BeautifulSoup
|
3
7
|
from janito.tools.adapters.local.adapter import register_local_tool
|
4
8
|
from janito.tools.tool_base import ToolBase, ToolPermissions
|
@@ -18,6 +22,7 @@ class FetchUrlTool(ToolBase):
|
|
18
22
|
max_length (int, optional): Maximum number of characters to return. Defaults to 5000.
|
19
23
|
max_lines (int, optional): Maximum number of lines to return. Defaults to 200.
|
20
24
|
context_chars (int, optional): Characters of context around search matches. Defaults to 400.
|
25
|
+
timeout (int, optional): Timeout in seconds for the HTTP request. Defaults to 10.
|
21
26
|
Returns:
|
22
27
|
str: Extracted text content from the web page, or a warning message. Example:
|
23
28
|
- "<main text content...>"
|
@@ -28,15 +33,101 @@ class FetchUrlTool(ToolBase):
|
|
28
33
|
permissions = ToolPermissions(read=True)
|
29
34
|
tool_name = "fetch_url"
|
30
35
|
|
31
|
-
def
|
36
|
+
def __init__(self):
|
37
|
+
super().__init__()
|
38
|
+
self.cache_dir = Path.home() / ".janito" / "cache" / "fetch_url"
|
39
|
+
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
40
|
+
self.cache_file = self.cache_dir / "error_cache.json"
|
41
|
+
self._load_cache()
|
42
|
+
|
43
|
+
def _load_cache(self):
|
44
|
+
"""Load error cache from disk."""
|
45
|
+
if self.cache_file.exists():
|
46
|
+
try:
|
47
|
+
with open(self.cache_file, 'r', encoding='utf-8') as f:
|
48
|
+
self.error_cache = json.load(f)
|
49
|
+
except (json.JSONDecodeError, IOError):
|
50
|
+
self.error_cache = {}
|
51
|
+
else:
|
52
|
+
self.error_cache = {}
|
53
|
+
|
54
|
+
def _save_cache(self):
|
55
|
+
"""Save error cache to disk."""
|
56
|
+
try:
|
57
|
+
with open(self.cache_file, 'w', encoding='utf-8') as f:
|
58
|
+
json.dump(self.error_cache, f, indent=2)
|
59
|
+
except IOError:
|
60
|
+
pass # Silently fail if we can't write cache
|
61
|
+
|
62
|
+
def _get_cached_error(self, url: str) -> tuple[str, bool]:
|
63
|
+
"""
|
64
|
+
Check if we have a cached error for this URL.
|
65
|
+
Returns (error_message, is_cached) tuple.
|
66
|
+
"""
|
67
|
+
if url not in self.error_cache:
|
68
|
+
return None, False
|
69
|
+
|
70
|
+
entry = self.error_cache[url]
|
71
|
+
current_time = time.time()
|
72
|
+
|
73
|
+
# Different expiration times for different status codes
|
74
|
+
if entry['status_code'] == 403:
|
75
|
+
# Cache 403 errors for 24 hours (more permanent)
|
76
|
+
expiration_time = 24 * 3600
|
77
|
+
elif entry['status_code'] == 404:
|
78
|
+
# Cache 404 errors for 1 hour (more temporary)
|
79
|
+
expiration_time = 3600
|
80
|
+
else:
|
81
|
+
# Cache other 4xx errors for 30 minutes
|
82
|
+
expiration_time = 1800
|
83
|
+
|
84
|
+
if current_time - entry['timestamp'] > expiration_time:
|
85
|
+
# Cache expired, remove it
|
86
|
+
del self.error_cache[url]
|
87
|
+
self._save_cache()
|
88
|
+
return None, False
|
89
|
+
|
90
|
+
return entry['message'], True
|
91
|
+
|
92
|
+
def _cache_error(self, url: str, status_code: int, message: str):
|
93
|
+
"""Cache an HTTP error response."""
|
94
|
+
self.error_cache[url] = {
|
95
|
+
'status_code': status_code,
|
96
|
+
'message': message,
|
97
|
+
'timestamp': time.time()
|
98
|
+
}
|
99
|
+
self._save_cache()
|
100
|
+
|
101
|
+
def _fetch_url_content(self, url: str, timeout: int = 10) -> str:
|
32
102
|
"""Fetch URL content and handle HTTP errors."""
|
103
|
+
# Check cache first for known errors
|
104
|
+
cached_error, is_cached = self._get_cached_error(url)
|
105
|
+
if cached_error:
|
106
|
+
self.report_warning(
|
107
|
+
tr(
|
108
|
+
"ℹ️ Using cached HTTP error for URL: {url}",
|
109
|
+
url=url,
|
110
|
+
),
|
111
|
+
ReportAction.READ,
|
112
|
+
)
|
113
|
+
return cached_error
|
114
|
+
|
33
115
|
try:
|
34
|
-
response = requests.get(url, timeout=
|
116
|
+
response = requests.get(url, timeout=timeout)
|
35
117
|
response.raise_for_status()
|
36
118
|
return response.text
|
37
119
|
except requests.exceptions.HTTPError as http_err:
|
38
120
|
status_code = http_err.response.status_code if http_err.response else None
|
39
121
|
if status_code and 400 <= status_code < 500:
|
122
|
+
error_message = tr(
|
123
|
+
"Warning: HTTP {status_code} error for URL: {url}",
|
124
|
+
status_code=status_code,
|
125
|
+
url=url,
|
126
|
+
)
|
127
|
+
# Cache 403 and 404 errors
|
128
|
+
if status_code in [403, 404]:
|
129
|
+
self._cache_error(url, status_code, error_message)
|
130
|
+
|
40
131
|
self.report_error(
|
41
132
|
tr(
|
42
133
|
"❗ HTTP {status_code} error for URL: {url}",
|
@@ -45,11 +136,7 @@ class FetchUrlTool(ToolBase):
|
|
45
136
|
),
|
46
137
|
ReportAction.READ,
|
47
138
|
)
|
48
|
-
return
|
49
|
-
"Warning: HTTP {status_code} error for URL: {url}",
|
50
|
-
status_code=status_code,
|
51
|
-
url=url,
|
52
|
-
)
|
139
|
+
return error_message
|
53
140
|
else:
|
54
141
|
self.report_error(
|
55
142
|
tr(
|
@@ -123,6 +210,7 @@ class FetchUrlTool(ToolBase):
|
|
123
210
|
max_length: int = 5000,
|
124
211
|
max_lines: int = 200,
|
125
212
|
context_chars: int = 400,
|
213
|
+
timeout: int = 10,
|
126
214
|
) -> str:
|
127
215
|
if not url.strip():
|
128
216
|
self.report_warning(tr("ℹ️ Empty URL provided."), ReportAction.READ)
|
@@ -131,7 +219,7 @@ class FetchUrlTool(ToolBase):
|
|
131
219
|
self.report_action(tr("🌐 Fetch URL '{url}' ...", url=url), ReportAction.READ)
|
132
220
|
|
133
221
|
# Fetch URL content
|
134
|
-
html_content = self._fetch_url_content(url)
|
222
|
+
html_content = self._fetch_url_content(url, timeout=timeout)
|
135
223
|
if html_content.startswith("Warning:"):
|
136
224
|
return html_content
|
137
225
|
|
File without changes
|