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.
- autobyteus/agent/bootstrap_steps/agent_bootstrapper.py +1 -1
- autobyteus/agent/bootstrap_steps/agent_runtime_queue_initialization_step.py +1 -1
- autobyteus/agent/bootstrap_steps/base_bootstrap_step.py +1 -1
- autobyteus/agent/bootstrap_steps/system_prompt_processing_step.py +1 -1
- autobyteus/agent/bootstrap_steps/workspace_context_initialization_step.py +1 -1
- autobyteus/agent/context/__init__.py +0 -5
- autobyteus/agent/context/agent_config.py +6 -2
- autobyteus/agent/context/agent_context.py +2 -5
- autobyteus/agent/context/agent_phase_manager.py +105 -5
- autobyteus/agent/context/agent_runtime_state.py +2 -2
- autobyteus/agent/context/phases.py +2 -0
- autobyteus/agent/events/__init__.py +0 -11
- autobyteus/agent/events/agent_events.py +0 -37
- autobyteus/agent/events/notifiers.py +25 -7
- autobyteus/agent/events/worker_event_dispatcher.py +1 -1
- autobyteus/agent/factory/agent_factory.py +6 -2
- autobyteus/agent/group/agent_group.py +16 -7
- autobyteus/agent/handlers/approved_tool_invocation_event_handler.py +28 -14
- autobyteus/agent/handlers/lifecycle_event_logger.py +1 -1
- autobyteus/agent/handlers/llm_complete_response_received_event_handler.py +4 -2
- autobyteus/agent/handlers/tool_invocation_request_event_handler.py +40 -15
- autobyteus/agent/handlers/tool_result_event_handler.py +12 -7
- autobyteus/agent/hooks/__init__.py +7 -0
- autobyteus/agent/hooks/base_phase_hook.py +11 -2
- autobyteus/agent/hooks/hook_definition.py +36 -0
- autobyteus/agent/hooks/hook_meta.py +37 -0
- autobyteus/agent/hooks/hook_registry.py +118 -0
- autobyteus/agent/input_processor/base_user_input_processor.py +6 -3
- autobyteus/agent/input_processor/passthrough_input_processor.py +2 -1
- autobyteus/agent/input_processor/processor_meta.py +1 -1
- autobyteus/agent/input_processor/processor_registry.py +19 -0
- autobyteus/agent/llm_response_processor/base_processor.py +6 -3
- autobyteus/agent/llm_response_processor/processor_meta.py +1 -1
- autobyteus/agent/llm_response_processor/processor_registry.py +19 -0
- autobyteus/agent/llm_response_processor/provider_aware_tool_usage_processor.py +2 -1
- autobyteus/agent/message/context_file_type.py +2 -3
- autobyteus/agent/phases/__init__.py +18 -0
- autobyteus/agent/phases/discover.py +52 -0
- autobyteus/agent/phases/manager.py +265 -0
- autobyteus/agent/phases/phase_enum.py +49 -0
- autobyteus/agent/phases/transition_decorator.py +40 -0
- autobyteus/agent/phases/transition_info.py +33 -0
- autobyteus/agent/remote_agent.py +1 -1
- autobyteus/agent/runtime/agent_runtime.py +5 -10
- autobyteus/agent/runtime/agent_worker.py +62 -19
- autobyteus/agent/streaming/agent_event_stream.py +58 -5
- autobyteus/agent/streaming/stream_event_payloads.py +24 -13
- autobyteus/agent/streaming/stream_events.py +14 -11
- autobyteus/agent/system_prompt_processor/base_processor.py +6 -3
- autobyteus/agent/system_prompt_processor/processor_meta.py +1 -1
- autobyteus/agent/system_prompt_processor/tool_manifest_injector_processor.py +45 -31
- autobyteus/agent/tool_invocation.py +29 -3
- autobyteus/agent/utils/wait_for_idle.py +1 -1
- autobyteus/agent/workspace/__init__.py +2 -0
- autobyteus/agent/workspace/base_workspace.py +33 -11
- autobyteus/agent/workspace/workspace_config.py +160 -0
- autobyteus/agent/workspace/workspace_definition.py +36 -0
- autobyteus/agent/workspace/workspace_meta.py +37 -0
- autobyteus/agent/workspace/workspace_registry.py +72 -0
- autobyteus/cli/__init__.py +4 -3
- autobyteus/cli/agent_cli.py +25 -207
- autobyteus/cli/cli_display.py +205 -0
- autobyteus/events/event_manager.py +2 -1
- autobyteus/events/event_types.py +3 -1
- autobyteus/llm/api/autobyteus_llm.py +2 -12
- autobyteus/llm/api/deepseek_llm.py +11 -173
- autobyteus/llm/api/grok_llm.py +11 -172
- autobyteus/llm/api/kimi_llm.py +24 -0
- autobyteus/llm/api/mistral_llm.py +4 -4
- autobyteus/llm/api/ollama_llm.py +2 -2
- autobyteus/llm/api/openai_compatible_llm.py +193 -0
- autobyteus/llm/api/openai_llm.py +11 -139
- autobyteus/llm/extensions/token_usage_tracking_extension.py +11 -1
- autobyteus/llm/llm_factory.py +168 -42
- autobyteus/llm/models.py +25 -29
- autobyteus/llm/ollama_provider.py +6 -2
- autobyteus/llm/ollama_provider_resolver.py +44 -0
- autobyteus/llm/providers.py +1 -0
- autobyteus/llm/token_counter/kimi_token_counter.py +24 -0
- autobyteus/llm/token_counter/token_counter_factory.py +3 -0
- autobyteus/llm/utils/messages.py +3 -3
- autobyteus/tools/__init__.py +2 -0
- autobyteus/tools/base_tool.py +7 -1
- autobyteus/tools/functional_tool.py +20 -5
- autobyteus/tools/mcp/call_handlers/stdio_handler.py +15 -1
- autobyteus/tools/mcp/config_service.py +106 -127
- autobyteus/tools/mcp/registrar.py +247 -59
- autobyteus/tools/mcp/types.py +5 -3
- autobyteus/tools/registry/tool_definition.py +8 -1
- autobyteus/tools/registry/tool_registry.py +18 -0
- autobyteus/tools/tool_category.py +11 -0
- autobyteus/tools/tool_meta.py +3 -1
- autobyteus/tools/tool_state.py +20 -0
- autobyteus/tools/usage/parsers/_json_extractor.py +99 -0
- autobyteus/tools/usage/parsers/default_json_tool_usage_parser.py +46 -77
- autobyteus/tools/usage/parsers/default_xml_tool_usage_parser.py +87 -96
- autobyteus/tools/usage/parsers/gemini_json_tool_usage_parser.py +37 -47
- autobyteus/tools/usage/parsers/openai_json_tool_usage_parser.py +112 -113
- {autobyteus-1.1.0.dist-info → autobyteus-1.1.2.dist-info}/METADATA +13 -12
- {autobyteus-1.1.0.dist-info → autobyteus-1.1.2.dist-info}/RECORD +103 -82
- {autobyteus-1.1.0.dist-info → autobyteus-1.1.2.dist-info}/WHEEL +0 -0
- {autobyteus-1.1.0.dist-info → autobyteus-1.1.2.dist-info}/licenses/LICENSE +0 -0
- {autobyteus-1.1.0.dist-info → autobyteus-1.1.2.dist-info}/top_level.txt +0 -0
autobyteus/llm/api/openai_llm.py
CHANGED
|
@@ -1,154 +1,26 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from typing import Optional
|
|
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.
|
|
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(
|
|
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
|
|
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__(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
autobyteus/llm/llm_factory.py
CHANGED
|
@@ -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="
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
142
|
-
value="claude-
|
|
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-
|
|
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="
|
|
152
|
-
value="anthropic.claude-
|
|
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-
|
|
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="
|
|
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="
|
|
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="
|
|
189
|
-
value="gemini-
|
|
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-
|
|
195
|
+
canonical_name="gemini-2.5-pro",
|
|
193
196
|
default_config=LLMConfig(
|
|
194
|
-
pricing_config=TokenPricingConfig(
|
|
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="
|
|
199
|
-
value="gemini-
|
|
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-
|
|
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="
|
|
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
|
|
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.
|
|
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., "
|
|
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
|
|
28
|
-
|
|
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., '
|
|
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
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
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
|
|
68
|
-
|
|
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. "
|
|
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: "
|
|
115
|
+
Example: "gpt-4o"
|
|
120
116
|
"""
|
|
121
117
|
return self._name
|
|
122
118
|
|