|  | @@ -0,0 +1,205 @@
 | 
	
		
			
				|  |  | +import json
 | 
	
		
			
				|  |  | +import logging
 | 
	
		
			
				|  |  | +from collections.abc import Generator
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +from tencentcloud.common import credential
 | 
	
		
			
				|  |  | +from tencentcloud.common.exception import TencentCloudSDKException
 | 
	
		
			
				|  |  | +from tencentcloud.common.profile.client_profile import ClientProfile
 | 
	
		
			
				|  |  | +from tencentcloud.common.profile.http_profile import HttpProfile
 | 
	
		
			
				|  |  | +from tencentcloud.hunyuan.v20230901 import hunyuan_client, models
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +from core.model_runtime.entities.llm_entities import LLMResult, LLMResultChunk, LLMResultChunkDelta
 | 
	
		
			
				|  |  | +from core.model_runtime.entities.message_entities import (
 | 
	
		
			
				|  |  | +    AssistantPromptMessage,
 | 
	
		
			
				|  |  | +    PromptMessage,
 | 
	
		
			
				|  |  | +    PromptMessageTool,
 | 
	
		
			
				|  |  | +    SystemPromptMessage,
 | 
	
		
			
				|  |  | +    UserPromptMessage,
 | 
	
		
			
				|  |  | +)
 | 
	
		
			
				|  |  | +from core.model_runtime.errors.invoke import InvokeError
 | 
	
		
			
				|  |  | +from core.model_runtime.errors.validate import CredentialsValidateFailedError
 | 
	
		
			
				|  |  | +from core.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +logger = logging.getLogger(__name__)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +class HunyuanLargeLanguageModel(LargeLanguageModel):
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def _invoke(self, model: str, credentials: dict, prompt_messages: list[PromptMessage],
 | 
	
		
			
				|  |  | +                model_parameters: dict, tools: list[PromptMessageTool] | None = None,
 | 
	
		
			
				|  |  | +                stop: list[str] | None = None, stream: bool = True, user: str | None = None) \
 | 
	
		
			
				|  |  | +            -> LLMResult | Generator:
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        client = self._setup_hunyuan_client(credentials)
 | 
	
		
			
				|  |  | +        request = models.ChatCompletionsRequest()
 | 
	
		
			
				|  |  | +        messages_dict = self._convert_prompt_messages_to_dicts(prompt_messages)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        custom_parameters = {
 | 
	
		
			
				|  |  | +            'Temperature': model_parameters.get('temperature', 0.0),
 | 
	
		
			
				|  |  | +            'TopP': model_parameters.get('top_p', 1.0)
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        params = {
 | 
	
		
			
				|  |  | +            "Model": model,
 | 
	
		
			
				|  |  | +            "Messages": messages_dict,
 | 
	
		
			
				|  |  | +            "Stream": stream,
 | 
	
		
			
				|  |  | +            **custom_parameters,
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        request.from_json_string(json.dumps(params))
 | 
	
		
			
				|  |  | +        response = client.ChatCompletions(request)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        if stream:
 | 
	
		
			
				|  |  | +            return self._handle_stream_chat_response(model, credentials, prompt_messages, response)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        return self._handle_chat_response(credentials, model, prompt_messages, response)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def validate_credentials(self, model: str, credentials: dict) -> None:
 | 
	
		
			
				|  |  | +        """
 | 
	
		
			
				|  |  | +        Validate credentials
 | 
	
		
			
				|  |  | +        """
 | 
	
		
			
				|  |  | +        try:
 | 
	
		
			
				|  |  | +            client = self._setup_hunyuan_client(credentials)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            req = models.ChatCompletionsRequest()
 | 
	
		
			
				|  |  | +            params = {
 | 
	
		
			
				|  |  | +                "Model": model,
 | 
	
		
			
				|  |  | +                "Messages": [{
 | 
	
		
			
				|  |  | +                    "Role": "user",
 | 
	
		
			
				|  |  | +                    "Content": "hello"
 | 
	
		
			
				|  |  | +                }],
 | 
	
		
			
				|  |  | +                "TopP": 1,
 | 
	
		
			
				|  |  | +                "Temperature": 0,
 | 
	
		
			
				|  |  | +                "Stream": False
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            req.from_json_string(json.dumps(params))
 | 
	
		
			
				|  |  | +            client.ChatCompletions(req)
 | 
	
		
			
				|  |  | +        except Exception as e:
 | 
	
		
			
				|  |  | +            raise CredentialsValidateFailedError(f'Credentials validation failed: {e}')
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def _setup_hunyuan_client(self, credentials):
 | 
	
		
			
				|  |  | +        secret_id = credentials['secret_id']
 | 
	
		
			
				|  |  | +        secret_key = credentials['secret_key']
 | 
	
		
			
				|  |  | +        cred = credential.Credential(secret_id, secret_key)
 | 
	
		
			
				|  |  | +        httpProfile = HttpProfile()
 | 
	
		
			
				|  |  | +        httpProfile.endpoint = "hunyuan.tencentcloudapi.com"
 | 
	
		
			
				|  |  | +        clientProfile = ClientProfile()
 | 
	
		
			
				|  |  | +        clientProfile.httpProfile = httpProfile
 | 
	
		
			
				|  |  | +        client = hunyuan_client.HunyuanClient(cred, "", clientProfile)
 | 
	
		
			
				|  |  | +        return client
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def _convert_prompt_messages_to_dicts(self, prompt_messages: list[PromptMessage]) -> list[dict]:
 | 
	
		
			
				|  |  | +        """Convert a list of PromptMessage objects to a list of dictionaries with 'Role' and 'Content' keys."""
 | 
	
		
			
				|  |  | +        return [{"Role": message.role.value, "Content": message.content} for message in prompt_messages]
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def _handle_stream_chat_response(self, model, credentials, prompt_messages, resp):
 | 
	
		
			
				|  |  | +        for index, event in enumerate(resp):
 | 
	
		
			
				|  |  | +            logging.debug("_handle_stream_chat_response, event: %s", event)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            data_str = event['data']
 | 
	
		
			
				|  |  | +            data = json.loads(data_str)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            choices = data.get('Choices', [])
 | 
	
		
			
				|  |  | +            if not choices:
 | 
	
		
			
				|  |  | +                continue
 | 
	
		
			
				|  |  | +            choice = choices[0]
 | 
	
		
			
				|  |  | +            delta = choice.get('Delta', {})
 | 
	
		
			
				|  |  | +            message_content = delta.get('Content', '')
 | 
	
		
			
				|  |  | +            finish_reason = choice.get('FinishReason', '')
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            usage = data.get('Usage', {})
 | 
	
		
			
				|  |  | +            prompt_tokens = usage.get('PromptTokens', 0)
 | 
	
		
			
				|  |  | +            completion_tokens = usage.get('CompletionTokens', 0)
 | 
	
		
			
				|  |  | +            usage = self._calc_response_usage(model, credentials, prompt_tokens, completion_tokens)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            assistant_prompt_message = AssistantPromptMessage(
 | 
	
		
			
				|  |  | +                content=message_content,
 | 
	
		
			
				|  |  | +                tool_calls=[]
 | 
	
		
			
				|  |  | +            )
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            delta_chunk = LLMResultChunkDelta(
 | 
	
		
			
				|  |  | +                index=index,
 | 
	
		
			
				|  |  | +                role=delta.get('Role', 'assistant'),
 | 
	
		
			
				|  |  | +                message=assistant_prompt_message,
 | 
	
		
			
				|  |  | +                usage=usage,
 | 
	
		
			
				|  |  | +                finish_reason=finish_reason,
 | 
	
		
			
				|  |  | +            )
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            yield LLMResultChunk(
 | 
	
		
			
				|  |  | +                model=model,
 | 
	
		
			
				|  |  | +                prompt_messages=prompt_messages,
 | 
	
		
			
				|  |  | +                delta=delta_chunk,
 | 
	
		
			
				|  |  | +            )
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def _handle_chat_response(self, credentials, model, prompt_messages, response):
 | 
	
		
			
				|  |  | +        usage = self._calc_response_usage(model, credentials, response.Usage.PromptTokens,
 | 
	
		
			
				|  |  | +                                          response.Usage.CompletionTokens)
 | 
	
		
			
				|  |  | +        assistant_prompt_message = PromptMessage(role="assistant")
 | 
	
		
			
				|  |  | +        assistant_prompt_message.content = response.Choices[0].Message.Content
 | 
	
		
			
				|  |  | +        result = LLMResult(
 | 
	
		
			
				|  |  | +            model=model,
 | 
	
		
			
				|  |  | +            prompt_messages=prompt_messages,
 | 
	
		
			
				|  |  | +            message=assistant_prompt_message,
 | 
	
		
			
				|  |  | +            usage=usage,
 | 
	
		
			
				|  |  | +        )
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        return result
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def get_num_tokens(self, model: str, credentials: dict, prompt_messages: list[PromptMessage],
 | 
	
		
			
				|  |  | +                       tools: list[PromptMessageTool] | None = None) -> int:
 | 
	
		
			
				|  |  | +        if len(prompt_messages) == 0:
 | 
	
		
			
				|  |  | +            return 0
 | 
	
		
			
				|  |  | +        prompt = self._convert_messages_to_prompt(prompt_messages)
 | 
	
		
			
				|  |  | +        return self._get_num_tokens_by_gpt2(prompt)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def _convert_messages_to_prompt(self, messages: list[PromptMessage]) -> str:
 | 
	
		
			
				|  |  | +        """
 | 
	
		
			
				|  |  | +        Format a list of messages into a full prompt for the Anthropic model
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        :param messages: List of PromptMessage to combine.
 | 
	
		
			
				|  |  | +        :return: Combined string with necessary human_prompt and ai_prompt tags.
 | 
	
		
			
				|  |  | +        """
 | 
	
		
			
				|  |  | +        messages = messages.copy()  # don't mutate the original list
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        text = "".join(
 | 
	
		
			
				|  |  | +            self._convert_one_message_to_text(message)
 | 
	
		
			
				|  |  | +            for message in messages
 | 
	
		
			
				|  |  | +        )
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        # trim off the trailing ' ' that might come from the "Assistant: "
 | 
	
		
			
				|  |  | +        return text.rstrip()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def _convert_one_message_to_text(self, message: PromptMessage) -> str:
 | 
	
		
			
				|  |  | +        """
 | 
	
		
			
				|  |  | +        Convert a single message to a string.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        :param message: PromptMessage to convert.
 | 
	
		
			
				|  |  | +        :return: String representation of the message.
 | 
	
		
			
				|  |  | +        """
 | 
	
		
			
				|  |  | +        human_prompt = "\n\nHuman:"
 | 
	
		
			
				|  |  | +        ai_prompt = "\n\nAssistant:"
 | 
	
		
			
				|  |  | +        content = message.content
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        if isinstance(message, UserPromptMessage):
 | 
	
		
			
				|  |  | +            message_text = f"{human_prompt} {content}"
 | 
	
		
			
				|  |  | +        elif isinstance(message, AssistantPromptMessage):
 | 
	
		
			
				|  |  | +            message_text = f"{ai_prompt} {content}"
 | 
	
		
			
				|  |  | +        elif isinstance(message, SystemPromptMessage):
 | 
	
		
			
				|  |  | +            message_text = content
 | 
	
		
			
				|  |  | +        else:
 | 
	
		
			
				|  |  | +            raise ValueError(f"Got unknown type {message}")
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        return message_text
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    @property
 | 
	
		
			
				|  |  | +    def _invoke_error_mapping(self) -> dict[type[InvokeError], list[type[Exception]]]:
 | 
	
		
			
				|  |  | +        """
 | 
	
		
			
				|  |  | +        Map model invoke error to unified error
 | 
	
		
			
				|  |  | +        The key is the error type thrown to the caller
 | 
	
		
			
				|  |  | +        The value is the error type thrown by the model,
 | 
	
		
			
				|  |  | +        which needs to be converted into a unified error type for the caller.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        :return: Invoke error mapping
 | 
	
		
			
				|  |  | +        """
 | 
	
		
			
				|  |  | +        return {
 | 
	
		
			
				|  |  | +            InvokeError: [TencentCloudSDKException],
 | 
	
		
			
				|  |  | +        }
 |