Fixing tool-happy function call over-use on AI on latest models - technique and investigation

(This can’t be done completely in assistants. I show chat completions.)

Both gpt-3.5-turbo and gpt-4 of the latest variety have a problem with calling unwanted functions that have hallucinations because a function would be of no use to fulfilling the user input. This started a few weeks ago.

This is especially brought out when asking for multiple similar answers, but can happen most any time, especially with 0-shot input.

I pondered what could cause this. I suspect it is a softmax issue, where the first four logprobs form a parallelogram of blocking in embedding space, or simply that providing uncertainty in how output should begin allows “start a function” token to rank high. We are blocked from doing research because of OpenAI’s limitations on exposing token numbers related to or within function output and unseen methods being used.


The solution is to make output to a user extremely distinct and unique from a function, so the AI basically has just two choices of the first thing to generate.

This can be something like an enforced prompted json container of user output, but this can impact the quality of response more than we’d desire.

My solution:

assistant response always starts with %%%%, which is token 5434. We then multi-shot the AI to show this.

  "messages": [
    {
        "role": "system", "content": """
You are ChatGPT, a large language model trained by OpenAI, based on the GPT-4 architecture.
Knowledge cutoff: 2023-04
Current date: 2024-02-13

All assistant responses begin with '%%%%'.
""".strip()},
    {
        "role": "user", "content": "Hello?"
    },
    {
        "role": "assistant", "content": "%%%%Hi! How can I help you today?"
    },
    {
        "role": "user", "content": "What is the capital of France? What is the capital of Germany?"
    },

    ],
   "tools": toolspec,

This would have called completely useless multi-tool functions without.


More oddities affecting developers that are out of their control.

How likely the AI is to respond to a user and not a function you would think would be in our control:


  "logit_bias": {
        5434: 3,  # the token for %%%%
    },

But set that to 100 against a question where a “random” function would just have mild usefulness. The AI is still trying to call a function, and filling the function with %%%% gets a 500 server error. Even using gpt-3.5-turbo-0125.

Set that to 20, against the same question (pick from a list), and still a function, even though the AI can no longer write:

{
  "id": "call_jBq8lmyQ8kFAkehIs54ctYi7",
  "type": "function",
  "function": {
    "name": "get_random_int",
    "arguments": "{\"%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"
  }
}

OpenAI has something beyond logprob that is affecting functions. Logprobs are also useless for fixing other types of problems, like those within response_format.


OpenAI: Give us the control. An API parameter tool_bias: 3 — because you broke the AI otherwise.

2 Likes

Would tool_choice: "none" do the trick? Source

It can be described as tool_choice: no effect.

Here’s one iteration I just re-ran, showing all varieties of stuff I previously hit the AI with, killing off what it could write except the most-certain token for a user (logits found with similar prompts that passed), and breaking function call parsing.

params = {
  "model": "gpt-3.5-turbo-0125",
  "tools": toolspec,
  #"tool_choice": {"type": "function", "function": {"name": "parallel_function_call"}},
  "tool_choice": None,
  "max_tokens": 90,
  "top_p":0.0001,
  #"logprobs": True,
  #"top_logprobs": 5,
  "logit_bias": {334:-100,  # "**"
                 16067:-100,  #"\u200b" zero width space
                 30848:-100,   #" \u200b" zero width space
                 73463:-100,  #" \u200b\u200b" two zero width space
                 88996:-100,  #"\u200b\u200b" two zero width space
                 90464:-100,  #"\u200c" zw joiner
                 12:-100,  # "-"
                 311:-100,    # " to"
                 28:0,     # "="
                 998:0,     # "to" - no effect unless 311 
                 5018:0,       # {"
                 517:-100,  #{
                 27364:0,   # "multi"
                 23627:0,   # "_tool
                 16330:0,  # "_use
                 47203:0,  # "parallel"
                 15638:0,  # " parallel"
                 72557:0,  # ".parallel"
                 60704:00, #Paris
                 578:-100,  # The
                 },

Regardless of all these biases, the parameter you cite, and being unable to send to tool recipient properly, the AI still can’t help but writing to the functions that would be useless for answering. Then has justify the use:

"message": {
"role": "assistant",
"content": "{\"tool_uses\":[{\"recipient_name\":\"functions.get_random_int\",\"parameters\":{\"range_start\":0,\"range_end\":1}},{\"recipient_name\":\"functions.get_random_int\",\"parameters\":{\"range_start\":0,\"range_end\":1}}]}\nI generated random numbers, and based on that:\n\nThe capital of France is Paris.\n\nThe capital of Germany is Berlin."

That initial streaming chunk with no content? Could it be one token that we specifically can’t affect and is not sent, of origin outside the normal completions process, that has incredible training weight plus its probable triggering of a json-mode?


What else can we do when the AI architecture can’t be stopped from functions? Add one more.

([{
        "type": "function",
        "function": {
            "name": "message_to_user",
            "description": "A response direct to the user. Is placed as an escape for any inappropriate function call",
            "parameters": {
                "type": "object",
                "properties": {
                    "response": {
                        "type": "string",
                        "description": "The full normal response to a user",
                    },
                },
                "required": ["response"]
            },
        }
    }]
)

Which still surprisingly is a toss-up if it gets used…and we get two function calls each with an answer.

Reminder: This was an issue reported three weeks ago by a user, easily able to be verified in diverse 0-shot examples and system prompts, and unfixed when gpt-3.5-turbo-0125 was released.

1 Like

Yeah I see the problem, this need to be fixed, there’s absolutely no reason why the model should do this when it’s been told specifically not to do so🧐