autobyteus 1.1.0__py3-none-any.whl → 1.1.2__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 (103) hide show
  1. autobyteus/agent/bootstrap_steps/agent_bootstrapper.py +1 -1
  2. autobyteus/agent/bootstrap_steps/agent_runtime_queue_initialization_step.py +1 -1
  3. autobyteus/agent/bootstrap_steps/base_bootstrap_step.py +1 -1
  4. autobyteus/agent/bootstrap_steps/system_prompt_processing_step.py +1 -1
  5. autobyteus/agent/bootstrap_steps/workspace_context_initialization_step.py +1 -1
  6. autobyteus/agent/context/__init__.py +0 -5
  7. autobyteus/agent/context/agent_config.py +6 -2
  8. autobyteus/agent/context/agent_context.py +2 -5
  9. autobyteus/agent/context/agent_phase_manager.py +105 -5
  10. autobyteus/agent/context/agent_runtime_state.py +2 -2
  11. autobyteus/agent/context/phases.py +2 -0
  12. autobyteus/agent/events/__init__.py +0 -11
  13. autobyteus/agent/events/agent_events.py +0 -37
  14. autobyteus/agent/events/notifiers.py +25 -7
  15. autobyteus/agent/events/worker_event_dispatcher.py +1 -1
  16. autobyteus/agent/factory/agent_factory.py +6 -2
  17. autobyteus/agent/group/agent_group.py +16 -7
  18. autobyteus/agent/handlers/approved_tool_invocation_event_handler.py +28 -14
  19. autobyteus/agent/handlers/lifecycle_event_logger.py +1 -1
  20. autobyteus/agent/handlers/llm_complete_response_received_event_handler.py +4 -2
  21. autobyteus/agent/handlers/tool_invocation_request_event_handler.py +40 -15
  22. autobyteus/agent/handlers/tool_result_event_handler.py +12 -7
  23. autobyteus/agent/hooks/__init__.py +7 -0
  24. autobyteus/agent/hooks/base_phase_hook.py +11 -2
  25. autobyteus/agent/hooks/hook_definition.py +36 -0
  26. autobyteus/agent/hooks/hook_meta.py +37 -0
  27. autobyteus/agent/hooks/hook_registry.py +118 -0
  28. autobyteus/agent/input_processor/base_user_input_processor.py +6 -3
  29. autobyteus/agent/input_processor/passthrough_input_processor.py +2 -1
  30. autobyteus/agent/input_processor/processor_meta.py +1 -1
  31. autobyteus/agent/input_processor/processor_registry.py +19 -0
  32. autobyteus/agent/llm_response_processor/base_processor.py +6 -3
  33. autobyteus/agent/llm_response_processor/processor_meta.py +1 -1
  34. autobyteus/agent/llm_response_processor/processor_registry.py +19 -0
  35. autobyteus/agent/llm_response_processor/provider_aware_tool_usage_processor.py +2 -1
  36. autobyteus/agent/message/context_file_type.py +2 -3
  37. autobyteus/agent/phases/__init__.py +18 -0
  38. autobyteus/agent/phases/discover.py +52 -0
  39. autobyteus/agent/phases/manager.py +265 -0
  40. autobyteus/agent/phases/phase_enum.py +49 -0
  41. autobyteus/agent/phases/transition_decorator.py +40 -0
  42. autobyteus/agent/phases/transition_info.py +33 -0
  43. autobyteus/agent/remote_agent.py +1 -1
  44. autobyteus/agent/runtime/agent_runtime.py +5 -10
  45. autobyteus/agent/runtime/agent_worker.py +62 -19
  46. autobyteus/agent/streaming/agent_event_stream.py +58 -5
  47. autobyteus/agent/streaming/stream_event_payloads.py +24 -13
  48. autobyteus/agent/streaming/stream_events.py +14 -11
  49. autobyteus/agent/system_prompt_processor/base_processor.py +6 -3
  50. autobyteus/agent/system_prompt_processor/processor_meta.py +1 -1
  51. autobyteus/agent/system_prompt_processor/tool_manifest_injector_processor.py +45 -31
  52. autobyteus/agent/tool_invocation.py +29 -3
  53. autobyteus/agent/utils/wait_for_idle.py +1 -1
  54. autobyteus/agent/workspace/__init__.py +2 -0
  55. autobyteus/agent/workspace/base_workspace.py +33 -11
  56. autobyteus/agent/workspace/workspace_config.py +160 -0
  57. autobyteus/agent/workspace/workspace_definition.py +36 -0
  58. autobyteus/agent/workspace/workspace_meta.py +37 -0
  59. autobyteus/agent/workspace/workspace_registry.py +72 -0
  60. autobyteus/cli/__init__.py +4 -3
  61. autobyteus/cli/agent_cli.py +25 -207
  62. autobyteus/cli/cli_display.py +205 -0
  63. autobyteus/events/event_manager.py +2 -1
  64. autobyteus/events/event_types.py +3 -1
  65. autobyteus/llm/api/autobyteus_llm.py +2 -12
  66. autobyteus/llm/api/deepseek_llm.py +11 -173
  67. autobyteus/llm/api/grok_llm.py +11 -172
  68. autobyteus/llm/api/kimi_llm.py +24 -0
  69. autobyteus/llm/api/mistral_llm.py +4 -4
  70. autobyteus/llm/api/ollama_llm.py +2 -2
  71. autobyteus/llm/api/openai_compatible_llm.py +193 -0
  72. autobyteus/llm/api/openai_llm.py +11 -139
  73. autobyteus/llm/extensions/token_usage_tracking_extension.py +11 -1
  74. autobyteus/llm/llm_factory.py +168 -42
  75. autobyteus/llm/models.py +25 -29
  76. autobyteus/llm/ollama_provider.py +6 -2
  77. autobyteus/llm/ollama_provider_resolver.py +44 -0
  78. autobyteus/llm/providers.py +1 -0
  79. autobyteus/llm/token_counter/kimi_token_counter.py +24 -0
  80. autobyteus/llm/token_counter/token_counter_factory.py +3 -0
  81. autobyteus/llm/utils/messages.py +3 -3
  82. autobyteus/tools/__init__.py +2 -0
  83. autobyteus/tools/base_tool.py +7 -1
  84. autobyteus/tools/functional_tool.py +20 -5
  85. autobyteus/tools/mcp/call_handlers/stdio_handler.py +15 -1
  86. autobyteus/tools/mcp/config_service.py +106 -127
  87. autobyteus/tools/mcp/registrar.py +247 -59
  88. autobyteus/tools/mcp/types.py +5 -3
  89. autobyteus/tools/registry/tool_definition.py +8 -1
  90. autobyteus/tools/registry/tool_registry.py +18 -0
  91. autobyteus/tools/tool_category.py +11 -0
  92. autobyteus/tools/tool_meta.py +3 -1
  93. autobyteus/tools/tool_state.py +20 -0
  94. autobyteus/tools/usage/parsers/_json_extractor.py +99 -0
  95. autobyteus/tools/usage/parsers/default_json_tool_usage_parser.py +46 -77
  96. autobyteus/tools/usage/parsers/default_xml_tool_usage_parser.py +87 -96
  97. autobyteus/tools/usage/parsers/gemini_json_tool_usage_parser.py +37 -47
  98. autobyteus/tools/usage/parsers/openai_json_tool_usage_parser.py +112 -113
  99. {autobyteus-1.1.0.dist-info → autobyteus-1.1.2.dist-info}/METADATA +13 -12
  100. {autobyteus-1.1.0.dist-info → autobyteus-1.1.2.dist-info}/RECORD +103 -82
  101. {autobyteus-1.1.0.dist-info → autobyteus-1.1.2.dist-info}/WHEEL +0 -0
  102. {autobyteus-1.1.0.dist-info → autobyteus-1.1.2.dist-info}/licenses/LICENSE +0 -0
  103. {autobyteus-1.1.0.dist-info → autobyteus-1.1.2.dist-info}/top_level.txt +0 -0
@@ -1,154 +1,26 @@
1
1
  import logging
2
- from typing import Optional, List, AsyncGenerator
3
- import openai
4
- from openai.types.completion_usage import CompletionUsage
5
- from openai.types.chat import ChatCompletionChunk
6
- import os
2
+ from typing import Optional
7
3
  from autobyteus.llm.models import LLMModel
8
- from autobyteus.llm.base_llm import BaseLLM
9
4
  from autobyteus.llm.utils.llm_config import LLMConfig
10
- from autobyteus.llm.utils.messages import MessageRole, Message
11
- from autobyteus.llm.utils.image_payload_formatter import process_image
12
- from autobyteus.llm.utils.token_usage import TokenUsage
13
- from autobyteus.llm.utils.response_types import CompleteResponse, ChunkResponse
5
+ from autobyteus.llm.api.openai_compatible_llm import OpenAICompatibleLLM
14
6
 
15
7
  logger = logging.getLogger(__name__)
16
8
 
17
- class OpenAILLM(BaseLLM):
9
+ class OpenAILLM(OpenAICompatibleLLM):
18
10
  def __init__(self, model: LLMModel = None, llm_config: LLMConfig = None):
19
11
  # Provide defaults if not specified
20
12
  if model is None:
21
- model = LLMModel.GPT_3_5_TURBO_API
13
+ model = LLMModel['gpt-4o'] # Use factory access
22
14
  if llm_config is None:
23
15
  llm_config = LLMConfig()
24
16
 
25
- super().__init__(model=model, llm_config=llm_config)
26
- self.initialize() # Class method called after super()
27
- self.max_tokens = 8000
28
- logger.info(f"OpenAILLM initialized with model: {self.model}")
29
-
30
- @classmethod
31
- def initialize(cls):
32
- openai_api_key = os.getenv("OPENAI_API_KEY")
33
- if not openai_api_key:
34
- logger.error("OPENAI_API_KEY environment variable is not set.")
35
- raise ValueError("OPENAI_API_KEY environment variable is not set.")
36
- openai.api_key = openai_api_key
37
- logger.info("OpenAI API key set successfully")
38
-
39
- def _create_token_usage(self, usage_data: Optional[CompletionUsage]) -> Optional[TokenUsage]:
40
- """Convert OpenAI usage data to TokenUsage format."""
41
- if not usage_data:
42
- return None
43
-
44
- return TokenUsage(
45
- prompt_tokens=usage_data.prompt_tokens,
46
- completion_tokens=usage_data.completion_tokens,
47
- total_tokens=usage_data.total_tokens
17
+ super().__init__(
18
+ model=model,
19
+ llm_config=llm_config,
20
+ api_key_env_var="OPENAI_API_KEY",
21
+ base_url="https://api.openai.com/v1"
48
22
  )
49
-
50
- async def _send_user_message_to_llm(
51
- self, user_message: str, image_urls: Optional[List[str]] = None, **kwargs
52
- ) -> CompleteResponse:
53
- content = []
54
-
55
- if user_message:
56
- content.append({"type": "text", "text": user_message})
57
-
58
- if image_urls:
59
- for image_url in image_urls:
60
- try:
61
- image_content = process_image(image_url)
62
- content.append(image_content)
63
- logger.info(f"Processed image: {image_url}")
64
- except ValueError as e:
65
- logger.error(f"Error processing image {image_url}: {str(e)}")
66
- continue
67
-
68
- self.add_user_message(content)
69
- logger.debug(f"Prepared message content: {content}")
70
-
71
- try:
72
- logger.info("Sending request to OpenAI API")
73
- response = openai.chat.completions.create(
74
- model=self.model.value,
75
- messages=[msg.to_dict() for msg in self.messages],
76
- max_tokens=self.max_tokens,
77
- )
78
- assistant_message = response.choices[0].message.content
79
- self.add_assistant_message(assistant_message)
80
-
81
- token_usage = self._create_token_usage(response.usage)
82
- logger.info("Received response from OpenAI API with usage data")
83
-
84
- return CompleteResponse(
85
- content=assistant_message,
86
- usage=token_usage
87
- )
88
- except Exception as e:
89
- logger.error(f"Error in OpenAI API request: {str(e)}")
90
- raise ValueError(f"Error in OpenAI API request: {str(e)}")
91
-
92
- async def _stream_user_message_to_llm(
93
- self, user_message: str, image_urls: Optional[List[str]] = None, **kwargs
94
- ) -> AsyncGenerator[ChunkResponse, None]:
95
- content = []
96
-
97
- if user_message:
98
- content.append({"type": "text", "text": user_message})
99
-
100
- if image_urls:
101
- for image_url in image_urls:
102
- try:
103
- image_content = process_image(image_url)
104
- content.append(image_content)
105
- logger.info(f"Processed image for streaming: {image_url}")
106
- except ValueError as e:
107
- logger.error(f"Error processing image for streaming {image_url}: {str(e)}")
108
- continue
109
-
110
- self.add_user_message(content)
111
- logger.debug(f"Prepared streaming message content: {content}")
112
-
113
- complete_response = ""
114
-
115
- try:
116
- logger.info("Starting streaming request to OpenAI API")
117
- stream = openai.chat.completions.create(
118
- model=self.model.value,
119
- messages=[msg.to_dict() for msg in self.messages],
120
- max_tokens=self.max_tokens,
121
- stream=True,
122
- stream_options={"include_usage": True}
123
- )
124
-
125
- for chunk in stream:
126
- chunk: ChatCompletionChunk
127
-
128
- # Check if this chunk has choices with content
129
- if chunk.choices and chunk.choices[0].delta.content is not None:
130
- token = chunk.choices[0].delta.content
131
- complete_response += token
132
- yield ChunkResponse(
133
- content=token,
134
- is_complete=False
135
- )
136
-
137
- # Handle the final chunk with usage data
138
- if hasattr(chunk, 'usage') and chunk.usage is not None:
139
- token_usage = self._create_token_usage(chunk.usage)
140
- # Add the assistant's complete response to the conversation history
141
- self.add_assistant_message(complete_response)
142
- logger.info("Completed streaming response from OpenAI API")
143
- yield ChunkResponse(
144
- content="",
145
- is_complete=True,
146
- usage=token_usage
147
- )
23
+ logger.info(f"OpenAILLM initialized with model: {self.model}")
148
24
 
149
- except Exception as e:
150
- logger.error(f"Error in OpenAI API streaming: {str(e)}")
151
- raise ValueError(f"Error in OpenAI API streaming: {str(e)}")
152
-
153
25
  async def cleanup(self):
154
- super().cleanup()
26
+ await super().cleanup()
@@ -1,4 +1,5 @@
1
1
  from typing import Optional, List, TYPE_CHECKING
2
+ import logging
2
3
  from autobyteus.llm.extensions.base_extension import LLMExtension
3
4
  from autobyteus.llm.token_counter.token_counter_factory import get_token_counter
4
5
  from autobyteus.llm.utils.token_usage import TokenUsage
@@ -9,6 +10,8 @@ from autobyteus.llm.utils.response_types import CompleteResponse
9
10
  if TYPE_CHECKING:
10
11
  from autobyteus.llm.base_llm import BaseLLM
11
12
 
13
+ logger = logging.getLogger(__name__)
14
+
12
15
  class TokenUsageTrackingExtension(LLMExtension):
13
16
  """
14
17
  Extension that tracks and monitors token usage and associated costs for LLM interactions.
@@ -37,7 +40,14 @@ class TokenUsageTrackingExtension(LLMExtension):
37
40
  Get the latest usage from tracker and optionally override token counts with provider's usage if available
38
41
  """
39
42
  latest_usage = self.usage_tracker.get_latest_usage()
40
-
43
+
44
+ if latest_usage is None:
45
+ logger.warning(
46
+ "No token usage record found in after_invoke. This may indicate the LLM implementation "
47
+ "did not call add_user_message. Skipping token usage update for this call."
48
+ )
49
+ return
50
+
41
51
  if isinstance(response, CompleteResponse) and response.usage:
42
52
  # Override token counts with provider's data if available
43
53
  latest_usage.prompt_tokens = response.usage.prompt_tokens
@@ -3,7 +3,7 @@ import logging
3
3
  import inspect
4
4
 
5
5
  from autobyteus.llm.autobyteus_provider import AutobyteusModelProvider
6
- from autobyteus.llm.models import LLMModel
6
+ from autobyteus.llm.models import LLMModel, ModelInfo, ProviderModelGroup
7
7
  from autobyteus.llm.providers import LLMProvider
8
8
  from autobyteus.llm.utils.llm_config import LLMConfig, TokenPricingConfig
9
9
  from autobyteus.llm.base_llm import BaseLLM
@@ -14,6 +14,7 @@ from autobyteus.llm.api.openai_llm import OpenAILLM
14
14
  from autobyteus.llm.api.ollama_llm import OllamaLLM
15
15
  from autobyteus.llm.api.deepseek_llm import DeepSeekLLM
16
16
  from autobyteus.llm.api.grok_llm import GrokLLM
17
+ from autobyteus.llm.api.kimi_llm import KimiLLM
17
18
  from autobyteus.llm.ollama_provider import OllamaModelProvider
18
19
  from autobyteus.utils.singleton import SingletonMeta
19
20
 
@@ -36,21 +37,6 @@ class LLMFactory(metaclass=SingletonMeta):
36
37
  LLMFactory._initialize_registry()
37
38
  LLMFactory._initialized = True
38
39
 
39
- @staticmethod
40
- def _clear_model_class_attributes():
41
- """
42
- Clear all LLMModel instances that were set as class attributes on the LLMModel class.
43
- This is necessary for reinitialization to avoid 'model already exists' errors.
44
- """
45
- # Get all attributes of the LLMModel class
46
- for attr_name in list(vars(LLMModel).keys()):
47
- attr_value = getattr(LLMModel, attr_name)
48
- # Check if the attribute is an instance of LLMModel
49
- if isinstance(attr_value, LLMModel):
50
- logger.debug(f"Removing LLMModel class attribute: {attr_name}")
51
- # Delete the attribute to avoid 'model already exists' errors during reinitialization
52
- delattr(LLMModel, attr_name)
53
-
54
40
  @staticmethod
55
41
  def reinitialize():
56
42
  """
@@ -66,9 +52,6 @@ class LLMFactory(metaclass=SingletonMeta):
66
52
  try:
67
53
  logger.info("Reinitializing LLM model registry...")
68
54
 
69
- # Clear all LLMModel instances set as class attributes
70
- LLMFactory._clear_model_class_attributes()
71
-
72
55
  # Reset the initialized flag
73
56
  LLMFactory._initialized = False
74
57
 
@@ -94,7 +77,7 @@ class LLMFactory(metaclass=SingletonMeta):
94
77
  supported_models = [
95
78
  # OPENAI Provider Models
96
79
  LLMModel(
97
- name="GPT_4o_API",
80
+ name="gpt-4o",
98
81
  value="gpt-4o",
99
82
  provider=LLMProvider.OPENAI,
100
83
  llm_class=OpenAILLM,
@@ -106,7 +89,7 @@ class LLMFactory(metaclass=SingletonMeta):
106
89
  )
107
90
  ),
108
91
  LLMModel(
109
- name="o3_API",
92
+ name="o3",
110
93
  value="o3",
111
94
  provider=LLMProvider.OPENAI,
112
95
  llm_class=OpenAILLM,
@@ -116,7 +99,7 @@ class LLMFactory(metaclass=SingletonMeta):
116
99
  )
117
100
  ),
118
101
  LLMModel(
119
- name="o4_MINI_API",
102
+ name="o4-mini",
120
103
  value="o4-mini",
121
104
  provider=LLMProvider.OPENAI,
122
105
  llm_class=OpenAILLM,
@@ -127,7 +110,7 @@ class LLMFactory(metaclass=SingletonMeta):
127
110
  ),
128
111
  # MISTRAL Provider Models
129
112
  LLMModel(
130
- name="MISTRAL_LARGE_API",
113
+ name="mistral-large",
131
114
  value="mistral-large-latest",
132
115
  provider=LLMProvider.MISTRAL,
133
116
  llm_class=MistralLLM,
@@ -138,28 +121,48 @@ class LLMFactory(metaclass=SingletonMeta):
138
121
  ),
139
122
  # ANTHROPIC Provider Models
140
123
  LLMModel(
141
- name="CLAUDE_3_7_SONNET_API",
142
- value="claude-3-7-sonnet-20250219",
124
+ name="claude-4-opus",
125
+ value="claude-opus-4-20250514",
126
+ provider=LLMProvider.ANTHROPIC,
127
+ llm_class=ClaudeLLM,
128
+ canonical_name="claude-4-opus",
129
+ default_config=LLMConfig(
130
+ pricing_config=TokenPricingConfig(15.00, 75.00)
131
+ )
132
+ ),
133
+ LLMModel(
134
+ name="bedrock-claude-4-opus",
135
+ value="anthropic.claude-opus-4-20250514-v1:0",
136
+ provider=LLMProvider.ANTHROPIC,
137
+ llm_class=ClaudeLLM,
138
+ canonical_name="claude-4-opus",
139
+ default_config=LLMConfig(
140
+ pricing_config=TokenPricingConfig(15.00, 75.00)
141
+ )
142
+ ),
143
+ LLMModel(
144
+ name="claude-4-sonnet",
145
+ value="claude-sonnet-4-20250514",
143
146
  provider=LLMProvider.ANTHROPIC,
144
147
  llm_class=ClaudeLLM,
145
- canonical_name="claude-3.7",
148
+ canonical_name="claude-4-sonnet",
146
149
  default_config=LLMConfig(
147
150
  pricing_config=TokenPricingConfig(3.00, 15.00)
148
151
  )
149
152
  ),
150
153
  LLMModel(
151
- name="BEDROCK_CLAUDE_3_7_SONNET_API",
152
- value="anthropic.claude-3-7-sonnet-20250219-v1:0",
154
+ name="bedrock-claude-4-sonnet",
155
+ value="anthropic.claude-sonnet-4-20250514-v1:0",
153
156
  provider=LLMProvider.ANTHROPIC,
154
157
  llm_class=ClaudeLLM,
155
- canonical_name="claude-3.7",
158
+ canonical_name="claude-4-sonnet",
156
159
  default_config=LLMConfig(
157
160
  pricing_config=TokenPricingConfig(3.00, 15.00)
158
161
  )
159
162
  ),
160
163
  # DEEPSEEK Provider Models
161
164
  LLMModel(
162
- name="DEEPSEEK_CHAT_API",
165
+ name="deepseek-chat",
163
166
  value="deepseek-chat",
164
167
  provider=LLMProvider.DEEPSEEK,
165
168
  llm_class=DeepSeekLLM,
@@ -172,7 +175,7 @@ class LLMFactory(metaclass=SingletonMeta):
172
175
  ),
173
176
  # Adding deepseek-reasoner support
174
177
  LLMModel(
175
- name="DEEPSEEK_REASONER_API",
178
+ name="deepseek-reasoner",
176
179
  value="deepseek-reasoner",
177
180
  provider=LLMProvider.DEEPSEEK,
178
181
  llm_class=DeepSeekLLM,
@@ -185,28 +188,48 @@ class LLMFactory(metaclass=SingletonMeta):
185
188
  ),
186
189
  # GEMINI Provider Models
187
190
  LLMModel(
188
- name="GEMINI_1_5_PRO_API",
189
- value="gemini-1-5-pro",
191
+ name="gemini-2.5-pro",
192
+ value="gemini-2.5-pro",
190
193
  provider=LLMProvider.GEMINI,
191
194
  llm_class=OpenAILLM,
192
- canonical_name="gemini-1.5-pro",
195
+ canonical_name="gemini-2.5-pro",
193
196
  default_config=LLMConfig(
194
- pricing_config=TokenPricingConfig(1.25, 5.00)
197
+ pricing_config=TokenPricingConfig(2.50, 10.00)
198
+ )
199
+ ),
200
+ LLMModel(
201
+ name="gemini-2.5-flash",
202
+ value="gemini-2.5-flash",
203
+ provider=LLMProvider.GEMINI,
204
+ llm_class=OpenAILLM,
205
+ canonical_name="gemini-2.5-flash",
206
+ default_config=LLMConfig(
207
+ pricing_config=TokenPricingConfig(0.15, 0.60)
208
+ )
209
+ ),
210
+ LLMModel(
211
+ name="gemini-2.0-flash",
212
+ value="gemini-2.0-flash",
213
+ provider=LLMProvider.GEMINI,
214
+ llm_class=OpenAILLM,
215
+ canonical_name="gemini-2.0-flash",
216
+ default_config=LLMConfig(
217
+ pricing_config=TokenPricingConfig(0.1, 0.40)
195
218
  )
196
219
  ),
197
220
  LLMModel(
198
- name="GEMINI_1_5_FLASH_API",
199
- value="gemini-1-5-flash",
221
+ name="gemini-2.0-flash-lite",
222
+ value="gemini-2.0-flash-lite",
200
223
  provider=LLMProvider.GEMINI,
201
224
  llm_class=OpenAILLM,
202
- canonical_name="gemini-1.5-flash",
225
+ canonical_name="gemini-2.0-flash-lite",
203
226
  default_config=LLMConfig(
204
227
  pricing_config=TokenPricingConfig(0.075, 0.30)
205
228
  )
206
229
  ),
207
230
  # GROK Provider Models
208
231
  LLMModel(
209
- name="GROK_2_1212_API",
232
+ name="grok-2-1212",
210
233
  value="grok-2-1212",
211
234
  provider=LLMProvider.GROK,
212
235
  llm_class=GrokLLM,
@@ -217,6 +240,67 @@ class LLMFactory(metaclass=SingletonMeta):
217
240
  pricing_config=TokenPricingConfig(2.0, 6.0)
218
241
  )
219
242
  ),
243
+ # KIMI Provider Models
244
+ LLMModel(
245
+ name="kimi-latest",
246
+ value="kimi-latest",
247
+ provider=LLMProvider.KIMI,
248
+ llm_class=KimiLLM,
249
+ canonical_name="kimi-latest",
250
+ default_config=LLMConfig(
251
+ pricing_config=TokenPricingConfig(1.38, 4.14)
252
+ )
253
+ ),
254
+ LLMModel(
255
+ name="moonshot-v1-8k",
256
+ value="moonshot-v1-8k",
257
+ provider=LLMProvider.KIMI,
258
+ llm_class=KimiLLM,
259
+ canonical_name="moonshot-v1-8k",
260
+ default_config=LLMConfig(
261
+ pricing_config=TokenPricingConfig(0.28, 1.38)
262
+ )
263
+ ),
264
+ LLMModel(
265
+ name="moonshot-v1-32k",
266
+ value="moonshot-v1-32k",
267
+ provider=LLMProvider.KIMI,
268
+ llm_class=KimiLLM,
269
+ canonical_name="moonshot-v1-32k",
270
+ default_config=LLMConfig(
271
+ pricing_config=TokenPricingConfig(0.69, 2.76)
272
+ )
273
+ ),
274
+ LLMModel(
275
+ name="moonshot-v1-128k",
276
+ value="moonshot-v1-128k",
277
+ provider=LLMProvider.KIMI,
278
+ llm_class=KimiLLM,
279
+ canonical_name="moonshot-v1-128k",
280
+ default_config=LLMConfig(
281
+ pricing_config=TokenPricingConfig(1.38, 4.14)
282
+ )
283
+ ),
284
+ LLMModel(
285
+ name="kimi-k2-0711-preview",
286
+ value="kimi-k2-0711-preview",
287
+ provider=LLMProvider.KIMI,
288
+ llm_class=KimiLLM,
289
+ canonical_name="kimi-k2-0711-preview",
290
+ default_config=LLMConfig(
291
+ pricing_config=TokenPricingConfig(0.55, 2.21)
292
+ )
293
+ ),
294
+ LLMModel(
295
+ name="kimi-thinking-preview",
296
+ value="kimi-thinking-preview",
297
+ provider=LLMProvider.KIMI,
298
+ llm_class=KimiLLM,
299
+ canonical_name="kimi-thinking-preview",
300
+ default_config=LLMConfig(
301
+ pricing_config=TokenPricingConfig(27.59, 27.59)
302
+ )
303
+ ),
220
304
  ]
221
305
  for model in supported_models:
222
306
  LLMFactory.register_model(model)
@@ -228,7 +312,18 @@ class LLMFactory(metaclass=SingletonMeta):
228
312
  def register_model(model: LLMModel):
229
313
  """
230
314
  Register a new LLM model, storing it under its provider category.
315
+ If a model with the same name already exists, it will be replaced.
231
316
  """
317
+ # Using a flat list of all models to check for existing model by name
318
+ all_models = [m for models in LLMFactory._models_by_provider.values() for m in models]
319
+
320
+ for existing_model in all_models:
321
+ if existing_model.name == model.name:
322
+ logger.warning(f"Model with name '{model.name}' is being redefined.")
323
+ # Remove the old model from its provider list
324
+ LLMFactory._models_by_provider[existing_model.provider].remove(existing_model)
325
+ break
326
+
232
327
  models = LLMFactory._models_by_provider.setdefault(model.provider, [])
233
328
  models.append(model)
234
329
 
@@ -238,7 +333,7 @@ class LLMFactory(metaclass=SingletonMeta):
238
333
  Create an LLM instance for the specified model identifier.
239
334
 
240
335
  Args:
241
- model_identifier (str): The model name or value to create an instance for.
336
+ model_identifier (str): The model name to create an instance for.
242
337
  llm_config (Optional[LLMConfig]): Configuration for the LLM. If None,
243
338
  the model's default configuration is used.
244
339
 
@@ -251,7 +346,7 @@ class LLMFactory(metaclass=SingletonMeta):
251
346
  LLMFactory.ensure_initialized()
252
347
  for models in LLMFactory._models_by_provider.values():
253
348
  for model_instance in models:
254
- if model_instance.value == model_identifier or model_instance.name == model_identifier:
349
+ if model_instance.name == model_identifier:
255
350
  return model_instance.create_llm(llm_config)
256
351
  raise ValueError(f"Unsupported model: {model_identifier}")
257
352
 
@@ -296,7 +391,7 @@ class LLMFactory(metaclass=SingletonMeta):
296
391
  Get the canonical name for a model by its name.
297
392
 
298
393
  Args:
299
- model_name (str): The model name (e.g., "GPT_4o_API")
394
+ model_name (str): The model name (e.g., "gpt_4o")
300
395
 
301
396
  Returns:
302
397
  Optional[str]: The canonical name if found, None otherwise
@@ -308,4 +403,35 @@ class LLMFactory(metaclass=SingletonMeta):
308
403
  return model_instance.canonical_name
309
404
  return None
310
405
 
406
+ @staticmethod
407
+ def get_models_grouped_by_provider() -> List[ProviderModelGroup]:
408
+ """
409
+ Returns a list of all providers, each with a list of its available models,
410
+ sorted by provider name and model name. Providers with no models are included
411
+ with an empty model list.
412
+ """
413
+ LLMFactory.ensure_initialized()
414
+ result: List[ProviderModelGroup] = []
415
+ # Sort all providers from the enum by name for consistent order
416
+ all_providers_sorted = sorted(list(LLMProvider), key=lambda p: p.name)
417
+
418
+ for provider in all_providers_sorted:
419
+ # Get models for the current provider, defaults to [] if none are registered
420
+ models = LLMFactory._models_by_provider.get(provider, [])
421
+
422
+ # Sort the models for this provider by name
423
+ sorted_models = sorted(models, key=lambda model: model.name)
424
+
425
+ model_infos = [
426
+ ModelInfo(name=model.name, canonical_name=model.canonical_name)
427
+ for model in sorted_models
428
+ ]
429
+
430
+ result.append(ProviderModelGroup(
431
+ provider=provider.name,
432
+ models=model_infos
433
+ ))
434
+
435
+ return result
436
+
311
437
  default_llm_factory = LLMFactory()
autobyteus/llm/models.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import logging
2
2
  from typing import TYPE_CHECKING, Type, Optional, List, Iterator
3
+ from dataclasses import dataclass
3
4
 
4
5
  from autobyteus.llm.providers import LLMProvider
5
6
  from autobyteus.llm.utils.llm_config import LLMConfig
@@ -10,6 +11,18 @@ if TYPE_CHECKING:
10
11
 
11
12
  logger = logging.getLogger(__name__)
12
13
 
14
+ @dataclass
15
+ class ModelInfo:
16
+ """A simple data structure for essential model information."""
17
+ name: str
18
+ canonical_name: str
19
+
20
+ @dataclass
21
+ class ProviderModelGroup:
22
+ """A data structure to group models by their provider."""
23
+ provider: str
24
+ models: List[ModelInfo]
25
+
13
26
  class LLMModelMeta(type):
14
27
  """
15
28
  Metaclass for LLMModel to make it iterable and support item access like Enums.
@@ -24,15 +37,12 @@ class LLMModelMeta(type):
24
37
  from autobyteus.llm.llm_factory import LLMFactory
25
38
  LLMFactory.ensure_initialized()
26
39
 
27
- for attr_name in dir(cls):
28
- if not attr_name.startswith('_'): # Skip private/dunder attributes
29
- attr_value = getattr(cls, attr_name)
30
- if isinstance(attr_value, cls): # Check if it's an LLMModel instance
31
- yield attr_value
40
+ for models in LLMFactory._models_by_provider.values():
41
+ yield from models
32
42
 
33
43
  def __getitem__(cls, name_or_value: str) -> 'LLMModel':
34
44
  """
35
- Allows dictionary-like access to LLMModel instances by name (e.g., 'GPT_4o_API')
45
+ Allows dictionary-like access to LLMModel instances by name (e.g., 'gpt-4o')
36
46
  or by value (e.g., 'gpt-4o').
37
47
  Search is performed by name first, then by value.
38
48
  """
@@ -40,13 +50,12 @@ class LLMModelMeta(type):
40
50
  from autobyteus.llm.llm_factory import LLMFactory
41
51
  LLMFactory.ensure_initialized()
42
52
 
43
- # 1. Try to find by name first (e.g., LLMModel['GPT_4o_API'])
44
- if hasattr(cls, name_or_value):
45
- attribute = getattr(cls, name_or_value)
46
- if isinstance(attribute, cls):
47
- return attribute
53
+ # 1. Try to find by name first
54
+ for model in cls:
55
+ if model.name == name_or_value:
56
+ return model
48
57
 
49
- # 2. If not found by name, iterate and find by value (e.g., LLMModel['gpt-4o'])
58
+ # 2. If not found by name, iterate and find by value
50
59
  for model in cls:
51
60
  if model.value == name_or_value:
52
61
  return model
@@ -64,17 +73,14 @@ class LLMModelMeta(type):
64
73
  LLMFactory.ensure_initialized()
65
74
 
66
75
  count = 0
67
- for attr_name in dir(cls):
68
- if not attr_name.startswith('_'):
69
- attr_value = getattr(cls, attr_name)
70
- if isinstance(attr_value, cls):
71
- count += 1
76
+ for models in LLMFactory._models_by_provider.values():
77
+ count += len(models)
72
78
  return count
73
79
 
74
80
  class LLMModel(metaclass=LLMModelMeta):
75
81
  """
76
82
  Represents a single model's metadata:
77
- - name (str): A human-readable label, e.g. "GPT-4 Official"
83
+ - name (str): A human-readable label, e.g. "gpt-4o"
78
84
  - value (str): A unique identifier used in code or APIs, e.g. "gpt-4o"
79
85
  - canonical_name (str): A shorter, standardized reference name for prompts, e.g. "gpt-4o" or "claude-3.7"
80
86
  - provider (LLMProvider): The provider enum
@@ -94,12 +100,6 @@ class LLMModel(metaclass=LLMModelMeta):
94
100
  canonical_name: str,
95
101
  default_config: Optional[LLMConfig] = None
96
102
  ):
97
- # Validate name doesn't already exist as a class attribute
98
- if hasattr(LLMModel, name):
99
- existing_model = getattr(LLMModel, name)
100
- if isinstance(existing_model, LLMModel):
101
- logger.warning(f"Model with name '{name}' is being redefined. This is expected during reinitialization.")
102
-
103
103
  self._name = name
104
104
  self._value = value
105
105
  self._canonical_name = canonical_name
@@ -107,16 +107,12 @@ class LLMModel(metaclass=LLMModelMeta):
107
107
  self.llm_class = llm_class
108
108
  self.default_config = default_config if default_config else LLMConfig()
109
109
 
110
- # Set this instance as a class attribute, making LLMModel.MODEL_NAME available.
111
- logger.debug(f"Setting LLMModel class attribute: {name}")
112
- setattr(LLMModel, name, self)
113
-
114
110
  @property
115
111
  def name(self) -> str:
116
112
  """
117
113
  A friendly or descriptive name for this model (could appear in UI).
118
114
  This is the key used for `LLMModel['MODEL_NAME']` access.
119
- Example: "GPT_4o_API"
115
+ Example: "gpt-4o"
120
116
  """
121
117
  return self._name
122
118