mirror of
https://github.com/hwchase17/langchain
synced 2024-11-10 01:10:59 +00:00
community: replace it with Tencent Cloud SDK (#24172)
Description: The old method will be discontinued; use the official SDK for more model options. Issue: None Dependencies: None Twitter handle: None Co-authored-by: trumanyan <trumanyan@tencent.com>
This commit is contained in:
parent
99eb31ec41
commit
096b66db4a
@ -1,13 +1,7 @@
|
||||
import base64
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
from typing import Any, Dict, Iterator, List, Mapping, Optional, Type
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import requests
|
||||
from langchain_core.callbacks import CallbackManagerForLLMRun
|
||||
from langchain_core.language_models.chat_models import (
|
||||
BaseChatModel,
|
||||
@ -34,18 +28,15 @@ from langchain_core.utils import (
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_API_BASE = "https://hunyuan.cloud.tencent.com"
|
||||
DEFAULT_PATH = "/hyllm/v1/chat/completions"
|
||||
|
||||
|
||||
def _convert_message_to_dict(message: BaseMessage) -> dict:
|
||||
message_dict: Dict[str, Any]
|
||||
if isinstance(message, ChatMessage):
|
||||
message_dict = {"role": message.role, "content": message.content}
|
||||
message_dict = {"Role": message.role, "Content": message.content}
|
||||
elif isinstance(message, HumanMessage):
|
||||
message_dict = {"role": "user", "content": message.content}
|
||||
message_dict = {"Role": "user", "Content": message.content}
|
||||
elif isinstance(message, AIMessage):
|
||||
message_dict = {"role": "assistant", "content": message.content}
|
||||
message_dict = {"Role": "assistant", "Content": message.content}
|
||||
else:
|
||||
raise TypeError(f"Got unknown type {message}")
|
||||
|
||||
@ -53,20 +44,20 @@ def _convert_message_to_dict(message: BaseMessage) -> dict:
|
||||
|
||||
|
||||
def _convert_dict_to_message(_dict: Mapping[str, Any]) -> BaseMessage:
|
||||
role = _dict["role"]
|
||||
role = _dict["Role"]
|
||||
if role == "user":
|
||||
return HumanMessage(content=_dict["content"])
|
||||
return HumanMessage(content=_dict["Content"])
|
||||
elif role == "assistant":
|
||||
return AIMessage(content=_dict.get("content", "") or "")
|
||||
return AIMessage(content=_dict.get("Content", "") or "")
|
||||
else:
|
||||
return ChatMessage(content=_dict["content"], role=role)
|
||||
return ChatMessage(content=_dict["Content"], role=role)
|
||||
|
||||
|
||||
def _convert_delta_to_message_chunk(
|
||||
_dict: Mapping[str, Any], default_class: Type[BaseMessageChunk]
|
||||
) -> BaseMessageChunk:
|
||||
role = _dict.get("role")
|
||||
content = _dict.get("content") or ""
|
||||
role = _dict.get("Role")
|
||||
content = _dict.get("Content") or ""
|
||||
|
||||
if role == "user" or default_class == HumanMessageChunk:
|
||||
return HumanMessageChunk(content=content)
|
||||
@ -78,43 +69,13 @@ def _convert_delta_to_message_chunk(
|
||||
return default_class(content=content) # type: ignore[call-arg]
|
||||
|
||||
|
||||
# signature generation
|
||||
# https://cloud.tencent.com/document/product/1729/97732#532252ce-e960-48a7-8821-940a9ce2ccf3
|
||||
def _signature(secret_key: SecretStr, url: str, payload: Dict[str, Any]) -> str:
|
||||
sorted_keys = sorted(payload.keys())
|
||||
|
||||
url_info = urlparse(url)
|
||||
|
||||
sign_str = url_info.netloc + url_info.path + "?"
|
||||
|
||||
for key in sorted_keys:
|
||||
value = payload[key]
|
||||
|
||||
if isinstance(value, list) or isinstance(value, dict):
|
||||
value = json.dumps(value, separators=(",", ":"), ensure_ascii=False)
|
||||
elif isinstance(value, float):
|
||||
value = "%g" % value
|
||||
|
||||
sign_str = sign_str + key + "=" + str(value) + "&"
|
||||
|
||||
sign_str = sign_str[:-1]
|
||||
|
||||
hmacstr = hmac.new(
|
||||
key=secret_key.get_secret_value().encode("utf-8"),
|
||||
msg=sign_str.encode("utf-8"),
|
||||
digestmod=hashlib.sha1,
|
||||
).digest()
|
||||
|
||||
return base64.b64encode(hmacstr).decode("utf-8")
|
||||
|
||||
|
||||
def _create_chat_result(response: Mapping[str, Any]) -> ChatResult:
|
||||
generations = []
|
||||
for choice in response["choices"]:
|
||||
message = _convert_dict_to_message(choice["messages"])
|
||||
for choice in response["Choices"]:
|
||||
message = _convert_dict_to_message(choice["Message"])
|
||||
generations.append(ChatGeneration(message=message))
|
||||
|
||||
token_usage = response["usage"]
|
||||
token_usage = response["Usage"]
|
||||
llm_output = {"token_usage": token_usage}
|
||||
return ChatResult(generations=generations, llm_output=llm_output)
|
||||
|
||||
@ -137,8 +98,6 @@ class ChatHunyuan(BaseChatModel):
|
||||
def lc_serializable(self) -> bool:
|
||||
return True
|
||||
|
||||
hunyuan_api_base: str = Field(default=DEFAULT_API_BASE)
|
||||
"""Hunyuan custom endpoints"""
|
||||
hunyuan_app_id: Optional[int] = None
|
||||
"""Hunyuan App ID"""
|
||||
hunyuan_secret_id: Optional[str] = None
|
||||
@ -149,13 +108,26 @@ class ChatHunyuan(BaseChatModel):
|
||||
"""Whether to stream the results or not."""
|
||||
request_timeout: int = 60
|
||||
"""Timeout for requests to Hunyuan API. Default is 60 seconds."""
|
||||
|
||||
query_id: Optional[str] = None
|
||||
"""Query id for troubleshooting"""
|
||||
temperature: float = 1.0
|
||||
"""What sampling temperature to use."""
|
||||
top_p: float = 1.0
|
||||
"""What probability mass to use."""
|
||||
model: str = "hunyuan-lite"
|
||||
"""What Model to use.
|
||||
Optional model:
|
||||
- hunyuan-lite、
|
||||
- hunyuan-standard
|
||||
- hunyuan-standard-256K
|
||||
- hunyuan-pro
|
||||
- hunyuan-code
|
||||
- hunyuan-role
|
||||
- hunyuan-functioncall
|
||||
- hunyuan-vision
|
||||
"""
|
||||
stream_moderation: bool = False
|
||||
"""Whether to review the results or not when streaming is true."""
|
||||
enable_enhancement: bool = True
|
||||
"""Whether to enhancement the results or not."""
|
||||
|
||||
model_kwargs: Dict[str, Any] = Field(default_factory=dict)
|
||||
"""Holds any model parameters valid for API call not explicitly specified."""
|
||||
@ -193,12 +165,6 @@ class ChatHunyuan(BaseChatModel):
|
||||
|
||||
@pre_init
|
||||
def validate_environment(cls, values: Dict) -> Dict:
|
||||
values["hunyuan_api_base"] = get_from_dict_or_env(
|
||||
values,
|
||||
"hunyuan_api_base",
|
||||
"HUNYUAN_API_BASE",
|
||||
DEFAULT_API_BASE,
|
||||
)
|
||||
values["hunyuan_app_id"] = get_from_dict_or_env(
|
||||
values,
|
||||
"hunyuan_app_id",
|
||||
@ -216,22 +182,19 @@ class ChatHunyuan(BaseChatModel):
|
||||
"HUNYUAN_SECRET_KEY",
|
||||
)
|
||||
)
|
||||
|
||||
return values
|
||||
|
||||
@property
|
||||
def _default_params(self) -> Dict[str, Any]:
|
||||
"""Get the default parameters for calling Hunyuan API."""
|
||||
normal_params = {
|
||||
"app_id": self.hunyuan_app_id,
|
||||
"secret_id": self.hunyuan_secret_id,
|
||||
"temperature": self.temperature,
|
||||
"top_p": self.top_p,
|
||||
"Temperature": self.temperature,
|
||||
"TopP": self.top_p,
|
||||
"Model": self.model,
|
||||
"Stream": self.streaming,
|
||||
"StreamModeration": self.stream_moderation,
|
||||
"EnableEnhancement": self.enable_enhancement,
|
||||
}
|
||||
|
||||
if self.query_id is not None:
|
||||
normal_params["query_id"] = self.query_id
|
||||
|
||||
return {**normal_params, **self.model_kwargs}
|
||||
|
||||
def _generate(
|
||||
@ -248,13 +211,7 @@ class ChatHunyuan(BaseChatModel):
|
||||
return generate_from_stream(stream_iter)
|
||||
|
||||
res = self._chat(messages, **kwargs)
|
||||
|
||||
response = res.json()
|
||||
|
||||
if "error" in response:
|
||||
raise ValueError(f"Error from Hunyuan api response: {response}")
|
||||
|
||||
return _create_chat_result(response)
|
||||
return _create_chat_result(json.loads(res.to_json_string()))
|
||||
|
||||
def _stream(
|
||||
self,
|
||||
@ -266,19 +223,17 @@ class ChatHunyuan(BaseChatModel):
|
||||
res = self._chat(messages, **kwargs)
|
||||
|
||||
default_chunk_class = AIMessageChunk
|
||||
for chunk in res.iter_lines():
|
||||
chunk = chunk.decode(encoding="UTF-8", errors="strict").replace(
|
||||
"data: ", ""
|
||||
)
|
||||
for chunk in res:
|
||||
chunk = chunk.get("data", "")
|
||||
if len(chunk) == 0:
|
||||
continue
|
||||
response = json.loads(chunk)
|
||||
if "error" in response:
|
||||
raise ValueError(f"Error from Hunyuan api response: {response}")
|
||||
|
||||
for choice in response["choices"]:
|
||||
for choice in response["Choices"]:
|
||||
chunk = _convert_delta_to_message_chunk(
|
||||
choice["delta"], default_chunk_class
|
||||
choice["Delta"], default_chunk_class
|
||||
)
|
||||
default_chunk_class = chunk.__class__
|
||||
cg_chunk = ChatGenerationChunk(message=chunk)
|
||||
@ -286,42 +241,32 @@ class ChatHunyuan(BaseChatModel):
|
||||
run_manager.on_llm_new_token(chunk.content, chunk=cg_chunk)
|
||||
yield cg_chunk
|
||||
|
||||
def _chat(self, messages: List[BaseMessage], **kwargs: Any) -> requests.Response:
|
||||
def _chat(self, messages: List[BaseMessage], **kwargs: Any) -> Any:
|
||||
if self.hunyuan_secret_key is None:
|
||||
raise ValueError("Hunyuan secret key is not set.")
|
||||
|
||||
try:
|
||||
from tencentcloud.common import credential
|
||||
from tencentcloud.hunyuan.v20230901 import hunyuan_client, models
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"Could not import tencentcloud python package. "
|
||||
"Please install it with `pip install tencentcloud-sdk-python`."
|
||||
)
|
||||
|
||||
parameters = {**self._default_params, **kwargs}
|
||||
|
||||
headers = parameters.pop("headers", {})
|
||||
timestamp = parameters.pop("timestamp", int(time.time()))
|
||||
expired = parameters.pop("expired", timestamp + 24 * 60 * 60)
|
||||
|
||||
payload = {
|
||||
"timestamp": timestamp,
|
||||
"expired": expired,
|
||||
"messages": [_convert_message_to_dict(m) for m in messages],
|
||||
cred = credential.Credential(
|
||||
self.hunyuan_secret_id, str(self.hunyuan_secret_key.get_secret_value())
|
||||
)
|
||||
client = hunyuan_client.HunyuanClient(cred, "")
|
||||
req = models.ChatCompletionsRequest()
|
||||
params = {
|
||||
"Messages": [_convert_message_to_dict(m) for m in messages],
|
||||
**parameters,
|
||||
}
|
||||
|
||||
if self.streaming:
|
||||
payload["stream"] = 1
|
||||
|
||||
url = self.hunyuan_api_base + DEFAULT_PATH
|
||||
|
||||
res = requests.post(
|
||||
url=url,
|
||||
timeout=self.request_timeout,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": _signature(
|
||||
secret_key=self.hunyuan_secret_key, url=url, payload=payload
|
||||
),
|
||||
**headers,
|
||||
},
|
||||
json=payload,
|
||||
stream=self.streaming,
|
||||
)
|
||||
return res
|
||||
req.from_json_string(json.dumps(params))
|
||||
resp = client.ChatCompletions(req)
|
||||
return resp
|
||||
|
||||
@property
|
||||
def _llm_type(self) -> str:
|
||||
|
@ -1,8 +1,10 @@
|
||||
import pytest
|
||||
from langchain_core.messages import AIMessage, HumanMessage
|
||||
|
||||
from langchain_community.chat_models.hunyuan import ChatHunyuan
|
||||
|
||||
|
||||
@pytest.mark.requires("tencentcloud-sdk-python")
|
||||
def test_chat_hunyuan() -> None:
|
||||
chat = ChatHunyuan()
|
||||
message = HumanMessage(content="Hello")
|
||||
@ -11,6 +13,7 @@ def test_chat_hunyuan() -> None:
|
||||
assert isinstance(response.content, str)
|
||||
|
||||
|
||||
@pytest.mark.requires("tencentcloud-sdk-python")
|
||||
def test_chat_hunyuan_with_temperature() -> None:
|
||||
chat = ChatHunyuan(temperature=0.6)
|
||||
message = HumanMessage(content="Hello")
|
||||
@ -19,6 +22,24 @@ def test_chat_hunyuan_with_temperature() -> None:
|
||||
assert isinstance(response.content, str)
|
||||
|
||||
|
||||
@pytest.mark.requires("tencentcloud-sdk-python")
|
||||
def test_chat_hunyuan_with_model_name() -> None:
|
||||
chat = ChatHunyuan(model="hunyuan-standard")
|
||||
message = HumanMessage(content="Hello")
|
||||
response = chat.invoke([message])
|
||||
assert isinstance(response, AIMessage)
|
||||
assert isinstance(response.content, str)
|
||||
|
||||
|
||||
@pytest.mark.requires("tencentcloud-sdk-python")
|
||||
def test_chat_hunyuan_with_stream() -> None:
|
||||
chat = ChatHunyuan(streaming=True)
|
||||
message = HumanMessage(content="Hello")
|
||||
response = chat.invoke([message])
|
||||
assert isinstance(response, AIMessage)
|
||||
assert isinstance(response.content, str)
|
||||
|
||||
|
||||
def test_extra_kwargs() -> None:
|
||||
chat = ChatHunyuan(temperature=0.88, top_p=0.7)
|
||||
assert chat.temperature == 0.88
|
||||
|
@ -8,27 +8,25 @@ from langchain_core.messages import (
|
||||
HumanMessageChunk,
|
||||
SystemMessage,
|
||||
)
|
||||
from langchain_core.pydantic_v1 import SecretStr
|
||||
|
||||
from langchain_community.chat_models.hunyuan import (
|
||||
_convert_delta_to_message_chunk,
|
||||
_convert_dict_to_message,
|
||||
_convert_message_to_dict,
|
||||
_signature,
|
||||
)
|
||||
|
||||
|
||||
def test__convert_message_to_dict_human() -> None:
|
||||
message = HumanMessage(content="foo")
|
||||
result = _convert_message_to_dict(message)
|
||||
expected_output = {"role": "user", "content": "foo"}
|
||||
expected_output = {"Role": "user", "Content": "foo"}
|
||||
assert result == expected_output
|
||||
|
||||
|
||||
def test__convert_message_to_dict_ai() -> None:
|
||||
message = AIMessage(content="foo")
|
||||
result = _convert_message_to_dict(message)
|
||||
expected_output = {"role": "assistant", "content": "foo"}
|
||||
expected_output = {"Role": "assistant", "Content": "foo"}
|
||||
assert result == expected_output
|
||||
|
||||
|
||||
@ -47,68 +45,35 @@ def test__convert_message_to_dict_function() -> None:
|
||||
|
||||
|
||||
def test__convert_dict_to_message_human() -> None:
|
||||
message_dict = {"role": "user", "content": "foo"}
|
||||
message_dict = {"Role": "user", "Content": "foo"}
|
||||
result = _convert_dict_to_message(message_dict)
|
||||
expected_output = HumanMessage(content="foo")
|
||||
assert result == expected_output
|
||||
|
||||
|
||||
def test__convert_dict_to_message_ai() -> None:
|
||||
message_dict = {"role": "assistant", "content": "foo"}
|
||||
message_dict = {"Role": "assistant", "Content": "foo"}
|
||||
result = _convert_dict_to_message(message_dict)
|
||||
expected_output = AIMessage(content="foo")
|
||||
assert result == expected_output
|
||||
|
||||
|
||||
def test__convert_dict_to_message_other_role() -> None:
|
||||
message_dict = {"role": "system", "content": "foo"}
|
||||
message_dict = {"Role": "system", "Content": "foo"}
|
||||
result = _convert_dict_to_message(message_dict)
|
||||
expected_output = ChatMessage(role="system", content="foo")
|
||||
assert result == expected_output
|
||||
|
||||
|
||||
def test__convert_delta_to_message_assistant() -> None:
|
||||
delta = {"role": "assistant", "content": "foo"}
|
||||
delta = {"Role": "assistant", "Content": "foo"}
|
||||
result = _convert_delta_to_message_chunk(delta, AIMessageChunk)
|
||||
expected_output = AIMessageChunk(content="foo")
|
||||
assert result == expected_output
|
||||
|
||||
|
||||
def test__convert_delta_to_message_human() -> None:
|
||||
delta = {"role": "user", "content": "foo"}
|
||||
delta = {"Role": "user", "Content": "foo"}
|
||||
result = _convert_delta_to_message_chunk(delta, HumanMessageChunk)
|
||||
expected_output = HumanMessageChunk(content="foo")
|
||||
assert result == expected_output
|
||||
|
||||
|
||||
def test__signature() -> None:
|
||||
secret_key = SecretStr("YOUR_SECRET_KEY")
|
||||
url = "https://hunyuan.cloud.tencent.com/hyllm/v1/chat/completions"
|
||||
|
||||
result = _signature(
|
||||
secret_key=secret_key,
|
||||
url=url,
|
||||
payload={
|
||||
"app_id": "YOUR_APP_ID",
|
||||
"secret_id": "YOUR_SECRET_ID",
|
||||
"query_id": "test_query_id_cb5d8156-0ce2-45af-86b4-d02f5c26a142",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "You are a helpful assistant that translates English"
|
||||
" to French.Translate this sentence from English to"
|
||||
" French. I love programming.",
|
||||
}
|
||||
],
|
||||
"temperature": 0.0,
|
||||
"top_p": 0.8,
|
||||
"stream": 1,
|
||||
"timestamp": 1697738378,
|
||||
"expired": 1697824778,
|
||||
},
|
||||
)
|
||||
|
||||
# The signature was generated by the demo provided by Huanyuan.
|
||||
# https://hunyuan-sdk-1256237915.cos.ap-guangzhou.myqcloud.com/python.zip
|
||||
expected_output = "MXBvqNCXyxJWfEyBwk1pYBVnxzo="
|
||||
assert result == expected_output
|
||||
|
Loading…
Reference in New Issue
Block a user