Skip to content

Commit 97e5319

Browse files
lmstudio integration (camel-ai#2193)
Co-authored-by: Sun Tao <[email protected]>
1 parent dad6e1a commit 97e5319

File tree

11 files changed

+336
-4
lines changed

11 files changed

+336
-4
lines changed

.env

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@
104104
# OPENBB_TOKEN="Fill your API key here"
105105

106106
# AWS API (https://aws.amazon.com/)
107-
# AWS_ACCESS_KEY_ID="Fill your Access Key ID here"
108-
# AWS_SECRET_ACCESS_KEY="Fill your Secret Access Key here"
107+
# BEDROCK_API_BASE_URL="Fill your Access Key ID here"
108+
# BEDROCK_API_KEY="Fill your Secret Access Key here"
109+
109110
# Bocha Platform API(https://open.bochaai.com)
110111
# BOCHA_API_KEY="Fill your API key here"

camel/configs/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from .groq_config import GROQ_API_PARAMS, GroqConfig
2222
from .internlm_config import INTERNLM_API_PARAMS, InternLMConfig
2323
from .litellm_config import LITELLM_API_PARAMS, LiteLLMConfig
24+
from .lmstudio_config import LMSTUDIO_API_PARAMS, LMStudioConfig
2425
from .mistral_config import MISTRAL_API_PARAMS, MistralConfig
2526
from .modelscope_config import MODELSCOPE_API_PARAMS, ModelScopeConfig
2627
from .moonshot_config import MOONSHOT_API_PARAMS, MoonshotConfig
@@ -100,4 +101,6 @@
100101
'AIML_API_PARAMS',
101102
'OpenRouterConfig',
102103
'OPENROUTER_API_PARAMS',
104+
'LMSTUDIO_API_PARAMS',
105+
'LMStudioConfig',
103106
]

camel/configs/lmstudio_config.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14+
from __future__ import annotations
15+
16+
from typing import Optional, Sequence, Union
17+
18+
from camel.configs.base_config import BaseConfig
19+
20+
21+
class LMStudioConfig(BaseConfig):
22+
r"""Defines the parameters for generating chat completions using OpenAI
23+
compatibility.
24+
25+
Args:
26+
temperature (float, optional): Sampling temperature to use, between
27+
:obj:`0` and :obj:`2`. Higher values make the output more random,
28+
while lower values make it more focused and deterministic.
29+
(default: :obj:`None`)
30+
top_p (float, optional): An alternative to sampling with temperature,
31+
called nucleus sampling, where the model considers the results of
32+
the tokens with top_p probability mass. So :obj:`0.1` means only
33+
the tokens comprising the top 10% probability mass are considered.
34+
(default: :obj:`None`)
35+
n (int, optional): How many chat completion choices to generate for
36+
each input message. (default: :obj:`None`)
37+
response_format (object, optional): An object specifying the format
38+
that the model must output. Compatible with GPT-4 Turbo and all
39+
GPT-3.5 Turbo models newer than gpt-3.5-turbo-1106. Setting to
40+
{"type": "json_object"} enables JSON mode, which guarantees the
41+
message the model generates is valid JSON. Important: when using
42+
JSON mode, you must also instruct the model to produce JSON
43+
yourself via a system or user message. Without this, the model
44+
may generate an unending stream of whitespace until the generation
45+
reaches the token limit, resulting in a long-running and seemingly
46+
"stuck" request. Also note that the message content may be
47+
partially cut off if finish_reason="length", which indicates the
48+
generation exceeded max_tokens or the conversation exceeded the
49+
max context length.
50+
stream (bool, optional): If True, partial message deltas will be sent
51+
as data-only server-sent events as they become available.
52+
(default: :obj:`None`)
53+
stop (str or list, optional): Up to :obj:`4` sequences where the API
54+
will stop generating further tokens. (default: :obj:`None`)
55+
max_tokens (int, optional): The maximum number of tokens to generate
56+
in the chat completion. The total length of input tokens and
57+
generated tokens is limited by the model's context length.
58+
(default: :obj:`None`)
59+
presence_penalty (float, optional): Number between :obj:`-2.0` and
60+
:obj:`2.0`. Positive values penalize new tokens based on whether
61+
they appear in the text so far, increasing the model's likelihood
62+
to talk about new topics. See more information about frequency and
63+
presence penalties. (default: :obj:`None`)
64+
frequency_penalty (float, optional): Number between :obj:`-2.0` and
65+
:obj:`2.0`. Positive values penalize new tokens based on their
66+
existing frequency in the text so far, decreasing the model's
67+
likelihood to repeat the same line verbatim. See more information
68+
about frequency and presence penalties. (default: :obj:`None`)
69+
user (str, optional): A unique identifier representing your end-user,
70+
which can help OpenAI to monitor and detect abuse.
71+
(default: :obj:`None`)
72+
tools (list[FunctionTool], optional): A list of tools the model may
73+
call. Currently, only functions are supported as a tool. Use this
74+
to provide a list of functions the model may generate JSON inputs
75+
for. A max of 128 functions are supported.
76+
tool_choice (Union[dict[str, str], str], optional): Controls which (if
77+
any) tool is called by the model. :obj:`"none"` means the model
78+
will not call any tool and instead generates a message.
79+
:obj:`"auto"` means the model can pick between generating a
80+
message or calling one or more tools. :obj:`"required"` means the
81+
model must call one or more tools. Specifying a particular tool
82+
via {"type": "function", "function": {"name": "my_function"}}
83+
forces the model to call that tool. :obj:`"none"` is the default
84+
when no tools are present. :obj:`"auto"` is the default if tools
85+
are present.
86+
"""
87+
88+
temperature: Optional[float] = None
89+
top_p: Optional[float] = None
90+
n: Optional[int] = None
91+
stream: Optional[bool] = None
92+
stop: Optional[Union[str, Sequence[str]]] = None
93+
max_tokens: Optional[int] = None
94+
presence_penalty: Optional[float] = None
95+
response_format: Optional[dict] = None
96+
frequency_penalty: Optional[float] = None
97+
user: Optional[str] = None
98+
tool_choice: Optional[Union[dict[str, str], str]] = None
99+
100+
101+
LMSTUDIO_API_PARAMS = {param for param in LMStudioConfig.model_fields.keys()}

camel/models/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from .groq_model import GroqModel
2525
from .internlm_model import InternLMModel
2626
from .litellm_model import LiteLLMModel
27+
from .lmstudio_model import LMStudioModel
2728
from .mistral_model import MistralModel
2829
from .model_factory import ModelFactory
2930
from .model_manager import ModelManager, ModelProcessingError
@@ -88,4 +89,5 @@
8889
'BaseAudioModel',
8990
'SiliconFlowModel',
9091
'VolcanoModel',
92+
'LMStudioModel',
9193
]

camel/models/lmstudio_model.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14+
import os
15+
from typing import Any, Dict, Optional, Union
16+
17+
from camel.configs import LMSTUDIO_API_PARAMS, LMStudioConfig
18+
from camel.models.openai_compatible_model import OpenAICompatibleModel
19+
from camel.types import ModelType
20+
from camel.utils import BaseTokenCounter
21+
22+
23+
class LMStudioModel(OpenAICompatibleModel):
24+
r"""LLM served by LMStudio in a unified OpenAICompatibleModel interface.
25+
26+
Args:
27+
model_type (Union[ModelType, str]): Model for which a backend is
28+
created.
29+
model_config_dict (Optional[Dict[str, Any]], optional): A dictionary
30+
that will be fed into:obj:`openai.ChatCompletion.create()`.
31+
If:obj:`None`, :obj:`LMStudioConfig().as_dict()` will be used.
32+
(default: :obj:`None`)
33+
api_key (Optional[str], optional): The API key for authenticating
34+
with the LMStudio service. (default: :obj:`None`).
35+
url (Optional[str], optional): The url to the LMStudio service.
36+
(default: :obj:`None`)
37+
token_counter (Optional[BaseTokenCounter], optional): Token counter to
38+
use for the model. If not provided, :obj:`OpenAITokenCounter(
39+
ModelType.GPT_4O_MINI)` will be used.
40+
(default: :obj:`None`)
41+
timeout (Optional[float], optional): The timeout value in seconds for
42+
API calls. If not provided, will fall back to the MODEL_TIMEOUT
43+
environment variable or default to 180 seconds.
44+
(default: :obj:`None`)
45+
"""
46+
47+
def __init__(
48+
self,
49+
model_type: Union[ModelType, str],
50+
model_config_dict: Optional[Dict[str, Any]] = None,
51+
api_key: Optional[str] = None,
52+
url: Optional[str] = None,
53+
token_counter: Optional[BaseTokenCounter] = None,
54+
timeout: Optional[float] = None,
55+
) -> None:
56+
if model_config_dict is None:
57+
model_config_dict = LMStudioConfig().as_dict()
58+
api_key = "NA"
59+
url = url or os.environ.get(
60+
"LMSTUDIO_API_BASE_URL", "http://localhost:1234/v1"
61+
)
62+
timeout = timeout or float(os.environ.get("MODEL_TIMEOUT", 180))
63+
super().__init__(
64+
model_type, model_config_dict, api_key, url, token_counter, timeout
65+
)
66+
67+
def check_model_config(self):
68+
r"""Check whether the model configuration contains any unexpected
69+
arguments to LMStudio API. But LMStudio API does not have any
70+
additional arguments to check.
71+
72+
Raises:
73+
ValueError: If the model configuration dictionary contains any
74+
unexpected arguments to LMStudio API.
75+
"""
76+
for param in self.model_config_dict:
77+
if param not in LMSTUDIO_API_PARAMS:
78+
raise ValueError(
79+
f"Unexpected argument `{param}` is "
80+
"input into LMStudio model backend."
81+
)

camel/models/model_factory.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from camel.models.groq_model import GroqModel
2828
from camel.models.internlm_model import InternLMModel
2929
from camel.models.litellm_model import LiteLLMModel
30+
from camel.models.lmstudio_model import LMStudioModel
3031
from camel.models.mistral_model import MistralModel
3132
from camel.models.modelscope_model import ModelScopeModel
3233
from camel.models.moonshot_model import MoonshotModel
@@ -131,6 +132,8 @@ def create(
131132
model_class = AnthropicModel
132133
elif model_platform.is_groq and model_type.is_groq:
133134
model_class = GroqModel
135+
elif model_platform.is_lmstudio and model_type.is_lmstudio:
136+
model_class = LMStudioModel
134137
elif model_platform.is_openrouter and model_type.is_openrouter:
135138
model_class = OpenRouterModel
136139
elif model_platform.is_zhipuai and model_type.is_zhipuai:

camel/types/enums.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ class ModelType(UnifiedModelType, Enum):
8484
OPENROUTER_LLAMA_4_SCOUT_FREE = "meta-llama/llama-4-scout:free"
8585
OPENROUTER_OLYMPICODER_7B = "open-r1/olympiccoder-7b:free"
8686

87+
# LMStudio models
88+
LMSTUDIO_GEMMA_3_1B = "gemma-3-1b"
89+
LMSTUDIO_GEMMA_3_4B = "gemma-3-4b"
90+
LMSTUDIO_GEMMA_3_12B = "gemma-3-12b"
91+
LMSTUDIO_GEMMA_3_27B = "gemma-3-27b"
92+
8793
# TogetherAI platform models support tool calling
8894
TOGETHER_LLAMA_3_1_8B = "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo"
8995
TOGETHER_LLAMA_3_1_70B = "meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo"
@@ -317,6 +323,7 @@ def support_native_tool_calling(self) -> bool:
317323
self.is_sambanova,
318324
self.is_groq,
319325
self.is_openrouter,
326+
self.is_lmstudio,
320327
self.is_sglang,
321328
self.is_moonshot,
322329
self.is_siliconflow,
@@ -437,6 +444,16 @@ def is_openrouter(self) -> bool:
437444
ModelType.OPENROUTER_OLYMPICODER_7B,
438445
}
439446

447+
@property
448+
def is_lmstudio(self) -> bool:
449+
r"""Returns whether this type of models is served by LMStudio."""
450+
return self in {
451+
ModelType.LMSTUDIO_GEMMA_3_1B,
452+
ModelType.LMSTUDIO_GEMMA_3_4B,
453+
ModelType.LMSTUDIO_GEMMA_3_12B,
454+
ModelType.LMSTUDIO_GEMMA_3_27B,
455+
}
456+
440457
@property
441458
def is_together(self) -> bool:
442459
r"""Returns whether this type of models is served by Together AI."""
@@ -713,6 +730,10 @@ def token_limit(self) -> int:
713730
ModelType.GLM_4V_FLASH,
714731
ModelType.GLM_4_AIRX,
715732
ModelType.OPENROUTER_OLYMPICODER_7B,
733+
ModelType.LMSTUDIO_GEMMA_3_1B,
734+
ModelType.LMSTUDIO_GEMMA_3_4B,
735+
ModelType.LMSTUDIO_GEMMA_3_12B,
736+
ModelType.LMSTUDIO_GEMMA_3_27B,
716737
}:
717738
return 8_192
718739
elif self in {
@@ -1072,6 +1093,7 @@ class ModelPlatformType(Enum):
10721093
OPENROUTER = "openrouter"
10731094
OLLAMA = "ollama"
10741095
LITELLM = "litellm"
1096+
LMSTUDIO = "lmstudio"
10751097
ZHIPU = "zhipuai"
10761098
GEMINI = "gemini"
10771099
VLLM = "vllm"
@@ -1132,6 +1154,11 @@ def is_openrouter(self) -> bool:
11321154
r"""Returns whether this platform is openrouter."""
11331155
return self is ModelPlatformType.OPENROUTER
11341156

1157+
@property
1158+
def is_lmstudio(self) -> bool:
1159+
r"""Returns whether this platform is lmstudio."""
1160+
return self is ModelPlatformType.LMSTUDIO
1161+
11351162
@property
11361163
def is_ollama(self) -> bool:
11371164
r"""Returns whether this platform is ollama."""

camel/types/unified_model_type.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ def is_openrouter(self) -> bool:
8888
r"""Returns whether the model is a OpenRouter served model."""
8989
return True
9090

91+
@property
92+
def is_lmstudio(self) -> bool:
93+
r"""Returns whether the model is a LMStudio served model."""
94+
return True
95+
9196
@property
9297
def is_ppio(self) -> bool:
9398
r"""Returns whether the model is a PPIO served model."""
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14+
15+
from camel.agents import ChatAgent
16+
from camel.configs import LMStudioConfig
17+
from camel.models import ModelFactory
18+
from camel.types import ModelPlatformType, ModelType
19+
20+
model = ModelFactory.create(
21+
model_platform=ModelPlatformType.LMSTUDIO,
22+
model_type=ModelType.LMSTUDIO_GEMMA_3_1B,
23+
model_config_dict=LMStudioConfig(temperature=0.2).as_dict(),
24+
)
25+
26+
# Define system message
27+
sys_msg = "You are a helpful assistant."
28+
29+
# Set agent
30+
camel_agent = ChatAgent(system_message=sys_msg, model=model)
31+
32+
user_msg = """Say hi to CAMEL AI, one open-source community
33+
dedicated to the study of autonomous and communicative agents."""
34+
35+
# Get response information
36+
response = camel_agent.step(user_msg)
37+
print(response.msgs[0].content)
38+
39+
'''
40+
===============================================================================
41+
Hello to the CAMEL AI community. It's great to see a group of like-minded
42+
individuals coming together to explore and advance the field of autonomous and
43+
communicative agents. Your open-source approach is truly commendable, as it
44+
fosters collaboration, innovation, and transparency. I'm excited to learn more
45+
about your projects and initiatives, and I'm happy to help in any way I can.
46+
Keep pushing the boundaries of AI research and development!
47+
===============================================================================
48+
'''

test/models/test_aws_bedrock_model.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@ def test_aws_bedrock_model_unexpected_argument():
7070
async def test_aws_bedrock_async_not_implemented():
7171
"""Test AWSBedrockModel async method raising NotImplementedError."""
7272
model = AWSBedrockModel(ModelType.AWS_CLAUDE_3_HAIKU)
73-
73+
7474
with pytest.raises(
7575
NotImplementedError,
76-
match="AWS Bedrock does not support async inference."
76+
match="AWS Bedrock does not support async inference.",
7777
):
7878
await model.agenerate("Test message")

0 commit comments

Comments
 (0)