{"slug": "give-your-laravel-ai-agent-real-time-web-knowledge", "title": "Give your Laravel AI agent real-time web knowledge", "summary": "A developer built a custom web research tool for the Laravel AI SDK to replace provider-native web search and fetch tools, which silently fail when a fallback provider lacks support. The solution uses the Tabstack API to return structured, cited reports, ensuring agents can browse the web reliably across any provider.", "body_md": "One of the best things about the Laravel AI SDK is that it doesn't lock you into a provider. Swap OpenAI for Anthropic, add Gemini as a failover, and your agent code barely changes. That's the entire pitch, and it mostly holds up.\n\nUntil your agent needs to browse the web.\n\nThe SDK ships two provider tools for giving agents access to the web: `WebSearch`\n\nand `WebFetch`\n\n. They're convenient, a couple lines and your agent can search or fetch a page. But they're not really part of the SDK's unified layer, they're implemented natively by whichever AI provider you're using, which means their availability depends entirely on which model you picked.\n\n`WebSearch`\n\nworks on Anthropic, OpenAI, Gemini, and OpenRouter. `WebFetch`\n\nonly works on Anthropic and Gemini. So if you configure a failover chain that includes Groq, DeepSeek, Mistral, or xAI, and one of those becomes the active provider, your agent quietly loses the ability to browse. Not an error, not a warning, just an agent that stops citing sources or stops noticing the page it was supposed to check even changed.\n\nThat's a rough thing to discover in production. You built a failover chain specifically so a rate limit or outage wouldn't take your feature down, and it turns out one of your fallback providers was silently missing a capability the whole time.\n\nSet the provider problem aside for a second. Even on a provider where `WebSearch`\n\nand `WebFetch`\n\nare both available, what you get back is raw: search results or fetched page content that the model has to reason over itself. There's no schema. There's no guaranteed citation tracking. If you want your agent to return a structured, sourced answer, that reasoning happens somewhere in the model's own inference, not in a layer you control or can test.\n\nFor a lot of use cases, that's fine. For anything where the answer needs to be defensible or reproducible, a research assistant, a competitive brief, anything a user might ask \"where did this come from,\" it's not enough.\n\nThe fix is to stop relying on provider-native web tools and give your agent a custom tool instead, one backed by an API rather than whichever model happens to be answering the prompt. That's exactly what a `Tool`\n\nclass in the Laravel AI SDK is for.\n\n`juststeveking/tabstack`\n\ngives you the client to build it on. Its `agent()->research()`\n\nmethod takes a question, searches multiple sources, synthesizes an answer, and streams back a report with citations, all as one call.\n\nSince the package doesn't ship Laravel integration out of the box, the first step is binding it in a service provider so it can be injected anywhere:\n\n``` php\n<?php\n\nnamespace App\\Providers;\n\nuse Illuminate\\Support\\ServiceProvider;\nuse JustSteveKing\\Tabstack\\Tabstack;\n\nclass AppServiceProvider extends ServiceProvider\n{\n    public function register(): void\n    {\n        $this->app->singleton(Tabstack::class, fn () => Tabstack::make(\n            apiKey: config('services.tabstack.key'),\n        ));\n    }\n}\n```\n\nAdd the key to `config/services.php`\n\n:\n\n``` js\n'tabstack' => [\n    'key' => env('TABSTACK_API_KEY'),\n],\n```\n\nNow the tool itself. This wraps `agent()->research()`\n\nand hands the agent back a report with its sources listed:\n\n``` php\n<?php\n\nnamespace App\\Ai\\Tools;\n\nuse Illuminate\\Contracts\\JsonSchema\\JsonSchema;\nuse JustSteveKing\\Tabstack\\Requests\\AgentResearch;\nuse JustSteveKing\\Tabstack\\Requests\\ResearchMode;\nuse JustSteveKing\\Tabstack\\Tabstack;\nuse Laravel\\Ai\\Contracts\\Tool;\nuse Laravel\\Ai\\Tools\\Request;\nuse Stringable;\n\nclass WebResearch implements Tool\n{\n    public function __construct(\n        private readonly Tabstack $tabstack,\n    ) {}\n\n    public function description(): Stringable|string\n    {\n        return 'Answers a question by researching multiple sources on the web and returns a synthesized, cited report. Use this when you need current information not in your training data.';\n    }\n\n    public function handle(Request $request): Stringable|string\n    {\n        $result = $this->tabstack->agent()->research(\n            params: new AgentResearch(\n                query: $request['query'],\n                mode: ResearchMode::Fast,\n            ),\n        )->result();\n\n        $sources = collect($result->metadata->get('citedPages', []))\n            ->map(fn ($page) => \"- {$page['title']}: {$page['url']}\")\n            ->implode(\"\\n\");\n\n        return \"{$result->report}\\n\\nSources:\\n{$sources}\";\n    }\n\n    public function schema(JsonSchema $schema): array\n    {\n        return [\n            'query' => $schema->string()\n                ->required()\n                ->description('The question to research'),\n        ];\n    }\n}\n```\n\nThe `->result()`\n\ncall blocks until the stream finishes and hands back a typed `ResearchResult`\n\nrather than making you consume events yourself, which keeps the tool's `handle`\n\nmethod simple. If you'd rather stream progress back to the agent as it happens, the client also gives you `->each()`\n\nand raw iteration, but for a tool call the blocking result is usually what you want.\n\nWire it into any agent through the `tools`\n\nmethod, same as any other tool:\n\n``` php\n<?php\n\nnamespace App\\Ai\\Agents;\n\nuse App\\Ai\\Tools\\WebResearch;\nuse Laravel\\Ai\\Contracts\\Agent;\nuse Laravel\\Ai\\Contracts\\HasTools;\nuse Laravel\\Ai\\Promptable;\n\nclass MarketAnalyst implements Agent, HasTools\n{\n    use Promptable;\n\n    public function instructions(): string\n    {\n        return 'You help analyze markets and competitors. Use the research tool whenever you need current information. Always mention your sources.';\n    }\n\n    public function tools(): iterable\n    {\n        return [\n            app(WebResearch::class),\n        ];\n    }\n}\n```\n\nNow switch providers all you want:\n\n``` php\n$response = (new MarketAnalyst)->prompt(\n    'What are the current pricing trends for cloud browser automation APIs?',\n    provider: [Lab::OpenAI, Lab::Anthropic, Lab::Groq],\n);\n```\n\nThe agent's ability to research the web doesn't depend on which of those three ends up answering the prompt. The tool is yours, it runs the same regardless of which model called it.\n\nYou could build something similar with `WebFetch`\n\non a supported provider, pointing it at a specific known URL. But that only works when you already know which page has the answer. `/research`\n\nis built for the opposite case, a question with no known source, where the value is in picking the right sources, reading them, and synthesizing a single answer with the receipts attached. That's a genuinely different job than fetching one page, and it's the piece the SDK's built-in tools don't attempt.\n\nThe Laravel AI SDK's provider independence is one of its best features, right up until you reach for a tool that isn't actually part of that independence. Web access shouldn't be something your agent loses depending on which model answered the prompt. Building it as a real `Tool`\n\nclass backed by an API, rather than leaning on whatever the current provider happens to expose, keeps that promise intact.\n\nIf you're building agents with the Laravel AI SDK and want this kind of tool ready to go, [juststeveking/tabstack on Packagist](https://packagist.org/packages/juststeveking/tabstack) is where I'd start.", "url": "https://wpnews.pro/news/give-your-laravel-ai-agent-real-time-web-knowledge", "canonical_source": "https://dev.to/juststevemcd/give-your-laravel-ai-agent-real-time-web-knowledge-478h", "published_at": "2026-07-16 18:50:58+00:00", "updated_at": "2026-07-16 19:02:38.197963+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents", "large-language-models"], "entities": ["Laravel AI SDK", "OpenAI", "Anthropic", "Gemini", "OpenRouter", "Groq", "DeepSeek", "Mistral"], "alternates": {"html": "https://wpnews.pro/news/give-your-laravel-ai-agent-real-time-web-knowledge", "markdown": "https://wpnews.pro/news/give-your-laravel-ai-agent-real-time-web-knowledge.md", "text": "https://wpnews.pro/news/give-your-laravel-ai-agent-real-time-web-knowledge.txt", "jsonld": "https://wpnews.pro/news/give-your-laravel-ai-agent-real-time-web-knowledge.jsonld"}}