Skip to content

generateText(): generated text is empty when used with custom tool #6414

Open
@eyaldar

Description

@eyaldar

Description

Hi, I have a custom tool I wrote and when I'm trying to use it with generateText it return an empty text result.
my tool definition (assume I see the code is called and return result)

tool({
                description: ' "Use this tool to fetch real-time web data for queries that require the latest information, niche topics, local context, or when the answer may have changed since the model’s last update. This includes news, recent events, evolving technologies, up-to-date documentation, or anything likely not captured in training data. The tool supports issuing queries to a search engine and browsing the returned links to extract relevant content. ALWAYS PASS engine parameter with \'google\'',
                parameters: z.object({
                    query: z.string()
                }),
                // Function to execute the search
                execute: async ({ query }) => {
                    try {
                        console.log(`[SERPAPI] Searching for: ${query}`);
                        // Format our query for SerpAPI directly
                        const params = new URLSearchParams({
                            engine: 'google',
                            api_key: apiKey,
                            q: query,
                            output: 'json'
                        });
                        // Make the request to SERP API
                        const response = await fetch(`https://serpapi.com/search?${params.toString()}`);
                        if (!response.ok) {
                            throw new Error(`SERP API error: ${response.status} ${response.statusText}`);
                        }
                        const data = await response.json();
                        console.log(`[SERPAPI] Received response with ${data.organic_results?.length || 0} results`);
                        // Format the results to match our expected schema defined in constants.js
                        const results = data.organic_results?.map(result => ({
                            url: result.link,
                            snippet: result.snippet || result.title // Fallback to title if snippet isn't available
                        })) || [];
                        // Return in the format our app expects
                        return JSON.stringify({ results });
                    } catch (error) {
                        // Enhanced error logging with full stack trace
                        console.error('[SERPAPI] Search error:');
                        console.error(error);
                        console.error(error.stack);
                        return JSON.stringify({ results: [] }); // Return empty results on error to match schema
                    }
                }
            })

And this is my call:

import { openai } from '@ai-sdk/openai';
import { generateText, Output } from 'ai';
import { z } from 'zod';
const searchResultsSchema = z.object({
  results: z.array(z.object({
    url: z.string(),
    snippet: z.string()
  }))
});
const response = await generateText({
    model: openai.responses('gpt-4o-mini'),
    prompt: `${prompt}
    IMPORTANT: You must ALWAYS return this exact structure no matter what the tool returns.
    NEVER return raw markdown or the original tool response or empty strings.`,
    tools: {
      serpapi_search: /* the tool definition above  */
    },
    tool_choice: { type: 'tool', toolName: 'serpapi_search' },
    experimental_output: Output.object({ schema: searchResultsSchema }),
    debug: true,
    maxToolRoundTrip: 2,
    onStepFinish({ text, toolCalls, toolResults, finishReason, usage }) {
      // Log the tool results
    }
});
        // Safely access results, handling null/undefined
        let results = response.experimental_output?.results || [];

Adding the following parts work:

    maxToolRoundTrip: 1,
    maxSteps: 2,
...
    experimental_prepareStep: async ({ model, stepNumber, maxSteps, steps }) => {
      if (stepNumber > 0) {
        return {
          experimental_activeTools: [],
        };
      }
    },

Is there a better way to control how many times a tool is called? what am I doing wrong?

AI SDK Version

  • ai: 4.3.16
  • @ai-sdk/openai: 1.3.22

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions