Open AI Tool Call not working as expected

Hi,

I have a doubt regarding the OpenAI Chat Completion API’s tool calling functionality. I’ve configured a function within the tool call setup that performs a vector search for my application.

My expectation is:

  • The function should only be triggered when the user’s query is related to vector search.
  • For general messages like greetings, “thank you”, or casual conversation, the function should not be called.

I’ve set tool_choice to "auto" expecting OpenAI to decide whether to invoke the function based on the user’s input. But in my tests, it’s not working as expected.
When I tried setting tool_choice to "required", it ends up calling the function every time, even when it’s unnecessary.

Could you please guide me on how to make this work as intended? Ideally, I want the function to be called only when relevant, and for general queries, the assistant should reply without triggering the tool.

Pictured is the AI usefully deciding to employ a function “file_search” with a field called msearch, on the Chat Completions endpoint. It doesn’t make an out-of-domain search when the function clearly states what knowledge is behind the semantic search vector store.

There is no searching when the database is understood. There is desired searching when the database is understood.

Things in the database, the AI makes a tool call for functions, even thought the function style doesn’t tell the AI what to do, it just offers the resource.

You will note that there is no alignment or description where I have given this system message: You are Professor Q, a helpful AI expert. Answers are the briefest truthful statements that fulfill an input.

What is the magic? No magic. Just treating the function as also a sort of prompt, providing its usefulness and knowledge domain - something that OpenAIs own file search in Responses simply doesn’t allow you to do, nor to even align the usage so it is not “user uploads”. Your function is clearly the better choice when you make its functions and its returns known.

Cheat Sheet

[ ] I agree that free consultation services are a nice thing..

Function item for chat completions, JSON

{
  "name": "file_search",
  "description": "Accepts an array of unique query terms for a semantic search in a knowledge database related to crop and soil sciences, agriculture, and farming practices.",
  "strict": true,
  "parameters": {
    "type": "object",
    "required": [
      "msearch"
    ],
    "properties": {
      "msearch": {
        "type": "array",
        "description": "Array of unique query terms for semantic search (hypothetical answers). Each term should be written in a manner similar to how you guess a section of a document would answer related questions.",
        "items": {
          "type": "string",
          "description": "A unique hypothetical answer term related to crop and soil sciences, agriculture, or farming practices."
        }
      }
    },
    "additionalProperties": false
  }
}

Thanks for your reply.

This is my code:

import { NextResponse } from 'next/server';
import OpenAI from 'openai';
import { logger, LOGLEVELS } from "@/lib/logger";
import { decrypt } from "@/utils/encryption";

const tools: OpenAI.ChatCompletionTool[] = [
    {
        type: "function",
        function: {
            name: "performVectorSearch",
            description: "Perform a vector similarity search to find relevant documents for a given query.",
            parameters: {
                type: "object",
                properties: {
                    text: {
                        type: "string",
                        description: "The query text to search for.",
                    },
                },
                required: ["text"],
            },
        },
    },
];

export async function POST(request: Request) {
    const { messages, preset, shouldStream } = await request.json();

    if (!messages || !Array.isArray(messages)) {
        return NextResponse.json(
            { error: 'Invalid messages format' },
            { status: 400 }
        );
    }  
   
    const openai = new OpenAI({
                baseURL: '',
                apiKey: api_key
                defaultHeaders: {'Content-Type': 'application/json'}
    });

    try {
       
            // Non-streaming mode
            const completion = await openai.chat.completions.create({
                messages: messages.map(msg => ({
                    role: msg.role,
                    content: msg.content
                })),
                model: model,
                temperature: parseFloat(temperature),
                max_tokens: parseInt(max_tokens),
                tools: tools,
                tool_choice: "auto", // Let the model decide whether to use the tool
            });

            console.log("performVectorSearch called 1Response", completion);

            // Handle tool calls
            const responseMessage = completion.choices[0].message;
            console.log("performVectorSearch called 2Response", responseMessage);
            if (responseMessage.tool_calls) {
                const toolCall = responseMessage.tool_calls[0];
                const { name, arguments: args } = toolCall.function;
                console.log("performVectorSearch called", name);
                if (name === "performVectorSearch") {
                    const { text } = JSON.parse(args);
                    console.log("performVectorSearch called", text);
                    const context = await performVectorSearch(text); // Call the tool

                    // Send the tool result back to the model
                    const secondResponse = await openai.chat.completions.create({
                        messages: [
                            ...messages,
                            responseMessage,
                            {
                                role: "tool",
                                name: "performVectorSearch",
                                content: context,
                            },
                        ],
                        model: model,
                        temperature: parseFloat(temperature),
                        max_tokens: parseInt(max_tokens),
                    });
                    console.log("performVectorSearch called secondResponse", secondResponse.choices[0].message.content);
                    return NextResponse.json({
                        text: secondResponse.choices[0].message.content,
                    });
                }
            }

            // If no tool is called, return the model's response directly
            if (!responseMessage.content)
                throw new Error('No response content from API');

            return NextResponse.json({
                text: responseMessage.content,
            });
        
    } catch (error) {
        const errorMessage =
            error instanceof Error ? error.message : "Failed to generate chat response";
        logger.log(
            LOGLEVELS.ERROR,
            `Error in /api/chat route: ${errorMessage}`
        );
        return NextResponse.json(
            { error: errorMessage },
            { status: 500 }
        );
    }
}


async function performVectorSearch(text: string): Promise<string> {
    const embedding = await generateEmbedding(text);
    if (!embedding) return "";

    const response = await fetch(`${APIURL}/api/search`, {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
        },
        body: JSON.stringify({ embedding }),
    });

    const data = await response.json();

    if (!response.ok) {
        console.error("Search API error:", data.error || response.status);
        return "";
    }

    if (!data.searchResults || data.searchResults.length === 0) {
        console.log("No search results found for the query");
        return "";
    }

    const context = data.searchResults.map((doc) => doc.content).join("\n\n");

    // return context;
    console.log(context)
    return `Context for: ${context}`;
}

async function generateEmbedding(content: string): Promise<number[]> {
    const response = await fetch(`${APIURL}/api/embedding`, {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
        },
        body: JSON.stringify({ content }),
    });

    const data = await response.json();

    if (!response.ok) {
        console.error("Embedding API error:", data.error || response.status);
        return null;
    }

    if (!data.embedding) {
        console.error("No embedding received from server");
        return null;
    }

    return data.embedding;
}

Prompt:

You are Chris, a professional sales representative of my company. Your role is to naturally and engagingly assist users with my company product and policies.  
        ### 🔹 **Your Style & Approach:**  
        **Be friendly & conversational.** Use contractions, avoid robotic wording. 
        **Keep answers simple, engaging, and direct.**              
        **Stick to company policies.** Never answer unrelated questions (e.g., sports, movies, history).  
        **If the user keeps asking off-topic questions, stay polite but firm.**         

I’m not sure what’s going wrong—it’s not working as expected on my end.
Could you help me identify the issue?
Do I need to make any changes to my code or the prompt?

This offers the AI nothing about what the function is actually useful for:

Follow the tone of the function description I provided in the expandable block, along with providing informative key names, and thusly sculpt the function to be purposeful and performative in offering the AI a tool to employ in knowable situations.

The AI reads what you write.