Skip to content

Commit faaaa11

Browse files
authored
feat(components): add Baidu Qianfan embeddings node (#6147)
* feat(components): add Baidu Qianfan embeddings node * fix(components): preserve explicit numeric values in Baidu embeddings * Improve Qianfan embedding defaults and guidance
1 parent b27e889 commit faaaa11

4 files changed

Lines changed: 240 additions & 0 deletions

File tree

packages/components/models.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2204,6 +2204,27 @@
22042204
}
22052205
]
22062206
},
2207+
{
2208+
"name": "baiduQianfanEmbeddings",
2209+
"models": [
2210+
{
2211+
"label": "Embedding-V1",
2212+
"name": "Embedding-V1"
2213+
},
2214+
{
2215+
"label": "bge-large-zh",
2216+
"name": "bge-large-zh"
2217+
},
2218+
{
2219+
"label": "bge-large-en",
2220+
"name": "bge-large-en"
2221+
},
2222+
{
2223+
"label": "tao-8k",
2224+
"name": "tao-8k"
2225+
}
2226+
]
2227+
},
22072228
{
22082229
"name": "mistralAIEmbeddings",
22092230
"models": [
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
jest.mock('@langchain/baidu-qianfan', () => ({
2+
BaiduQianfanEmbeddings: jest.fn().mockImplementation((fields) => ({ fields }))
3+
}))
4+
5+
jest.mock('../../../src/utils', () => ({
6+
getBaseClasses: jest.fn().mockReturnValue(['Embeddings']),
7+
getCredentialData: jest.fn(),
8+
getCredentialParam: jest.fn()
9+
}))
10+
11+
jest.mock('../../../src/modelLoader', () => ({
12+
MODEL_TYPE: { EMBEDDING: 'embedding' },
13+
getModels: jest.fn()
14+
}))
15+
16+
import { getCredentialData, getCredentialParam } from '../../../src/utils'
17+
import { getModels } from '../../../src/modelLoader'
18+
19+
const { nodeClass: BaiduQianfanEmbedding } = require('./BaiduQianfanEmbedding')
20+
21+
describe('BaiduQianfanEmbedding', () => {
22+
beforeEach(() => {
23+
jest.clearAllMocks()
24+
})
25+
26+
it('loads embedding model options from the shared model loader', async () => {
27+
;(getModels as jest.Mock).mockResolvedValue([{ label: 'Embedding-V1', name: 'Embedding-V1' }])
28+
29+
const node = new BaiduQianfanEmbedding()
30+
const models = await node.loadMethods.listModels()
31+
32+
expect(getModels).toHaveBeenCalledWith('embedding', 'baiduQianfanEmbeddings')
33+
expect(models).toEqual([{ label: 'Embedding-V1', name: 'Embedding-V1' }])
34+
})
35+
36+
it('maps credential, custom model names, and optional embedding parameters into BaiduQianfanEmbeddings', async () => {
37+
;(getCredentialData as jest.Mock).mockResolvedValue({
38+
qianfanAccessKey: 'access-key',
39+
qianfanSecretKey: 'secret-key'
40+
})
41+
;(getCredentialParam as jest.Mock).mockImplementation((key, credentialData) => credentialData[key])
42+
43+
const node = new BaiduQianfanEmbedding()
44+
const model = await node.init(
45+
{
46+
credential: 'cred-1',
47+
inputs: {
48+
modelName: 'bge-large-zh',
49+
customModelName: 'Qwen3-Embedding-4B',
50+
stripNewLines: true,
51+
batchSize: '8',
52+
timeout: '15000'
53+
}
54+
},
55+
'',
56+
{}
57+
)
58+
59+
expect(model.fields).toMatchObject({
60+
modelName: 'Qwen3-Embedding-4B',
61+
qianfanAccessKey: 'access-key',
62+
qianfanSecretKey: 'secret-key',
63+
stripNewLines: true,
64+
batchSize: 8,
65+
timeout: 15000
66+
})
67+
})
68+
69+
it('preserves explicit zero values for numeric parameters', async () => {
70+
;(getCredentialData as jest.Mock).mockResolvedValue({
71+
qianfanAccessKey: 'access-key',
72+
qianfanSecretKey: 'secret-key'
73+
})
74+
;(getCredentialParam as jest.Mock).mockImplementation((key, credentialData) => credentialData[key])
75+
76+
const node = new BaiduQianfanEmbedding()
77+
const model = await node.init(
78+
{
79+
credential: 'cred-1',
80+
inputs: {
81+
modelName: 'Embedding-V1',
82+
batchSize: '0',
83+
timeout: '0'
84+
}
85+
},
86+
'',
87+
{}
88+
)
89+
90+
expect(model.fields).toMatchObject({
91+
modelName: 'Embedding-V1',
92+
batchSize: 0,
93+
timeout: 0
94+
})
95+
})
96+
})
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { BaiduQianfanEmbeddings, BaiduQianfanEmbeddingsParams } from '@langchain/baidu-qianfan'
2+
import { ICommonObject, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../src/Interface'
3+
import { MODEL_TYPE, getModels } from '../../../src/modelLoader'
4+
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
5+
6+
class BaiduQianfanEmbedding_Embeddings implements INode {
7+
label: string
8+
name: string
9+
version: number
10+
type: string
11+
icon: string
12+
category: string
13+
description: string
14+
baseClasses: string[]
15+
credential: INodeParams
16+
inputs: INodeParams[]
17+
18+
constructor() {
19+
this.label = 'Baidu Qianfan Embedding'
20+
this.name = 'baiduQianfanEmbeddings'
21+
this.version = 1.0
22+
this.type = 'BaiduQianfanEmbeddings'
23+
this.icon = 'baiduwenxin.svg'
24+
this.category = 'Embeddings'
25+
this.description = 'Baidu Qianfan API to generate embeddings for a given text'
26+
this.baseClasses = [this.type, ...getBaseClasses(BaiduQianfanEmbeddings)]
27+
this.credential = {
28+
label: 'Connect Credential',
29+
name: 'credential',
30+
type: 'credential',
31+
credentialNames: ['baiduQianfanApi']
32+
}
33+
this.inputs = [
34+
{
35+
label: 'Model Name',
36+
name: 'modelName',
37+
type: 'asyncOptions',
38+
loadMethod: 'listModels',
39+
default: 'Embedding-V1'
40+
},
41+
{
42+
label: 'Custom Model Name',
43+
name: 'customModelName',
44+
type: 'string',
45+
placeholder: 'Qwen3-Embedding-4B',
46+
description: 'Custom model name to use. If provided, it will override the selected model.',
47+
additionalParams: true,
48+
optional: true
49+
},
50+
{
51+
label: 'Strip New Lines',
52+
name: 'stripNewLines',
53+
type: 'boolean',
54+
optional: true,
55+
additionalParams: true,
56+
description: 'Remove new lines from input text before embedding to reduce token count'
57+
},
58+
{
59+
label: 'Batch Size',
60+
name: 'batchSize',
61+
type: 'number',
62+
optional: true,
63+
default: 1,
64+
additionalParams: true,
65+
description: 'Number of texts sent in each embedding request',
66+
warning:
67+
'Qianfan has stricter limits on individual text length. If you encounter a length error, reduce chunk size to 500 and set Batch Size to 1.'
68+
},
69+
{
70+
label: 'Timeout',
71+
name: 'timeout',
72+
type: 'number',
73+
optional: true,
74+
additionalParams: true,
75+
description: 'Request timeout in milliseconds'
76+
}
77+
]
78+
}
79+
80+
//@ts-ignore
81+
loadMethods = {
82+
async listModels(): Promise<INodeOptionsValue[]> {
83+
return await getModels(MODEL_TYPE.EMBEDDING, 'baiduQianfanEmbeddings')
84+
}
85+
}
86+
87+
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
88+
const modelName = nodeData.inputs?.modelName as BaiduQianfanEmbeddingsParams['modelName']
89+
const customModelName = nodeData.inputs?.customModelName as string
90+
const stripNewLines = nodeData.inputs?.stripNewLines as boolean
91+
const batchSize = nodeData.inputs?.batchSize as string
92+
const timeout = nodeData.inputs?.timeout as string
93+
94+
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
95+
const qianfanAccessKey = getCredentialParam('qianfanAccessKey', credentialData, nodeData)
96+
const qianfanSecretKey = getCredentialParam('qianfanSecretKey', credentialData, nodeData)
97+
98+
const obj: Partial<BaiduQianfanEmbeddingsParams> & {
99+
qianfanAccessKey?: string
100+
qianfanSecretKey?: string
101+
} = {
102+
modelName: (customModelName || modelName) as BaiduQianfanEmbeddingsParams['modelName'],
103+
qianfanAccessKey,
104+
qianfanSecretKey
105+
}
106+
107+
if (typeof stripNewLines === 'boolean') obj.stripNewLines = stripNewLines
108+
if (batchSize !== undefined && batchSize !== null && batchSize !== '') obj.batchSize = parseInt(batchSize, 10)
109+
if (timeout !== undefined && timeout !== null && timeout !== '') obj.timeout = parseInt(timeout, 10)
110+
111+
const model = new BaiduQianfanEmbeddings(obj)
112+
return model
113+
}
114+
}
115+
116+
module.exports = { nodeClass: BaiduQianfanEmbedding_Embeddings }
Lines changed: 7 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)