janito 2.7.0__py3-none-any.whl → 2.9.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/__init__.py +0 -1
- janito/__main__.py +0 -1
- janito/_version.py +0 -3
- janito/agent/setup_agent.py +77 -10
- janito/agent/templates/profiles/{system_prompt_template_plain_software_developer.txt.j2 → system_prompt_template_Developer_with_Python_Tools.txt.j2} +5 -1
- janito/agent/templates/profiles/system_prompt_template_developer.txt.j2 +3 -12
- janito/cli/__init__.py +0 -1
- janito/cli/chat_mode/bindings.py +1 -1
- janito/cli/chat_mode/chat_entry.py +0 -2
- janito/cli/chat_mode/prompt_style.py +0 -3
- janito/cli/chat_mode/script_runner.py +9 -5
- janito/cli/chat_mode/session.py +100 -37
- janito/cli/chat_mode/session_profile_select.py +61 -52
- janito/cli/chat_mode/shell/commands/__init__.py +1 -5
- janito/cli/chat_mode/shell/commands/_priv_check.py +1 -0
- janito/cli/chat_mode/shell/commands/_priv_status.py +13 -0
- janito/cli/chat_mode/shell/commands/bang.py +10 -3
- janito/cli/chat_mode/shell/commands/conversation_restart.py +24 -7
- janito/cli/chat_mode/shell/commands/execute.py +22 -7
- janito/cli/chat_mode/shell/commands/help.py +4 -1
- janito/cli/chat_mode/shell/commands/model.py +13 -5
- janito/cli/chat_mode/shell/commands/privileges.py +21 -0
- janito/cli/chat_mode/shell/commands/prompt.py +0 -2
- janito/cli/chat_mode/shell/commands/read.py +22 -5
- janito/cli/chat_mode/shell/commands/tools.py +15 -4
- janito/cli/chat_mode/shell/commands/write.py +22 -5
- janito/cli/chat_mode/shell/input_history.py +3 -1
- janito/cli/chat_mode/shell/session/manager.py +0 -2
- janito/cli/chat_mode/toolbar.py +25 -19
- janito/cli/cli_commands/list_config.py +31 -0
- janito/cli/cli_commands/list_models.py +1 -1
- janito/cli/cli_commands/list_profiles.py +79 -0
- janito/cli/cli_commands/list_providers.py +1 -0
- janito/cli/cli_commands/list_tools.py +35 -7
- janito/cli/cli_commands/model_utils.py +5 -3
- janito/cli/cli_commands/show_config.py +16 -11
- janito/cli/cli_commands/show_system_prompt.py +23 -9
- janito/cli/config.py +0 -13
- janito/cli/core/getters.py +16 -1
- janito/cli/core/runner.py +25 -8
- janito/cli/core/setters.py +13 -76
- janito/cli/main_cli.py +60 -27
- janito/cli/prompt_core.py +19 -18
- janito/cli/prompt_setup.py +6 -3
- janito/cli/rich_terminal_reporter.py +19 -5
- janito/cli/single_shot_mode/handler.py +14 -5
- janito/cli/verbose_output.py +5 -1
- janito/config.py +1 -0
- janito/config_manager.py +15 -2
- janito/drivers/azure_openai/driver.py +27 -30
- janito/drivers/openai/driver.py +53 -36
- janito/formatting_token.py +12 -4
- janito/llm/agent.py +15 -6
- janito/llm/driver.py +1 -0
- janito/llm/provider.py +1 -1
- janito/provider_registry.py +31 -70
- janito/providers/__init__.py +1 -0
- janito/providers/anthropic/model_info.py +0 -1
- janito/providers/anthropic/provider.py +9 -14
- janito/providers/azure_openai/provider.py +10 -5
- janito/providers/deepseek/provider.py +5 -4
- janito/providers/google/model_info.py +4 -2
- janito/providers/google/provider.py +11 -5
- janito/providers/groq/__init__.py +1 -0
- janito/providers/groq/model_info.py +45 -0
- janito/providers/groq/provider.py +76 -0
- janito/providers/moonshotai/provider.py +11 -4
- janito/providers/openai/model_info.py +0 -1
- janito/providers/openai/provider.py +6 -7
- janito/tools/__init__.py +2 -0
- janito/tools/adapters/local/__init__.py +2 -1
- janito/tools/adapters/local/adapter.py +21 -4
- janito/tools/adapters/local/ask_user.py +1 -0
- janito/tools/adapters/local/copy_file.py +1 -0
- janito/tools/adapters/local/create_directory.py +1 -0
- janito/tools/adapters/local/create_file.py +1 -0
- janito/tools/adapters/local/delete_text_in_file.py +2 -1
- janito/tools/adapters/local/fetch_url.py +1 -0
- janito/tools/adapters/local/find_files.py +7 -6
- janito/tools/adapters/local/get_file_outline/core.py +1 -0
- janito/tools/adapters/local/get_file_outline/java_outline.py +22 -15
- janito/tools/adapters/local/get_file_outline/search_outline.py +1 -0
- janito/tools/adapters/local/move_file.py +1 -0
- janito/tools/adapters/local/open_html_in_browser.py +15 -5
- janito/tools/adapters/local/open_url.py +1 -0
- janito/tools/adapters/local/python_code_run.py +1 -0
- janito/tools/adapters/local/python_command_run.py +1 -0
- janito/tools/adapters/local/python_file_run.py +1 -0
- janito/tools/adapters/local/read_files.py +19 -4
- janito/tools/adapters/local/remove_directory.py +1 -0
- janito/tools/adapters/local/remove_file.py +1 -0
- janito/tools/adapters/local/replace_text_in_file.py +4 -3
- janito/tools/adapters/local/run_bash_command.py +1 -0
- janito/tools/adapters/local/run_powershell_command.py +1 -0
- janito/tools/adapters/local/search_text/core.py +18 -17
- janito/tools/adapters/local/search_text/match_lines.py +5 -5
- janito/tools/adapters/local/search_text/pattern_utils.py +1 -1
- janito/tools/adapters/local/search_text/traverse_directory.py +7 -7
- janito/tools/adapters/local/validate_file_syntax/core.py +1 -1
- janito/tools/adapters/local/validate_file_syntax/html_validator.py +8 -1
- janito/tools/disabled_tools.py +68 -0
- janito/tools/path_security.py +18 -11
- janito/tools/permissions.py +6 -0
- janito/tools/permissions_parse.py +4 -3
- janito/tools/tool_base.py +11 -5
- janito/tools/tool_use_tracker.py +1 -4
- janito/tools/tool_utils.py +1 -1
- janito/tools/tools_adapter.py +57 -25
- {janito-2.7.0.dist-info → janito-2.9.0.dist-info}/METADATA +11 -19
- janito-2.9.0.dist-info/RECORD +205 -0
- janito/cli/chat_mode/shell/commands/livelogs.py +0 -49
- janito/drivers/mistralai/driver.py +0 -41
- janito/providers/mistralai/model_info.py +0 -37
- janito/providers/mistralai/provider.py +0 -72
- janito/providers/provider_static_info.py +0 -21
- janito-2.7.0.dist-info/RECORD +0 -202
- /janito/agent/templates/profiles/{system_prompt_template_assistant.txt.j2 → system_prompt_template_model_conversation_without_tools_or_context.txt.j2} +0 -0
- {janito-2.7.0.dist-info → janito-2.9.0.dist-info}/WHEEL +0 -0
- {janito-2.7.0.dist-info → janito-2.9.0.dist-info}/entry_points.txt +0 -0
- {janito-2.7.0.dist-info → janito-2.9.0.dist-info}/licenses/LICENSE +0 -0
- {janito-2.7.0.dist-info → janito-2.9.0.dist-info}/top_level.txt +0 -0
@@ -1,14 +1,6 @@
|
|
1
1
|
from janito.drivers.openai.driver import OpenAIModelDriver
|
2
2
|
|
3
|
-
|
4
|
-
try:
|
5
|
-
from openai import AzureOpenAI
|
6
|
-
|
7
|
-
DRIVER_AVAILABLE = True
|
8
|
-
DRIVER_UNAVAILABLE_REASON = None
|
9
|
-
except ImportError:
|
10
|
-
DRIVER_AVAILABLE = False
|
11
|
-
DRIVER_UNAVAILABLE_REASON = "Missing dependency: openai (pip install openai)"
|
3
|
+
from openai import AzureOpenAI
|
12
4
|
|
13
5
|
from janito.llm.driver_config import LLMDriverConfig
|
14
6
|
|
@@ -16,30 +8,22 @@ from janito.llm.driver_config import LLMDriverConfig
|
|
16
8
|
class AzureOpenAIModelDriver(OpenAIModelDriver):
|
17
9
|
def start(self, *args, **kwargs):
|
18
10
|
# Ensure azure_deployment_name is set before starting
|
19
|
-
config = getattr(self,
|
11
|
+
config = getattr(self, "config", None)
|
20
12
|
deployment_name = None
|
21
|
-
if config and hasattr(config,
|
22
|
-
deployment_name = config.extra.get(
|
13
|
+
if config and hasattr(config, "extra"):
|
14
|
+
deployment_name = config.extra.get("azure_deployment_name")
|
23
15
|
if not deployment_name:
|
24
|
-
raise RuntimeError(
|
16
|
+
raise RuntimeError(
|
17
|
+
"AzureOpenAIModelDriver requires 'azure_deployment_name' to be set in config.extra['azure_deployment_name'] before starting."
|
18
|
+
)
|
25
19
|
# Call parent start if exists
|
26
|
-
if hasattr(super(),
|
20
|
+
if hasattr(super(), "start"):
|
27
21
|
return super().start(*args, **kwargs)
|
28
22
|
|
29
|
-
available = DRIVER_AVAILABLE
|
30
|
-
unavailable_reason = DRIVER_UNAVAILABLE_REASON
|
31
|
-
|
32
|
-
@classmethod
|
33
|
-
def is_available(cls):
|
34
|
-
return cls.available
|
35
|
-
|
36
23
|
required_config = {"base_url"} # Update key as used in your config logic
|
37
24
|
|
38
25
|
def __init__(self, tools_adapter=None, provider_name=None):
|
39
|
-
|
40
|
-
raise ImportError(
|
41
|
-
f"AzureOpenAIModelDriver unavailable: {self.unavailable_reason}"
|
42
|
-
)
|
26
|
+
|
43
27
|
# Ensure proper parent initialization
|
44
28
|
super().__init__(tools_adapter=tools_adapter, provider_name=provider_name)
|
45
29
|
self.azure_endpoint = None
|
@@ -52,25 +36,35 @@ class AzureOpenAIModelDriver(OpenAIModelDriver):
|
|
52
36
|
Also ensures tool schemas are included if tools_adapter is present.
|
53
37
|
"""
|
54
38
|
api_kwargs = super()._prepare_api_kwargs(config, conversation)
|
55
|
-
deployment_name =
|
39
|
+
deployment_name = (
|
40
|
+
config.extra.get("azure_deployment_name")
|
41
|
+
if hasattr(config, "extra")
|
42
|
+
else None
|
43
|
+
)
|
56
44
|
if deployment_name:
|
57
45
|
api_kwargs["model"] = deployment_name
|
58
46
|
# Patch: Ensure tools are included for Azure as for OpenAI
|
59
47
|
if self.tools_adapter:
|
60
48
|
try:
|
61
|
-
from janito.providers.openai.schema_generator import
|
49
|
+
from janito.providers.openai.schema_generator import (
|
50
|
+
generate_tool_schemas,
|
51
|
+
)
|
52
|
+
|
62
53
|
tool_classes = self.tools_adapter.get_tool_classes()
|
63
54
|
tool_schemas = generate_tool_schemas(tool_classes)
|
64
55
|
api_kwargs["tools"] = tool_schemas
|
65
56
|
except Exception as e:
|
66
57
|
api_kwargs["tools"] = []
|
67
58
|
if hasattr(config, "verbose_api") and config.verbose_api:
|
68
|
-
print(
|
59
|
+
print(
|
60
|
+
f"[AzureOpenAIModelDriver] Tool schema generation failed: {e}"
|
61
|
+
)
|
69
62
|
return api_kwargs
|
70
63
|
|
71
64
|
def _instantiate_openai_client(self, config):
|
72
65
|
try:
|
73
66
|
from openai import AzureOpenAI
|
67
|
+
|
74
68
|
api_key_display = str(config.api_key)
|
75
69
|
if api_key_display and len(api_key_display) > 8:
|
76
70
|
api_key_display = api_key_display[:4] + "..." + api_key_display[-4:]
|
@@ -83,8 +77,11 @@ class AzureOpenAIModelDriver(OpenAIModelDriver):
|
|
83
77
|
client = AzureOpenAI(**client_kwargs)
|
84
78
|
return client
|
85
79
|
except Exception as e:
|
86
|
-
print(
|
80
|
+
print(
|
81
|
+
f"[ERROR] Exception during AzureOpenAI client instantiation: {e}",
|
82
|
+
flush=True,
|
83
|
+
)
|
87
84
|
import traceback
|
85
|
+
|
88
86
|
print(traceback.format_exc(), flush=True)
|
89
87
|
raise
|
90
|
-
|
janito/drivers/openai/driver.py
CHANGED
@@ -12,15 +12,7 @@ from janito.llm.driver_input import DriverInput
|
|
12
12
|
from janito.driver_events import RequestFinished, RequestStatus, RateLimitRetry
|
13
13
|
from janito.llm.message_parts import TextMessagePart, FunctionCallMessagePart
|
14
14
|
|
15
|
-
|
16
|
-
try:
|
17
|
-
import openai
|
18
|
-
|
19
|
-
DRIVER_AVAILABLE = True
|
20
|
-
DRIVER_UNAVAILABLE_REASON = None
|
21
|
-
except ImportError:
|
22
|
-
DRIVER_AVAILABLE = False
|
23
|
-
DRIVER_UNAVAILABLE_REASON = "Missing dependency: openai (pip install openai)"
|
15
|
+
import openai
|
24
16
|
|
25
17
|
|
26
18
|
class OpenAIModelDriver(LLMDriver):
|
@@ -33,8 +25,6 @@ class OpenAIModelDriver(LLMDriver):
|
|
33
25
|
"""
|
34
26
|
OpenAI LLM driver (threaded, queue-based, stateless). Uses input/output queues accessible via instance attributes.
|
35
27
|
"""
|
36
|
-
available = DRIVER_AVAILABLE
|
37
|
-
unavailable_reason = DRIVER_UNAVAILABLE_REASON
|
38
28
|
|
39
29
|
def __init__(self, tools_adapter=None, provider_name=None):
|
40
30
|
super().__init__(tools_adapter=tools_adapter, provider_name=provider_name)
|
@@ -84,13 +74,18 @@ class OpenAIModelDriver(LLMDriver):
|
|
84
74
|
api_kwargs[p] = v
|
85
75
|
api_kwargs["messages"] = conversation
|
86
76
|
api_kwargs["stream"] = False
|
77
|
+
# Always return the prepared kwargs, even if no tools are registered. The
|
78
|
+
# OpenAI Python SDK expects a **mapping** – passing *None* will raise
|
79
|
+
# ``TypeError: argument after ** must be a mapping, not NoneType``.
|
87
80
|
return api_kwargs
|
88
81
|
|
89
82
|
def _call_api(self, driver_input: DriverInput):
|
90
83
|
"""Call the OpenAI-compatible chat completion endpoint with retry and error handling."""
|
91
84
|
cancel_event = getattr(driver_input, "cancel_event", None)
|
92
85
|
config = driver_input.config
|
93
|
-
conversation = self.convert_history_to_api_messages(
|
86
|
+
conversation = self.convert_history_to_api_messages(
|
87
|
+
driver_input.conversation_history
|
88
|
+
)
|
94
89
|
request_id = getattr(config, "request_id", None)
|
95
90
|
self._print_api_call_start(config)
|
96
91
|
client = self._instantiate_openai_client(config)
|
@@ -108,14 +103,18 @@ class OpenAIModelDriver(LLMDriver):
|
|
108
103
|
self._handle_api_success(config, result, request_id)
|
109
104
|
return result
|
110
105
|
except Exception as e:
|
111
|
-
if self._handle_api_exception(
|
106
|
+
if self._handle_api_exception(
|
107
|
+
e, config, api_kwargs, attempt, max_retries, request_id
|
108
|
+
):
|
112
109
|
attempt += 1
|
113
110
|
continue
|
114
111
|
raise
|
115
112
|
|
116
113
|
def _print_api_call_start(self, config):
|
117
114
|
if getattr(config, "verbose_api", False):
|
118
|
-
tool_adapter_name =
|
115
|
+
tool_adapter_name = (
|
116
|
+
type(self.tools_adapter).__name__ if self.tools_adapter else None
|
117
|
+
)
|
119
118
|
tool_names = []
|
120
119
|
if self.tools_adapter and hasattr(self.tools_adapter, "list_tools"):
|
121
120
|
try:
|
@@ -156,17 +155,21 @@ class OpenAIModelDriver(LLMDriver):
|
|
156
155
|
print("[OpenAI] API RESPONSE:", flush=True)
|
157
156
|
pretty.pprint(result)
|
158
157
|
|
159
|
-
def _handle_api_exception(
|
158
|
+
def _handle_api_exception(
|
159
|
+
self, e, config, api_kwargs, attempt, max_retries, request_id
|
160
|
+
):
|
160
161
|
status_code = getattr(e, "status_code", None)
|
161
162
|
err_str = str(e)
|
162
163
|
lower_err = err_str.lower()
|
163
164
|
is_insufficient_quota = (
|
164
|
-
"insufficient_quota" in lower_err
|
165
|
+
"insufficient_quota" in lower_err
|
166
|
+
or "exceeded your current quota" in lower_err
|
165
167
|
)
|
166
168
|
is_rate_limit = (
|
167
|
-
|
168
|
-
|
169
|
-
|
169
|
+
status_code == 429
|
170
|
+
or "error code: 429" in lower_err
|
171
|
+
or "resource_exhausted" in lower_err
|
172
|
+
) and not is_insufficient_quota
|
170
173
|
if not is_rate_limit or attempt > max_retries:
|
171
174
|
self._handle_fatal_exception(e, config, api_kwargs)
|
172
175
|
retry_delay = self._extract_retry_delay_seconds(e)
|
@@ -189,7 +192,9 @@ class OpenAIModelDriver(LLMDriver):
|
|
189
192
|
)
|
190
193
|
start_wait = time.time()
|
191
194
|
while time.time() - start_wait < retry_delay:
|
192
|
-
if self._check_cancel(
|
195
|
+
if self._check_cancel(
|
196
|
+
getattr(config, "cancel_event", None), request_id, before_call=False
|
197
|
+
):
|
193
198
|
return False
|
194
199
|
time.sleep(0.1)
|
195
200
|
return True
|
@@ -208,7 +213,9 @@ class OpenAIModelDriver(LLMDriver):
|
|
208
213
|
else:
|
209
214
|
payload = str(exception)
|
210
215
|
# Look for 'retryDelay': '41s' or similar
|
211
|
-
m = re.search(
|
216
|
+
m = re.search(
|
217
|
+
r"retryDelay['\"]?\s*[:=]\s*['\"]?(\d+(?:\.\d+)?)(s)?", payload
|
218
|
+
)
|
212
219
|
if m:
|
213
220
|
return float(m.group(1))
|
214
221
|
# Fallback: generic number of seconds in the message
|
@@ -249,13 +256,17 @@ class OpenAIModelDriver(LLMDriver):
|
|
249
256
|
# HTTP debug wrapper
|
250
257
|
if os.environ.get("OPENAI_DEBUG_HTTP", "0") == "1":
|
251
258
|
from http.client import HTTPConnection
|
259
|
+
|
252
260
|
HTTPConnection.debuglevel = 1
|
253
261
|
logging.basicConfig()
|
254
262
|
logging.getLogger().setLevel(logging.DEBUG)
|
255
263
|
requests_log = logging.getLogger("http.client")
|
256
264
|
requests_log.setLevel(logging.DEBUG)
|
257
265
|
requests_log.propagate = True
|
258
|
-
print(
|
266
|
+
print(
|
267
|
+
"[OpenAIModelDriver] HTTP debug enabled via OPENAI_DEBUG_HTTP=1",
|
268
|
+
flush=True,
|
269
|
+
)
|
259
270
|
|
260
271
|
client = openai.OpenAI(**client_kwargs)
|
261
272
|
return client
|
@@ -377,26 +388,32 @@ class OpenAIModelDriver(LLMDriver):
|
|
377
388
|
results = [content]
|
378
389
|
for result in results:
|
379
390
|
if isinstance(result, dict):
|
380
|
-
api_messages.append(
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
391
|
+
api_messages.append(
|
392
|
+
{
|
393
|
+
"role": "tool",
|
394
|
+
"content": result.get("content", ""),
|
395
|
+
"name": result.get("name", ""),
|
396
|
+
"tool_call_id": result.get("tool_call_id", ""),
|
397
|
+
}
|
398
|
+
)
|
386
399
|
else:
|
387
|
-
api_messages.append(
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
400
|
+
api_messages.append(
|
401
|
+
{
|
402
|
+
"role": "tool",
|
403
|
+
"content": str(result),
|
404
|
+
"name": "",
|
405
|
+
"tool_call_id": "",
|
406
|
+
}
|
407
|
+
)
|
393
408
|
|
394
409
|
def _handle_tool_calls(self, api_messages, content):
|
395
410
|
try:
|
396
411
|
tool_calls = json.loads(content) if isinstance(content, str) else content
|
397
412
|
except Exception:
|
398
413
|
tool_calls = []
|
399
|
-
api_messages.append(
|
414
|
+
api_messages.append(
|
415
|
+
{"role": "assistant", "content": "", "tool_calls": tool_calls}
|
416
|
+
)
|
400
417
|
|
401
418
|
def _handle_other_roles(self, api_messages, msg, role, content):
|
402
419
|
if role == "function":
|
@@ -433,4 +450,4 @@ class OpenAIModelDriver(LLMDriver):
|
|
433
450
|
)
|
434
451
|
)
|
435
452
|
# Extend here for other message part types if needed
|
436
|
-
return parts
|
453
|
+
return parts
|
janito/formatting_token.py
CHANGED
@@ -25,7 +25,9 @@ def format_tokens(n, tag=None, use_rich=False):
|
|
25
25
|
return val
|
26
26
|
|
27
27
|
|
28
|
-
def format_token_message_summary(
|
28
|
+
def format_token_message_summary(
|
29
|
+
msg_count, usage, width=96, use_rich=False, elapsed=None
|
30
|
+
):
|
29
31
|
"""
|
30
32
|
Returns a string (rich or pt markup) summarizing message count, last token usage, and elapsed time.
|
31
33
|
"""
|
@@ -40,16 +42,22 @@ def format_token_message_summary(msg_count, usage, width=96, use_rich=False, ela
|
|
40
42
|
f"Completion: {format_tokens(completion_tokens, 'tokens_out', use_rich)}, "
|
41
43
|
f"Total: {format_tokens(total_tokens, 'tokens_total', use_rich)}"
|
42
44
|
)
|
43
|
-
elapsed_part =
|
45
|
+
elapsed_part = (
|
46
|
+
f" | Elapsed: [cyan]{elapsed:.2f}s[/cyan]" if elapsed is not None else ""
|
47
|
+
)
|
44
48
|
return f"{left}{tokens_part}{elapsed_part}"
|
45
49
|
|
46
50
|
|
47
|
-
def print_token_message_summary(
|
51
|
+
def print_token_message_summary(
|
52
|
+
console, msg_count=None, usage=None, width=96, elapsed=None
|
53
|
+
):
|
48
54
|
"""Prints the summary using rich markup, using defaults from perf_singleton if not given. Optionally includes elapsed time."""
|
49
55
|
if usage is None:
|
50
56
|
usage = performance_collector.get_last_request_usage()
|
51
57
|
if msg_count is None:
|
52
58
|
msg_count = performance_collector.get_total_turns() or 0
|
53
|
-
line = format_token_message_summary(
|
59
|
+
line = format_token_message_summary(
|
60
|
+
msg_count, usage, width, use_rich=True, elapsed=elapsed
|
61
|
+
)
|
54
62
|
if line.strip():
|
55
63
|
console.print(Rule(line))
|
janito/llm/agent.py
CHANGED
@@ -97,6 +97,7 @@ class LLMAgent:
|
|
97
97
|
# Refresh allowed_permissions in context before rendering
|
98
98
|
from janito.tools.permissions import get_global_allowed_permissions
|
99
99
|
from janito.tools.tool_base import ToolPermissions
|
100
|
+
|
100
101
|
perms = get_global_allowed_permissions()
|
101
102
|
if isinstance(perms, ToolPermissions):
|
102
103
|
perm_str = ""
|
@@ -171,7 +172,7 @@ class LLMAgent:
|
|
171
172
|
)
|
172
173
|
|
173
174
|
def _process_next_response(
|
174
|
-
self, poll_timeout: float = 1.0, max_wait_time: float =
|
175
|
+
self, poll_timeout: float = 1.0, max_wait_time: float = 600.0
|
175
176
|
):
|
176
177
|
"""
|
177
178
|
Wait for a single event from the output queue (with timeout), process it, and return the result.
|
@@ -213,7 +214,6 @@ class LLMAgent:
|
|
213
214
|
]:
|
214
215
|
return (event, False)
|
215
216
|
|
216
|
-
|
217
217
|
def _get_event_from_output_queue(self, poll_timeout):
|
218
218
|
try:
|
219
219
|
return self.output_queue.get(timeout=poll_timeout)
|
@@ -306,6 +306,7 @@ class LLMAgent:
|
|
306
306
|
config = self.llm_provider.driver_config
|
307
307
|
loop_count = 1
|
308
308
|
import threading
|
309
|
+
|
309
310
|
cancel_event = threading.Event()
|
310
311
|
while True:
|
311
312
|
self._print_verbose_chat_loop(loop_count)
|
@@ -317,7 +318,9 @@ class LLMAgent:
|
|
317
318
|
cancel_event.set()
|
318
319
|
raise
|
319
320
|
if getattr(self, "verbose_agent", False):
|
320
|
-
print(
|
321
|
+
print(
|
322
|
+
f"[agent] [DEBUG] Returned from _process_next_response: result={result}, added_tool_results={added_tool_results}"
|
323
|
+
)
|
321
324
|
if self._should_exit_chat_loop(result, added_tool_results):
|
322
325
|
return result
|
323
326
|
loop_count += 1
|
@@ -332,11 +335,15 @@ class LLMAgent:
|
|
332
335
|
def _should_exit_chat_loop(self, result, added_tool_results):
|
333
336
|
if result is None:
|
334
337
|
if getattr(self, "verbose_agent", False):
|
335
|
-
print(
|
338
|
+
print(
|
339
|
+
"[agent] [INFO] Exiting chat loop: _process_next_response returned None result (likely timeout or error). Returning (None, False)."
|
340
|
+
)
|
336
341
|
return True
|
337
342
|
if not added_tool_results:
|
338
343
|
if getattr(self, "verbose_agent", False):
|
339
|
-
print(
|
344
|
+
print(
|
345
|
+
f"[agent] [INFO] Exiting chat loop: _process_next_response returned added_tool_results=False (final response or no more tool calls). Returning result: {result}"
|
346
|
+
)
|
340
347
|
return True
|
341
348
|
return False
|
342
349
|
|
@@ -434,7 +441,9 @@ class LLMAgent:
|
|
434
441
|
config.model = model_name
|
435
442
|
config.temperature = self._safe_float(getattr(model_spec, "default_temp", None))
|
436
443
|
config.max_tokens = self._safe_int(getattr(model_spec, "max_response", None))
|
437
|
-
config.max_completion_tokens = self._safe_int(
|
444
|
+
config.max_completion_tokens = self._safe_int(
|
445
|
+
getattr(model_spec, "max_cot", None)
|
446
|
+
)
|
438
447
|
config.top_p = None
|
439
448
|
config.presence_penalty = None
|
440
449
|
config.frequency_penalty = None
|
janito/llm/driver.py
CHANGED
@@ -55,6 +55,7 @@ class LLMDriver(ABC):
|
|
55
55
|
# Validate all tool schemas before starting the thread
|
56
56
|
if self.tools_adapter is not None:
|
57
57
|
from janito.tools.tools_schema import ToolSchemaBase
|
58
|
+
|
58
59
|
validator = ToolSchemaBase()
|
59
60
|
for tool in self.tools_adapter.get_tools():
|
60
61
|
# Validate the tool's class (not instance)
|
janito/llm/provider.py
CHANGED
@@ -17,7 +17,7 @@ class LLMProvider(ABC):
|
|
17
17
|
Abstract base class for Large Language Model (LLM) providers.
|
18
18
|
|
19
19
|
Provider Usage and Driver Communication Flow:
|
20
|
-
1. Provider class is selected (e.g., OpenAIProvider
|
20
|
+
1. Provider class is selected (e.g., OpenAIProvider).
|
21
21
|
2. An instance of the provider is created. This instance is bound to a specific configuration (LLMDriverConfig) containing model, credentials, etc.
|
22
22
|
3. All drivers created by that provider instance are associated with the bound config.
|
23
23
|
4. To communicate with an LLM, call create_driver() on the provider instance, which yields a driver configured for the attached config. Every driver created via this method inherits the provider's configuration.
|
janito/provider_registry.py
CHANGED
@@ -5,7 +5,6 @@ ProviderRegistry: Handles provider listing and selection logic for janito CLI.
|
|
5
5
|
from rich.table import Table
|
6
6
|
from janito.cli.console import shared_console
|
7
7
|
from janito.providers.registry import LLMProviderRegistry
|
8
|
-
from janito.providers.provider_static_info import STATIC_PROVIDER_METADATA
|
9
8
|
from janito.llm.auth import LLMAuthManager
|
10
9
|
import sys
|
11
10
|
from janito.exceptions import MissingProviderSelectionException
|
@@ -21,7 +20,9 @@ class ProviderRegistry:
|
|
21
20
|
self._print_table(table)
|
22
21
|
|
23
22
|
def _get_provider_names(self):
|
24
|
-
|
23
|
+
from janito.providers.registry import LLMProviderRegistry
|
24
|
+
|
25
|
+
return LLMProviderRegistry.list_providers()
|
25
26
|
|
26
27
|
def _create_table(self):
|
27
28
|
table = Table(title="Supported LLM Providers")
|
@@ -71,77 +72,35 @@ class ProviderRegistry:
|
|
71
72
|
print(ascii_row)
|
72
73
|
|
73
74
|
def _get_provider_info(self, provider_name):
|
74
|
-
|
75
|
-
|
76
|
-
maintainer =
|
77
|
-
|
78
|
-
if maintainer_val == "Needs maintainer"
|
79
|
-
else f"👤 {maintainer_val}"
|
80
|
-
)
|
81
|
-
model_names = "-"
|
82
|
-
unavailable_reason = None
|
75
|
+
provider_class = LLMProviderRegistry.get(provider_name)
|
76
|
+
maintainer = getattr(provider_class, "MAINTAINER", "-")
|
77
|
+
maintainer = f"👤 {maintainer}" if maintainer != "-" else maintainer
|
78
|
+
model_names = self._get_model_names(provider_name)
|
83
79
|
skip = False
|
84
|
-
try:
|
85
|
-
provider_class = LLMProviderRegistry.get(provider_name)
|
86
|
-
creds = LLMAuthManager().get_credentials(provider_name)
|
87
|
-
provider_instance = None
|
88
|
-
instantiation_failed = False
|
89
|
-
try:
|
90
|
-
provider_instance = provider_class()
|
91
|
-
except NotImplementedError:
|
92
|
-
skip = True
|
93
|
-
unavailable_reason = "Not implemented"
|
94
|
-
model_names = f"[red]❌ Not implemented[/red]"
|
95
|
-
except Exception as e:
|
96
|
-
instantiation_failed = True
|
97
|
-
unavailable_reason = (
|
98
|
-
f"Unavailable (import error or missing dependency): {str(e)}"
|
99
|
-
)
|
100
|
-
model_names = f"[red]❌ {unavailable_reason}[/red]"
|
101
|
-
if not instantiation_failed and provider_instance is not None:
|
102
|
-
available, unavailable_reason = self._get_availability(
|
103
|
-
provider_instance
|
104
|
-
)
|
105
|
-
if (
|
106
|
-
not available
|
107
|
-
and unavailable_reason
|
108
|
-
and "not implemented" in str(unavailable_reason).lower()
|
109
|
-
):
|
110
|
-
skip = True
|
111
|
-
if available:
|
112
|
-
model_names = self._get_model_names(provider_name)
|
113
|
-
else:
|
114
|
-
model_names = f"[red]❌ {unavailable_reason}[/red]"
|
115
|
-
except Exception as import_error:
|
116
|
-
model_names = f"[red]❌ Unavailable (cannot import provider module): {str(import_error)}[/red]"
|
117
80
|
return (provider_name, maintainer, model_names, skip)
|
118
81
|
|
119
|
-
def
|
82
|
+
def _get_model_names(self, provider_name):
|
120
83
|
try:
|
121
|
-
|
122
|
-
|
84
|
+
provider_class = LLMProviderRegistry.get(provider_name)
|
85
|
+
module_parts = provider_class.__module__.split(".")
|
86
|
+
# Build the correct import path: janito.providers.{provider}.model_info
|
87
|
+
model_info_module = f"janito.providers.{provider_name}.model_info"
|
88
|
+
model_info_mod = __import__(model_info_module, fromlist=["MODEL_SPECS"])
|
89
|
+
|
90
|
+
# Handle different model spec variable names
|
91
|
+
model_specs = None
|
92
|
+
if hasattr(model_info_mod, "MODEL_SPECS"):
|
93
|
+
model_specs = model_info_mod.MODEL_SPECS
|
94
|
+
elif hasattr(model_info_mod, "MOONSHOTAI_MODEL_SPECS"):
|
95
|
+
model_specs = model_info_mod.MOONSHOTAI_MODEL_SPECS
|
96
|
+
|
97
|
+
if provider_name == "groq":
|
98
|
+
return "<any> (must be provided)"
|
99
|
+
if model_specs:
|
100
|
+
return ", ".join(model_specs.keys())
|
101
|
+
return "-"
|
123
102
|
except Exception as e:
|
124
|
-
|
125
|
-
unavailable_reason = f"Error reading runtime availability: {str(e)}"
|
126
|
-
return available, unavailable_reason
|
127
|
-
|
128
|
-
def _get_model_names(self, provider_name):
|
129
|
-
provider_to_specs = {
|
130
|
-
"openai": "janito.providers.openai.model_info",
|
131
|
-
"azure_openai": "janito.providers.azure_openai.model_info",
|
132
|
-
"google": "janito.providers.google.model_info",
|
133
|
-
"anthropic": "janito.providers.anthropic.model_info",
|
134
|
-
"deepseek": "janito.providers.deepseek.model_info",
|
135
|
-
}
|
136
|
-
if provider_name in provider_to_specs:
|
137
|
-
try:
|
138
|
-
mod = __import__(
|
139
|
-
provider_to_specs[provider_name], fromlist=["MODEL_SPECS"]
|
140
|
-
)
|
141
|
-
return ", ".join(mod.MODEL_SPECS.keys())
|
142
|
-
except Exception:
|
143
|
-
return "(Error)"
|
144
|
-
return "-"
|
103
|
+
return "-"
|
145
104
|
|
146
105
|
def _maintainer_sort_key(self, row):
|
147
106
|
maint = row[1]
|
@@ -157,8 +116,10 @@ class ProviderRegistry:
|
|
157
116
|
return None
|
158
117
|
provider_class = LLMProviderRegistry.get(provider_name)
|
159
118
|
if provider_class is None:
|
160
|
-
available =
|
161
|
-
print(
|
119
|
+
available = ", ".join(LLMProviderRegistry.list_providers())
|
120
|
+
print(
|
121
|
+
f"Error: Provider '{provider_name}' is not recognized. Available providers: {available}."
|
122
|
+
)
|
162
123
|
return None
|
163
124
|
return provider_class
|
164
125
|
|
janito/providers/__init__.py
CHANGED
@@ -1,12 +1,3 @@
|
|
1
|
-
from janito.llm.provider import LLMProvider
|
2
|
-
from janito.llm.model import LLMModelInfo
|
3
|
-
from janito.llm.auth import LLMAuthManager
|
4
|
-
from janito.llm.driver_config import LLMDriverConfig
|
5
|
-
from janito.tools import get_local_tools_adapter
|
6
|
-
from janito.providers.registry import LLMProviderRegistry
|
7
|
-
|
8
|
-
from .model_info import MODEL_SPECS
|
9
|
-
|
10
1
|
from janito.llm.provider import LLMProvider
|
11
2
|
from janito.llm.model import LLMModelInfo
|
12
3
|
from janito.llm.auth import LLMAuthManager
|
@@ -16,9 +7,11 @@ from janito.providers.registry import LLMProviderRegistry
|
|
16
7
|
from .model_info import MODEL_SPECS
|
17
8
|
from janito.drivers.openai.driver import OpenAIModelDriver
|
18
9
|
|
10
|
+
|
19
11
|
class AnthropicProvider(LLMProvider):
|
20
12
|
name = "anthropic"
|
21
|
-
|
13
|
+
NAME = "anthropic"
|
14
|
+
MAINTAINER = "Alberto Minetti <alberto.minetti@gmail.com>"
|
22
15
|
MODEL_SPECS = MODEL_SPECS
|
23
16
|
DEFAULT_MODEL = "claude-3-7-sonnet-20250219"
|
24
17
|
|
@@ -27,10 +20,10 @@ class AnthropicProvider(LLMProvider):
|
|
27
20
|
):
|
28
21
|
self._tools_adapter = get_local_tools_adapter()
|
29
22
|
self.auth_manager = auth_manager or LLMAuthManager()
|
30
|
-
self._api_key = self.auth_manager.get_credentials(type(self).
|
23
|
+
self._api_key = self.auth_manager.get_credentials(type(self).NAME)
|
31
24
|
self._tools_adapter = get_local_tools_adapter()
|
32
25
|
self._driver_config = config or LLMDriverConfig(model=None)
|
33
|
-
if not getattr(self._driver_config,
|
26
|
+
if not getattr(self._driver_config, "model", None):
|
34
27
|
self._driver_config.model = self.DEFAULT_MODEL
|
35
28
|
if not self._driver_config.api_key:
|
36
29
|
self._driver_config.api_key = self._api_key
|
@@ -42,7 +35,9 @@ class AnthropicProvider(LLMProvider):
|
|
42
35
|
@property
|
43
36
|
def driver(self) -> OpenAIModelDriver:
|
44
37
|
if not self.available:
|
45
|
-
raise ImportError(
|
38
|
+
raise ImportError(
|
39
|
+
f"AnthropicProvider unavailable: {self.unavailable_reason}"
|
40
|
+
)
|
46
41
|
return self._driver
|
47
42
|
|
48
43
|
@property
|
@@ -77,4 +72,4 @@ class AnthropicProvider(LLMProvider):
|
|
77
72
|
return self._tools_adapter.execute_by_name(tool_name, *args, **kwargs)
|
78
73
|
|
79
74
|
|
80
|
-
LLMProviderRegistry.register(AnthropicProvider.
|
75
|
+
LLMProviderRegistry.register(AnthropicProvider.NAME, AnthropicProvider)
|