From 153dafffec43ee351e538fafd0b4fa46cfcee9c5 Mon Sep 17 00:00:00 2001 From: TeCHiScy <741195+TeCHiScy@users.noreply.github.com> Date: Sat, 1 Jun 2024 07:23:59 +0800 Subject: [PATCH] community[minor]: Add support for Tencent Hunyuan Chat Model and Embeddings (#5476) * community: Add support for Tencent Hunyuan chat model and embeddings Tencent provides its Hunyuan chat model and embeddings through Tencent cloud. This PR adds support for the [chat model](https://cloud.tencent.com/document/product/1729/105701) as well as [embedding](https://cloud.tencent.com/document/product/1729/102832). * community[minor]: Add support for Tencent Hunyuan Chat Model and Embeddings * refactor Chat Model streaming implementation with AsyncGenerator * update stream example * community[minor]: Add support for Tencent Hunyuan Chat Model and Embeddings * make host configurable * community[minor]: Add support for Tencent Hunyuan Chat Model and Embeddings * support both nodejs and browser environment * update documents and examples * community[minor]: Add support for Tencent Hunyuan Chat Model and Embeddings * make separate chat model and embedding entrypoints for web vs. node * community[minor]: Add support for Tencent Hunyuan Chat Model and Embeddings * rename docs sidebar name * community[minor]: Add support for Tencent Hunyuan Chat Model and Embeddings * format code * Update build, lint, format * Type export --------- Co-authored-by: jacoblee93 --- ...pto-js-npm-4.2.2-21357d525e-727daa0d2d.zip | Bin 0 -> 19490 bytes .../docs/integrations/chat/index.mdx | 1 + .../integrations/chat/tencent_hunyuan.mdx | 41 ++ .../text_embedding/tencent_hunyuan.mdx | 38 ++ .../chat/integration_tencent_hunyuan.ts | 111 ++++ .../src/models/embeddings/tencent_hunyuan.ts | 13 + libs/langchain-community/.gitignore | 16 + libs/langchain-community/langchain.config.js | 8 + libs/langchain-community/package.json | 58 ++ .../src/chat_models/tencent_hunyuan/base.ts | 552 ++++++++++++++++++ .../src/chat_models/tencent_hunyuan/index.ts | 44 ++ .../src/chat_models/tencent_hunyuan/web.ts | 43 ++ .../tests/chattencenthunyuan.int.test.ts | 142 +++++ .../src/embeddings/tencent_hunyuan/base.ts | 194 ++++++ .../src/embeddings/tencent_hunyuan/index.ts | 16 + .../src/embeddings/tencent_hunyuan/web.ts | 16 + .../embeddings/tests/tencent_hunyuan.test.ts | 32 + .../src/load/import_constants.ts | 4 + .../src/load/import_type.ts | 2 + .../src/utils/tencent_hunyuan/common.ts | 31 + .../src/utils/tencent_hunyuan/index.ts | 47 ++ .../src/utils/tencent_hunyuan/web.ts | 39 ++ yarn.lock | 12 + 23 files changed, 1460 insertions(+) create mode 100644 .yarn/cache/@types-crypto-js-npm-4.2.2-21357d525e-727daa0d2d.zip create mode 100644 docs/core_docs/docs/integrations/chat/tencent_hunyuan.mdx create mode 100644 docs/core_docs/docs/integrations/text_embedding/tencent_hunyuan.mdx create mode 100644 examples/src/models/chat/integration_tencent_hunyuan.ts create mode 100644 examples/src/models/embeddings/tencent_hunyuan.ts create mode 100644 libs/langchain-community/src/chat_models/tencent_hunyuan/base.ts create mode 100644 libs/langchain-community/src/chat_models/tencent_hunyuan/index.ts create mode 100644 libs/langchain-community/src/chat_models/tencent_hunyuan/web.ts create mode 100644 libs/langchain-community/src/chat_models/tests/chattencenthunyuan.int.test.ts create mode 100644 libs/langchain-community/src/embeddings/tencent_hunyuan/base.ts create mode 100644 libs/langchain-community/src/embeddings/tencent_hunyuan/index.ts create mode 100644 libs/langchain-community/src/embeddings/tencent_hunyuan/web.ts create mode 100644 libs/langchain-community/src/embeddings/tests/tencent_hunyuan.test.ts create mode 100644 libs/langchain-community/src/utils/tencent_hunyuan/common.ts create mode 100644 libs/langchain-community/src/utils/tencent_hunyuan/index.ts create mode 100644 libs/langchain-community/src/utils/tencent_hunyuan/web.ts diff --git a/.yarn/cache/@types-crypto-js-npm-4.2.2-21357d525e-727daa0d2d.zip b/.yarn/cache/@types-crypto-js-npm-4.2.2-21357d525e-727daa0d2d.zip new file mode 100644 index 0000000000000000000000000000000000000000..255875e46710f7bad88de2c6e3d11c2e53a194eb GIT binary patch literal 19490 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 IJ|4vX0oC?_IsgCw literal 0 HcmV?d00001 diff --git a/docs/core_docs/docs/integrations/chat/index.mdx b/docs/core_docs/docs/integrations/chat/index.mdx index 15413c67fd79..51ba69379288 100644 --- a/docs/core_docs/docs/integrations/chat/index.mdx +++ b/docs/core_docs/docs/integrations/chat/index.mdx @@ -43,6 +43,7 @@ The table shows, for each integration, which features have been implemented with | ChatMistralAI | ✅ | ❌ | ✅ | ❌ | ✅ | ✅ | | ChatOllama | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | | ChatOpenAI | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| ChatTencentHunyuan | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | | ChatTogetherAI | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | | ChatYandexGPT | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | | ChatZhipuAI | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | diff --git a/docs/core_docs/docs/integrations/chat/tencent_hunyuan.mdx b/docs/core_docs/docs/integrations/chat/tencent_hunyuan.mdx new file mode 100644 index 000000000000..339cd7af9bd5 --- /dev/null +++ b/docs/core_docs/docs/integrations/chat/tencent_hunyuan.mdx @@ -0,0 +1,41 @@ +--- +sidebar_label: Tencent Hunyuan +--- + +import CodeBlock from "@theme/CodeBlock"; + +# ChatTencentHunyuan + +LangChain.js supports the Tencent Hunyuan family of models. + +https://cloud.tencent.com/document/product/1729/104753 + +## Setup + +1. Sign up for a Tencent Cloud account [here](https://cloud.tencent.com/register). +2. Create SecretID & SecretKey [here](https://console.cloud.tencent.com/cam/capi). +3. Set SecretID and SecretKey as environment variables named `TENCENT_SECRET_ID` and `TENCENT_SECRET_KEY`, respectively. + +import IntegrationInstallTooltip from "@mdx_components/integration_install_tooltip.mdx"; + + + +```bash npm2yarn +npm install @langchain/community +``` + +If you are using LangChain.js in a browser environment, you'll also need to install the following dependencies: + +```bash npm2yarn +npm install crypto-js +``` + +And then make sure that you import from the `web` as shown below. + +## Usage + +Here's an example: + +import TencentHunyuan from "@examples/models/chat/integration_tencent_hunyuan.ts"; + +{TencentHunyuan} diff --git a/docs/core_docs/docs/integrations/text_embedding/tencent_hunyuan.mdx b/docs/core_docs/docs/integrations/text_embedding/tencent_hunyuan.mdx new file mode 100644 index 000000000000..0020db655ba8 --- /dev/null +++ b/docs/core_docs/docs/integrations/text_embedding/tencent_hunyuan.mdx @@ -0,0 +1,38 @@ +--- +sidebar_label: Tencent Hunyuan +--- + +# TencentHunyuan + +The `TencentHunyuanEmbeddings` class uses the Tencent Hunyuan API to generate embeddings for a given text. + +## Setup + +1. Sign up for a Tencent Cloud account [here](https://cloud.tencent.com/register). +2. Create SecretID & SecretKey [here](https://console.cloud.tencent.com/cam/capi). +3. Set SecretID and SecretKey as environment variables named `TENCENT_SECRET_ID` and `TENCENT_SECRET_KEY`, respectively. + +import IntegrationInstallTooltip from "@mdx_components/integration_install_tooltip.mdx"; + + + +```bash npm2yarn +npm install @langchain/community +``` + +If you are using LangChain.js in a browser environment, you'll also need to install the following dependencies: + +```bash npm2yarn +npm install crypto-js +``` + +And then make sure that you import from the `web` as shown below. + +## Usage + +Here's an example: + +import CodeBlock from "@theme/CodeBlock"; +import TencentHunyuan from "@examples/models/embeddings/tencent_hunyuan.ts"; + +{TencentHunyuan} diff --git a/examples/src/models/chat/integration_tencent_hunyuan.ts b/examples/src/models/chat/integration_tencent_hunyuan.ts new file mode 100644 index 000000000000..31da4272f57f --- /dev/null +++ b/examples/src/models/chat/integration_tencent_hunyuan.ts @@ -0,0 +1,111 @@ +// in nodejs environment +import { ChatTencentHunyuan } from "@langchain/community/chat_models/tencent_hunyuan"; + +// in browser environment +// import { ChatTencentHunyuan } from "@langchain/community/chat_models/tencent_hunyuan/web"; + +import { HumanMessage } from "@langchain/core/messages"; +import type { LLMResult } from "@langchain/core/outputs"; + +const messages = [new HumanMessage("Hello")]; + +// Default model is hunyuan-pro +const hunyuanPro = new ChatTencentHunyuan({ + streaming: false, + temperature: 1, +}); + +let res = await hunyuanPro.invoke(messages); +console.log(res); +/* +AIMessage { + content: 'Hello! How can I help you today?Is there anything I can do for you?', + name: undefined, + additional_kwargs: {}, + response_metadata: { + tokenUsage: { totalTokens: 20, promptTokens: 1, completionTokens: 19 } + }, + tool_calls: [], + invalid_tool_calls: [] +} +*/ + +// Use hunyuan-lite +const hunyuanLite = new ChatTencentHunyuan({ + model: "hunyuan-lite", + streaming: false, +}); + +res = await hunyuanLite.invoke(messages); +console.log(res); +/* +AIMessage { + content: '你好!很高兴为你提供服务~有什么我可以帮助你的吗?', + name: undefined, + additional_kwargs: {}, + response_metadata: { + tokenUsage: { totalTokens: 14, promptTokens: 1, completionTokens: 13 } + }, + tool_calls: [], + invalid_tool_calls: [] +} +*/ + +// Use hunyuan-lite with streaming +const hunyuanLiteStream = new ChatTencentHunyuan({ + model: "hunyuan-lite", + streaming: true, + temperature: 1, +}); + +hunyuanLiteStream.invoke(messages, { + callbacks: [ + { + handleLLMEnd(output: LLMResult) { + console.log(output); + /* + { + generations: [ + [ + [Object], [Object], + [Object], [Object], + [Object], [Object], + [Object], [Object], + [Object] + ] + ], + llmOutput: { + tokenUsage: { totalTokens: 9, promptTokens: 1, completionTokens: 8 } + } + } + */ + }, + handleLLMNewToken(token: string) { + console.log(`token: ${token}`); + /* + token: 你好 + token: ! + token: 很高兴 + token: 能 + token: 为您 + token: 解答 + token: 问题 + token: 和建议 + token: 方案 + token: . + token: 如果您 + token: 有其他 + token: 需要帮助 + token: 的地方 + token: , + token: + token: 随时 + token: 告诉我 + token: 哦 + token: ~ + token: + */ + }, + }, + ], +}); diff --git a/examples/src/models/embeddings/tencent_hunyuan.ts b/examples/src/models/embeddings/tencent_hunyuan.ts new file mode 100644 index 000000000000..123a5930579d --- /dev/null +++ b/examples/src/models/embeddings/tencent_hunyuan.ts @@ -0,0 +1,13 @@ +// in nodejs environment +import { TencentHunyuanEmbeddings } from "@langchain/community/embeddings/tencent_hunyuan"; + +// in browser environment +// import { TencentHunyuanEmbeddings } from "@langchain/community/embeddings/tencent_hunyuan/web"; + +/* Embed queries */ +const embeddings = new TencentHunyuanEmbeddings(); +const res = await embeddings.embedQuery("你好,世界!"); +console.log(res); +/* Embed documents */ +const documentRes = await embeddings.embedDocuments(["你好,世界!", "再见"]); +console.log({ documentRes }); diff --git a/libs/langchain-community/.gitignore b/libs/langchain-community/.gitignore index 3932edf88b61..bad275d50cf4 100644 --- a/libs/langchain-community/.gitignore +++ b/libs/langchain-community/.gitignore @@ -190,6 +190,14 @@ embeddings/tensorflow.cjs embeddings/tensorflow.js embeddings/tensorflow.d.ts embeddings/tensorflow.d.cts +embeddings/tencent_hunyuan.cjs +embeddings/tencent_hunyuan.js +embeddings/tencent_hunyuan.d.ts +embeddings/tencent_hunyuan.d.cts +embeddings/tencent_hunyuan/web.cjs +embeddings/tencent_hunyuan/web.js +embeddings/tencent_hunyuan/web.d.ts +embeddings/tencent_hunyuan/web.d.cts embeddings/togetherai.cjs embeddings/togetherai.js embeddings/togetherai.d.ts @@ -554,6 +562,14 @@ chat_models/premai.cjs chat_models/premai.js chat_models/premai.d.ts chat_models/premai.d.cts +chat_models/tencent_hunyuan.cjs +chat_models/tencent_hunyuan.js +chat_models/tencent_hunyuan.d.ts +chat_models/tencent_hunyuan.d.cts +chat_models/tencent_hunyuan/web.cjs +chat_models/tencent_hunyuan/web.js +chat_models/tencent_hunyuan/web.d.ts +chat_models/tencent_hunyuan/web.d.cts chat_models/togetherai.cjs chat_models/togetherai.js chat_models/togetherai.d.ts diff --git a/libs/langchain-community/langchain.config.js b/libs/langchain-community/langchain.config.js index 12b94735c697..d5c4c6b15eb3 100644 --- a/libs/langchain-community/langchain.config.js +++ b/libs/langchain-community/langchain.config.js @@ -81,6 +81,8 @@ export const config = { "embeddings/ollama": "embeddings/ollama", "embeddings/premai": "embeddings/premai", "embeddings/tensorflow": "embeddings/tensorflow", + "embeddings/tencent_hunyuan": "embeddings/tencent_hunyuan/index", + "embeddings/tencent_hunyuan/web": "embeddings/tencent_hunyuan/web", "embeddings/togetherai": "embeddings/togetherai", "embeddings/voyage": "embeddings/voyage", "embeddings/zhipuai": "embeddings/zhipuai", @@ -175,6 +177,8 @@ export const config = { "chat_models/ollama": "chat_models/ollama", "chat_models/portkey": "chat_models/portkey", "chat_models/premai": "chat_models/premai", + "chat_models/tencent_hunyuan": "chat_models/tencent_hunyuan/index", + "chat_models/tencent_hunyuan/web": "chat_models/tencent_hunyuan/web", "chat_models/togetherai": "chat_models/togetherai", "chat_models/webllm": "chat_models/webllm", "chat_models/yandex": "chat_models/yandex", @@ -333,6 +337,8 @@ export const config = { "embeddings/llama_cpp", "embeddings/gradient_ai", "embeddings/premai", + "embeddings/tencent_hunyuan", + "embeddings/tencent_hunyuan/web", "embeddings/zhipuai", "llms/load", "llms/cohere", @@ -402,6 +408,8 @@ export const config = { "chat_models/llama_cpp", "chat_models/portkey", "chat_models/premai", + "chat_models/tencent_hunyuan", + "chat_models/tencent_hunyuan/web", "chat_models/iflytek_xinghuo", "chat_models/iflytek_xinghuo/web", "chat_models/webllm", diff --git a/libs/langchain-community/package.json b/libs/langchain-community/package.json index bb7a4876f2d2..d516808070e7 100644 --- a/libs/langchain-community/package.json +++ b/libs/langchain-community/package.json @@ -112,6 +112,7 @@ "@tensorflow/tfjs-core": "^3.6.0", "@tsconfig/recommended": "^1.0.2", "@types/better-sqlite3": "^7.6.9", + "@types/crypto-js": "^4.2.2", "@types/flat": "^5.0.2", "@types/html-to-text": "^9", "@types/jsdom": "^21.1.1", @@ -147,6 +148,7 @@ "cohere-ai": ">=6.0.0", "convex": "^1.3.1", "couchbase": "^4.3.0", + "crypto-js": "^4.2.0", "d3-dsv": "^2.0.0", "datastore-core": "^9.2.9", "discord.js": "^14.14.1", @@ -286,6 +288,7 @@ "cohere-ai": "*", "convex": "^1.3.1", "couchbase": "^4.3.0", + "crypto-js": "^4.2.0", "d3-dsv": "^2.0.0", "discord.js": "^14.14.1", "dria": "^0.0.3", @@ -549,6 +552,9 @@ "couchbase": { "optional": true }, + "crypto-js": { + "optional": true + }, "d3-dsv": { "optional": true }, @@ -1130,6 +1136,24 @@ "import": "./embeddings/tensorflow.js", "require": "./embeddings/tensorflow.cjs" }, + "./embeddings/tencent_hunyuan": { + "types": { + "import": "./embeddings/tencent_hunyuan.d.ts", + "require": "./embeddings/tencent_hunyuan.d.cts", + "default": "./embeddings/tencent_hunyuan.d.ts" + }, + "import": "./embeddings/tencent_hunyuan.js", + "require": "./embeddings/tencent_hunyuan.cjs" + }, + "./embeddings/tencent_hunyuan/web": { + "types": { + "import": "./embeddings/tencent_hunyuan/web.d.ts", + "require": "./embeddings/tencent_hunyuan/web.d.cts", + "default": "./embeddings/tencent_hunyuan/web.d.ts" + }, + "import": "./embeddings/tencent_hunyuan/web.js", + "require": "./embeddings/tencent_hunyuan/web.cjs" + }, "./embeddings/togetherai": { "types": { "import": "./embeddings/togetherai.d.ts", @@ -1949,6 +1973,24 @@ "import": "./chat_models/premai.js", "require": "./chat_models/premai.cjs" }, + "./chat_models/tencent_hunyuan": { + "types": { + "import": "./chat_models/tencent_hunyuan.d.ts", + "require": "./chat_models/tencent_hunyuan.d.cts", + "default": "./chat_models/tencent_hunyuan.d.ts" + }, + "import": "./chat_models/tencent_hunyuan.js", + "require": "./chat_models/tencent_hunyuan.cjs" + }, + "./chat_models/tencent_hunyuan/web": { + "types": { + "import": "./chat_models/tencent_hunyuan/web.d.ts", + "require": "./chat_models/tencent_hunyuan/web.d.cts", + "default": "./chat_models/tencent_hunyuan/web.d.ts" + }, + "import": "./chat_models/tencent_hunyuan/web.js", + "require": "./chat_models/tencent_hunyuan/web.cjs" + }, "./chat_models/togetherai": { "types": { "import": "./chat_models/togetherai.d.ts", @@ -3153,6 +3195,14 @@ "embeddings/tensorflow.js", "embeddings/tensorflow.d.ts", "embeddings/tensorflow.d.cts", + "embeddings/tencent_hunyuan.cjs", + "embeddings/tencent_hunyuan.js", + "embeddings/tencent_hunyuan.d.ts", + "embeddings/tencent_hunyuan.d.cts", + "embeddings/tencent_hunyuan/web.cjs", + "embeddings/tencent_hunyuan/web.js", + "embeddings/tencent_hunyuan/web.d.ts", + "embeddings/tencent_hunyuan/web.d.cts", "embeddings/togetherai.cjs", "embeddings/togetherai.js", "embeddings/togetherai.d.ts", @@ -3517,6 +3567,14 @@ "chat_models/premai.js", "chat_models/premai.d.ts", "chat_models/premai.d.cts", + "chat_models/tencent_hunyuan.cjs", + "chat_models/tencent_hunyuan.js", + "chat_models/tencent_hunyuan.d.ts", + "chat_models/tencent_hunyuan.d.cts", + "chat_models/tencent_hunyuan/web.cjs", + "chat_models/tencent_hunyuan/web.js", + "chat_models/tencent_hunyuan/web.d.ts", + "chat_models/tencent_hunyuan/web.d.cts", "chat_models/togetherai.cjs", "chat_models/togetherai.js", "chat_models/togetherai.d.ts", diff --git a/libs/langchain-community/src/chat_models/tencent_hunyuan/base.ts b/libs/langchain-community/src/chat_models/tencent_hunyuan/base.ts new file mode 100644 index 000000000000..cb6474703290 --- /dev/null +++ b/libs/langchain-community/src/chat_models/tencent_hunyuan/base.ts @@ -0,0 +1,552 @@ +import { + BaseChatModel, + type BaseChatModelParams, +} from "@langchain/core/language_models/chat_models"; +import { + AIMessage, + BaseMessage, + ChatMessage, + AIMessageChunk, +} from "@langchain/core/messages"; +import { + ChatGeneration, + ChatResult, + ChatGenerationChunk, +} from "@langchain/core/outputs"; +import { CallbackManagerForLLMRun } from "@langchain/core/callbacks/manager"; +import { getEnvironmentVariable } from "@langchain/core/utils/env"; +import { IterableReadableStream } from "@langchain/core/utils/stream"; +import { sign } from "../../utils/tencent_hunyuan/common.js"; + +/** + * Type representing the role of a message in the Hunyuan chat model. + */ +export type HunyuanMessageRole = "system" | "assistant" | "user"; + +/** + * Interface representing a message in the Hunyuan chat model. + */ +interface HunyuanMessage { + Role: HunyuanMessageRole; + Content: string; +} + +/** + * Models available, see https://cloud.tencent.com/document/product/1729/104753. + */ +type ModelName = + | (string & NonNullable) + // hunyuan-lite + | "hunyuan-lite" // context size: 4k, input size: 3k, output size: 1k + // hunyuan-standard + | "hunyuan-standard" // alias for hunyuan-standard-32K + | "hunyuan-standard-32K" // context size: 32k, input size: 30k, output size: 2k + | "hunyuan-standard-256K" // context size: 256k, input size: 250k, output size: 6k + // hunyuan-pro + | "hunyuan-pro"; // context size: 32k, input size: 28k, output size: 4k + +/** + * Interface representing the usage of tokens in a chat completion. + * See https://cloud.tencent.com/document/api/1729/101838#Usage. + */ +interface Usage { + TotalTokens?: number; + PromptTokens?: number; + CompletionTokens?: number; +} + +/** + * Interface representing a request for a chat completion. + * See https://cloud.tencent.com/document/api/1729/105701. + */ +interface ChatCompletionRequest { + Model: ModelName; + Messages: HunyuanMessage[]; + Stream?: boolean; + StreamModeration?: boolean; + EnableEnhancement?: boolean; + Temperature?: number; + TopP?: number; +} + +/** + * Interface representing a chat completion choice message. + * See https://cloud.tencent.com/document/api/1729/101838#Message. + */ +interface ChoiceMessage { + Role: string; + Content: string; +} + +/** + * Interface representing a chat completion choice. + * See https://cloud.tencent.com/document/api/1729/101838#Choice. + */ +interface Choice { + FinishReason: "stop" | "sensitive" | ""; + Delta: ChoiceMessage; + Message: ChoiceMessage; +} + +/** + * Interface representing a error response from a chat completion. + */ +interface Error { + Code: string; + Message: string; +} + +/** + * Interface representing a response from a chat completion. + * See https://cloud.tencent.com/document/product/1729/105701. + */ +interface ChatCompletionResponse { + Created: number; + Usage: Usage; + Note: string; + Choices: Choice[]; + Id?: string; + RequestId?: string; + Error?: Error; + ErrorMsg?: Error; +} + +/** + * Interface defining the input to the ChatTencentHunyuan class. + */ +export interface TencentHunyuanChatInput { + /** + * Tencent Cloud API Host. + * @default "hunyuan.tencentcloudapi.com" + */ + host?: string; + + /** + * Model name to use. + * @default "hunyuan-pro" + */ + model: ModelName; + + /** + * Whether to stream the results or not. Defaults to false. + * @default false + */ + streaming?: boolean; + + /** + * SecretID to use when making requests, can be obtained from https://console.cloud.tencent.com/cam/capi. + * Defaults to the value of `TENCENT_SECRET_ID` environment variable. + */ + tencentSecretId?: string; + + /** + * Secret key to use when making requests, can be obtained from https://console.cloud.tencent.com/cam/capi. + * Defaults to the value of `TENCENT_SECRET_KEY` environment variable. + */ + tencentSecretKey?: string; + + /** + * Amount of randomness injected into the response. Ranges + * from 0.0 to 2.0. Use temp closer to 0 for analytical / + * multiple choice, and temp closer to 1 for creative + * and generative tasks. Defaults to 1.0.95. + */ + temperature?: number; + + /** + * Total probability mass of tokens to consider at each step. Range + * from 0 to 1.0. Defaults to 1.0. + */ + topP?: number; +} + +/** + * Interface defining the input to the ChatTencentHunyuan class. + */ +interface TencentHunyuanChatInputWithSign extends TencentHunyuanChatInput { + /** + * Tencent Cloud API v3 sign method. + */ + sign: sign; +} + +/** + * Function that converts a base message to a Hunyuan message role. + * @param message Base message to convert. + * @returns The Hunyuan message role. + */ +function messageToRole(message: BaseMessage): HunyuanMessageRole { + const type = message._getType(); + switch (type) { + case "ai": + return "assistant"; + case "human": + return "user"; + case "system": + return "system"; + case "function": + throw new Error("Function messages not supported"); + case "generic": { + if (!ChatMessage.isInstance(message)) { + throw new Error("Invalid generic chat message"); + } + if (["system", "assistant", "user"].includes(message.role)) { + return message.role as HunyuanMessageRole; + } + throw new Error(`Unknown message role: ${message.role}`); + } + default: + throw new Error(`Unknown message type: ${type}`); + } +} + +/** + * Wrapper around Tencent Hunyuan large language models that use the Chat endpoint. + * + * To use you should have the `TENCENT_SECRET_ID` and `TENCENT_SECRET_KEY` + * environment variable set. + * + * @augments BaseLLM + * @augments TencentHunyuanInput + * @example + * ```typescript + * const messages = [new HumanMessage("Hello")]; + * + * const hunyuanLite = new ChatTencentHunyuan({ + * model: "hunyuan-lite", + * tencentSecretId: "YOUR-SECRET-ID", + * tencentSecretKey: "YOUR-SECRET-KEY", + * }); + * + * let res = await hunyuanLite.call(messages); + * + * const hunyuanPro = new ChatTencentHunyuan({ + * model: "hunyuan-pro", + * temperature: 1, + * tencentSecretId: "YOUR-SECRET-ID", + * tencentSecretKey: "YOUR-SECRET-KEY", + * }); + * + * res = await hunyuanPro.call(messages); + * ``` + */ +export class ChatTencentHunyuan + extends BaseChatModel + implements TencentHunyuanChatInputWithSign +{ + static lc_name() { + return "ChatTencentHunyuan"; + } + + get callKeys(): string[] { + return ["stop", "signal", "options"]; + } + + get lc_secrets(): { [key: string]: string } | undefined { + return { + tencentSecretId: "TENCENT_SECRET_ID", + tencentSecretKey: "TENCENT_SECRET_KEY", + }; + } + + get lc_aliases(): { [key: string]: string } | undefined { + return undefined; + } + + lc_serializable = true; + + tencentSecretId?: string; + + tencentSecretKey?: string; + + streaming = false; + + host = "hunyuan.tencentcloudapi.com"; + + model = "hunyuan-pro"; + + temperature?: number | undefined; + + topP?: number | undefined; + + sign: sign; + + constructor( + fields?: Partial & BaseChatModelParams + ) { + super(fields ?? {}); + + this.tencentSecretId = + fields?.tencentSecretId ?? getEnvironmentVariable("TENCENT_SECRET_ID"); + if (!this.tencentSecretId) { + throw new Error("Tencent SecretID not found"); + } + + this.tencentSecretKey = + fields?.tencentSecretKey ?? getEnvironmentVariable("TENCENT_SECRET_KEY"); + if (!this.tencentSecretKey) { + throw new Error("Tencent SecretKey not found"); + } + + this.host = fields?.host ?? this.host; + this.topP = fields?.topP ?? this.topP; + this.model = fields?.model ?? this.model; + this.streaming = fields?.streaming ?? this.streaming; + this.temperature = fields?.temperature ?? this.temperature; + if (!fields?.sign) { + throw new Error("Sign method undefined"); + } + this.sign = fields?.sign; + } + + /** + * Get the parameters used to invoke the model + */ + invocationParams(): Omit { + return { + TopP: this.topP, + Model: this.model, + Stream: this.streaming, + Temperature: this.temperature, + }; + } + + /** + * Get the HTTP headers used to invoke the model + */ + invocationHeaders(request: object, timestamp: number): HeadersInit { + const headers = { + "Content-Type": "application/json", + "X-TC-Action": "ChatCompletions", + "X-TC-Version": "2023-09-01", + "X-TC-Timestamp": timestamp.toString(), + Authorization: "", + }; + + headers.Authorization = this.sign( + this.host, + request, + timestamp, + this.tencentSecretId ?? "", + this.tencentSecretKey ?? "", + headers + ); + return headers; + } + + async *_streamResponseChunks( + messages: BaseMessage[], + options: this["ParsedCallOptions"], + runManager?: CallbackManagerForLLMRun + ): AsyncGenerator { + const stream = await this.caller.call(async () => + this.createStream( + { + ...this.invocationParams(), + Messages: messages.map((message) => ({ + Role: messageToRole(message), + Content: message.content as string, + })), + }, + options?.signal + ) + ); + + for await (const chunk of stream) { + // handle streaming error + if (chunk.ErrorMsg?.Message) { + throw new Error(`[${chunk.Id}] ${chunk.ErrorMsg?.Message}`); + } + + const { + Choices: [ + { + Delta: { Content }, + FinishReason, + }, + ], + } = chunk; + yield new ChatGenerationChunk({ + text: Content, + message: new AIMessageChunk({ content: Content }), + generationInfo: FinishReason + ? { + usage: chunk.Usage, + request_id: chunk.Id, + finish_reason: FinishReason, + } + : undefined, + }); + await runManager?.handleLLMNewToken(Content); + } + } + + private async *createStream( + request: ChatCompletionRequest, + signal?: AbortSignal + ): AsyncGenerator { + const timestamp = Math.trunc(Date.now() / 1000); + const headers = this.invocationHeaders(request, timestamp); + const response = await fetch(`https://${this.host}`, { + headers, + method: "POST", + body: JSON.stringify(request), + signal, + }); + + if (!response.ok) { + const text = await response.text(); + throw new Error( + `Hunyuan call failed with status code ${response.status}: ${text}` + ); + } + + if ( + !response.headers.get("content-type")?.startsWith("text/event-stream") + ) { + const text = await response.text(); + try { + const data = JSON.parse(text); + if (data?.Response?.Error?.Message) { + throw new Error( + `[${data?.Response?.RequestId}] ${data?.Response?.Error?.Message}` + ); + } + } catch (e) { + throw new Error( + `Could not begin Hunyuan stream, received a non-JSON parseable response: ${text}.` + ); + } + } + + if (!response.body) { + throw new Error( + `Could not begin Hunyuan stream, received empty body response.` + ); + } + + const decoder = new TextDecoder("utf-8"); + const stream = IterableReadableStream.fromReadableStream(response.body); + let extra = ""; + for await (const chunk of stream) { + const decoded = extra + decoder.decode(chunk); + const lines = decoded.split("\n"); + extra = lines.pop() || ""; + for (const line of lines) { + if (!line.startsWith("data:")) { + continue; + } + try { + yield JSON.parse(line.slice("data:".length).trim()); + } catch (e) { + console.warn(`Received a non-JSON parseable chunk: ${line}`); + } + } + } + } + + /** @ignore */ + async _generate( + messages: BaseMessage[], + options?: this["ParsedCallOptions"], + runManager?: CallbackManagerForLLMRun + ): Promise { + const params = this.invocationParams(); + if (params.Stream) { + let usage: Usage = {}; + const stream = this._streamResponseChunks( + messages, + options ?? {}, + runManager + ); + + const generations: ChatGeneration[] = []; + for await (const chunk of stream) { + const text = chunk.text ?? ""; + generations.push({ + text, + message: new AIMessage(text), + }); + usage = chunk.generationInfo?.usage; + } + return { + generations, + llmOutput: { + tokenUsage: { + totalTokens: usage.TotalTokens, + promptTokens: usage.PromptTokens, + completionTokens: usage.CompletionTokens, + }, + }, + }; + } + + const data = await this.completionWithRetry( + { + ...params, + Messages: messages.map((message) => ({ + Role: messageToRole(message), + Content: message.content as string, + })), + }, + options?.signal + ).then((data) => { + const response: ChatCompletionResponse = data?.Response; + if (response?.Error?.Message) { + throw new Error(`[${response.RequestId}] ${response.Error.Message}`); + } + return response; + }); + + const text = data.Choices[0]?.Message?.Content ?? ""; + const { + TotalTokens = 0, + PromptTokens = 0, + CompletionTokens = 0, + } = data.Usage; + + return { + generations: [ + { + text, + message: new AIMessage(text), + }, + ], + llmOutput: { + tokenUsage: { + totalTokens: TotalTokens, + promptTokens: PromptTokens, + completionTokens: CompletionTokens, + }, + }, + }; + } + + /** @ignore */ + async completionWithRetry( + request: ChatCompletionRequest, + signal?: AbortSignal + ) { + return this.caller.call(async () => { + const timestamp = Math.trunc(Date.now() / 1000); + const headers = this.invocationHeaders(request, timestamp); + const response = await fetch(`https://${this.host}`, { + headers, + method: "POST", + body: JSON.stringify(request), + signal, + }); + + return response.json(); + }); + } + + _llmType() { + return "tencenthunyuan"; + } + + /** @ignore */ + _combineLLMOutput() { + return []; + } +} diff --git a/libs/langchain-community/src/chat_models/tencent_hunyuan/index.ts b/libs/langchain-community/src/chat_models/tencent_hunyuan/index.ts new file mode 100644 index 000000000000..fddd8807911b --- /dev/null +++ b/libs/langchain-community/src/chat_models/tencent_hunyuan/index.ts @@ -0,0 +1,44 @@ +import { type BaseChatModelParams } from "@langchain/core/language_models/chat_models"; +import { sign } from "../../utils/tencent_hunyuan/index.js"; +import { + ChatTencentHunyuan as BaseChatTencentHunyuan, + TencentHunyuanChatInput, +} from "./base.js"; + +/** + * Wrapper around Tencent Hunyuan large language models that use the Chat endpoint. + * + * To use you should have the `TENCENT_SECRET_ID` and `TENCENT_SECRET_KEY` + * environment variable set. + * + * @augments BaseLLM + * @augments TencentHunyuanInput + * @example + * ```typescript + * const messages = [new HumanMessage("Hello")]; + * + * const hunyuanLite = new ChatTencentHunyuan({ + * model: "hunyuan-lite", + * tencentSecretId: "YOUR-SECRET-ID", + * tencentSecretKey: "YOUR-SECRET-KEY", + * }); + * + * let res = await hunyuanLite.call(messages); + * + * const hunyuanPro = new ChatTencentHunyuan({ + * model: "hunyuan-pro", + * temperature: 1, + * tencentSecretId: "YOUR-SECRET-ID", + * tencentSecretKey: "YOUR-SECRET-KEY", + * }); + * + * res = await hunyuanPro.call(messages); + * ``` + */ +export class ChatTencentHunyuan extends BaseChatTencentHunyuan { + constructor(fields?: Partial & BaseChatModelParams) { + super({ ...fields, sign } ?? { sign }); + } +} + +export { TencentHunyuanChatInput } from "./base.js"; diff --git a/libs/langchain-community/src/chat_models/tencent_hunyuan/web.ts b/libs/langchain-community/src/chat_models/tencent_hunyuan/web.ts new file mode 100644 index 000000000000..2d6d0e771b04 --- /dev/null +++ b/libs/langchain-community/src/chat_models/tencent_hunyuan/web.ts @@ -0,0 +1,43 @@ +import { type BaseChatModelParams } from "@langchain/core/language_models/chat_models"; +import { sign } from "../../utils/tencent_hunyuan/web.js"; +import { + ChatTencentHunyuan as BaseChatTencentHunyuan, + TencentHunyuanChatInput, +} from "./base.js"; + +/** + * Wrapper around Tencent Hunyuan large language models that use the Chat endpoint. + * + * To use you should have the `TENCENT_SECRET_ID` and `TENCENT_SECRET_KEY` + * environment variable set. + * + * @augments BaseLLM + * @augments TencentHunyuanInput + * @example + * ```typescript + * const messages = [new HumanMessage("Hello")]; + * + * const hunyuanLite = new ChatTencentHunyuan({ + * model: "hunyuan-lite", + * tencentSecretId: "YOUR-SECRET-ID", + * tencentSecretKey: "YOUR-SECRET-KEY", + * }); + * + * let res = await hunyuanLite.call(messages); + * + * const hunyuanPro = new ChatTencentHunyuan({ + * model: "hunyuan-pro", + * temperature: 1, + * tencentSecretId: "YOUR-SECRET-ID", + * tencentSecretKey: "YOUR-SECRET-KEY", + * }); + * + * res = await hunyuanPro.call(messages); + * ``` + */ +export class ChatTencentHunyuan extends BaseChatTencentHunyuan { + constructor(fields?: Partial & BaseChatModelParams) { + super({ ...fields, sign } ?? { sign }); + } +} +export { TencentHunyuanChatInput } from "./base.js"; diff --git a/libs/langchain-community/src/chat_models/tests/chattencenthunyuan.int.test.ts b/libs/langchain-community/src/chat_models/tests/chattencenthunyuan.int.test.ts new file mode 100644 index 000000000000..fa816f49a255 --- /dev/null +++ b/libs/langchain-community/src/chat_models/tests/chattencenthunyuan.int.test.ts @@ -0,0 +1,142 @@ +import { test, expect } from "@jest/globals"; +import { + BaseMessage, + SystemMessage, + HumanMessage, +} from "@langchain/core/messages"; +import { ChatTencentHunyuan } from "../tencent_hunyuan/index.js"; + +interface TestConfig { + model: string | undefined; + config: { + description?: string; + temperature?: number; + topP?: number; + streaming?: boolean; + callbacks?: Array<{ + nrNewTokens?: number; + streamedCompletion?: string; + handleLLMNewToken?: (token: string) => Promise; + }>; + }; + messages?: BaseMessage[]; + shouldThrow?: boolean; +} + +const runTest = async ({ + model, + config, + messages = [new HumanMessage("Hello!")], + shouldThrow = false, +}: TestConfig) => { + const description = `Test ChatTencentHunyuan ${model || "default model"} ${ + config.description || "" + }`.trim(); + let nrNewTokens = 0; + let streamedCompletion = ""; + if (config.streaming) { + // eslint-disable-next-line no-param-reassign + config.callbacks = [ + { + async handleLLMNewToken(token: string) { + nrNewTokens += 1; + streamedCompletion += token; + }, + }, + ]; + } + test.skip(description, async () => { + const chat = new ChatTencentHunyuan({ + model, + ...config, + }); + + if (shouldThrow) { + await expect(chat.invoke(messages)).rejects.toThrow(); + return; + } + + const res = await chat.invoke(messages); + console.log({ res }); + + if (config.streaming) { + expect(nrNewTokens > 0).toBe(true); + expect(res.text).toBe(streamedCompletion); + } + }); +}; + +const testConfigs: TestConfig[] = [ + { model: undefined, config: {} }, + { model: "hunyuan-lite", config: {} }, + { + model: "hunyuan-lite", + config: { description: "with temperature", temperature: 1 }, + }, + { model: "hunyuan-lite", config: { description: "with topP", topP: 1 } }, + { + model: "hunyuan-lite", + config: { description: "with penaltyScore" }, + }, + { + model: "hunyuan-lite", + config: { + description: "in streaming mode", + streaming: true, + }, + messages: [new HumanMessage("您好,请讲个长笑话")], + }, + { + model: "hunyuan-lite", + config: { + description: "illegal input should throw an error", + temperature: 0, + }, + shouldThrow: true, + }, + { + model: "hunyuan-lite", + config: { + description: "illegal input in streaming mode should throw an error", + streaming: true, + temperature: 0, + }, + shouldThrow: true, + }, + { model: "hunyuan-pro", config: {} }, + { + model: "hunyuan-pro", + config: { + description: "in streaming mode", + streaming: true, + }, + messages: [new HumanMessage("您好,请讲个长笑话")], + }, + { + model: "hunyuan-pro", + config: { + description: "with system message", + }, + messages: [ + new SystemMessage("你是一个说文言文的人"), + new HumanMessage("Hello!"), + ], + }, + { + model: "hunyuan-standard", + config: {}, + }, + { + model: "hunyuan-lite", + config: {}, + }, + { + model: "hunyuan-standard-256K", + config: {}, + }, +]; + +testConfigs.forEach((testConfig) => { + // eslint-disable-next-line no-void + void runTest(testConfig); +}); diff --git a/libs/langchain-community/src/embeddings/tencent_hunyuan/base.ts b/libs/langchain-community/src/embeddings/tencent_hunyuan/base.ts new file mode 100644 index 000000000000..24e680818645 --- /dev/null +++ b/libs/langchain-community/src/embeddings/tencent_hunyuan/base.ts @@ -0,0 +1,194 @@ +import { getEnvironmentVariable } from "@langchain/core/utils/env"; +import { Embeddings, type EmbeddingsParams } from "@langchain/core/embeddings"; +import { sign } from "../../utils/tencent_hunyuan/common.js"; + +/** + * Interface that extends EmbeddingsParams and defines additional + * parameters specific to the TencentHunyuanEmbeddingsParams class. + */ +export interface TencentHunyuanEmbeddingsParams extends EmbeddingsParams { + /** + * Tencent Cloud API Host. + * @default "hunyuan.tencentcloudapi.com" + */ + host?: string; + + /** + * SecretID to use when making requests, can be obtained from https://console.cloud.tencent.com/cam/capi. + * Defaults to the value of `TENCENT_SECRET_ID` environment variable. + */ + tencentSecretId?: string; + + /** + * Secret key to use when making requests, can be obtained from https://console.cloud.tencent.com/cam/capi. + * Defaults to the value of `TENCENT_SECRET_KEY` environment variable. + */ + tencentSecretKey?: string; +} + +/** + * Interface that extends EmbeddingsParams and defines additional + * parameters specific to the TencentHunyuanEmbeddingsParams class. + */ +interface TencentHunyuanEmbeddingsParamsWithSign + extends TencentHunyuanEmbeddingsParams { + /** + * Tencent Cloud API v3 sign method. + */ + sign: sign; +} + +/** + * Interface representing the embedding data. + * See https://cloud.tencent.com/document/api/1729/101838#EmbeddingData. + */ +interface EmbeddingData { + Embedding: number[]; + Index: number; + Object: string; +} + +/** + * Interface representing the usage of tokens in text embedding. + * See https://cloud.tencent.com/document/api/1729/101838#EmbeddingUsage. + */ +interface EmbeddingUsage { + TotalTokens: number; + PromptTokens: number; + CompletionTokens?: number; +} + +/** + * Interface representing a error response from Tencent Hunyuan embedding. + * See https://cloud.tencent.com/document/product/1729/102832 + */ +interface Error { + Code: string; + Message: string; +} + +/** + * Interface representing a response from Tencent Hunyuan embedding. + * See https://cloud.tencent.com/document/product/1729/102832 + */ +interface GetEmbeddingResponse { + Response: { + Error?: Error; + Data: EmbeddingData[]; + Usage: EmbeddingUsage; + RequestId: string; + }; +} + +/** + * Class for generating embeddings using the Tencent Hunyuan API. + */ +export class TencentHunyuanEmbeddings + extends Embeddings + implements TencentHunyuanEmbeddingsParams +{ + tencentSecretId?: string; + + tencentSecretKey?: string; + + host = "hunyuan.tencentcloudapi.com"; + + sign: sign; + + constructor(fields?: TencentHunyuanEmbeddingsParamsWithSign) { + super(fields ?? {}); + + this.tencentSecretId = + fields?.tencentSecretId ?? getEnvironmentVariable("TENCENT_SECRET_ID"); + if (!this.tencentSecretId) { + throw new Error("Tencent SecretID not found"); + } + + this.tencentSecretKey = + fields?.tencentSecretKey ?? getEnvironmentVariable("TENCENT_SECRET_KEY"); + if (!this.tencentSecretKey) { + throw new Error("Tencent SecretKey not found"); + } + + this.host = fields?.host ?? this.host; + if (!fields?.sign) { + throw new Error("Sign method undefined"); + } + this.sign = fields?.sign; + } + + /** + * Private method to make a request to the TogetherAI API to generate + * embeddings. Handles the retry logic and returns the response from the API. + * @param {string} input The input text to embed. + * @returns Promise that resolves to the response from the API. + * @TODO Figure out return type and statically type it. + */ + private async embeddingWithRetry( + input: string + ): Promise { + const request = { Input: input }; + const timestamp = Math.trunc(Date.now() / 1000); + const headers = { + "Content-Type": "application/json", + "X-TC-Action": "GetEmbedding", + "X-TC-Version": "2023-09-01", + "X-TC-Timestamp": timestamp.toString(), + Authorization: "", + }; + + headers.Authorization = this.sign( + this.host, + request, + timestamp, + this.tencentSecretId ?? "", + this.tencentSecretKey ?? "", + headers + ); + + return this.caller.call(async () => { + const response = await fetch(`https://${this.host}`, { + headers, + method: "POST", + body: JSON.stringify(request), + }); + + if (response.ok) { + return response.json(); + } + + throw new Error( + `Error getting embeddings from Tencent Hunyuan. ${JSON.stringify( + await response.json(), + null, + 2 + )}` + ); + }); + } + + /** + * Method to generate an embedding for a single document. Calls the + * embeddingWithRetry method with the document as the input. + * @param {string} text Document to generate an embedding for. + * @returns {Promise} Promise that resolves to an embedding for the document. + */ + async embedQuery(text: string): Promise { + const { Response } = await this.embeddingWithRetry(text); + if (Response?.Error?.Message) { + throw new Error(`[${Response.RequestId}] ${Response.Error.Message}`); + } + return Response.Data[0].Embedding; + } + + /** + * Method that takes an array of documents as input and returns a promise + * that resolves to a 2D array of embeddings for each document. It calls + * the embedQuery method for each document in the array. + * @param documents Array of documents for which to generate embeddings. + * @returns Promise that resolves to a 2D array of embeddings for each input document. + */ + embedDocuments(documents: string[]): Promise { + return Promise.all(documents.map((doc) => this.embedQuery(doc))); + } +} diff --git a/libs/langchain-community/src/embeddings/tencent_hunyuan/index.ts b/libs/langchain-community/src/embeddings/tencent_hunyuan/index.ts new file mode 100644 index 000000000000..4265a23b4df7 --- /dev/null +++ b/libs/langchain-community/src/embeddings/tencent_hunyuan/index.ts @@ -0,0 +1,16 @@ +import { sign } from "../../utils/tencent_hunyuan/index.js"; +import { + TencentHunyuanEmbeddings as BaseTencentHunyuanEmbeddings, + TencentHunyuanEmbeddingsParams, +} from "./base.js"; + +/** + * Class for generating embeddings using the Tencent Hunyuan API. + */ +export class TencentHunyuanEmbeddings extends BaseTencentHunyuanEmbeddings { + constructor(fields?: TencentHunyuanEmbeddingsParams) { + super({ ...fields, sign } ?? { sign }); + } +} + +export { type TencentHunyuanEmbeddingsParams } from "./base.js"; diff --git a/libs/langchain-community/src/embeddings/tencent_hunyuan/web.ts b/libs/langchain-community/src/embeddings/tencent_hunyuan/web.ts new file mode 100644 index 000000000000..0dc4ca4735f2 --- /dev/null +++ b/libs/langchain-community/src/embeddings/tencent_hunyuan/web.ts @@ -0,0 +1,16 @@ +import { sign } from "../../utils/tencent_hunyuan/web.js"; +import { + TencentHunyuanEmbeddings as BaseTencentHunyuanEmbeddings, + TencentHunyuanEmbeddingsParams, +} from "./base.js"; + +/** + * Class for generating embeddings using the Tencent Hunyuan API. + */ +export class TencentHunyuanEmbeddings extends BaseTencentHunyuanEmbeddings { + constructor(fields?: TencentHunyuanEmbeddingsParams) { + super({ ...fields, sign } ?? { sign }); + } +} + +export { type TencentHunyuanEmbeddingsParams } from "./base.js"; diff --git a/libs/langchain-community/src/embeddings/tests/tencent_hunyuan.test.ts b/libs/langchain-community/src/embeddings/tests/tencent_hunyuan.test.ts new file mode 100644 index 000000000000..52a430da5cc1 --- /dev/null +++ b/libs/langchain-community/src/embeddings/tests/tencent_hunyuan.test.ts @@ -0,0 +1,32 @@ +import { test, expect } from "@jest/globals"; +import { TencentHunyuanEmbeddings } from "../tencent_hunyuan/index.js"; + +test.skip("Test TencentHunyuanEmbeddings.embedQuery", async () => { + const embeddings = new TencentHunyuanEmbeddings(); + const res = await embeddings.embedQuery("Hello world"); + expect(typeof res[0]).toBe("number"); +}); + +test.skip("Test TencentHunyuanEmbeddings.embedDocuments", async () => { + const embeddings = new TencentHunyuanEmbeddings(); + const res = await embeddings.embedDocuments(["Hello world", "Bye bye"]); + expect(res).toHaveLength(2); + expect(typeof res[0][0]).toBe("number"); + expect(typeof res[1][0]).toBe("number"); +}); + +test.skip("Test TencentHunyuanEmbeddings concurrency", async () => { + const embeddings = new TencentHunyuanEmbeddings(); + const res = await embeddings.embedDocuments([ + "Hello world", + "Bye bye", + "Hello world", + "Bye bye", + "Hello world", + "Bye bye", + ]); + expect(res).toHaveLength(6); + expect(res.find((embedding) => typeof embedding[0] !== "number")).toBe( + undefined + ); +}); diff --git a/libs/langchain-community/src/load/import_constants.ts b/libs/langchain-community/src/load/import_constants.ts index 6c9228d80469..8497a6b7405b 100644 --- a/libs/langchain-community/src/load/import_constants.ts +++ b/libs/langchain-community/src/load/import_constants.ts @@ -19,6 +19,8 @@ export const optionalImportEntrypoints: string[] = [ "langchain_community/embeddings/llama_cpp", "langchain_community/embeddings/premai", "langchain_community/embeddings/tensorflow", + "langchain_community/embeddings/tencent_hunyuan", + "langchain_community/embeddings/tencent_hunyuan/web", "langchain_community/embeddings/zhipuai", "langchain_community/llms/bedrock", "langchain_community/llms/bedrock/web", @@ -89,6 +91,8 @@ export const optionalImportEntrypoints: string[] = [ "langchain_community/chat_models/llama_cpp", "langchain_community/chat_models/portkey", "langchain_community/chat_models/premai", + "langchain_community/chat_models/tencent_hunyuan", + "langchain_community/chat_models/tencent_hunyuan/web", "langchain_community/chat_models/webllm", "langchain_community/chat_models/zhipuai", "langchain_community/callbacks/handlers/llmonitor", diff --git a/libs/langchain-community/src/load/import_type.ts b/libs/langchain-community/src/load/import_type.ts index fc6d6515562f..bf4f947d36cf 100644 --- a/libs/langchain-community/src/load/import_type.ts +++ b/libs/langchain-community/src/load/import_type.ts @@ -52,6 +52,8 @@ export interface SecretMap { REMOTE_RETRIEVER_AUTH_BEARER?: string; REPLICATE_API_TOKEN?: string; SEARXNG_API_BASE?: string; + TENCENT_SECRET_ID?: string; + TENCENT_SECRET_KEY?: string; TOGETHER_AI_API_KEY?: string; TURBOPUFFER_API_KEY?: string; UPSTASH_REDIS_REST_TOKEN?: string; diff --git a/libs/langchain-community/src/utils/tencent_hunyuan/common.ts b/libs/langchain-community/src/utils/tencent_hunyuan/common.ts new file mode 100644 index 000000000000..779375c0d7b4 --- /dev/null +++ b/libs/langchain-community/src/utils/tencent_hunyuan/common.ts @@ -0,0 +1,31 @@ +export const service = "hunyuan"; +export const signedHeaders = `content-type;host`; + +export const getDate = (timestamp: number) => { + const date = new Date(timestamp * 1000); + const year = date.getUTCFullYear(); + const month = `0${(date.getUTCMonth() + 1).toString()}`.slice(-2); + const day = `0${date.getUTCDate()}`.slice(-2); + return `${year}-${month}-${day}`; +}; + +/** + * Method that calculate Tencent Cloud API v3 signature + * for making requests to the Tencent Cloud API. + * See https://cloud.tencent.com/document/api/1729/101843. + * @param host Tencent Cloud API host. + * @param payload HTTP request body. + * @param timestamp Sign timestamp in seconds. + * @param secretId Tencent Cloud Secret ID, which can be obtained from https://console.cloud.tencent.com/cam/capi. + * @param secretKey Tencent Cloud Secret Key, which can be obtained from https://console.cloud.tencent.com/cam/capi. + * @param headers HTTP request headers. + * @returns The signature for making requests to the Tencent API. + */ +export type sign = ( + host: string, + payload: object, + timestamp: number, + secretId: string, + secretKey: string, + headers: Record +) => string; diff --git a/libs/langchain-community/src/utils/tencent_hunyuan/index.ts b/libs/langchain-community/src/utils/tencent_hunyuan/index.ts new file mode 100644 index 000000000000..55fb66625b2c --- /dev/null +++ b/libs/langchain-community/src/utils/tencent_hunyuan/index.ts @@ -0,0 +1,47 @@ +import { createHash, createHmac, BinaryLike } from "node:crypto"; +import { getDate, service, signedHeaders } from "./common.js"; + +const sha256 = (data: string) => + createHash("sha256").update(data).digest("hex"); + +const hmacSha256 = (data: string, key: BinaryLike) => + createHmac("sha256", key).update(data).digest(); + +const hmacSha256Hex = (data: string, key: BinaryLike) => + createHmac("sha256", key).update(data).digest("hex"); + +/** + * Method that calculate Tencent Cloud API v3 signature + * for making requests to the Tencent Cloud API. + * See https://cloud.tencent.com/document/api/1729/101843. + * @param host Tencent Cloud API host. + * @param payload HTTP request body. + * @param timestamp Sign timestamp in seconds. + * @param secretId Tencent Cloud Secret ID, which can be obtained from https://console.cloud.tencent.com/cam/capi. + * @param secretKey Tencent Cloud Secret Key, which can be obtained from https://console.cloud.tencent.com/cam/capi. + * @param headers HTTP request headers. + * @returns The signature for making requests to the Tencent API. + */ +export const sign = ( + host: string, + payload: object, + timestamp: number, + secretId: string, + secretKey: string, + headers: Record +): string => { + const contentType = headers["Content-Type"]; + const payloadHash = sha256(JSON.stringify(payload)); + const canonicalRequest = `POST\n/\n\ncontent-type:${contentType}\nhost:${host}\n\n${signedHeaders}\n${payloadHash}`; + const date = getDate(timestamp); + const signature = hmacSha256Hex( + `TC3-HMAC-SHA256\n${timestamp}\n${date}/${service}/tc3_request\n${sha256( + canonicalRequest + )}`, + hmacSha256( + "tc3_request", + hmacSha256(service, hmacSha256(date, `TC3${secretKey}`)) + ) + ); + return `TC3-HMAC-SHA256 Credential=${secretId}/${date}/${service}/tc3_request, SignedHeaders=${signedHeaders}, Signature=${signature}`; +}; diff --git a/libs/langchain-community/src/utils/tencent_hunyuan/web.ts b/libs/langchain-community/src/utils/tencent_hunyuan/web.ts new file mode 100644 index 000000000000..26c826130558 --- /dev/null +++ b/libs/langchain-community/src/utils/tencent_hunyuan/web.ts @@ -0,0 +1,39 @@ +import sha256 from "crypto-js/sha256.js"; +import hmacSha256 from "crypto-js/hmac-sha256.js"; +import { getDate, service, signedHeaders } from "./common.js"; + +/** + * Method that calculate Tencent Cloud API v3 signature + * for making requests to the Tencent Cloud API. + * See https://cloud.tencent.com/document/api/1729/101843. + * @param host Tencent Cloud API host. + * @param payload HTTP request body. + * @param timestamp Sign timestamp in seconds. + * @param secretId Tencent Cloud Secret ID, which can be obtained from https://console.cloud.tencent.com/cam/capi. + * @param secretKey Tencent Cloud Secret Key, which can be obtained from https://console.cloud.tencent.com/cam/capi. + * @param headers HTTP request headers. + * @returns The signature for making requests to the Tencent API. + */ +export const sign = ( + host: string, + payload: object, + timestamp: number, + secretId: string, + secretKey: string, + headers: Record +): string => { + const contentType = headers["Content-Type"]; + const payloadHash = sha256(JSON.stringify(payload)); + const canonicalRequest = `POST\n/\n\ncontent-type:${contentType}\nhost:${host}\n\n${signedHeaders}\n${payloadHash}`; + const date = getDate(timestamp); + const signature = hmacSha256( + `TC3-HMAC-SHA256\n${timestamp}\n${date}/${service}/tc3_request\n${sha256( + canonicalRequest + ).toString()}`, + hmacSha256( + "tc3_request", + hmacSha256(service, hmacSha256(date, `TC3${secretKey}`)) + ) + ).toString(); + return `TC3-HMAC-SHA256 Credential=${secretId}/${date}/${service}/tc3_request, SignedHeaders=${signedHeaders}, Signature=${signature}`; +}; diff --git a/yarn.lock b/yarn.lock index 7159b03ba4aa..cc19004e2f1e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9125,6 +9125,7 @@ __metadata: "@tensorflow/tfjs-core": ^3.6.0 "@tsconfig/recommended": ^1.0.2 "@types/better-sqlite3": ^7.6.9 + "@types/crypto-js": ^4.2.2 "@types/flat": ^5.0.2 "@types/html-to-text": ^9 "@types/jsdom": ^21.1.1 @@ -9161,6 +9162,7 @@ __metadata: cohere-ai: ">=6.0.0" convex: ^1.3.1 couchbase: ^4.3.0 + crypto-js: ^4.2.0 d3-dsv: ^2.0.0 datastore-core: ^9.2.9 discord.js: ^14.14.1 @@ -9307,6 +9309,7 @@ __metadata: cohere-ai: "*" convex: ^1.3.1 couchbase: ^4.3.0 + crypto-js: ^4.2.0 d3-dsv: ^2.0.0 discord.js: ^14.14.1 dria: ^0.0.3 @@ -9498,6 +9501,8 @@ __metadata: optional: true couchbase: optional: true + crypto-js: + optional: true d3-dsv: optional: true discord.js: @@ -14593,6 +14598,13 @@ __metadata: languageName: node linkType: hard +"@types/crypto-js@npm:^4.2.2": + version: 4.2.2 + resolution: "@types/crypto-js@npm:4.2.2" + checksum: 727daa0d2db35f0abefbab865c23213b6ee6a270e27e177939bbe4b70d1e84c2202d9fac4ea84859c4b4d49a4ee50f948f601327a39b69ec013288018ba07ca5 + languageName: node + linkType: hard + "@types/d3-dsv@npm:^2": version: 2.0.3 resolution: "@types/d3-dsv@npm:2.0.3"