From 9b4111579c1e8d67c5c354e99cb0c970c58b7952 Mon Sep 17 00:00:00 2001 From: Asher-hss <101127070+Asher-hss@users.noreply.github.com> Date: Wed, 5 Feb 2025 05:27:13 -0800 Subject: [PATCH] feat: Integrate siliconflow model (#1541) Co-authored-by: Wendong-Fan <133094783+Wendong-Fan@users.noreply.github.com> Co-authored-by: Wendong --- .github/workflows/build_package.yml | 1 + .github/workflows/pytest_apps.yml | 2 + .github/workflows/pytest_package.yml | 2 + camel/configs/__init__.py | 3 + camel/configs/siliconflow_config.py | 91 ++++++++++++ camel/embeddings/jina_embedding.py | 7 +- camel/models/model_factory.py | 3 + camel/models/siliconflow_model.py | 142 +++++++++++++++++++ camel/types/enums.py | 40 +++++- examples/models/siliconflow_model_example.py | 47 ++++++ 10 files changed, 336 insertions(+), 2 deletions(-) create mode 100644 camel/configs/siliconflow_config.py create mode 100644 camel/models/siliconflow_model.py create mode 100644 examples/models/siliconflow_model_example.py diff --git a/.github/workflows/build_package.yml b/.github/workflows/build_package.yml index d5fd5474c0..449550efae 100644 --- a/.github/workflows/build_package.yml +++ b/.github/workflows/build_package.yml @@ -80,6 +80,7 @@ jobs: DISCORD_BOT_TOKEN: "${{ secrets.DISCORD_BOT_TOKEN }}" INTERNLM_API_KEY: "${{ secrets.INTERNLM_API_KEY }}" JINA_API_KEY: "${{ secrets.JINA_API_KEY }}" + SILICONFLOW_API_KEY: "${{ secrets.SILICONFLOW_API_KEY }}" MOONSHOT_API_KEY: "${{ secrets.MOONSHOT_API_KEY }}" run: | source venv/bin/activate diff --git a/.github/workflows/pytest_apps.yml b/.github/workflows/pytest_apps.yml index e07fdbc362..5b078d00b8 100644 --- a/.github/workflows/pytest_apps.yml +++ b/.github/workflows/pytest_apps.yml @@ -31,6 +31,7 @@ jobs: COHERE_API_KEY: "${{ secrets.COHERE_API_KEY }}" INTERNLM_API_KEY: "${{ secrets.INTERNLM_API_KEY }}" MOONSHOT_API_KEY: "${{ secrets.MOONSHOT_API_KEY }}" + SILICONFLOW_API_KEY: "${{ secrets.SILICONFLOW_API_KEY }}" run: poetry run pytest -v apps/ pytest_examples: @@ -51,4 +52,5 @@ jobs: COHERE_API_KEY: "${{ secrets.COHERE_API_KEY }}" INTERNLM_API_KEY: "${{ secrets.INTERNLM_API_KEY }}" MOONSHOT_API_KEY: "${{ secrets.MOONSHOT_API_KEY }}" + SILICONFLOW_API_KEY: "${{ secrets.SILICONFLOW_API_KEY }}" run: poetry run pytest -v examples/ diff --git a/.github/workflows/pytest_package.yml b/.github/workflows/pytest_package.yml index eb227633b7..3c0526ef57 100644 --- a/.github/workflows/pytest_package.yml +++ b/.github/workflows/pytest_package.yml @@ -60,6 +60,7 @@ jobs: INTERNLM_API_KEY: "${{ secrets.INTERNLM_API_KEY }}" JINA_API_KEY: "${{ secrets.JINA_API_KEY }}" MOONSHOT_API_KEY: "${{ secrets.MOONSHOT_API_KEY }}" + SILICONFLOW_API_KEY: "${{ secrets.SILICONFLOW_API_KEY }}" run: poetry run pytest --fast-test-mode test/ pytest_package_llm_test: @@ -109,6 +110,7 @@ jobs: INTERNLM_API_KEY: "${{ secrets.INTERNLM_API_KEY }}" JINA_API_KEY: "${{ secrets.JINA_API_KEY }}" MOONSHOT_API_KEY: "${{ secrets.MOONSHOT_API_KEY }}" + SILICONFLOW_API_KEY: "${{ secrets.SILICONFLOW_API_KEY }}" run: poetry run pytest --llm-test-only test/ pytest_package_very_slow_test: diff --git a/camel/configs/__init__.py b/camel/configs/__init__.py index 3a3250858f..9b3df60395 100644 --- a/camel/configs/__init__.py +++ b/camel/configs/__init__.py @@ -33,6 +33,7 @@ SambaVerseAPIConfig, ) from .sglang_config import SGLANG_API_PARAMS, SGLangConfig +from .siliconflow_config import SILICONFLOW_API_PARAMS, SiliconFlowConfig from .togetherai_config import TOGETHERAI_API_PARAMS, TogetherAIConfig from .vllm_config import VLLM_API_PARAMS, VLLMConfig from .yi_config import YI_API_PARAMS, YiConfig @@ -82,4 +83,6 @@ 'INTERNLM_API_PARAMS', 'MoonshotConfig', "MOONSHOT_API_PARAMS", + 'SiliconFlowConfig', + 'SILICONFLOW_API_PARAMS', ] diff --git a/camel/configs/siliconflow_config.py b/camel/configs/siliconflow_config.py new file mode 100644 index 0000000000..c2a76d4f2c --- /dev/null +++ b/camel/configs/siliconflow_config.py @@ -0,0 +1,91 @@ +# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. ========= +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. ========= +from __future__ import annotations + +from typing import Any, Sequence, Type, Union + +from pydantic import BaseModel + +from camel.configs.base_config import BaseConfig +from camel.types import NOT_GIVEN, NotGiven + + +class SiliconFlowConfig(BaseConfig): + r"""Defines the parameters for generating chat completions using the + SiliconFlow API. + + Args: + temperature (float, optional): Determines the degree of randomness + in the response. (default: :obj:`0.7`) + top_p (float, optional): The top_p (nucleus) parameter is used to + dynamically adjust the number of choices for each predicted token + based on the cumulative probabilities. (default: :obj:`0.7`) + n (int, optional): Number of generations to return. (default::obj:`1`) + response_format (object, optional): An object specifying the format + that the model must output. + stream (bool, optional): If set, tokens are returned as Server-Sent + Events as they are made available. (default: :obj:`False`) + stop (str or list, optional): Up to :obj:`4` sequences where the API + will stop generating further tokens. (default: :obj:`None`) + max_tokens (int, optional): The maximum number of tokens to generate. + (default: :obj:`None`) + frequency_penalty (float, optional): Number between :obj:`-2.0` and + :obj:`2.0`. Positive values penalize new tokens based on their + existing frequency in the text so far, decreasing the model's + likelihood to repeat the same line verbatim. See more information + about frequency and presence penalties. (default: :obj:`0.0`) + tools (list[FunctionTool], optional): A list of tools the model may + call. Currently, only functions are supported as a tool. Use this + to provide a list of functions the model may generate JSON inputs + for. A max of 128 functions are supported. + """ + + temperature: float = 0.7 + top_p: float = 0.7 + n: int = 1 + stream: bool = False + stop: Union[str, Sequence[str], NotGiven] = NOT_GIVEN + max_tokens: Union[int, NotGiven] = NOT_GIVEN + response_format: Union[Type[BaseModel], dict, NotGiven] = NOT_GIVEN + frequency_penalty: float = 0.0 + + def as_dict(self) -> dict[str, Any]: + r"""Convert the current configuration to a dictionary. + + This method converts the current configuration object to a dictionary + representation, which can be used for serialization or other purposes. + + Returns: + dict[str, Any]: A dictionary representation of the current + configuration. + """ + config_dict = self.model_dump() + if self.tools: + from camel.toolkits import FunctionTool + + tools_schema = [] + for tool in self.tools: + if not isinstance(tool, FunctionTool): + raise ValueError( + f"The tool {tool} should " + "be an instance of `FunctionTool`." + ) + tools_schema.append(tool.get_openai_tool_schema()) + config_dict["tools"] = NOT_GIVEN + return config_dict + + +SILICONFLOW_API_PARAMS = { + param for param in SiliconFlowConfig.model_fields.keys() +} diff --git a/camel/embeddings/jina_embedding.py b/camel/embeddings/jina_embedding.py index eca4473dea..db13d7c5cc 100644 --- a/camel/embeddings/jina_embedding.py +++ b/camel/embeddings/jina_embedding.py @@ -35,6 +35,11 @@ class JinaEmbedding(BaseEmbedding[Union[str, Image.Image]]): Jina AI. (default: :obj:`None`) dimensions (Optional[int], optional): The dimension of the output embeddings. (default: :obj:`None`) + embedding_type (Optional[str], optional): The type of embedding format + to generate. Options: 'int8' (binary encoding with higher storage + and transfer efficiency), 'uint8' (unsigned binary encoding with + higher storage and transfer efficiency), 'base64' (base64 string + encoding with higher transfer efficiency). (default: :obj:`None`) task (Optional[str], optional): The type of task for text embeddings. Options: retrieval.query, retrieval.passage, text-matching, classification, separation. (default: :obj:`None`) @@ -120,7 +125,7 @@ def embed_list( else: raise ValueError( f"Input type {type(obj)} is not supported. " - "Must be either str or PIL.Image" + "Must be either str or PIL.Image." ) data = { diff --git a/camel/models/model_factory.py b/camel/models/model_factory.py index 8ffc83e01c..560dcdc244 100644 --- a/camel/models/model_factory.py +++ b/camel/models/model_factory.py @@ -32,6 +32,7 @@ from camel.models.reka_model import RekaModel from camel.models.samba_model import SambaModel from camel.models.sglang_model import SGLangModel +from camel.models.siliconflow_model import SiliconFlowModel from camel.models.stub_model import StubModel from camel.models.togetherai_model import TogetherAIModel from camel.models.vllm_model import VLLMModel @@ -101,6 +102,8 @@ def create( model_class = LiteLLMModel elif model_platform.is_nvidia: model_class = NvidiaModel + elif model_platform.is_siliconflow: + model_class = SiliconFlowModel elif model_platform.is_openai and model_type.is_openai: model_class = OpenAIModel diff --git a/camel/models/siliconflow_model.py b/camel/models/siliconflow_model.py new file mode 100644 index 0000000000..f2197a7d92 --- /dev/null +++ b/camel/models/siliconflow_model.py @@ -0,0 +1,142 @@ +# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. ========= +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. ========= +import os +from typing import Any, Dict, List, Optional, Union + +from openai import OpenAI, Stream + +from camel.configs import SILICONFLOW_API_PARAMS, SiliconFlowConfig +from camel.messages import OpenAIMessage +from camel.models import BaseModelBackend +from camel.types import ( + ChatCompletion, + ChatCompletionChunk, + ModelType, +) +from camel.utils import ( + BaseTokenCounter, + OpenAITokenCounter, + api_keys_required, +) + + +class SiliconFlowModel(BaseModelBackend): + r"""SiliconFlow API in a unified BaseModelBackend interface. + + Args: + model_type (Union[ModelType, str]): Model for which a backend is + created. + model_config_dict (Optional[Dict[str, Any]], optional): A dictionary + that will be fed into OpenAI client. If :obj:`None`, + :obj:`SiliconFlowConfig().as_dict()` will be used. + (default: :obj:`None`) + api_key (Optional[str], optional): The API key for authenticating with + the SiliconFlow service. (default: :obj:`None`) + url (Optional[str], optional): The URL to the SiliconFlow service. If + not provided, :obj:`https://api.siliconflow.cn/v1/` will be used. + (default: :obj:`None`) + token_counter (Optional[BaseTokenCounter], optional): Token counter to + use for the model. If not provided, :obj:`OpenAITokenCounter( + ModelType.GPT_4O_MINI)` will be used. + (default: :obj:`None`) + """ + + @api_keys_required( + [ + ("api_key", 'SILICONFLOW_API_KEY'), + ] + ) + def __init__( + self, + model_type: Union[ModelType, str], + model_config_dict: Optional[Dict[str, Any]] = None, + api_key: Optional[str] = None, + url: Optional[str] = None, + token_counter: Optional[BaseTokenCounter] = None, + ) -> None: + if model_config_dict is None: + model_config_dict = SiliconFlowConfig().as_dict() + api_key = api_key or os.environ.get("SILICONFLOW_API_KEY") + url = url or os.environ.get( + "SILICONFLOW_API_BASE_URL", + "https://api.siliconflow.cn/v1/", + ) + super().__init__( + model_type, model_config_dict, api_key, url, token_counter + ) + self._client = OpenAI( + timeout=180, + max_retries=3, + api_key=self._api_key, + base_url=self._url, + ) + + def run( + self, + messages: List[OpenAIMessage], + ) -> Union[ChatCompletion, Stream[ChatCompletionChunk]]: + r"""Runs inference of SiliconFlow chat completion. + + Args: + messages (List[OpenAIMessage]): Message list with the chat history + in OpenAI API format. + + Returns: + Union[ChatCompletion, Stream[ChatCompletionChunk]]: + `ChatCompletion` in the non-stream mode, or + `Stream[ChatCompletionChunk]` in the stream mode. + """ + response = self._client.chat.completions.create( + messages=messages, + model=self.model_type, + **self.model_config_dict, + ) + return response + + @property + def token_counter(self) -> BaseTokenCounter: + r"""Initialize the token counter for the model backend. + + Returns: + BaseTokenCounter: The token counter following the model's + tokenization style. + """ + if not self._token_counter: + self._token_counter = OpenAITokenCounter(ModelType.GPT_4O_MINI) + return self._token_counter + + def check_model_config(self): + r"""Check whether the model configuration contains any + unexpected arguments to SiliconFlow API. + + Raises: + ValueError: If the model configuration dictionary contains any + unexpected arguments to SiliconFlow API. + """ + for param in self.model_config_dict: + if param not in SILICONFLOW_API_PARAMS: + raise ValueError( + f"Unexpected argument `{param}` is " + "input into SiliconFlow model backend." + ) + + @property + def stream(self) -> bool: + """Returns whether the model is in stream mode, which sends partial + results each time. + + Returns: + bool: Whether the model is in stream mode. + """ + return self.model_config_dict.get('stream', False) diff --git a/camel/types/enums.py b/camel/types/enums.py index b694ad2fd2..74c8efabe7 100644 --- a/camel/types/enums.py +++ b/camel/types/enums.py @@ -175,6 +175,20 @@ class ModelType(UnifiedModelType, Enum): MOONSHOT_V1_32K = "moonshot-v1-32k" MOONSHOT_V1_128K = "moonshot-v1-128k" + # SiliconFlow models support tool calling + SILICONFLOW_DEEPSEEK_V2_5 = "deepseek-ai/DeepSeek-V2.5" + SILICONFLOW_DEEPSEEK_V3 = "deepseek-ai/DeepSeek-V3" + SILICONFLOW_INTERN_LM2_5_20B_CHAT = "internlm/internlm2_5-20b-chat" + SILICONFLOW_INTERN_LM2_5_7B_CHAT = "internlm/internlm2_5-7b-chat" + SILICONFLOW_PRO_INTERN_LM2_5_7B_CHAT = "Pro/internlm/internlm2_5-7b-chat" + SILICONFLOW_QWEN2_5_72B_INSTRUCT = "Qwen/Qwen2.5-72B-Instruct" + SILICONFLOW_QWEN2_5_32B_INSTRUCT = "Qwen/Qwen2.5-32B-Instruct" + SILICONFLOW_QWEN2_5_14B_INSTRUCT = "Qwen/Qwen2.5-14B-Instruct" + SILICONFLOW_QWEN2_5_7B_INSTRUCT = "Qwen/Qwen2.5-7B-Instruct" + SILICONFLOW_PRO_QWEN2_5_7B_INSTRUCT = "Pro/Qwen/Qwen2.5-7B-Instruct" + SILICONFLOW_THUDM_GLM_4_9B_CHAT = "THUDM/glm-4-9b-chat" + SILICONFLOW_PRO_THUDM_GLM_4_9B_CHAT = "Pro/THUDM/glm-4-9b-chat" + def __str__(self): return self.value @@ -207,6 +221,7 @@ def support_native_tool_calling(self) -> bool: self.is_groq, self.is_sglang, self.is_moonshot, + self.is_siliconflow, ] ) @@ -292,7 +307,7 @@ def is_together(self) -> bool: @property def is_sambanova(self) -> bool: - r"""Returns whether this type of models is served by SambaNova AI.""" + r"""Returns whether this type of model is served by SambaNova AI.""" return self in { ModelType.SAMBA_LLAMA_3_1_8B, ModelType.SAMBA_LLAMA_3_1_70B, @@ -450,6 +465,23 @@ def is_sglang(self) -> bool: ModelType.SGLANG_QWEN_2_5_72B, } + @property + def is_siliconflow(self) -> bool: + return self in { + ModelType.SILICONFLOW_DEEPSEEK_V2_5, + ModelType.SILICONFLOW_DEEPSEEK_V3, + ModelType.SILICONFLOW_INTERN_LM2_5_20B_CHAT, + ModelType.SILICONFLOW_INTERN_LM2_5_7B_CHAT, + ModelType.SILICONFLOW_PRO_INTERN_LM2_5_7B_CHAT, + ModelType.SILICONFLOW_QWEN2_5_72B_INSTRUCT, + ModelType.SILICONFLOW_QWEN2_5_32B_INSTRUCT, + ModelType.SILICONFLOW_QWEN2_5_14B_INSTRUCT, + ModelType.SILICONFLOW_QWEN2_5_7B_INSTRUCT, + ModelType.SILICONFLOW_PRO_QWEN2_5_7B_INSTRUCT, + ModelType.SILICONFLOW_THUDM_GLM_4_9B_CHAT, + ModelType.SILICONFLOW_PRO_THUDM_GLM_4_9B_CHAT, + } + @property def token_limit(self) -> int: r"""Returns the maximum token limit for a given model. @@ -785,6 +817,7 @@ class ModelPlatformType(Enum): SGLANG = "sglang" INTERNLM = "internlm" MOONSHOT = "moonshot" + SILICONFLOW = "siliconflow" @property def is_openai(self) -> bool: @@ -897,6 +930,11 @@ def is_moonshot(self) -> bool: r"""Returns whether this platform is Moonshot model.""" return self is ModelPlatformType.MOONSHOT + @property + def is_siliconflow(self) -> bool: + r"""Returns whether this platform is SiliconFlow.""" + return self is ModelPlatformType.SILICONFLOW + class AudioModelType(Enum): TTS_1 = "tts-1" diff --git a/examples/models/siliconflow_model_example.py b/examples/models/siliconflow_model_example.py new file mode 100644 index 0000000000..f60c454427 --- /dev/null +++ b/examples/models/siliconflow_model_example.py @@ -0,0 +1,47 @@ +# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. ========= +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. ========= +from camel.agents import ChatAgent +from camel.configs import SiliconFlowConfig +from camel.models import ModelFactory +from camel.types import ModelPlatformType + +model = ModelFactory.create( + model_platform=ModelPlatformType.SILICONFLOW, + model_type="deepseek-ai/DeepSeek-R1", + model_config_dict=SiliconFlowConfig(temperature=0.2).as_dict(), +) + +# Define system message +sys_msg = "You are a helpful assistant." + +# Set agent +camel_agent = ChatAgent(system_message=sys_msg, model=model) + +user_msg = """Say hi to CAMEL AI, one open-source community + dedicated to the study of autonomous and communicative agents.""" + +# Get response information +response = camel_agent.step(user_msg) +print(response.msgs[0].content) + +''' +=============================================================================== +Hello CAMEL AI community! 👋 Your dedication to advancing the study of +autonomous and communicative agents through open-source collaboration is truly +inspiring. The work you're doing to push the boundaries of AI interaction and +cooperative systems will undoubtedly shape the future of intelligent +technologies. Keep innovating, exploring, and fostering that spirit of shared +learning—the world is excited to see what you create next! 🚀 +=============================================================================== +'''