🖥️ feat: Add Proxy Support for Tavily API Tool (#10770)

* 🖥️ feat: Add Proxy Support for Tavily API Tool

- Integrated ProxyAgent from undici to enable proxy support for API requests in TavilySearch and TavilySearchResults.
- Updated fetch options to conditionally include the proxy configuration based on the environment variable, enhancing flexibility for network requests.

* ci: TavilySearchResults with Proxy Support Tests

- Added tests to verify the integration of ProxyAgent for API requests in TavilySearchResults.
- Implemented conditional logic to check for the PROXY environment variable, ensuring correct usage of ProxyAgent based on its presence.
- Updated test setup to clear mocks before each test for improved isolation and reliability.
This commit is contained in:
Danny Avila 2025-12-02 09:11:56 -05:00 committed by GitHub
parent ef5540f278
commit 1477da4987
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 66 additions and 6 deletions

View file

@ -1,4 +1,5 @@
const { z } = require('zod'); const { z } = require('zod');
const { ProxyAgent, fetch } = require('undici');
const { tool } = require('@langchain/core/tools'); const { tool } = require('@langchain/core/tools');
const { getApiKey } = require('./credentials'); const { getApiKey } = require('./credentials');
@ -19,13 +20,19 @@ function createTavilySearchTool(fields = {}) {
...kwargs, ...kwargs,
}; };
const response = await fetch('https://api.tavily.com/search', { const fetchOptions = {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
body: JSON.stringify(requestBody), body: JSON.stringify(requestBody),
}); };
if (process.env.PROXY) {
fetchOptions.dispatcher = new ProxyAgent(process.env.PROXY);
}
const response = await fetch('https://api.tavily.com/search', fetchOptions);
const json = await response.json(); const json = await response.json();
if (!response.ok) { if (!response.ok) {

View file

@ -1,4 +1,5 @@
const { z } = require('zod'); const { z } = require('zod');
const { ProxyAgent, fetch } = require('undici');
const { Tool } = require('@langchain/core/tools'); const { Tool } = require('@langchain/core/tools');
const { getEnvironmentVariable } = require('@langchain/core/utils/env'); const { getEnvironmentVariable } = require('@langchain/core/utils/env');
@ -102,13 +103,19 @@ class TavilySearchResults extends Tool {
...this.kwargs, ...this.kwargs,
}; };
const response = await fetch('https://api.tavily.com/search', { const fetchOptions = {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
body: JSON.stringify(requestBody), body: JSON.stringify(requestBody),
}); };
if (process.env.PROXY) {
fetchOptions.dispatcher = new ProxyAgent(process.env.PROXY);
}
const response = await fetch('https://api.tavily.com/search', fetchOptions);
const json = await response.json(); const json = await response.json();
if (!response.ok) { if (!response.ok) {

View file

@ -1,6 +1,7 @@
const { fetch, ProxyAgent } = require('undici');
const TavilySearchResults = require('../TavilySearchResults'); const TavilySearchResults = require('../TavilySearchResults');
jest.mock('node-fetch'); jest.mock('undici');
jest.mock('@langchain/core/utils/env'); jest.mock('@langchain/core/utils/env');
describe('TavilySearchResults', () => { describe('TavilySearchResults', () => {
@ -13,6 +14,7 @@ describe('TavilySearchResults', () => {
beforeEach(() => { beforeEach(() => {
jest.resetModules(); jest.resetModules();
jest.clearAllMocks();
process.env = { process.env = {
...originalEnv, ...originalEnv,
TAVILY_API_KEY: mockApiKey, TAVILY_API_KEY: mockApiKey,
@ -20,7 +22,6 @@ describe('TavilySearchResults', () => {
}); });
afterEach(() => { afterEach(() => {
jest.clearAllMocks();
process.env = originalEnv; process.env = originalEnv;
}); });
@ -35,4 +36,49 @@ describe('TavilySearchResults', () => {
}); });
expect(instance.apiKey).toBe(mockApiKey); expect(instance.apiKey).toBe(mockApiKey);
}); });
describe('proxy support', () => {
const mockResponse = {
ok: true,
json: jest.fn().mockResolvedValue({ results: [] }),
};
beforeEach(() => {
fetch.mockResolvedValue(mockResponse);
});
it('should use ProxyAgent when PROXY env var is set', async () => {
const proxyUrl = 'http://proxy.example.com:8080';
process.env.PROXY = proxyUrl;
const mockProxyAgent = { type: 'proxy-agent' };
ProxyAgent.mockImplementation(() => mockProxyAgent);
const instance = new TavilySearchResults({ TAVILY_API_KEY: mockApiKey });
await instance._call({ query: 'test query' });
expect(ProxyAgent).toHaveBeenCalledWith(proxyUrl);
expect(fetch).toHaveBeenCalledWith(
'https://api.tavily.com/search',
expect.objectContaining({
dispatcher: mockProxyAgent,
}),
);
});
it('should not use ProxyAgent when PROXY env var is not set', async () => {
delete process.env.PROXY;
const instance = new TavilySearchResults({ TAVILY_API_KEY: mockApiKey });
await instance._call({ query: 'test query' });
expect(ProxyAgent).not.toHaveBeenCalled();
expect(fetch).toHaveBeenCalledWith(
'https://api.tavily.com/search',
expect.not.objectContaining({
dispatcher: expect.anything(),
}),
);
});
});
}); });