janito 2.1.1__py3-none-any.whl → 2.3.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 (137) hide show
  1. janito/__init__.py +6 -6
  2. janito/agent/setup_agent.py +14 -5
  3. janito/agent/templates/profiles/system_prompt_template_main.txt.j2 +3 -1
  4. janito/cli/chat_mode/bindings.py +6 -0
  5. janito/cli/chat_mode/session.py +16 -0
  6. janito/cli/chat_mode/shell/autocomplete.py +21 -21
  7. janito/cli/chat_mode/shell/commands/__init__.py +3 -2
  8. janito/cli/chat_mode/shell/commands/clear.py +12 -12
  9. janito/cli/chat_mode/shell/commands/exec.py +27 -0
  10. janito/cli/chat_mode/shell/commands/multi.py +51 -51
  11. janito/cli/chat_mode/shell/commands/tools.py +17 -6
  12. janito/cli/chat_mode/shell/input_history.py +62 -62
  13. janito/cli/chat_mode/shell/session/manager.py +1 -0
  14. janito/cli/chat_mode/toolbar.py +3 -1
  15. janito/cli/cli_commands/list_models.py +35 -35
  16. janito/cli/cli_commands/list_providers.py +9 -9
  17. janito/cli/cli_commands/list_tools.py +53 -53
  18. janito/cli/cli_commands/model_selection.py +50 -50
  19. janito/cli/cli_commands/model_utils.py +13 -2
  20. janito/cli/cli_commands/set_api_key.py +19 -19
  21. janito/cli/cli_commands/show_config.py +51 -51
  22. janito/cli/cli_commands/show_system_prompt.py +62 -62
  23. janito/cli/config.py +2 -1
  24. janito/cli/core/__init__.py +4 -4
  25. janito/cli/core/event_logger.py +59 -59
  26. janito/cli/core/getters.py +3 -1
  27. janito/cli/core/runner.py +27 -6
  28. janito/cli/core/setters.py +5 -1
  29. janito/cli/core/unsetters.py +54 -54
  30. janito/cli/main_cli.py +12 -1
  31. janito/cli/prompt_core.py +5 -2
  32. janito/cli/rich_terminal_reporter.py +22 -3
  33. janito/cli/single_shot_mode/__init__.py +6 -6
  34. janito/cli/single_shot_mode/handler.py +11 -1
  35. janito/cli/verbose_output.py +1 -1
  36. janito/config.py +5 -5
  37. janito/config_manager.py +2 -0
  38. janito/driver_events.py +14 -0
  39. janito/drivers/anthropic/driver.py +113 -113
  40. janito/drivers/azure_openai/driver.py +38 -3
  41. janito/drivers/driver_registry.py +0 -2
  42. janito/drivers/openai/driver.py +196 -36
  43. janito/formatting_token.py +54 -54
  44. janito/i18n/__init__.py +35 -35
  45. janito/i18n/messages.py +23 -23
  46. janito/i18n/pt.py +47 -47
  47. janito/llm/__init__.py +5 -5
  48. janito/llm/agent.py +443 -443
  49. janito/llm/auth.py +1 -0
  50. janito/llm/driver.py +7 -1
  51. janito/llm/driver_config.py +1 -0
  52. janito/llm/driver_config_builder.py +34 -34
  53. janito/llm/driver_input.py +12 -12
  54. janito/llm/message_parts.py +60 -60
  55. janito/llm/model.py +38 -38
  56. janito/llm/provider.py +196 -196
  57. janito/provider_config.py +7 -3
  58. janito/provider_registry.py +29 -5
  59. janito/providers/__init__.py +1 -0
  60. janito/providers/anthropic/model_info.py +22 -22
  61. janito/providers/anthropic/provider.py +2 -2
  62. janito/providers/azure_openai/model_info.py +7 -6
  63. janito/providers/azure_openai/provider.py +44 -2
  64. janito/providers/deepseek/__init__.py +1 -1
  65. janito/providers/deepseek/model_info.py +16 -16
  66. janito/providers/deepseek/provider.py +91 -91
  67. janito/providers/google/model_info.py +21 -29
  68. janito/providers/google/provider.py +49 -38
  69. janito/providers/mistralai/provider.py +2 -2
  70. janito/providers/openai/model_info.py +0 -11
  71. janito/providers/openai/provider.py +1 -1
  72. janito/providers/provider_static_info.py +2 -3
  73. janito/providers/registry.py +26 -26
  74. janito/tools/adapters/__init__.py +1 -1
  75. janito/tools/adapters/local/__init__.py +62 -62
  76. janito/tools/adapters/local/adapter.py +33 -11
  77. janito/tools/adapters/local/ask_user.py +102 -102
  78. janito/tools/adapters/local/copy_file.py +84 -84
  79. janito/tools/adapters/local/create_directory.py +69 -69
  80. janito/tools/adapters/local/create_file.py +82 -82
  81. janito/tools/adapters/local/delete_text_in_file.py +4 -7
  82. janito/tools/adapters/local/fetch_url.py +97 -97
  83. janito/tools/adapters/local/find_files.py +138 -140
  84. janito/tools/adapters/local/get_file_outline/__init__.py +1 -1
  85. janito/tools/adapters/local/get_file_outline/core.py +117 -151
  86. janito/tools/adapters/local/get_file_outline/java_outline.py +40 -0
  87. janito/tools/adapters/local/get_file_outline/markdown_outline.py +14 -14
  88. janito/tools/adapters/local/get_file_outline/python_outline.py +303 -303
  89. janito/tools/adapters/local/get_file_outline/python_outline_v2.py +156 -156
  90. janito/tools/adapters/local/get_file_outline/search_outline.py +33 -33
  91. janito/tools/adapters/local/move_file.py +3 -13
  92. janito/tools/adapters/local/open_html_in_browser.py +24 -29
  93. janito/tools/adapters/local/open_url.py +3 -2
  94. janito/tools/adapters/local/python_code_run.py +166 -166
  95. janito/tools/adapters/local/python_command_run.py +164 -164
  96. janito/tools/adapters/local/python_file_run.py +163 -163
  97. janito/tools/adapters/local/remove_directory.py +6 -17
  98. janito/tools/adapters/local/remove_file.py +9 -15
  99. janito/tools/adapters/local/replace_text_in_file.py +6 -9
  100. janito/tools/adapters/local/run_bash_command.py +176 -176
  101. janito/tools/adapters/local/run_powershell_command.py +219 -219
  102. janito/tools/adapters/local/search_text/__init__.py +1 -1
  103. janito/tools/adapters/local/search_text/core.py +201 -201
  104. janito/tools/adapters/local/search_text/match_lines.py +1 -1
  105. janito/tools/adapters/local/search_text/pattern_utils.py +73 -73
  106. janito/tools/adapters/local/search_text/traverse_directory.py +145 -145
  107. janito/tools/adapters/local/validate_file_syntax/__init__.py +1 -1
  108. janito/tools/adapters/local/validate_file_syntax/core.py +106 -106
  109. janito/tools/adapters/local/validate_file_syntax/css_validator.py +35 -35
  110. janito/tools/adapters/local/validate_file_syntax/html_validator.py +93 -93
  111. janito/tools/adapters/local/validate_file_syntax/js_validator.py +27 -27
  112. janito/tools/adapters/local/validate_file_syntax/json_validator.py +6 -6
  113. janito/tools/adapters/local/validate_file_syntax/markdown_validator.py +109 -109
  114. janito/tools/adapters/local/validate_file_syntax/ps1_validator.py +32 -32
  115. janito/tools/adapters/local/validate_file_syntax/python_validator.py +5 -5
  116. janito/tools/adapters/local/validate_file_syntax/xml_validator.py +11 -11
  117. janito/tools/adapters/local/validate_file_syntax/yaml_validator.py +6 -6
  118. janito/tools/adapters/local/view_file.py +167 -167
  119. janito/tools/inspect_registry.py +17 -17
  120. janito/tools/tool_base.py +105 -105
  121. janito/tools/tool_events.py +58 -58
  122. janito/tools/tool_run_exception.py +12 -12
  123. janito/tools/tool_use_tracker.py +81 -81
  124. janito/tools/tool_utils.py +45 -45
  125. janito/tools/tools_adapter.py +78 -6
  126. janito/tools/tools_schema.py +104 -104
  127. janito/version.py +4 -4
  128. {janito-2.1.1.dist-info → janito-2.3.0.dist-info}/METADATA +388 -232
  129. janito-2.3.0.dist-info/RECORD +181 -0
  130. janito-2.3.0.dist-info/licenses/LICENSE +21 -0
  131. janito/cli/chat_mode/shell/commands/last.py +0 -137
  132. janito/drivers/google_genai/driver.py +0 -54
  133. janito/drivers/google_genai/schema_generator.py +0 -67
  134. janito-2.1.1.dist-info/RECORD +0 -181
  135. {janito-2.1.1.dist-info → janito-2.3.0.dist-info}/WHEEL +0 -0
  136. {janito-2.1.1.dist-info → janito-2.3.0.dist-info}/entry_points.txt +0 -0
  137. {janito-2.1.1.dist-info → janito-2.3.0.dist-info}/top_level.txt +0 -0
janito/llm/provider.py CHANGED
@@ -1,196 +1,196 @@
1
- from abc import ABC, abstractmethod
2
- import importlib
3
- from janito.llm.driver import LLMDriver
4
-
5
-
6
- class LLMProvider(ABC):
7
- def create_driver(self):
8
- """
9
- Returns a new instance of the configured driver for this provider.
10
- Subclasses must implement this method.
11
- """
12
- raise NotImplementedError(
13
- "LLMProvider subclasses must implement create_driver()."
14
- )
15
-
16
- """
17
- Abstract base class for Large Language Model (LLM) providers.
18
-
19
- Provider Usage and Driver Communication Flow:
20
- 1. Provider class is selected (e.g., OpenAIProvider, MistralProvider).
21
- 2. An instance of the provider is created. This instance is bound to a specific configuration (LLMDriverConfig) containing model, credentials, etc.
22
- 3. All drivers created by that provider instance are associated with the bound config.
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.
24
-
25
- Key: You do not create/configure a driver directly—always go through the provider to ensure correct configuration binding to the provider instance.
26
-
27
- Subclasses must implement the core interface for interacting with LLM APIs and define `provider_name` as a class attribute.
28
- """
29
-
30
- name: str = None # Must be set on subclasses
31
- DEFAULT_MODEL: str = None # Should be set by subclasses
32
-
33
- def __init_subclass__(cls, **kwargs):
34
- super().__init_subclass__(**kwargs)
35
- if (
36
- not hasattr(cls, "name")
37
- or not isinstance(getattr(cls, "name"), str)
38
- or not cls.name
39
- ):
40
- raise TypeError(
41
- f"Class {cls.__name__} must define a class attribute 'name' (non-empty str)"
42
- )
43
- if (
44
- not hasattr(cls, "DEFAULT_MODEL")
45
- or getattr(cls, "DEFAULT_MODEL", None) is None
46
- ):
47
- raise TypeError(
48
- f"Class {cls.__name__} must define a class attribute 'DEFAULT_MODEL' (non-empty str)"
49
- )
50
-
51
- def fill_missing_device_info(self, config):
52
- """
53
- Fill missing LLMDriverConfig fields (max_tokens, temperature, etc) from MODEL_SPECS for the chosen model.
54
- Mutates the config in place.
55
- """
56
- if not hasattr(self, "MODEL_SPECS"):
57
- return
58
- model_name = getattr(config, "model", None) or getattr(
59
- self, "DEFAULT_MODEL", None
60
- )
61
- model_info = self.MODEL_SPECS.get(model_name)
62
- if not model_info:
63
- return
64
- # Handle common fields from model_info
65
- spec_dict = (
66
- model_info.to_dict() if hasattr(model_info, "to_dict") else dict(model_info)
67
- )
68
- if (
69
- hasattr(config, "max_tokens")
70
- and getattr(config, "max_tokens", None) is None
71
- ):
72
- val = spec_dict.get("max_tokens") or spec_dict.get("max_response")
73
- if val is not None:
74
- try:
75
- config.max_tokens = int(val)
76
- except Exception:
77
- pass
78
- if (
79
- hasattr(config, "temperature")
80
- and getattr(config, "temperature", None) is None
81
- ):
82
- val = spec_dict.get("temperature")
83
- if val is None:
84
- val = spec_dict.get("default_temp")
85
- if val is not None:
86
- try:
87
- config.temperature = float(val)
88
- except Exception:
89
- pass
90
-
91
- @property
92
- @abstractmethod
93
- def driver(self) -> LLMDriver:
94
- pass
95
-
96
- def is_model_available(self, model_name):
97
- """
98
- Returns True if the given model is available for this provider.
99
- Default implementation checks MODEL_SPECS; override for dynamic providers.
100
- """
101
- if not hasattr(self, "MODEL_SPECS"):
102
- return False
103
- return model_name in self.MODEL_SPECS
104
-
105
- def get_model_info(self, model_name=None):
106
- """
107
- Return the info dict for a given model (driver, params, etc). If model_name is None, return all model info dicts.
108
- MODEL_SPECS must be dict[str, LLMModelInfo].
109
- """
110
- if not hasattr(self, "MODEL_SPECS"):
111
- raise NotImplementedError(
112
- "This provider does not have a MODEL_SPECS attribute."
113
- )
114
- if model_name is None:
115
- return {
116
- name: model_info.to_dict()
117
- for name, model_info in self.MODEL_SPECS.items()
118
- }
119
- if model_name in self.MODEL_SPECS:
120
- return self.MODEL_SPECS[model_name].to_dict()
121
- return None
122
-
123
- def _validate_model_specs(self):
124
- if not hasattr(self, "MODEL_SPECS"):
125
- raise NotImplementedError(
126
- "This provider does not have a MODEL_SPECS attribute."
127
- )
128
-
129
- def _get_model_name_from_config(self, config):
130
- return (config or {}).get("model_name", getattr(self, "DEFAULT_MODEL", None))
131
-
132
- def _get_model_spec_entry(self, model_name):
133
- spec = self.MODEL_SPECS.get(model_name, None)
134
- if spec is None:
135
- raise ValueError(f"Model '{model_name}' not found in MODEL_SPECS.")
136
- return spec
137
-
138
- def _get_driver_name_from_spec(self, spec):
139
- driver_name = None
140
- if hasattr(spec, "driver") and spec.driver:
141
- driver_name = spec.driver
142
- elif hasattr(spec, "other") and isinstance(spec.other, dict):
143
- driver_name = spec.other.get("driver", None)
144
- return driver_name
145
-
146
- def _resolve_driver_class(self, driver_name):
147
- if not driver_name:
148
- raise NotImplementedError(
149
- "No driver class found or specified for this MODEL_SPECS entry."
150
- )
151
- module_root = "janito.drivers"
152
- probable_path = None
153
- mapping = {
154
- "OpenAIResponsesModelDriver": "openai_responses.driver",
155
- "OpenAIModelDriver": "openai.driver",
156
- "AzureOpenAIModelDriver": "azure_openai.driver",
157
- "GoogleGenaiModelDriver": "google_genai.driver",
158
- }
159
- if driver_name in mapping:
160
- probable_path = mapping[driver_name]
161
- module_path = f"{module_root}.{probable_path}"
162
- mod = importlib.import_module(module_path)
163
- return getattr(mod, driver_name)
164
- # Attempt dynamic fallback based on convention
165
- if driver_name.endswith("ModelDriver"):
166
- base = driver_name[: -len("ModelDriver")]
167
- mod_name = base.replace("_", "").lower()
168
- module_path = f"{module_root}.{mod_name}.driver"
169
- try:
170
- mod = importlib.import_module(module_path)
171
- return getattr(mod, driver_name)
172
- except Exception:
173
- pass
174
- raise NotImplementedError(
175
- "No driver class found for driver_name: {}".format(driver_name)
176
- )
177
-
178
- def _validate_required_config(self, driver_class, config, driver_name):
179
- required = getattr(driver_class, "required_config", None)
180
- if required:
181
- missing = [
182
- k
183
- for k in required
184
- if not config or k not in config or config.get(k) in (None, "")
185
- ]
186
- if missing:
187
- raise ValueError(
188
- f"Missing required config for {driver_name}: {', '.join(missing)}"
189
- )
190
-
191
- def create_agent(self, tools_adapter=None, agent_name: str = None, **kwargs):
192
- from janito.llm.agent import LLMAgent
193
-
194
- # Dynamically create driver if supported, else fallback to existing.
195
- driver = self.driver
196
- return LLMAgent(self, tools_adapter, agent_name=agent_name, **kwargs)
1
+ from abc import ABC, abstractmethod
2
+ import importlib
3
+ from janito.llm.driver import LLMDriver
4
+
5
+
6
+ class LLMProvider(ABC):
7
+ def create_driver(self):
8
+ """
9
+ Returns a new instance of the configured driver for this provider.
10
+ Subclasses must implement this method.
11
+ """
12
+ raise NotImplementedError(
13
+ "LLMProvider subclasses must implement create_driver()."
14
+ )
15
+
16
+ """
17
+ Abstract base class for Large Language Model (LLM) providers.
18
+
19
+ Provider Usage and Driver Communication Flow:
20
+ 1. Provider class is selected (e.g., OpenAIProvider, MistralProvider).
21
+ 2. An instance of the provider is created. This instance is bound to a specific configuration (LLMDriverConfig) containing model, credentials, etc.
22
+ 3. All drivers created by that provider instance are associated with the bound config.
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.
24
+
25
+ Key: You do not create/configure a driver directly—always go through the provider to ensure correct configuration binding to the provider instance.
26
+
27
+ Subclasses must implement the core interface for interacting with LLM APIs and define `provider_name` as a class attribute.
28
+ """
29
+
30
+ name: str = None # Must be set on subclasses
31
+ DEFAULT_MODEL: str = None # Should be set by subclasses
32
+
33
+ def __init_subclass__(cls, **kwargs):
34
+ super().__init_subclass__(**kwargs)
35
+ if (
36
+ not hasattr(cls, "name")
37
+ or not isinstance(getattr(cls, "name"), str)
38
+ or not cls.name
39
+ ):
40
+ raise TypeError(
41
+ f"Class {cls.__name__} must define a class attribute 'name' (non-empty str)"
42
+ )
43
+ if (
44
+ not hasattr(cls, "DEFAULT_MODEL")
45
+ or getattr(cls, "DEFAULT_MODEL", None) is None
46
+ ):
47
+ raise TypeError(
48
+ f"Class {cls.__name__} must define a class attribute 'DEFAULT_MODEL' (non-empty str)"
49
+ )
50
+
51
+ def fill_missing_device_info(self, config):
52
+ """
53
+ Fill missing LLMDriverConfig fields (max_tokens, temperature, etc) from MODEL_SPECS for the chosen model.
54
+ Mutates the config in place.
55
+ """
56
+ if not hasattr(self, "MODEL_SPECS"):
57
+ return
58
+ model_name = getattr(config, "model", None) or getattr(
59
+ self, "DEFAULT_MODEL", None
60
+ )
61
+ model_info = self.MODEL_SPECS.get(model_name)
62
+ if not model_info:
63
+ return
64
+ # Handle common fields from model_info
65
+ spec_dict = (
66
+ model_info.to_dict() if hasattr(model_info, "to_dict") else dict(model_info)
67
+ )
68
+ if (
69
+ hasattr(config, "max_tokens")
70
+ and getattr(config, "max_tokens", None) is None
71
+ ):
72
+ val = spec_dict.get("max_tokens") or spec_dict.get("max_response")
73
+ if val is not None:
74
+ try:
75
+ config.max_tokens = int(val)
76
+ except Exception:
77
+ pass
78
+ if (
79
+ hasattr(config, "temperature")
80
+ and getattr(config, "temperature", None) is None
81
+ ):
82
+ val = spec_dict.get("temperature")
83
+ if val is None:
84
+ val = spec_dict.get("default_temp")
85
+ if val is not None:
86
+ try:
87
+ config.temperature = float(val)
88
+ except Exception:
89
+ pass
90
+
91
+ @property
92
+ @abstractmethod
93
+ def driver(self) -> LLMDriver:
94
+ pass
95
+
96
+ def is_model_available(self, model_name):
97
+ """
98
+ Returns True if the given model is available for this provider.
99
+ Default implementation checks MODEL_SPECS; override for dynamic providers.
100
+ """
101
+ if not hasattr(self, "MODEL_SPECS"):
102
+ return False
103
+ return model_name in self.MODEL_SPECS
104
+
105
+ def get_model_info(self, model_name=None):
106
+ """
107
+ Return the info dict for a given model (driver, params, etc). If model_name is None, return all model info dicts.
108
+ MODEL_SPECS must be dict[str, LLMModelInfo].
109
+ """
110
+ if not hasattr(self, "MODEL_SPECS"):
111
+ raise NotImplementedError(
112
+ "This provider does not have a MODEL_SPECS attribute."
113
+ )
114
+ if model_name is None:
115
+ return {
116
+ name: model_info.to_dict()
117
+ for name, model_info in self.MODEL_SPECS.items()
118
+ }
119
+ if model_name in self.MODEL_SPECS:
120
+ return self.MODEL_SPECS[model_name].to_dict()
121
+ return None
122
+
123
+ def _validate_model_specs(self):
124
+ if not hasattr(self, "MODEL_SPECS"):
125
+ raise NotImplementedError(
126
+ "This provider does not have a MODEL_SPECS attribute."
127
+ )
128
+
129
+ def _get_model_name_from_config(self, config):
130
+ return (config or {}).get("model_name", getattr(self, "DEFAULT_MODEL", None))
131
+
132
+ def _get_model_spec_entry(self, model_name):
133
+ spec = self.MODEL_SPECS.get(model_name, None)
134
+ if spec is None:
135
+ raise ValueError(f"Model '{model_name}' not found in MODEL_SPECS.")
136
+ return spec
137
+
138
+ def _get_driver_name_from_spec(self, spec):
139
+ driver_name = None
140
+ if hasattr(spec, "driver") and spec.driver:
141
+ driver_name = spec.driver
142
+ elif hasattr(spec, "other") and isinstance(spec.other, dict):
143
+ driver_name = spec.other.get("driver", None)
144
+ return driver_name
145
+
146
+ def _resolve_driver_class(self, driver_name):
147
+ if not driver_name:
148
+ raise NotImplementedError(
149
+ "No driver class found or specified for this MODEL_SPECS entry."
150
+ )
151
+ module_root = "janito.drivers"
152
+ probable_path = None
153
+ mapping = {
154
+ "OpenAIResponsesModelDriver": "openai_responses.driver",
155
+ "OpenAIModelDriver": "openai.driver",
156
+ "AzureOpenAIModelDriver": "azure_openai.driver",
157
+ "GoogleGenaiModelDriver": "google_genai.driver",
158
+ }
159
+ if driver_name in mapping:
160
+ probable_path = mapping[driver_name]
161
+ module_path = f"{module_root}.{probable_path}"
162
+ mod = importlib.import_module(module_path)
163
+ return getattr(mod, driver_name)
164
+ # Attempt dynamic fallback based on convention
165
+ if driver_name.endswith("ModelDriver"):
166
+ base = driver_name[: -len("ModelDriver")]
167
+ mod_name = base.replace("_", "").lower()
168
+ module_path = f"{module_root}.{mod_name}.driver"
169
+ try:
170
+ mod = importlib.import_module(module_path)
171
+ return getattr(mod, driver_name)
172
+ except Exception:
173
+ pass
174
+ raise NotImplementedError(
175
+ "No driver class found for driver_name: {}".format(driver_name)
176
+ )
177
+
178
+ def _validate_required_config(self, driver_class, config, driver_name):
179
+ required = getattr(driver_class, "required_config", None)
180
+ if required:
181
+ missing = [
182
+ k
183
+ for k in required
184
+ if not config or k not in config or config.get(k) in (None, "")
185
+ ]
186
+ if missing:
187
+ raise ValueError(
188
+ f"Missing required config for {driver_name}: {', '.join(missing)}"
189
+ )
190
+
191
+ def create_agent(self, tools_adapter=None, agent_name: str = None, **kwargs):
192
+ from janito.llm.agent import LLMAgent
193
+
194
+ # Dynamically create driver if supported, else fallback to existing.
195
+ driver = self.driver
196
+ return LLMAgent(self, tools_adapter, agent_name=agent_name, **kwargs)
janito/provider_config.py CHANGED
@@ -36,6 +36,7 @@ def set_provider_config(provider, key, value):
36
36
  config.file_config["providers"] = cfg
37
37
  with open(config.config_path, "w", encoding="utf-8") as f:
38
38
  json.dump(config.file_config, f, indent=2)
39
+ f.write("\n")
39
40
 
40
41
 
41
42
  def set_provider_model_config(provider, model, key, value):
@@ -51,6 +52,7 @@ def set_provider_model_config(provider, model, key, value):
51
52
  config.file_config["providers"] = cfg
52
53
  with open(config.config_path, "w", encoding="utf-8") as f:
53
54
  json.dump(config.file_config, f, indent=2)
55
+ f.write("\n")
54
56
 
55
57
 
56
58
  def get_provider_model_config(provider, model):
@@ -71,9 +73,11 @@ def get_effective_model(provider=None, requested_model=None):
71
73
  provider_model = config.get_provider_config(provider).get("model")
72
74
  if provider_model:
73
75
  return provider_model
74
- global_model = config.get("model")
75
- if global_model:
76
- return global_model
76
+ # Only use global model if no provider is specified
77
+ if provider is None:
78
+ global_model = config.get("model")
79
+ if global_model:
80
+ return global_model
77
81
  return None
78
82
 
79
83
 
@@ -48,6 +48,24 @@ class ProviderRegistry:
48
48
  table.add_section()
49
49
 
50
50
  def _print_table(self, table):
51
+ """Print the table using rich when running in a terminal; otherwise fall back to a plain ASCII listing.
52
+ This avoids UnicodeDecodeError when the parent process captures the output with a non-UTF8 encoding.
53
+ """
54
+ import sys
55
+
56
+ if sys.stdout.isatty():
57
+ # Safe to use rich's unicode output when attached to an interactive terminal.
58
+ shared_console.print(table)
59
+ return
60
+
61
+ # Fallback: plain ASCII output
62
+ print("Supported LLM Providers")
63
+ print("Provider | Maintainer | Model Names")
64
+ for row in table.rows:
65
+ # row is a rich.table.Row -> row.cells is a list of Text objects
66
+ cells_text = [str(cell) for cell in row.cells]
67
+ ascii_row = " | ".join(cells_text).encode("ascii", "ignore").decode("ascii")
68
+ print(ascii_row)
51
69
  shared_console.print(table)
52
70
 
53
71
  def _get_provider_info(self, provider_name):
@@ -129,18 +147,24 @@ class ProviderRegistry:
129
147
  return (is_needs_maint, row[2] != "✅ Auth")
130
148
 
131
149
  def get_provider(self, provider_name):
132
- """Return the provider class for the given provider name."""
150
+ """Return the provider class for the given provider name. Returns None if not found."""
133
151
  from janito.providers.registry import LLMProviderRegistry
134
152
 
135
153
  if not provider_name:
136
- raise ValueError("Provider name must be specified.")
137
- return LLMProviderRegistry.get(provider_name)
154
+ print("Error: Provider name must be specified.")
155
+ return None
156
+ provider_class = LLMProviderRegistry.get(provider_name)
157
+ if provider_class is None:
158
+ available = ', '.join(LLMProviderRegistry.list_providers())
159
+ print(f"Error: Provider '{provider_name}' is not recognized. Available providers: {available}.")
160
+ return None
161
+ return provider_class
138
162
 
139
163
  def get_instance(self, provider_name, config=None):
140
- """Return an instance of the provider for the given provider name, optionally passing a config object."""
164
+ """Return an instance of the provider for the given provider name, optionally passing a config object. Returns None if not found."""
141
165
  provider_class = self.get_provider(provider_name)
142
166
  if provider_class is None:
143
- raise ValueError(f"No provider class found for '{provider_name}'")
167
+ return None
144
168
  if config is not None:
145
169
  return provider_class(config=config)
146
170
  return provider_class()
@@ -1,5 +1,6 @@
1
1
  # Ensure all providers are registered by importing their modules
2
2
  import janito.providers.openai.provider
3
+ import janito.providers.google.provider
3
4
  import janito.providers.mistralai.provider
4
5
  import janito.providers.google.provider
5
6
  import janito.providers.azure_openai.provider
@@ -1,22 +1,22 @@
1
- from janito.llm.model import LLMModelInfo
2
-
3
- MODEL_SPECS = {
4
- "claude-3-opus-20240229": LLMModelInfo(
5
- name="claude-3-opus-20240229",
6
- max_response=200000,
7
- default_temp=0.7,
8
- driver="AnthropicModelDriver",
9
- ),
10
- "claude-3-sonnet-20240229": LLMModelInfo(
11
- name="claude-3-sonnet-20240229",
12
- max_response=200000,
13
- default_temp=0.7,
14
- driver="AnthropicModelDriver",
15
- ),
16
- "claude-3-haiku-20240307": LLMModelInfo(
17
- name="claude-3-haiku-20240307",
18
- max_response=200000,
19
- default_temp=0.7,
20
- driver="AnthropicModelDriver",
21
- ),
22
- }
1
+ from janito.llm.model import LLMModelInfo
2
+
3
+ MODEL_SPECS = {
4
+ "claude-3-opus-20240229": LLMModelInfo(
5
+ name="claude-3-opus-20240229",
6
+ max_response=200000,
7
+ default_temp=0.7,
8
+ driver="AnthropicModelDriver",
9
+ ),
10
+ "claude-3-sonnet-20240229": LLMModelInfo(
11
+ name="claude-3-sonnet-20240229",
12
+ max_response=200000,
13
+ default_temp=0.7,
14
+ driver="AnthropicModelDriver",
15
+ ),
16
+ "claude-3-haiku-20240307": LLMModelInfo(
17
+ name="claude-3-haiku-20240307",
18
+ max_response=200000,
19
+ default_temp=0.7,
20
+ driver="AnthropicModelDriver",
21
+ ),
22
+ }
@@ -2,7 +2,7 @@ from janito.llm.provider import LLMProvider
2
2
  from janito.llm.model import LLMModelInfo
3
3
  from janito.llm.auth import LLMAuthManager
4
4
  from janito.llm.driver_config import LLMDriverConfig
5
- from janito.tools.adapters.local.adapter import LocalToolsAdapter
5
+ from janito.tools import get_local_tools_adapter
6
6
  from janito.providers.registry import LLMProviderRegistry
7
7
 
8
8
  from .model_info import MODEL_SPECS
@@ -28,7 +28,7 @@ class AnthropicProvider(LLMProvider):
28
28
  return
29
29
  self.auth_manager = auth_manager or LLMAuthManager()
30
30
  self._api_key = self.auth_manager.get_credentials(type(self).name)
31
- self._tools_adapter = LocalToolsAdapter()
31
+ self._tools_adapter = get_local_tools_adapter()
32
32
  self._info = config or LLMDriverConfig(model=None)
33
33
  if not self._info.model:
34
34
  self._info.model = self.DEFAULT_MODEL
@@ -1,14 +1,15 @@
1
1
  from janito.llm.model import LLMModelInfo
2
+ from janito.providers.openai.model_info import MODEL_SPECS as OPENAI_MODEL_SPECS
2
3
 
3
4
  MODEL_SPECS = {
4
5
  "azure_openai_deployment": LLMModelInfo(
5
6
  name="azure_openai_deployment",
6
- context="N/A",
7
- max_input="N/A",
8
- max_cot="N/A",
9
- max_response="N/A",
10
- thinking_supported=False,
11
- default_temp=0.2,
7
+ context=OPENAI_MODEL_SPECS["gpt-4o"].context,
8
+ max_input=OPENAI_MODEL_SPECS["gpt-4o"].max_input,
9
+ max_cot=OPENAI_MODEL_SPECS["gpt-4o"].max_cot,
10
+ max_response=OPENAI_MODEL_SPECS["gpt-4o"].max_response,
11
+ thinking_supported=OPENAI_MODEL_SPECS["gpt-4o"].thinking_supported,
12
+ default_temp=OPENAI_MODEL_SPECS["gpt-4o"].default_temp,
12
13
  open="azure_openai",
13
14
  driver="AzureOpenAIModelDriver",
14
15
  )
@@ -2,7 +2,7 @@ from janito.llm.provider import LLMProvider
2
2
  from janito.llm.model import LLMModelInfo
3
3
  from janito.llm.auth import LLMAuthManager
4
4
  from janito.llm.driver_config import LLMDriverConfig
5
- from janito.tools.adapters.local.adapter import LocalToolsAdapter
5
+ from janito.tools import get_local_tools_adapter
6
6
  from janito.providers.registry import LLMProviderRegistry
7
7
 
8
8
  from .model_info import MODEL_SPECS
@@ -28,7 +28,7 @@ class AzureOpenAIProvider(LLMProvider):
28
28
  return
29
29
  self._auth_manager = auth_manager or LLMAuthManager()
30
30
  self._api_key = self._auth_manager.get_credentials(type(self).name)
31
- self._tools_adapter = LocalToolsAdapter()
31
+ self._tools_adapter = get_local_tools_adapter()
32
32
  self._driver_config = config or LLMDriverConfig(model=None)
33
33
  if not self._driver_config.model:
34
34
  self._driver_config.model = self.DEFAULT_MODEL
@@ -36,6 +36,11 @@ class AzureOpenAIProvider(LLMProvider):
36
36
  self._driver_config.api_key = self._api_key
37
37
  if not self._driver_config.extra.get("api_version"):
38
38
  self._driver_config.extra["api_version"] = "2023-05-15"
39
+ # Inject azure_deployment_name from config if present
40
+ from janito.config import config as global_config
41
+ deployment_name = global_config.get("azure_deployment_name")
42
+ if deployment_name:
43
+ self._driver_config.extra["azure_deployment_name"] = deployment_name
39
44
  self.fill_missing_device_info(self._driver_config)
40
45
  self._driver = AzureOpenAIModelDriver(tools_adapter=self._tools_adapter)
41
46
 
@@ -61,6 +66,38 @@ class AzureOpenAIProvider(LLMProvider):
61
66
  """
62
67
  return True
63
68
 
69
+ def get_model_info(self, model_name=None):
70
+ """
71
+ For Azure OpenAI, accept any deployment name as a valid model name.
72
+ If the model_name is not in MODEL_SPECS, return a generic info dict.
73
+ """
74
+ if model_name is None:
75
+ # Return all known specs, but note: only static ones are listed
76
+ return {name: model_info.to_dict() for name, model_info in self.MODEL_SPECS.items()}
77
+ if model_name in self.MODEL_SPECS:
78
+ return self.MODEL_SPECS[model_name].to_dict()
79
+ # Accept any deployment name as a valid model
80
+ return {
81
+ "name": model_name,
82
+ "context": "N/A",
83
+ "max_input": "N/A",
84
+ "max_cot": "N/A",
85
+ "max_response": "N/A",
86
+ "thinking_supported": False,
87
+ "default_temp": 0.2,
88
+ "open": "azure_openai",
89
+ "driver": "AzureOpenAIModelDriver",
90
+ }
91
+
92
+ def create_driver(self):
93
+ """
94
+ Creates and returns a new AzureOpenAIModelDriver instance with the provider's configuration and tools adapter.
95
+ """
96
+ driver = AzureOpenAIModelDriver(tools_adapter=self._tools_adapter)
97
+ driver.config = self._driver_config
98
+ # NOTE: The caller is responsible for calling driver.start() if background processing is needed.
99
+ return driver
100
+
64
101
  def create_agent(self, tools_adapter=None, agent_name: str = None, **kwargs):
65
102
  from janito.llm.agent import LLMAgent
66
103
  from janito.drivers.azure_openai.driver import AzureOpenAIModelDriver
@@ -69,6 +106,11 @@ class AzureOpenAIProvider(LLMProvider):
69
106
  driver = AzureOpenAIModelDriver(tools_adapter=tools_adapter)
70
107
  return LLMAgent(self, tools_adapter, agent_name=agent_name, **kwargs)
71
108
 
109
+ @property
110
+ def driver_config(self):
111
+ """Public, read-only access to the provider's LLMDriverConfig object."""
112
+ return self._driver_config
113
+
72
114
  def execute_tool(self, tool_name: str, event_bus, *args, **kwargs):
73
115
  # Use direct execution via adapter:
74
116
  self._tools_adapter.event_bus = event_bus
@@ -1 +1 @@
1
- # Deepseek provider package marker
1
+ # Deepseek provider package marker