Skip to content

Commit 79c52aa

Browse files
feat(openai): support web search tool
1 parent 2be74c8 commit 79c52aa

File tree

5 files changed

+338
-2
lines changed

5 files changed

+338
-2
lines changed

libs/providers/langchain-openai/README.md

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,80 @@ const model = new ChatOpenAI({
7070
const response = await model.stream(new HumanMessage("Hello world!"));
7171
```
7272

73+
## Tools
74+
75+
This package provides LangChain-compatible wrappers for OpenAI's built-in tools for the Responses API.
76+
77+
### Web Search Tool
78+
79+
The web search tool allows OpenAI models to search the web for up-to-date information before generating a response. Web search supports three main types:
80+
81+
1. **Non-reasoning web search**: Quick lookups where the model passes queries directly to the search tool
82+
2. **Agentic search with reasoning models**: The model actively manages the search process, analyzing results and deciding whether to keep searching
83+
3. **Deep research**: Extended investigations using models like `o3-deep-research` or `gpt-5` with high reasoning effort
84+
85+
```typescript
86+
import { ChatOpenAI, tools } from "@langchain/openai";
87+
88+
const model = new ChatOpenAI({
89+
model: "gpt-4o",
90+
});
91+
92+
// Basic usage
93+
const response = await model.invoke(
94+
"What was a positive news story from today?",
95+
{
96+
tools: [tools.webSearch()],
97+
}
98+
);
99+
```
100+
101+
**Domain filtering** - Limit search results to specific domains (up to 100):
102+
103+
```typescript
104+
const response = await model.invoke("Latest AI research news", {
105+
tools: [
106+
tools.webSearch({
107+
filters: {
108+
allowedDomains: ["arxiv.org", "nature.com", "science.org"],
109+
},
110+
}),
111+
],
112+
});
113+
```
114+
115+
**User location** - Refine search results based on geography:
116+
117+
```typescript
118+
const response = await model.invoke("What are the best restaurants near me?", {
119+
tools: [
120+
tools.webSearch({
121+
userLocation: {
122+
type: "approximate",
123+
country: "US",
124+
city: "San Francisco",
125+
region: "California",
126+
timezone: "America/Los_Angeles",
127+
},
128+
}),
129+
],
130+
});
131+
```
132+
133+
**Cache-only mode** - Disable live internet access:
134+
135+
```typescript
136+
const response = await model.invoke("Find information about OpenAI", {
137+
tools: [
138+
tools.webSearch({
139+
externalWebAccess: false,
140+
}),
141+
],
142+
});
143+
```
144+
145+
For more information, see [OpenAI's Web Search Documentation](https://platform.openai.com/docs/guides/tools-web-search).
146+
73147
## Embeddings
74148

75149
This package also adds support for OpenAI's embeddings model.
@@ -111,8 +185,8 @@ Test files should live within a `tests/` file in the `src/` folder. Unit tests s
111185
end in `.int.test.ts`:
112186

113187
```bash
114-
$ pnpm test
115-
$ pnpm test:int
188+
pnpm test
189+
pnpm test:int
116190
```
117191

118192
### Lint & Format
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,12 @@
11
export * from "./dalle.js";
2+
3+
import { webSearch } from "./webSearch.js";
4+
export type {
5+
WebSearchTool,
6+
WebSearchFilters,
7+
WebSearchOptions,
8+
} from "./webSearch.js";
9+
10+
export const tools = {
11+
webSearch,
12+
};
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { expect, it, describe } from "vitest";
2+
import {
3+
HumanMessage,
4+
AIMessage,
5+
ContentBlock,
6+
} from "@langchain/core/messages";
7+
8+
import { tools } from "../index.js";
9+
import { ChatOpenAI } from "../../chat_models/index.js";
10+
11+
describe("OpenAI Web Search Tool Tests", () => {
12+
it(
13+
"webSearch creates a basic valid tool definition",
14+
async () => {
15+
const llm = new ChatOpenAI({ model: "gpt-4o-mini" });
16+
const llmWithWebSearch = llm.bindTools([
17+
tools.webSearch({
18+
userLocation: {
19+
type: "approximate",
20+
country: "US",
21+
city: "San Francisco",
22+
region: "California",
23+
timezone: "America/Los_Angeles",
24+
},
25+
}),
26+
]);
27+
28+
const response = await llmWithWebSearch.invoke([
29+
new HumanMessage("What is the current weather?"),
30+
]);
31+
32+
console.log(response.content);
33+
expect(response).toBeInstanceOf(AIMessage);
34+
expect(Array.isArray(response.content)).toBe(true);
35+
expect(
36+
(response.content as ContentBlock.Text[]).find(
37+
(block) =>
38+
block.type === "text" && block.text?.includes("San Francisco")
39+
)
40+
).toBeTruthy();
41+
},
42+
{
43+
/**
44+
* for some reason the location not always is taken into account, so we retry a few times
45+
*/
46+
retry: 5,
47+
}
48+
);
49+
});
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { expect, it, describe } from "vitest";
2+
import { tools } from "../index.js";
3+
4+
describe("OpenAI Web Search Tool Tests", () => {
5+
it("webSearch creates a basic valid tool definition", () => {
6+
expect(tools.webSearch()).toMatchInlineSnapshot(`
7+
{
8+
"filters": undefined,
9+
"search_context_size": undefined,
10+
"type": "web_search",
11+
"user_location": undefined,
12+
}
13+
`);
14+
});
15+
16+
it("webSearch creates tool with domain filtering", () => {
17+
expect(
18+
tools.webSearch({
19+
filters: {
20+
allowedDomains: ["openai.com", "arxiv.org", "nature.com"],
21+
},
22+
})
23+
).toMatchInlineSnapshot(`
24+
{
25+
"filters": {
26+
"allowed_domains": [
27+
"openai.com",
28+
"arxiv.org",
29+
"nature.com",
30+
],
31+
},
32+
"search_context_size": undefined,
33+
"type": "web_search",
34+
"user_location": undefined,
35+
}
36+
`);
37+
});
38+
39+
it("webSearch creates tool with user location", () => {
40+
expect(
41+
tools.webSearch({
42+
userLocation: {
43+
type: "approximate",
44+
country: "US",
45+
city: "San Francisco",
46+
region: "California",
47+
timezone: "America/Los_Angeles",
48+
},
49+
})
50+
).toMatchInlineSnapshot(`
51+
{
52+
"filters": undefined,
53+
"search_context_size": undefined,
54+
"type": "web_search",
55+
"user_location": {
56+
"city": "San Francisco",
57+
"country": "US",
58+
"region": "California",
59+
"timezone": "America/Los_Angeles",
60+
"type": "approximate",
61+
},
62+
}
63+
`);
64+
});
65+
});
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import { OpenAI as OpenAIClient } from "openai";
2+
3+
/**
4+
* User location configuration for geographic search refinement.
5+
*/
6+
export interface WebSearchUserLocation {
7+
/**
8+
* The type of location. Currently only "approximate" is supported.
9+
*/
10+
type: "approximate";
11+
/**
12+
* Two-letter ISO country code (e.g., "US", "GB").
13+
* @see https://en.wikipedia.org/wiki/ISO_3166-1
14+
*/
15+
country?: string;
16+
/**
17+
* City name (e.g., "San Francisco", "London").
18+
*/
19+
city?: string;
20+
/**
21+
* Region or state name (e.g., "California", "London").
22+
*/
23+
region?: string;
24+
/**
25+
* IANA timezone (e.g., "America/Los_Angeles", "Europe/London").
26+
* @see https://timeapi.io/documentation/iana-timezones
27+
*/
28+
timezone?: string;
29+
}
30+
31+
/**
32+
* Domain filtering configuration for web search.
33+
*/
34+
export interface WebSearchFilters {
35+
/**
36+
* Allow-list of up to 100 domains to limit search results.
37+
* Omit the HTTP/HTTPS prefix (e.g., "openai.com" instead of "https://openai.com/").
38+
* Includes subdomains automatically.
39+
*/
40+
allowedDomains?: string[];
41+
}
42+
43+
/**
44+
* Options for the OpenAI web search tool.
45+
*/
46+
export interface WebSearchOptions {
47+
/**
48+
* Domain filtering configuration.
49+
* Limit results to a specific set of up to 100 domains.
50+
*/
51+
filters?: WebSearchFilters;
52+
/**
53+
* Approximate user location for geographic search refinement.
54+
* Not supported for deep research models.
55+
*/
56+
userLocation?: WebSearchUserLocation;
57+
/**
58+
* High level guidance for the amount of context window space to use for the
59+
* search. One of `low`, `medium`, or `high`. `medium` is the default.
60+
*/
61+
search_context_size?: "low" | "medium" | "high";
62+
}
63+
64+
/**
65+
* OpenAI web search tool type for the Responses API.
66+
*/
67+
export type WebSearchTool = OpenAIClient.Responses.WebSearchTool;
68+
69+
/**
70+
* Creates a web search tool that allows OpenAI models to search the web
71+
* for up-to-date information before generating a response.
72+
*
73+
* Web search supports three main types:
74+
* 1. **Non-reasoning web search**: Quick lookups where the model passes queries
75+
* directly to the search tool.
76+
* 2. **Agentic search with reasoning models**: The model actively manages the
77+
* search process, analyzing results and deciding whether to keep searching.
78+
* 3. **Deep research**: Extended investigations using models like `o3-deep-research`
79+
* or `gpt-5` with high reasoning effort.
80+
*
81+
* @see {@link https://platform.openai.com/docs/guides/tools-web-search | OpenAI Web Search Documentation}
82+
* @param options - Configuration options for the web search tool
83+
* @returns A web search tool definition to be passed to the OpenAI Responses API
84+
*
85+
* @example
86+
* ```typescript
87+
* import { ChatOpenAI, tools } from "@langchain/openai";
88+
*
89+
* const model = new ChatOpenAI({
90+
* model: "gpt-4o",
91+
* });
92+
*
93+
* // Basic usage
94+
* const response = await model.invoke("What was a positive news story from today?", {
95+
* tools: [tools.webSearch()],
96+
* });
97+
*
98+
* // With domain filtering
99+
* const filteredResponse = await model.invoke("Latest AI research news", {
100+
* tools: [tools.webSearch({
101+
* filters: {
102+
* allowedDomains: ["arxiv.org", "nature.com", "science.org"],
103+
* },
104+
* })],
105+
* });
106+
*
107+
* // With user location for geographic relevance
108+
* const localResponse = await model.invoke("What are the best restaurants near me?", {
109+
* tools: [tools.webSearch({
110+
* userLocation: {
111+
* type: "approximate",
112+
* country: "US",
113+
* city: "San Francisco",
114+
* region: "California",
115+
* timezone: "America/Los_Angeles",
116+
* },
117+
* })],
118+
* });
119+
*
120+
* // Cache-only mode (no live internet access)
121+
* const cachedResponse = await model.invoke("Find information about OpenAI", {
122+
* tools: [tools.webSearch({
123+
* externalWebAccess: false,
124+
* })],
125+
* });
126+
* ```
127+
*/
128+
export function webSearch(options?: WebSearchOptions): WebSearchTool {
129+
return {
130+
type: "web_search",
131+
filters: options?.filters?.allowedDomains
132+
? { allowed_domains: options.filters.allowedDomains }
133+
: undefined,
134+
user_location: options?.userLocation,
135+
search_context_size: options?.search_context_size,
136+
};
137+
}

0 commit comments

Comments
 (0)