Welcome to the WebSearchTool project! In this tutorial series, we are going to build a bridge that allows an AI (like Claude) to access the internet.
Imagine you have a brilliant assistant who is locked in a library with books from 2023. If you ask, "What is the stock price of Apple right now?", they can't answer. They need a phone to check the internet.
In this project, we are building that "phone." We call it the WebSearchTool.
In our code, a "Tool" is essentially a specialized contractor we hire to help the AI. For the AI to use this contractor effectively, we need a clear Job Description.
The Tool Definition tells the AI:
Before looking at code, let's look at the "Lifecycle"βthe step-by-step process that happens every time the AI wants to search the web.
call): The tool actually goes out to the internet and fetches data.Here is how the User, the AI, and our Tool interact:
Let's look at how we build this in TypeScript. We use a helper function called buildTool. This function wraps up all our logic into a neat package the system can understand.
First, we give the tool a name and a description. This is how the AI recognizes it.
import { buildTool } from '../../Tool.js'
import { WEB_SEARCH_TOOL_NAME } from './prompt.js'
export const WebSearchTool = buildTool({
name: WEB_SEARCH_TOOL_NAME, // Usually 'web_search'
userFacingName() {
return 'Web Search' // What the human sees in the UI
},
async description(input) {
// This tells the AI what is happening
return `Claude wants to search the web for: ${input.query}`
},
// ... continued below
Explanation:
name: The internal ID used by the code.userFacingName: A pretty name shown to the user in the interface.description: A dynamic description of what the tool is currently doing.The tool needs to know exactly what inputs it requires (like a search query) and what output it produces. We define these using "Schemas".
Note: We will dive deep into the specific code for this in Chapter 2: Data Contract (Schemas).
// ... inside buildTool
get inputSchema(): InputSchema {
return inputSchema() // Defines that we need a "query" string
},
get outputSchema(): OutputSchema {
return outputSchema() // Defines that we return a list of results
},
Explanation:
This acts like a contract. The tool promises: "If you give me data matching inputSchema, I will give you back data matching outputSchema."
Before we let the tool run, we must ensure the request is valid. We don't want to search for empty strings or prohibited domains.
async validateInput(input) {
const { query, allowed_domains, blocked_domains } = input
// Rule: We need a query to search!
if (!query.length) {
return {
result: false,
message: 'Error: Missing query',
errorCode: 1,
}
}
// ... (more validation logic)
return { result: true }
},
Explanation:
validateInput is the bouncer at the door. It checks the input before any expensive or dangerous code runs. If this returns false, the tool execution stops immediately.
call)
This is the heart of the tool. The call method is where the actual work happens.
Note: The actual searching logic involves streaming and prompting, which are covered in Chapter 3: Prompt Engineering Context and Chapter 4: Streaming Execution Strategy.
Here is a simplified view of the call method:
async call(input, context, _canUseTool, _parentMessage, onProgress) {
const startTime = performance.now()
const { query } = input
// 1. Create a message to send to the search subsystem
const userMessage = createUserMessage({
content: 'Perform a web search for the query: ' + query,
})
// 2. Run the search (Simplified for this chapter)
// This connects to the internet and gets results
const data = await performSearchLogic(userMessage, context, onProgress)
// 3. Return the package
return { data }
},
Explanation:
query.performSearchLogic) to actually browse the web.isEnabled)Finally, we need to decide if the tool should even appear as an option.
isEnabled() {
const provider = getAPIProvider() // e.g., 'firstParty', 'vertex'
// Example: Only enable for certain providers
if (provider === 'firstParty') {
return true
}
// Disable for others unless specific conditions are met
return false
},
Explanation:
The isEnabled function runs very early. If it returns false, the AI won't even know this tool exists. This is useful for feature flagging or restricting expensive tools to premium users.
You have now defined the "Contractor." You've given it a name, a description, rules for validation, and a method to execute the job.
However, simply defining the tool isn't enough. The AI needs to know exactly how to format the data it sends to the tool, and exactly how to read the data the tool sends back.
In the next chapter, we will look at how we enforce these rules strictly.
Next Chapter: Data Contract (Schemas)
Generated by Code IQ