Jelajahi Sumber

feat: DeepSeek (#4162)

Su Yang 11 bulan lalu
induk
melakukan
9f440c11e0

+ 1 - 0
api/core/model_runtime/model_providers/_position.yaml

@@ -27,3 +27,4 @@
 - openllm
 - localai
 - openai_api_compatible
+- deepseek

TEMPAT SAMPAH
api/core/model_runtime/model_providers/deepseek/_assets/icon_l_en.png


File diff ditekan karena terlalu besar
+ 20 - 0
api/core/model_runtime/model_providers/deepseek/_assets/icon_l_en.svg


TEMPAT SAMPAH
api/core/model_runtime/model_providers/deepseek/_assets/icon_s_en.png


File diff ditekan karena terlalu besar
+ 1 - 0
api/core/model_runtime/model_providers/deepseek/_assets/icon_s_en.svg


+ 4 - 1
api/core/model_runtime/model_providers/deepseek/deepseek.py

@@ -7,7 +7,8 @@ from core.model_runtime.model_providers.__base.model_provider import ModelProvid
 logger = logging.getLogger(__name__)
 
 
-class DeepseekProvider(ModelProvider):
+
+class DeepSeekProvider(ModelProvider):
 
     def validate_provider_credentials(self, credentials: dict) -> None:
         """
@@ -19,6 +20,8 @@ class DeepseekProvider(ModelProvider):
         try:
             model_instance = self.get_model_instance(ModelType.LLM)
 
+            # Use `deepseek-chat` model for validate,
+            # no matter what model you pass in, text completion model or chat model
             model_instance.validate_credentials(
                 model='deepseek-chat',
                 credentials=credentials

+ 19 - 6
api/core/model_runtime/model_providers/deepseek/deepseek.yaml

@@ -1,15 +1,19 @@
 provider: deepseek
 label:
-  en_US: Deepseek
+  en_US: deepseek
+  zh_Hans: 深度求索
+description:
+  en_US: Models provided by deepseek, such as deepseek-chat、deepseek-coder.
+  zh_Hans: 深度求索提供的模型,例如 deepseek-chat、deepseek-coder 。
 icon_small:
-  en_US: icon_s_en.png
+  en_US: icon_s_en.svg
 icon_large:
-  en_US: icon_l_en.png
-background: "#FFFFFF"
+  en_US: icon_l_en.svg
+background: "#c0cdff"
 help:
   title:
-    en_US: Get your API Key from Deepseek
-    zh_Hans: 从 Deepseek 获取 API Key
+    en_US: Get your API Key from deepseek
+    zh_Hans: 从深度求索获取 API Key
   url:
     en_US: https://platform.deepseek.com/api_keys
 supported_model_types:
@@ -26,3 +30,12 @@ provider_credential_schema:
       placeholder:
         zh_Hans: 在此输入您的 API Key
         en_US: Enter your API Key
+    - variable: endpoint_url
+      label:
+        zh_Hans: 自定义 API endpoint 地址
+        en_US: CUstom API endpoint URL
+      type: text-input
+      required: false
+      placeholder:
+        zh_Hans: Base URL, e.g. https://api.deepseek.com/v1 or https://api.deepseek.com
+        en_US: Base URL, e.g. https://api.deepseek.com/v1 or https://api.deepseek.com

+ 46 - 8
api/core/model_runtime/model_providers/deepseek/llm/deepseek-chat.yaml

@@ -11,16 +11,54 @@ model_properties:
 parameter_rules:
   - name: temperature
     use_template: temperature
-    min: 0
-    max: 1
-    default: 0.5
-  - name: top_p
-    use_template: top_p
-    min: 0
-    max: 1
+    type: float
     default: 1
+    min: 0.0
+    max: 2.0
+    help:
+      zh_Hans: 控制生成结果的多样性和随机性。数值越小,越严谨;数值越大,越发散。
+      en_US: Control the diversity and randomness of generated results. The smaller the value, the more rigorous it is; the larger the value, the more divergent it is.
   - name: max_tokens
     use_template: max_tokens
+    type: int
+    default: 4096
     min: 1
     max: 32000
-    default: 1024
+    help:
+      zh_Hans: 指定生成结果长度的上限。如果生成结果截断,可以调大该参数。
+      en_US: Specifies the upper limit on the length of generated results. If the generated results are truncated, you can increase this parameter.
+  - name: top_p
+    use_template: top_p
+    type: float
+    default: 1
+    min: 0.01
+    max: 1.00
+    help:
+      zh_Hans: 控制生成结果的随机性。数值越小,随机性越弱;数值越大,随机性越强。一般而言,top_p 和 temperature 两个参数选择一个进行调整即可。
+      en_US: Control the randomness of generated results. The smaller the value, the weaker the randomness; the larger the value, the stronger the randomness. Generally speaking, you can adjust one of the two parameters top_p and temperature.
+  - name: logprobs
+    help:
+      zh_Hans: 是否返回所输出 token 的对数概率。如果为 true,则在 message 的 content 中返回每个输出 token 的对数概率。
+      en_US: Whether to return the log probability of the output token. If true, returns the log probability of each output token in the content of message .
+    type: boolean
+  - name: top_logprobs
+    type: int
+    default: 0
+    min: 0
+    max: 20
+    help:
+      zh_Hans: 一个介于 0 到 20 之间的整数 N,指定每个输出位置返回输出概率 top N 的 token,且返回这些 token 的对数概率。指定此参数时,logprobs 必须为 true。
+      en_US: An integer N between 0 and 20, specifying that each output position returns the top N tokens with output probability, and returns the logarithmic probability of these tokens. When specifying this parameter, logprobs must be true.
+  - name: frequency_penalty
+    use_template: frequency_penalty
+    default: 0
+    min: -2.0
+    max: 2.0
+    help:
+      zh_Hans: 介于 -2.0 和 2.0 之间的数字。如果该值为正,那么新 token 会根据其在已有文本中的出现频率受到相应的惩罚,降低模型重复相同内容的可能性。
+      en_US: A number between -2.0 and 2.0. If the value is positive, new tokens are penalized based on their frequency of occurrence in existing text, reducing the likelihood that the model will repeat the same content.
+pricing:
+  input: '1'
+  output: '2'
+  unit: '0.000001'
+  currency: RMB

+ 91 - 5
api/core/model_runtime/model_providers/deepseek/llm/llm.py

@@ -1,18 +1,24 @@
 from collections.abc import Generator
 from typing import Optional, Union
+from urllib.parse import urlparse
+
+import tiktoken
 
 from core.model_runtime.entities.llm_entities import LLMResult
-from core.model_runtime.entities.message_entities import PromptMessage, PromptMessageTool
-from core.model_runtime.model_providers.openai_api_compatible.llm.llm import OAIAPICompatLargeLanguageModel
+from core.model_runtime.entities.message_entities import (
+    PromptMessage,
+    PromptMessageTool,
+)
+from core.model_runtime.model_providers.openai.llm.llm import OpenAILargeLanguageModel
+
 
+class DeepSeekLargeLanguageModel(OpenAILargeLanguageModel):
 
-class DeepseekLargeLanguageModel(OAIAPICompatLargeLanguageModel):
     def _invoke(self, model: str, credentials: dict,
                 prompt_messages: list[PromptMessage], model_parameters: dict,
                 tools: Optional[list[PromptMessageTool]] = None, stop: Optional[list[str]] = None,
                 stream: bool = True, user: Optional[str] = None) \
             -> Union[LLMResult, Generator]:
-        
         self._add_custom_parameters(credentials)
 
         return super()._invoke(model, credentials, prompt_messages, model_parameters, tools, stop, stream, user)
@@ -21,7 +27,87 @@ class DeepseekLargeLanguageModel(OAIAPICompatLargeLanguageModel):
         self._add_custom_parameters(credentials)
         super().validate_credentials(model, credentials)
 
+
+    # refactored from openai model runtime, use cl100k_base for calculate token number
+    def _num_tokens_from_string(self, model: str, text: str,
+                                tools: Optional[list[PromptMessageTool]] = None) -> int:
+        """
+        Calculate num tokens for text completion model with tiktoken package.
+
+        :param model: model name
+        :param text: prompt text
+        :param tools: tools for tool calling
+        :return: number of tokens
+        """
+        encoding = tiktoken.get_encoding("cl100k_base")
+        num_tokens = len(encoding.encode(text))
+
+        if tools:
+            num_tokens += self._num_tokens_for_tools(encoding, tools)
+
+        return num_tokens
+
+    # refactored from openai model runtime, use cl100k_base for calculate token number
+    def _num_tokens_from_messages(self, model: str, messages: list[PromptMessage],
+                                  tools: Optional[list[PromptMessageTool]] = None) -> int:
+        """Calculate num tokens for gpt-3.5-turbo and gpt-4 with tiktoken package.
+
+        Official documentation: https://github.com/openai/openai-cookbook/blob/
+        main/examples/How_to_format_inputs_to_ChatGPT_models.ipynb"""
+        encoding = tiktoken.get_encoding("cl100k_base")
+        tokens_per_message = 3
+        tokens_per_name = 1
+
+        num_tokens = 0
+        messages_dict = [self._convert_prompt_message_to_dict(m) for m in messages]
+        for message in messages_dict:
+            num_tokens += tokens_per_message
+            for key, value in message.items():
+                # Cast str(value) in case the message value is not a string
+                # This occurs with function messages
+                # TODO: The current token calculation method for the image type is not implemented,
+                #  which need to download the image and then get the resolution for calculation,
+                #  and will increase the request delay
+                if isinstance(value, list):
+                    text = ''
+                    for item in value:
+                        if isinstance(item, dict) and item['type'] == 'text':
+                            text += item['text']
+
+                    value = text
+
+                if key == "tool_calls":
+                    for tool_call in value:
+                        for t_key, t_value in tool_call.items():
+                            num_tokens += len(encoding.encode(t_key))
+                            if t_key == "function":
+                                for f_key, f_value in t_value.items():
+                                    num_tokens += len(encoding.encode(f_key))
+                                    num_tokens += len(encoding.encode(f_value))
+                            else:
+                                num_tokens += len(encoding.encode(t_key))
+                                num_tokens += len(encoding.encode(t_value))
+                else:
+                    num_tokens += len(encoding.encode(str(value)))
+
+                if key == "name":
+                    num_tokens += tokens_per_name
+
+        # every reply is primed with <im_start>assistant
+        num_tokens += 3
+
+        if tools:
+            num_tokens += self._num_tokens_for_tools(encoding, tools)
+
+        return num_tokens
+
     @staticmethod
     def _add_custom_parameters(credentials: dict) -> None:
         credentials['mode'] = 'chat'
-        credentials['endpoint_url'] = 'https://api.deepseek.com/'
+        credentials['openai_api_key']=credentials['api_key']
+        if 'endpoint_url' not in credentials or credentials['endpoint_url'] == "":
+            credentials['openai_api_base']='https://api.deepseek.com'
+        else:
+            parsed_url = urlparse(credentials['endpoint_url'])
+            credentials['openai_api_base']=f"{parsed_url.scheme}://{parsed_url.netloc}"
+

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini