Skip to content

Commit 0f5122f

Browse files
feat(xai): Enables native xAI Live Search tool integration
Introduces native support for xAI's Live Search tool, allowing users to configure search parameters such as sources (web, news, X, RSS) and filters directly within the tool. This replaces the previous `allowed_domains` and `excluded_domains` fields with a more flexible `sources` configuration. The changes include: - Adds a new `live_search.ts` file under `src/tools/` that implements the `xaiLiveSearch` factory, responsible for building tools compatible with the xAI API. - Updates `chat_models.ts` to handle the new `sources` parameter, providing improved control over search behavior. - Modifies the `README.md` file to reflect the updated API and usage examples.
1 parent bbe6a7b commit 0f5122f

File tree

10 files changed

+722
-113
lines changed

10 files changed

+722
-113
lines changed

libs/providers/langchain-anthropic/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,4 @@
9797
"README.md",
9898
"LICENSE"
9999
]
100-
}
100+
}

libs/providers/langchain-openai/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,4 @@
100100
"LICENSE"
101101
],
102102
"module": "./dist/index.js"
103-
}
103+
}

libs/providers/langchain-xai/README.md

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,77 @@ const result = await model.invoke("Find recent news about SpaceX", {
7979
searchParameters: {
8080
mode: "on",
8181
max_search_results: 10,
82-
allowed_domains: ["spacex.com", "nasa.gov"],
82+
sources: [
83+
{
84+
type: "web",
85+
allowed_websites: ["spacex.com", "nasa.gov"],
86+
},
87+
],
88+
},
89+
});
90+
```
91+
92+
### Configuring data sources with `sources`
93+
94+
You can configure which data sources Live Search should use via the `sources` field
95+
in `searchParameters`. Each entry corresponds to one of the sources described in the
96+
official xAI Live Search docs (`web`, `news`, `x`, `rss`).
97+
98+
```typescript
99+
const result = await model.invoke(
100+
"What are the latest updates from xAI and related news?",
101+
{
102+
searchParameters: {
103+
mode: "on",
104+
sources: [
105+
{
106+
type: "web",
107+
// Only search on these websites
108+
allowed_websites: ["x.ai"],
109+
},
110+
{
111+
type: "news",
112+
// Exclude specific news websites
113+
excluded_websites: ["bbc.co.uk"],
114+
},
115+
{
116+
type: "x",
117+
// Focus on specific X handles
118+
included_x_handles: ["xai"],
119+
},
120+
],
121+
},
122+
}
123+
);
124+
```
125+
126+
You can also use RSS feeds as a data source:
127+
128+
```typescript
129+
const result = await model.invoke("Summarize the latest posts from this feed", {
130+
searchParameters: {
131+
mode: "on",
132+
sources: [
133+
{
134+
type: "rss",
135+
links: ["https://example.com/feed.rss"],
136+
},
137+
],
83138
},
84139
});
85140
```
86141

142+
> Notes:
143+
>
144+
> - The previous `allowed_domains` / `excluded_domains` fields are not
145+
> supported in this provider. Use `sources` with `allowed_websites` and
146+
> `excluded_websites` instead.
147+
> - In TypeScript, the `XAISearchParameters` and `sources` types use the same
148+
> `snake_case` field names as the underlying JSON API (for example
149+
> `allowed_websites`, `excluded_websites`, `included_x_handles`). There are no
150+
> separate camelCase aliases (`allowedWebsites`, etc.), which keeps the
151+
> provider aligned with the official xAI documentation.
152+
87153
### Combining live_search with custom tools
88154

89155
```typescript

libs/providers/langchain-xai/src/chat_models.ts

Lines changed: 77 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ import {
88
LangSmithParams,
99
type BaseChatModelParams,
1010
} from "@langchain/core/language_models/chat_models";
11+
import {
12+
isLangChainTool,
13+
convertToOpenAITool,
14+
} from "@langchain/core/utils/function_calling";
1115
import { ModelProfile } from "@langchain/core/language_models/profile";
1216
import { Serialized } from "@langchain/core/load/serializable";
1317
import {
@@ -23,6 +27,13 @@ import {
2327
type OpenAIClient,
2428
ChatOpenAICompletions,
2529
} from "@langchain/openai";
30+
import {
31+
buildSearchParametersPayload,
32+
filterXAIBuiltInTools,
33+
mergeSearchParams,
34+
type XAISearchParameters,
35+
type XAISearchParametersPayload,
36+
} from "./live_search.js";
2637
import PROFILES from "./profiles.js";
2738

2839
export type OpenAIToolChoice =
@@ -34,7 +45,7 @@ export type OpenAIToolChoice =
3445
* xAI's built-in live_search tool type.
3546
* Enables the model to search the web for real-time information.
3647
*/
37-
export interface XAILiveSearchTool {
48+
export interface XAILiveSearchTool extends XAISearchParameters {
3849
/**
3950
* The type of the tool. Must be "live_search" for xAI's built-in search.
4051
*/
@@ -46,6 +57,13 @@ export interface XAILiveSearchTool {
4657
*/
4758
export type XAIBuiltInTool = XAILiveSearchTool;
4859

60+
/**
61+
* Set of all supported xAI built-in server-side tool types.
62+
* This allows us to easily extend support for future built-in tools
63+
* without changing the core detection logic.
64+
*/
65+
const XAI_BUILT_IN_TOOL_TYPES = new Set<string>(["live_search"]);
66+
4967
/**
5068
* Tool type that includes both standard tools and xAI built-in tools.
5169
*/
@@ -54,54 +72,6 @@ type ChatXAIToolType =
5472
| OpenAIClient.ChatCompletionTool
5573
| XAIBuiltInTool;
5674

57-
/**
58-
* Search parameters for xAI's Live Search API.
59-
* Controls how the model searches for and retrieves real-time information.
60-
*
61-
* @note The Live Search API is being deprecated by xAI in favor of
62-
* the agentic tool calling approach. Consider using `tools: [{ type: "live_search" }]`
63-
* for future compatibility.
64-
*/
65-
export interface XAISearchParameters {
66-
/**
67-
* Controls when the model should perform a search.
68-
* - "auto": Let the model decide when to search (default)
69-
* - "on": Always search for every request
70-
* - "off": Never search
71-
*/
72-
mode?: "auto" | "on" | "off";
73-
/**
74-
* Maximum number of search results to return.
75-
* @default 5
76-
*/
77-
max_search_results?: number;
78-
/**
79-
* Filter search results to only include content from after this date.
80-
* Format: ISO 8601 date string (e.g., "2024-01-01")
81-
*/
82-
from_date?: string;
83-
/**
84-
* Filter search results to only include content from before this date.
85-
* Format: ISO 8601 date string (e.g., "2024-12-31")
86-
*/
87-
to_date?: string;
88-
/**
89-
* Whether to return citations/sources for the search results.
90-
* @default true
91-
*/
92-
return_citations?: boolean;
93-
/**
94-
* Specific domains to include in the search.
95-
* Example: ["wikipedia.org", "arxiv.org"]
96-
*/
97-
allowed_domains?: string[];
98-
/**
99-
* Specific domains to exclude from the search.
100-
* Example: ["reddit.com"]
101-
*/
102-
excluded_domains?: string[];
103-
}
104-
10575
/**
10676
* xAI-specific invocation parameters that extend the OpenAI completion params
10777
* with xAI's search_parameters field.
@@ -114,15 +84,7 @@ export type ChatXAICompletionsInvocationParams = Omit<
11484
* Search parameters for xAI's Live Search API.
11585
* When present, enables the model to search the web for real-time information.
11686
*/
117-
search_parameters?: {
118-
mode: "auto" | "on" | "off";
119-
max_search_results?: number;
120-
from_date?: string;
121-
to_date?: string;
122-
return_citations?: boolean;
123-
allowed_domains?: string[];
124-
excluded_domains?: string[];
125-
};
87+
search_parameters?: XAISearchParametersPayload;
12688
};
12789

12890
/**
@@ -173,7 +135,8 @@ export function isXAIBuiltInTool(
173135
typeof tool === "object" &&
174136
tool !== null &&
175137
"type" in tool &&
176-
(tool as XAIBuiltInTool).type === "live_search"
138+
typeof (tool as { type?: unknown }).type === "string" &&
139+
XAI_BUILT_IN_TOOL_TYPES.has((tool as { type: string }).type)
177140
);
178141
}
179142

@@ -635,7 +598,9 @@ export interface ChatXAIInput extends BaseChatModelParams {
635598
* searchParameters: {
636599
* mode: "on",
637600
* max_search_results: 10,
638-
* allowed_domains: ["spacex.com", "nasa.gov"],
601+
* sources: [
602+
* { type: "web", allowed_websites: ["spacex.com", "nasa.gov"] },
603+
* ],
639604
* }
640605
* });
641606
* ```
@@ -716,15 +681,7 @@ export class ChatXAI extends ChatOpenAICompletions<ChatXAICallOptions> {
716681
protected _getEffectiveSearchParameters(
717682
options?: this["ParsedCallOptions"]
718683
): XAISearchParameters | undefined {
719-
const callSearchParams = options?.searchParameters;
720-
if (!this.searchParameters && !callSearchParams) {
721-
return undefined;
722-
}
723-
// Merge instance-level with call-level, call-level takes precedence
724-
return {
725-
...this.searchParameters,
726-
...callSearchParams,
727-
};
684+
return mergeSearchParams(this.searchParameters, options?.searchParameters);
728685
}
729686

730687
/**
@@ -736,6 +693,42 @@ export class ChatXAI extends ChatOpenAICompletions<ChatXAICallOptions> {
736693
return tools?.some(isXAIBuiltInTool) ?? false;
737694
}
738695

696+
/**
697+
* Formats tools to xAI/OpenAI format, preserving provider-specific definitions.
698+
*
699+
* @param tools The tools to format
700+
* @returns The formatted tools
701+
*/
702+
formatStructuredToolToXAI(
703+
tools: ChatXAIToolType[]
704+
): (OpenAIClient.ChatCompletionTool | XAIBuiltInTool)[] | undefined {
705+
if (!tools || !tools.length) {
706+
return undefined;
707+
}
708+
return tools.map((tool) => {
709+
// 1. Check for provider definition first (from xaiLiveSearch factory)
710+
if (isLangChainTool(tool) && tool.extras?.providerToolDefinition) {
711+
return tool.extras.providerToolDefinition as XAIBuiltInTool;
712+
}
713+
// 2. Check for built-in tools (legacy { type: "live_search" })
714+
if (isXAIBuiltInTool(tool)) {
715+
return tool;
716+
}
717+
// 3. Convert standard tools to OpenAI format
718+
return convertToOpenAITool(tool) as OpenAIClient.ChatCompletionTool;
719+
});
720+
}
721+
722+
override bindTools(
723+
tools: ChatXAIToolType[],
724+
kwargs?: Partial<ChatXAICallOptions>
725+
): Runnable<BaseLanguageModelInput, AIMessageChunk, ChatXAICallOptions> {
726+
return this.withConfig({
727+
tools: this.formatStructuredToolToXAI(tools),
728+
...kwargs,
729+
} as Partial<ChatXAICallOptions>);
730+
}
731+
739732
/** @internal */
740733
override invocationParams(
741734
options?: this["ParsedCallOptions"],
@@ -746,43 +739,22 @@ export class ChatXAI extends ChatOpenAICompletions<ChatXAICallOptions> {
746739
// Cast to xAI-specific params type
747740
const params: ChatXAICompletionsInvocationParams = { ...baseParams };
748741

749-
// Get effective search parameters from instance and call options
750-
const effectiveSearchParams = this._getEffectiveSearchParameters(options);
751-
752742
// Check if live_search tool is being used
753-
const hasLiveSearchTool = this._hasBuiltInTools(
754-
options?.tools as ChatXAIToolType[] | undefined
743+
// We also need to extract params from the tool definition if present
744+
const liveSearchTool = options?.tools?.find(isXAIBuiltInTool) as
745+
| XAILiveSearchTool
746+
| undefined;
747+
748+
const mergedSearchParams = mergeSearchParams(
749+
this.searchParameters,
750+
options?.searchParameters,
751+
liveSearchTool
755752
);
756753

757754
// Add search_parameters if needed
758-
if (effectiveSearchParams || hasLiveSearchTool) {
759-
const searchParams = hasLiveSearchTool
760-
? { mode: "auto" as const, ...effectiveSearchParams }
761-
: effectiveSearchParams;
762-
763-
if (searchParams) {
764-
params.search_parameters = {
765-
mode: searchParams.mode ?? "auto",
766-
...(searchParams.max_search_results !== undefined && {
767-
max_search_results: searchParams.max_search_results,
768-
}),
769-
...(searchParams.from_date !== undefined && {
770-
from_date: searchParams.from_date,
771-
}),
772-
...(searchParams.to_date !== undefined && {
773-
to_date: searchParams.to_date,
774-
}),
775-
...(searchParams.return_citations !== undefined && {
776-
return_citations: searchParams.return_citations,
777-
}),
778-
...(searchParams.allowed_domains !== undefined && {
779-
allowed_domains: searchParams.allowed_domains,
780-
}),
781-
...(searchParams.excluded_domains !== undefined && {
782-
excluded_domains: searchParams.excluded_domains,
783-
}),
784-
};
785-
}
755+
if (mergedSearchParams) {
756+
params.search_parameters =
757+
buildSearchParametersPayload(mergedSearchParams);
786758
}
787759

788760
return params;
@@ -828,16 +800,11 @@ export class ChatXAI extends ChatOpenAICompletions<ChatXAICallOptions> {
828800
return msg;
829801
});
830802

831-
// Filter out xAI built-in tools from the standard tools array
832-
// Built-in tools are handled via search_parameters (added in invocationParams)
833803
let filteredTools: OpenAIClient.ChatCompletionTool[] | undefined;
834804
if (request.tools) {
835-
filteredTools = request.tools.filter(
836-
(tool) => !isXAIBuiltInTool(tool)
837-
) as OpenAIClient.ChatCompletionTool[];
838-
if (filteredTools.length === 0) {
839-
filteredTools = undefined;
840-
}
805+
filteredTools = filterXAIBuiltInTools(request.tools) as
806+
| OpenAIClient.ChatCompletionTool[]
807+
| undefined;
841808
}
842809

843810
const newRequest = {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from "./chat_models.js";
2+
export * from "./tools/live_search.js";

0 commit comments

Comments
 (0)