This guide wires a Trawley scraper into an agent built with the
Vercel AI SDK. The model gets one tool, searchListings,
that calls Trawley's hybrid search endpoint.
When a user asks a question about your data, the model calls the tool, reads the
structured results, and answers.
Prerequisites
- A Trawley scraper that has completed at least one run, and its scraper ID.
- An AI SDK project with a model provider configured (this example uses
@ai-sdk/openai, but any provider works).
npm install ai @ai-sdk/openai zod
Define the tool
The tool has one input, query, and calls the hybrid endpoint inside execute.
import { tool } from 'ai'
import { z } from 'zod'
const TRAWLEY_SCRAPER_ID = 'scr_8f2a1c9e'
export const searchListings = tool({
description:
'Search live property listings from acmehomes.co.uk. Accepts a natural ' +
'language query; constraints like price, bedrooms, or date are understood. ' +
'Returns structured records. Use whenever the user asks about specific ' +
'listings, availability, or current data.',
inputSchema: z.object({
query: z
.string()
.describe('A natural language description of what to find, e.g. "3 bed houses under £500k with a garden"'),
}),
execute: async ({ query }) => {
const params = new URLSearchParams({ search: query, take: '10' })
const res = await fetch(
`https://api.trawley.ai/v1/scrapers/${TRAWLEY_SCRAPER_ID}/hybrid?${params}`,
)
if (!res.ok) {
return { error: `Search failed with status ${res.status}` }
}
const { data } = await res.json()
return { results: data }
},
})
The AI SDK uses inputSchema (not parameters) to describe a tool's input.
The describe() text on each field is sent to the model, so spend a sentence
making it clear.
Give it to the model
Pass the tool to generateText and let the model run. stopWhen: stepCountIs(5)
lets the model call the tool and then continue to a final answer in the same
call.
import { generateText, stepCountIs } from 'ai'
import { openai } from '@ai-sdk/openai'
import { searchListings } from './search-listings'
const { text } = await generateText({
model: openai('gpt-4.1'),
tools: { searchListings },
stopWhen: stepCountIs(5),
prompt: 'Find me a 3 bedroom house near Kendal with a garden under £500k.',
})
console.log(text)
What happens
The model reads the request
It sees the searchListings tool and decides the question needs live data.
It calls the tool
The model generates a query argument such as "3 bedroom house Kendal garden under 500000". Your execute function calls hybrid search.
Trawley returns structured records
The data array comes back as the tool result and is handed to the model.
The model answers
It uses the records to write a grounded reply, citing real prices and locations.
Streaming to a UI
For a chat interface, swap generateText for streamText and return a UI
message stream. The tool definition is identical.
import { streamText, stepCountIs } from 'ai'
import { openai } from '@ai-sdk/openai'
import { searchListings } from './search-listings'
const result = streamText({
model: openai('gpt-4.1'),
tools: { searchListings },
stopWhen: stepCountIs(5),
messages,
})
return result.toUIMessageStreamResponse()
Tips
- Return less, not more. Trim each record to the fields the model needs. Smaller tool results mean cheaper, faster, more focused answers.
- Let hybrid search do the filtering. Do not add a tool input per filter.
One
querystring covers price, bedrooms, dates, and location because the endpoint interprets them. See tools overview. - Handle the empty case. If a scraper has no completed run,
datais empty. Returning{ results: [] }lets the model tell the user nothing was found.