Web search
Web search lets a model retrieve current information with a provider-hosted tool. Add a WebSearchTool to the request; the provider executes the search and the SDK returns the answer as text with normalized Citation metadata.
Configure web search
Section titled “Configure web search”interface WebSearchTool { type: "web_search"; /** * Restricts search results to these domains when supported by the provider. */ allowed_domains?: string[]; /** * An approximate user location used to localize web search results. */ user_location?: WebSearchUserLocation;}
interface WebSearchUserLocation { /** * The city of the user. */ city?: string; /** * The region or state of the user. */ region?: string; /** * The two-letter ISO 3166-1 country code of the user. */ country?: string; /** * The IANA timezone of the user. */ timezone?: string;}pub struct WebSearchTool { /// Restricts search results to these domains when supported by the /// provider. #[serde(skip_serializing_if = "Option::is_none")] pub allowed_domains: Option<Vec<String>>, /// An approximate user location used to localize web search results. #[serde(skip_serializing_if = "Option::is_none")] pub user_location: Option<WebSearchUserLocation>,}
pub struct WebSearchUserLocation { /// The city of the user. #[serde(skip_serializing_if = "Option::is_none")] pub city: Option<String>, /// The region or state of the user. #[serde(skip_serializing_if = "Option::is_none")] pub region: Option<String>, /// The two-letter ISO 3166-1 country code of the user. #[serde(skip_serializing_if = "Option::is_none")] pub country: Option<String>, /// The IANA timezone of the user. #[serde(skip_serializing_if = "Option::is_none")] pub timezone: Option<String>,}type WebSearchTool struct { // Restricts search results to these domains when supported by the provider. AllowedDomains []string `json:"allowed_domains,omitempty"` // An approximate user location used to localize web search results. UserLocation *WebSearchUserLocation `json:"user_location,omitempty"`}
type WebSearchUserLocation struct { // The city of the user. City *string `json:"city,omitempty"` // The region or state of the user. Region *string `json:"region,omitempty"` // The two-letter ISO 3166-1 country code of the user. Country *string `json:"country,omitempty"` // The IANA timezone of the user. Timezone *string `json:"timezone,omitempty"`}allowed_domains restricts results to specific domains, while user_location provides an approximate location for localized results. OpenAI and Anthropic support both options. Google supports the base web search tool and rejects these options rather than silently ignoring them.
Web search is supported by OpenAI Responses, Anthropic Messages, and Google GenerateContent. It is not supported by the OpenAI Chat Completions, Cohere V2 Chat, or Mistral Chat Completions adapters.
Generate with web search
Section titled “Generate with web search”The example defaults to OpenAI. Set PROVIDER and MODEL to run the same code with another supported provider and model.
import { getModel } from "./get-model.ts";
const provider = process.env["PROVIDER"] ?? "openai";const modelId = process.env["MODEL"] ?? "gpt-5.6-sol";const model = getModel(provider, modelId);
const response = await model.generate({ messages: [ { role: "user", content: [ { type: "text", text: "Use web search to find the official IANA page about reserved domains. Reply with one sentence containing the word IANA and cite the source.", }, ], }, ], tools: [{ type: "web_search" }],});
console.dir(response.content, { depth: null });use dotenvy::dotenv;use llm_sdk::{LanguageModelInput, Message, Part, WebSearchTool};
mod common;
#[tokio::main]async fn main() { dotenv().ok();
let provider = std::env::var("PROVIDER").unwrap_or_else(|_| "openai".to_string()); let model_id = std::env::var("MODEL").unwrap_or_else(|_| "gpt-5.6-sol".to_string()); let model = common::get_model(&provider, &model_id);
let response = model .generate( LanguageModelInput::new([Message::user([Part::text( "Use web search to find the official IANA page about reserved domains. Reply with \ one sentence containing the word IANA and cite the source.", )])]) .with_tools([WebSearchTool::new()]), ) .await .unwrap();
println!("{:#?}", response.content);}package main
import ( "context" "log" "os"
llmsdk "github.com/hoangvvo/llm-sdk/sdk-go" "github.com/hoangvvo/llm-sdk/sdk-go/examples" "github.com/sanity-io/litter")
func main() { provider := os.Getenv("PROVIDER") if provider == "" { provider = "openai" } modelID := os.Getenv("MODEL") if modelID == "" { modelID = "gpt-5.6-sol" } model := examples.GetModel(provider, modelID)
response, err := model.Generate( context.Background(), llmsdk.NewLanguageModelInput( []llmsdk.Message{ llmsdk.NewUserMessage( llmsdk.NewTextPart("Use web search to find the official IANA page about reserved domains. Reply with one sentence containing the word IANA and cite the source."), ), }, llmsdk.WithInputTools(llmsdk.NewWebSearchTool()), ), ) if err != nil { log.Fatalf("Generation failed: %v", err) }
litter.Dump(response.Content)}Stream with web search
Section titled “Stream with web search”Streaming returns citation deltas with the text deltas. The accumulator combines them into citations on the final text parts.
import { StreamAccumulator } from "@hoangvvo/llm-sdk";import { getModel } from "./get-model.ts";
const provider = process.env["PROVIDER"] ?? "openai";const modelId = process.env["MODEL"] ?? "gpt-5.6-sol";const model = getModel(provider, modelId);
const stream = model.stream({ messages: [ { role: "user", content: [ { type: "text", text: "Use web search to find the official IANA page about reserved domains. Reply with one sentence containing the word IANA and cite the source.", }, ], }, ], tools: [{ type: "web_search" }],});
const accumulator = new StreamAccumulator();
let current = await stream.next();while (!current.done) { console.dir(current.value, { depth: null }); accumulator.addPartial(current.value); current = await stream.next();}
const response = accumulator.computeResponse();console.dir(response.content, { depth: null });use dotenvy::dotenv;use futures::stream::StreamExt;use llm_sdk::{LanguageModelInput, Message, Part, StreamAccumulator, WebSearchTool};
mod common;
#[tokio::main]async fn main() { dotenv().ok();
let provider = std::env::var("PROVIDER").unwrap_or_else(|_| "openai".to_string()); let model_id = std::env::var("MODEL").unwrap_or_else(|_| "gpt-5.6-sol".to_string()); let model = common::get_model(&provider, &model_id);
let mut stream = model .stream( LanguageModelInput::new([Message::user([Part::text( "Use web search to find the official IANA page about reserved domains. Reply with \ one sentence containing the word IANA and cite the source.", )])]) .with_tools([WebSearchTool::new()]), ) .await .unwrap();
let mut accumulator = StreamAccumulator::new();
while let Some(partial) = stream.next().await { let partial = partial.unwrap(); println!("{partial:#?}"); accumulator.add_partial(partial).unwrap(); }
let response = accumulator.compute_response().unwrap(); println!("{:#?}", response.content);}package main
import ( "context" "log" "os"
llmsdk "github.com/hoangvvo/llm-sdk/sdk-go" "github.com/hoangvvo/llm-sdk/sdk-go/examples" "github.com/sanity-io/litter")
func main() { provider := os.Getenv("PROVIDER") if provider == "" { provider = "openai" } modelID := os.Getenv("MODEL") if modelID == "" { modelID = "gpt-5.6-sol" } model := examples.GetModel(provider, modelID)
stream, err := model.Stream( context.Background(), llmsdk.NewLanguageModelInput( []llmsdk.Message{ llmsdk.NewUserMessage( llmsdk.NewTextPart("Use web search to find the official IANA page about reserved domains. Reply with one sentence containing the word IANA and cite the source."), ), }, llmsdk.WithInputTools(llmsdk.NewWebSearchTool()), ), ) if err != nil { log.Fatalf("Stream failed: %v", err) }
accumulator := llmsdk.NewStreamAccumulator()
for stream.Next() { current := stream.Current() litter.Dump(current)
if err := accumulator.AddPartial(*current); err != nil { log.Printf("Failed to add partial: %v", err) } }
if err := stream.Err(); err != nil { log.Fatalf("Stream error: %v", err) }
response, err := accumulator.ComputeResponse() if err != nil { log.Fatalf("Failed to compute response: %v", err) }
litter.Dump(response.Content)}