[9.0] [ML] Fix OpenAI connector does not use the action proxy configuration for all subactions (#219617) (#224124)

# Backport

This will backport the following commits from `main` to `9.0`:
- [[ML] Fix OpenAI connector does not use the action proxy configuration
for all subactions
(#219617)](https://github.com/elastic/kibana/pull/219617)

<!--- Backport version: 10.0.0 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sorenlouv/backport)

<!--BACKPORT [{"author":{"name":"Quynh Nguyen
(Quinn)","email":"43350163+qn895@users.noreply.github.com"},"sourceCommit":{"committedDate":"2025-06-05T17:34:27Z","message":"[ML]
Fix OpenAI connector does not use the action proxy configuration for all
subactions (#219617)\n\n## Summary\n\nThis PR fixes
https://github.com/elastic/kibana/issues/214057 by adding\nthe
httpsAgent/httpAgent to the OpenAI client.\n\n### Checklist\n\nCheck the
PR satisfies following conditions. \n\nReviewers should verify this PR
satisfies this list as well.\n\n- [ ] Any text added follows [EUI's
writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\nsentence case text and includes
[i18n\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\n-
[
]\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\nwas
added for features that require explanation or tutorials\n- [x] [Unit or
functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere
updated or added to match the most common scenarios\n- [ ] If a plugin
configuration key changed, check if it needs to be\nallowlisted in the
cloud and added to the
[docker\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\n-
[ ] This was checked for breaking HTTP API changes, and any
breaking\nchanges have been approved by the breaking-change committee.
The\n`release_note:breaking` label should be applied in these
situations.\n- [ ] [Flaky
Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\nused on any tests changed\n- [ ] The PR description includes the
appropriate Release Notes section,\nand the correct `release_note:*`
label is applied per
the\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\n\n###
Identify risks\n\nDoes this PR introduce any risks? For example,
consider risks like hard\nto test bugs, performance regression,
potential of data loss.\n\nDescribe the risk, its severity, and
mitigation for each identified\nrisk. Invite stakeholders and evaluate
how to proceed before merging.\n\n- [ ] [See some
risk\nexamples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx)\n-
[ ] ...\n\n---------\n\nCo-authored-by: Elastic Machine
<elasticmachine@users.noreply.github.com>","sha":"2564d6de38855a32be94ececd062cf1820dc52bb","branchLabelMapping":{"^v9.1.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","release_note:fix",":ml","backport:version","Team:AI
Infra","v9.1.0","v8.19.0","v9.0.3","v8.18.3"],"title":"[ML] Fix OpenAI
connector does not use the action proxy configuration for all
subactions","number":219617,"url":"https://github.com/elastic/kibana/pull/219617","mergeCommit":{"message":"[ML]
Fix OpenAI connector does not use the action proxy configuration for all
subactions (#219617)\n\n## Summary\n\nThis PR fixes
https://github.com/elastic/kibana/issues/214057 by adding\nthe
httpsAgent/httpAgent to the OpenAI client.\n\n### Checklist\n\nCheck the
PR satisfies following conditions. \n\nReviewers should verify this PR
satisfies this list as well.\n\n- [ ] Any text added follows [EUI's
writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\nsentence case text and includes
[i18n\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\n-
[
]\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\nwas
added for features that require explanation or tutorials\n- [x] [Unit or
functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere
updated or added to match the most common scenarios\n- [ ] If a plugin
configuration key changed, check if it needs to be\nallowlisted in the
cloud and added to the
[docker\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\n-
[ ] This was checked for breaking HTTP API changes, and any
breaking\nchanges have been approved by the breaking-change committee.
The\n`release_note:breaking` label should be applied in these
situations.\n- [ ] [Flaky
Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\nused on any tests changed\n- [ ] The PR description includes the
appropriate Release Notes section,\nand the correct `release_note:*`
label is applied per
the\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\n\n###
Identify risks\n\nDoes this PR introduce any risks? For example,
consider risks like hard\nto test bugs, performance regression,
potential of data loss.\n\nDescribe the risk, its severity, and
mitigation for each identified\nrisk. Invite stakeholders and evaluate
how to proceed before merging.\n\n- [ ] [See some
risk\nexamples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx)\n-
[ ] ...\n\n---------\n\nCo-authored-by: Elastic Machine
<elasticmachine@users.noreply.github.com>","sha":"2564d6de38855a32be94ececd062cf1820dc52bb"}},"sourceBranch":"main","suggestedTargetBranches":["9.0","8.18"],"targetPullRequestStates":[{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/219617","number":219617,"mergeCommit":{"message":"[ML]
Fix OpenAI connector does not use the action proxy configuration for all
subactions (#219617)\n\n## Summary\n\nThis PR fixes
https://github.com/elastic/kibana/issues/214057 by adding\nthe
httpsAgent/httpAgent to the OpenAI client.\n\n### Checklist\n\nCheck the
PR satisfies following conditions. \n\nReviewers should verify this PR
satisfies this list as well.\n\n- [ ] Any text added follows [EUI's
writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\nsentence case text and includes
[i18n\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\n-
[
]\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\nwas
added for features that require explanation or tutorials\n- [x] [Unit or
functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere
updated or added to match the most common scenarios\n- [ ] If a plugin
configuration key changed, check if it needs to be\nallowlisted in the
cloud and added to the
[docker\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\n-
[ ] This was checked for breaking HTTP API changes, and any
breaking\nchanges have been approved by the breaking-change committee.
The\n`release_note:breaking` label should be applied in these
situations.\n- [ ] [Flaky
Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\nused on any tests changed\n- [ ] The PR description includes the
appropriate Release Notes section,\nand the correct `release_note:*`
label is applied per
the\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\n\n###
Identify risks\n\nDoes this PR introduce any risks? For example,
consider risks like hard\nto test bugs, performance regression,
potential of data loss.\n\nDescribe the risk, its severity, and
mitigation for each identified\nrisk. Invite stakeholders and evaluate
how to proceed before merging.\n\n- [ ] [See some
risk\nexamples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx)\n-
[ ] ...\n\n---------\n\nCo-authored-by: Elastic Machine
<elasticmachine@users.noreply.github.com>","sha":"2564d6de38855a32be94ececd062cf1820dc52bb"}},{"branch":"8.19","label":"v8.19.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"url":"https://github.com/elastic/kibana/pull/222869","number":222869,"state":"MERGED","mergeCommit":{"sha":"b48ff971cada9153ab220a78d012204a53681efe","message":"[8.19]
[ML] Fix OpenAI connector does not use the action proxy configuration
for all subactions (#219617) (#222869)\n\n# Backport\n\nThis will
backport the following commits from `main` to `8.19`:\n- [[ML] Fix
OpenAI connector does not use the action proxy configuration\nfor all
subactions\n(#219617)](https://github.com/elastic/kibana/pull/219617)\n\n\n\n###
Questions ?\nPlease refer to the [Backport
tool\ndocumentation](https://github.com/sorenlouv/backport)\n\n\n\nCo-authored-by:
Quynh Nguyen (Quinn)
<43350163+qn895@users.noreply.github.com>\nCo-authored-by: Elastic
Machine
<elasticmachine@users.noreply.github.com>"}},{"branch":"9.0","label":"v9.0.3","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.18","label":"v8.18.3","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Quynh Nguyen (Quinn) 2025-06-16 15:05:45 -05:00 committed by GitHub
parent d322792ae5
commit db0497a12c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 155 additions and 2 deletions

View file

@ -14,6 +14,9 @@ import { ActionsConfigurationUtilities } from '../actions_config';
import { getNodeSSLOptions, getSSLSettingsFromConfig } from './get_node_ssl_options';
import { SSLSettings } from '../types';
/**
* Create http and https proxy agents with custom proxy /hosts/SSL settings from configurationUtilities
*/
interface GetCustomAgentsResponse {
httpAgent: HttpAgent | undefined;
httpsAgent: HttpsAgent | undefined;

View file

@ -43,7 +43,7 @@ export abstract class SubActionConnector<Config, Secrets> {
[k: string]: ((params: unknown) => unknown) | unknown;
private axiosInstance: AxiosInstance;
private subActions: Map<string, SubAction> = new Map();
private configurationUtilities: ActionsConfigurationUtilities;
protected configurationUtilities: ActionsConfigurationUtilities;
protected readonly kibanaRequest?: KibanaRequest;
protected logger: Logger;
protected esClient: ElasticsearchClient;

View file

@ -0,0 +1,142 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { DEFAULT_TIMEOUT_MS, OPENAI_CONNECTOR_ID } from '../../../common/openai/constants';
import { actionsMock } from '@kbn/actions-plugin/server/mocks';
import { DEFAULT_OPENAI_MODEL } from '../../../common/openai/constants';
import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock';
import { OpenAIConnector } from './openai';
import { OpenAiProviderType } from '../../../common/openai/constants';
import { loggingSystemMock } from '@kbn/core/server/mocks';
import { ConnectorUsageCollector } from '@kbn/actions-plugin/server/types';
import { RunActionResponseSchema } from '../../../common/openai/schema';
const logger = loggingSystemMock.createLogger();
// Mock an instance of the OpenAI class
// with overridden flag for purpose of jest test
jest.mock('openai', () => {
const UnmodifiedOpenAIClient = jest.requireActual('openai').default;
return {
__esModule: true,
default: jest.fn().mockImplementation((config) => {
return new UnmodifiedOpenAIClient({
...config,
dangerouslyAllowBrowser: true,
});
}),
};
});
describe('OpenAI with proxy config', () => {
let mockProxiedRequest: jest.Mock;
let connectorUsageCollector: ConnectorUsageCollector;
const mockDefaults = {
timeout: DEFAULT_TIMEOUT_MS,
url: 'https://api.openai.com/v1/chat/completions',
method: 'post',
responseSchema: RunActionResponseSchema,
};
const mockResponse = {
headers: {},
data: {},
};
const configurationUtilities = actionsConfigMock.create();
const PROXY_HOST = 'proxy.custom.elastic.co';
const PROXY_URL = `http://${PROXY_HOST}`;
configurationUtilities.getProxySettings.mockReturnValue({
proxyUrl: PROXY_URL,
proxySSLSettings: {
verificationMode: 'none',
},
proxyBypassHosts: undefined,
proxyOnlyHosts: undefined,
});
const connector = new OpenAIConnector({
configurationUtilities,
connector: { id: '1', type: OPENAI_CONNECTOR_ID },
config: {
apiUrl: 'https://api.openai.com/v1/chat/completions',
apiProvider: OpenAiProviderType.OpenAi,
defaultModel: DEFAULT_OPENAI_MODEL,
organizationId: 'org-id',
projectId: 'proj-id',
headers: {
'X-My-Custom-Header': 'foo',
Authorization: 'override',
},
},
secrets: { apiKey: '123' },
logger,
services: actionsMock.createServices(),
});
const sampleOpenAiBody = {
messages: [
{
role: 'user',
content: 'Hello world',
},
],
};
beforeEach(() => {
connectorUsageCollector = new ConnectorUsageCollector({
logger,
connectorId: 'test-connector-id',
});
mockProxiedRequest = jest.fn().mockResolvedValue(mockResponse);
// @ts-ignore
connector.request = mockProxiedRequest;
jest.clearAllMocks();
});
it('verifies that the OpenAI client is initialized with the custom proxy HTTP agent', () => {
// @ts-ignore .openAI is private
const openAIClient = connector.openAI;
// Verify the client was initialized with the custom agent configuration
expect(openAIClient).toBeDefined();
expect(openAIClient.httpAgent).toBeDefined();
expect(openAIClient.httpAgent.proxy).toBeDefined();
expect(openAIClient.httpAgent.proxy.host).toBe(PROXY_HOST);
expect(openAIClient.httpAgent.proxy.port).toBe(80);
});
it('verifies that requests use the configured HTTP agent', async () => {
// Make a test request
const response = await connector.runApi(
{ body: JSON.stringify(sampleOpenAiBody) },
connectorUsageCollector
);
expect(mockProxiedRequest).toBeCalledTimes(1);
expect(mockProxiedRequest).toHaveBeenCalledWith(
{
...mockDefaults,
signal: undefined,
data: JSON.stringify({
...sampleOpenAiBody,
stream: false,
model: DEFAULT_OPENAI_MODEL,
}),
headers: {
Authorization: 'Bearer 123',
'X-My-Custom-Header': 'foo',
'content-type': 'application/json',
'OpenAI-Organization': 'org-id',
'OpenAI-Project': 'proj-id',
},
},
connectorUsageCollector
);
expect(response).toEqual(mockResponse.data);
});
});

View file

@ -17,6 +17,7 @@ import {
} from 'openai/resources/chat/completions';
import { Stream } from 'openai/streaming';
import { ConnectorUsageCollector } from '@kbn/actions-plugin/server/types';
import { getCustomAgents } from '@kbn/actions-plugin/server/lib/get_custom_agents';
import { removeEndpointFromUrl } from './lib/openai_utils';
import {
RunActionParamsSchema,
@ -63,7 +64,6 @@ export class OpenAIConnector extends SubActionConnector<Config, Secrets> {
constructor(params: ServiceParams<Config, Secrets>) {
super(params);
this.url = this.config.apiUrl;
this.provider = this.config.apiProvider;
this.key = this.secrets.apiKey;
@ -75,6 +75,12 @@ export class OpenAIConnector extends SubActionConnector<Config, Secrets> {
...('projectId' in this.config ? { 'OpenAI-Project': this.config.projectId } : {}),
};
const { httpAgent, httpsAgent } = getCustomAgents(
this.configurationUtilities,
this.logger,
this.url
);
this.openAI =
this.config.apiProvider === OpenAiProviderType.AzureAi
? new OpenAI({
@ -85,6 +91,7 @@ export class OpenAIConnector extends SubActionConnector<Config, Secrets> {
...this.headers,
'api-key': this.secrets.apiKey,
},
httpAgent: httpsAgent ?? httpAgent,
})
: new OpenAI({
baseURL: removeEndpointFromUrl(this.config.apiUrl),
@ -92,6 +99,7 @@ export class OpenAIConnector extends SubActionConnector<Config, Secrets> {
defaultHeaders: {
...this.headers,
},
httpAgent: httpsAgent ?? httpAgent,
});
this.registerSubActions();