Skip to content

Commit 18bc438

Browse files
committed
fix(chatopenai): honor proxy URL
1 parent f4e2794 commit 18bc438

4 files changed

Lines changed: 101 additions & 2 deletions

File tree

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
jest.mock('@langchain/openai', () => ({
2+
ChatOpenAI: jest.fn(),
3+
ChatOpenAIFields: {}
4+
}))
5+
6+
jest.mock('undici', () => ({
7+
ProxyAgent: jest.fn().mockImplementation((url) => ({ proxyUrl: url }))
8+
}))
9+
10+
jest.mock('../../../src/utils', () => ({
11+
getBaseClasses: jest.fn().mockReturnValue(['BaseChatModel']),
12+
getCredentialData: jest.fn(),
13+
getCredentialParam: jest.fn(),
14+
isReasoningModelOpenAI: jest.fn().mockReturnValue(false)
15+
}))
16+
17+
jest.mock('../../../src/modelLoader', () => ({
18+
MODEL_TYPE: { CHAT: 'chat' },
19+
getModels: jest.fn()
20+
}))
21+
22+
jest.mock('./FlowiseChatOpenAI', () => ({
23+
ChatOpenAI: jest.fn().mockImplementation((id, fields) => ({
24+
id,
25+
fields,
26+
setMultiModalOption: jest.fn()
27+
}))
28+
}))
29+
30+
import { ProxyAgent } from 'undici'
31+
import { getCredentialData, getCredentialParam } from '../../../src/utils'
32+
33+
const { ChatOpenAI } = require('./FlowiseChatOpenAI')
34+
const { nodeClass: ChatOpenAINode } = require('./ChatOpenAI')
35+
36+
describe('ChatOpenAI node', () => {
37+
beforeEach(() => {
38+
jest.clearAllMocks()
39+
})
40+
41+
it('passes proxyUrl to the OpenAI client fetch dispatcher', async () => {
42+
;(getCredentialData as jest.Mock).mockResolvedValue({ openAIApiKey: 'sk-test' })
43+
;(getCredentialParam as jest.Mock).mockImplementation((key, credentialData) => credentialData[key])
44+
45+
const node = new ChatOpenAINode()
46+
await node.init(
47+
{
48+
id: 'chatOpenAI_0',
49+
credential: 'cred-1',
50+
inputs: {
51+
modelName: 'gpt-4o-mini',
52+
temperature: '0.2',
53+
proxyUrl: 'http://corporate-proxy.example.com:3128',
54+
baseOptions: {
55+
'OpenAI-Beta': 'assistants=v2'
56+
}
57+
}
58+
},
59+
'',
60+
{}
61+
)
62+
63+
expect(ProxyAgent).toHaveBeenCalledWith('http://corporate-proxy.example.com:3128')
64+
expect(ChatOpenAI).toHaveBeenCalledWith(
65+
'chatOpenAI_0',
66+
expect.objectContaining({
67+
configuration: {
68+
defaultHeaders: {
69+
'OpenAI-Beta': 'assistants=v2'
70+
},
71+
fetchOptions: {
72+
dispatcher: { proxyUrl: 'http://corporate-proxy.example.com:3128' }
73+
}
74+
}
75+
})
76+
)
77+
})
78+
})

packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { getBaseClasses, getCredentialData, getCredentialParam, isReasoningModel
55
import { ChatOpenAI } from './FlowiseChatOpenAI'
66
import { getModels, MODEL_TYPE } from '../../../src/modelLoader'
77
import { OpenAI as OpenAIClient } from 'openai'
8+
import { ProxyAgent } from 'undici'
89

910
class ChatOpenAI_ChatModels implements INode {
1011
label: string
@@ -200,6 +201,14 @@ class ChatOpenAI_ChatModels implements INode {
200201
description: 'Override the default base URL for the API, e.g., "https://api.example.com/v2/',
201202
additionalParams: true
202203
},
204+
{
205+
label: 'Proxy Url',
206+
name: 'proxyUrl',
207+
type: 'string',
208+
optional: true,
209+
description: 'Proxy URL to use for OpenAI API requests, e.g., "http://proxy.example.com:3128"',
210+
additionalParams: true
211+
},
203212
{
204213
label: 'Base Options',
205214
name: 'baseOptions',
@@ -230,6 +239,7 @@ class ChatOpenAI_ChatModels implements INode {
230239
const streaming = nodeData.inputs?.streaming as boolean
231240
const strictToolCalling = nodeData.inputs?.strictToolCalling as boolean
232241
const basePath = nodeData.inputs?.basepath as string
242+
const proxyUrl = nodeData.inputs?.proxyUrl as string
233243
const baseOptions = nodeData.inputs?.baseOptions
234244
const reasoningEffort = nodeData.inputs?.reasoningEffort as OpenAIClient.ReasoningEffort | null
235245
const reasoningSummary = nodeData.inputs?.reasoningSummary as 'auto' | 'concise' | 'detailed' | null
@@ -286,10 +296,17 @@ class ChatOpenAI_ChatModels implements INode {
286296
}
287297
}
288298

289-
if (basePath || parsedBaseOptions) {
299+
if (basePath || parsedBaseOptions || proxyUrl) {
290300
obj.configuration = {
291301
baseURL: basePath,
292-
defaultHeaders: parsedBaseOptions
302+
defaultHeaders: parsedBaseOptions,
303+
...(proxyUrl
304+
? {
305+
fetchOptions: {
306+
dispatcher: new ProxyAgent(proxyUrl)
307+
}
308+
}
309+
: {})
293310
}
294311
}
295312

packages/components/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@
179179
"srt-parser-2": "^1.2.3",
180180
"supergateway": "3.0.1",
181181
"typeorm": "^0.3.6",
182+
"undici": "6.23.0",
182183
"uuid": "^10.0.0",
183184
"vm2": "3.11.2",
184185
"weaviate-client": "3.12.0",

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)