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.
Files changed (121) hide show
  1. janito/__init__.py +0 -1
  2. janito/__main__.py +0 -1
  3. janito/_version.py +0 -3
  4. janito/agent/setup_agent.py +77 -10
  5. janito/agent/templates/profiles/{system_prompt_template_plain_software_developer.txt.j2 → system_prompt_template_Developer_with_Python_Tools.txt.j2} +5 -1
  6. janito/agent/templates/profiles/system_prompt_template_developer.txt.j2 +3 -12
  7. janito/cli/__init__.py +0 -1
  8. janito/cli/chat_mode/bindings.py +1 -1
  9. janito/cli/chat_mode/chat_entry.py +0 -2
  10. janito/cli/chat_mode/prompt_style.py +0 -3
  11. janito/cli/chat_mode/script_runner.py +9 -5
  12. janito/cli/chat_mode/session.py +100 -37
  13. janito/cli/chat_mode/session_profile_select.py +61 -52
  14. janito/cli/chat_mode/shell/commands/__init__.py +1 -5
  15. janito/cli/chat_mode/shell/commands/_priv_check.py +1 -0
  16. janito/cli/chat_mode/shell/commands/_priv_status.py +13 -0
  17. janito/cli/chat_mode/shell/commands/bang.py +10 -3
  18. janito/cli/chat_mode/shell/commands/conversation_restart.py +24 -7
  19. janito/cli/chat_mode/shell/commands/execute.py +22 -7
  20. janito/cli/chat_mode/shell/commands/help.py +4 -1
  21. janito/cli/chat_mode/shell/commands/model.py +13 -5
  22. janito/cli/chat_mode/shell/commands/privileges.py +21 -0
  23. janito/cli/chat_mode/shell/commands/prompt.py +0 -2
  24. janito/cli/chat_mode/shell/commands/read.py +22 -5
  25. janito/cli/chat_mode/shell/commands/tools.py +15 -4
  26. janito/cli/chat_mode/shell/commands/write.py +22 -5
  27. janito/cli/chat_mode/shell/input_history.py +3 -1
  28. janito/cli/chat_mode/shell/session/manager.py +0 -2
  29. janito/cli/chat_mode/toolbar.py +25 -19
  30. janito/cli/cli_commands/list_config.py +31 -0
  31. janito/cli/cli_commands/list_models.py +1 -1
  32. janito/cli/cli_commands/list_profiles.py +79 -0
  33. janito/cli/cli_commands/list_providers.py +1 -0
  34. janito/cli/cli_commands/list_tools.py +35 -7
  35. janito/cli/cli_commands/model_utils.py +5 -3
  36. janito/cli/cli_commands/show_config.py +16 -11
  37. janito/cli/cli_commands/show_system_prompt.py +23 -9
  38. janito/cli/config.py +0 -13
  39. janito/cli/core/getters.py +16 -1
  40. janito/cli/core/runner.py +25 -8
  41. janito/cli/core/setters.py +13 -76
  42. janito/cli/main_cli.py +60 -27
  43. janito/cli/prompt_core.py +19 -18
  44. janito/cli/prompt_setup.py +6 -3
  45. janito/cli/rich_terminal_reporter.py +19 -5
  46. janito/cli/single_shot_mode/handler.py +14 -5
  47. janito/cli/verbose_output.py +5 -1
  48. janito/config.py +1 -0
  49. janito/config_manager.py +15 -2
  50. janito/drivers/azure_openai/driver.py +27 -30
  51. janito/drivers/openai/driver.py +53 -36
  52. janito/formatting_token.py +12 -4
  53. janito/llm/agent.py +15 -6
  54. janito/llm/driver.py +1 -0
  55. janito/llm/provider.py +1 -1
  56. janito/provider_registry.py +31 -70
  57. janito/providers/__init__.py +1 -0
  58. janito/providers/anthropic/model_info.py +0 -1
  59. janito/providers/anthropic/provider.py +9 -14
  60. janito/providers/azure_openai/provider.py +10 -5
  61. janito/providers/deepseek/provider.py +5 -4
  62. janito/providers/google/model_info.py +4 -2
  63. janito/providers/google/provider.py +11 -5
  64. janito/providers/groq/__init__.py +1 -0
  65. janito/providers/groq/model_info.py +45 -0
  66. janito/providers/groq/provider.py +76 -0
  67. janito/providers/moonshotai/provider.py +11 -4
  68. janito/providers/openai/model_info.py +0 -1
  69. janito/providers/openai/provider.py +6 -7
  70. janito/tools/__init__.py +2 -0
  71. janito/tools/adapters/local/__init__.py +2 -1
  72. janito/tools/adapters/local/adapter.py +21 -4
  73. janito/tools/adapters/local/ask_user.py +1 -0
  74. janito/tools/adapters/local/copy_file.py +1 -0
  75. janito/tools/adapters/local/create_directory.py +1 -0
  76. janito/tools/adapters/local/create_file.py +1 -0
  77. janito/tools/adapters/local/delete_text_in_file.py +2 -1
  78. janito/tools/adapters/local/fetch_url.py +1 -0
  79. janito/tools/adapters/local/find_files.py +7 -6
  80. janito/tools/adapters/local/get_file_outline/core.py +1 -0
  81. janito/tools/adapters/local/get_file_outline/java_outline.py +22 -15
  82. janito/tools/adapters/local/get_file_outline/search_outline.py +1 -0
  83. janito/tools/adapters/local/move_file.py +1 -0
  84. janito/tools/adapters/local/open_html_in_browser.py +15 -5
  85. janito/tools/adapters/local/open_url.py +1 -0
  86. janito/tools/adapters/local/python_code_run.py +1 -0
  87. janito/tools/adapters/local/python_command_run.py +1 -0
  88. janito/tools/adapters/local/python_file_run.py +1 -0
  89. janito/tools/adapters/local/read_files.py +19 -4
  90. janito/tools/adapters/local/remove_directory.py +1 -0
  91. janito/tools/adapters/local/remove_file.py +1 -0
  92. janito/tools/adapters/local/replace_text_in_file.py +4 -3
  93. janito/tools/adapters/local/run_bash_command.py +1 -0
  94. janito/tools/adapters/local/run_powershell_command.py +1 -0
  95. janito/tools/adapters/local/search_text/core.py +18 -17
  96. janito/tools/adapters/local/search_text/match_lines.py +5 -5
  97. janito/tools/adapters/local/search_text/pattern_utils.py +1 -1
  98. janito/tools/adapters/local/search_text/traverse_directory.py +7 -7
  99. janito/tools/adapters/local/validate_file_syntax/core.py +1 -1
  100. janito/tools/adapters/local/validate_file_syntax/html_validator.py +8 -1
  101. janito/tools/disabled_tools.py +68 -0
  102. janito/tools/path_security.py +18 -11
  103. janito/tools/permissions.py +6 -0
  104. janito/tools/permissions_parse.py +4 -3
  105. janito/tools/tool_base.py +11 -5
  106. janito/tools/tool_use_tracker.py +1 -4
  107. janito/tools/tool_utils.py +1 -1
  108. janito/tools/tools_adapter.py +57 -25
  109. {janito-2.7.0.dist-info → janito-2.9.0.dist-info}/METADATA +11 -19
  110. janito-2.9.0.dist-info/RECORD +205 -0
  111. janito/cli/chat_mode/shell/commands/livelogs.py +0 -49
  112. janito/drivers/mistralai/driver.py +0 -41
  113. janito/providers/mistralai/model_info.py +0 -37
  114. janito/providers/mistralai/provider.py +0 -72
  115. janito/providers/provider_static_info.py +0 -21
  116. janito-2.7.0.dist-info/RECORD +0 -202
  117. /janito/agent/templates/profiles/{system_prompt_template_assistant.txt.j2 → system_prompt_template_model_conversation_without_tools_or_context.txt.j2} +0 -0
  118. {janito-2.7.0.dist-info → janito-2.9.0.dist-info}/WHEEL +0 -0
  119. {janito-2.7.0.dist-info → janito-2.9.0.dist-info}/entry_points.txt +0 -0
  120. {janito-2.7.0.dist-info → janito-2.9.0.dist-info}/licenses/LICENSE +0 -0
  121. {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
- # Safe import of AzureOpenAI SDK
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, 'config', None)
11
+ config = getattr(self, "config", None)
20
12
  deployment_name = None
21
- if config and hasattr(config, 'extra'):
22
- deployment_name = config.extra.get('azure_deployment_name')
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("AzureOpenAIModelDriver requires 'azure_deployment_name' to be set in config.extra['azure_deployment_name'] before starting.")
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(), 'start'):
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
- if not self.available:
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 = config.extra.get("azure_deployment_name") if hasattr(config, "extra") else None
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 generate_tool_schemas
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(f"[AzureOpenAIModelDriver] Tool schema generation failed: {e}")
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(f"[ERROR] Exception during AzureOpenAI client instantiation: {e}", flush=True)
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
-
@@ -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
- # Safe import of openai SDK
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(driver_input.conversation_history)
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(e, config, api_kwargs, attempt, max_retries, request_id):
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 = type(self.tools_adapter).__name__ if self.tools_adapter else None
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(self, e, config, api_kwargs, attempt, max_retries, request_id):
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 or "exceeded your current 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
- (status_code == 429 or "error code: 429" in lower_err or "resource_exhausted" in lower_err)
168
- and not is_insufficient_quota
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(getattr(config, "cancel_event", None), request_id, before_call=False):
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(r"retryDelay['\"]?\s*[:=]\s*['\"]?(\d+(?:\.\d+)?)(s)?", payload)
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("[OpenAIModelDriver] HTTP debug enabled via OPENAI_DEBUG_HTTP=1", flush=True)
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
- "role": "tool",
382
- "content": result.get("content", ""),
383
- "name": result.get("name", ""),
384
- "tool_call_id": result.get("tool_call_id", ""),
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
- "role": "tool",
389
- "content": str(result),
390
- "name": "",
391
- "tool_call_id": "",
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({"role": "assistant", "content": "", "tool_calls": tool_calls})
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
@@ -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(msg_count, usage, width=96, use_rich=False, elapsed=None):
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 = f" | Elapsed: [cyan]{elapsed:.2f}s[/cyan]" if elapsed is not None else ""
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(console, msg_count=None, usage=None, width=96, elapsed=None):
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(msg_count, usage, width, use_rich=True, elapsed=elapsed)
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 = 300.0
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(f"[agent] [DEBUG] Returned from _process_next_response: result={result}, added_tool_results={added_tool_results}")
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("[agent] [INFO] Exiting chat loop: _process_next_response returned None result (likely timeout or error). Returning (None, False).")
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(f"[agent] [INFO] Exiting chat loop: _process_next_response returned added_tool_results=False (final response or no more tool calls). Returning result: {result}")
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(getattr(model_spec, "max_cot", None))
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, MistralProvider).
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.
@@ -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
- return list(STATIC_PROVIDER_METADATA.keys())
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
- static_info = STATIC_PROVIDER_METADATA.get(provider_name, {})
75
- maintainer_val = static_info.get("maintainer", "-")
76
- maintainer = (
77
- "[red]🚨 Needs maintainer[/red]"
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 _get_availability(self, provider_instance):
82
+ def _get_model_names(self, provider_name):
120
83
  try:
121
- available = getattr(provider_instance, "available", True)
122
- unavailable_reason = getattr(provider_instance, "unavailable_reason", None)
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
- available = False
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 = ', '.join(LLMProviderRegistry.list_providers())
161
- print(f"Error: Provider '{provider_name}' is not recognized. Available providers: {available}.")
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
 
@@ -5,3 +5,4 @@ import janito.providers.azure_openai.provider
5
5
  import janito.providers.anthropic.provider
6
6
  import janito.providers.deepseek.provider
7
7
  import janito.providers.moonshotai.provider
8
+ import janito.providers.groq.provider
@@ -38,4 +38,3 @@ MODEL_SPECS = {
38
38
  driver="OpenAIModelDriver",
39
39
  ),
40
40
  }
41
-
@@ -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
- maintainer = "Needs maintainer"
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).name)
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, 'model', None):
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(f"AnthropicProvider unavailable: {self.unavailable_reason}")
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.name, AnthropicProvider)
75
+ LLMProviderRegistry.register(AnthropicProvider.NAME, AnthropicProvider)