Automation & Agents

AI Agent "Empty Response" Debugging Checklist: 5 Steps from Symptom to Root Cause

A model that can chat is not necessarily a model that can call tools. When you see an empty response, zero tool calls and no error message, swap the model first and edit the prompt second, and use an isolation test that bypasses the framework to split the variables in one move.

10/13/2026 | 6 min read | For anyone who has built an AI agent — about 20 minutes to find whether the model or your setup is at fault

The one idea that matters (and the most counterintuitive)

A model that can chat is not necessarily a model that can call tools. They are two different abilities.

The common instinct is: the model replies normally, so the model is fine, so my prompt or config must be broken. That inference is wrong. The same model can:

  • chat fluently ✅
  • call “read-only” tools correctly ✅
  • but return nothing when it has to produce a “run a command” tool call ❌

So when you see the symptom set below, the first thing to swap is the model, not the prompt:

Empty response (no text and no reasoning content) + tool call count of 0 + no error or rate-limit message in the log

Do it in the opposite order and you will spend hours editing a prompt that was correct all along. This applies to Hermes, LangChain, AutoGen or a hand-written agent loop.

Step by step

Step 1: Make sure you are looking at the current config

After editing a config file, always restart the service. A long-running agent (gateway, daemon, background service) reads its config and prompt into memory once and never rereads the file. Compare the service start time with the config file’s last-modified time. If the service started earlier, it is running the old version.

Step 2: Read the log and grab three numbers

Do not look at the chat window. Look at the log:

  1. How many API calls this turn made
  2. Tool call count (tool_turns or similar), the most important one
  3. Any 429 or other error code

How to read it: tool call count = 0 means the model never issued a tool call, so the question is whether the model is willing or able to produce one. A 429 is rate limiting, which is a different problem (rate limits tell you loudly). No error but an empty response means keep going.

Step 3: Confirm the tool is really enabled

Most frameworks have a command that lists the enabled tools. Check that the tool you think is there is really in the list. It takes 30 seconds and removes the silly causes, so the later reasoning stands on solid ground.

Step 4: Run the isolation test (the deciding step)

Goal: cut “framework problem” from “model problem” in one stroke. Method: bypass your whole agent framework, call the model API with a raw HTTP request, and send the identical system prompt and tool definition with the identical question.

Python skeleton (OpenAI-compatible format as an example; for other vendors change the URL and field names):

import json, os, urllib.request

API_KEY = os.environ["YOUR_API_KEY"]        # read from an env var, never hard-code it
SYSTEM_PROMPT = open("your_system_prompt.md", encoding="utf-8").read()

# Use just one simple tool: copy the definition of the one that "breaks" in your framework
TOOLS = [{
    "type": "function",
    "function": {
        "name": "terminal",
        "description": "Execute a shell command and return its output.",
        "parameters": {
            "type": "object",
            "properties": {"command": {"type": "string"}},
            "required": ["command"],
        },
    },
}]

body = {
    "model": "your-model-name",
    "messages": [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": "Run whoami with the terminal tool and give me the result"},
    ],
    "tools": TOOLS,
}

req = urllib.request.Request(
    "https://your-model-endpoint/v1/chat/completions",
    data=json.dumps(body).encode("utf-8"),
    headers={"Content-Type": "application/json",
             "Authorization": f"Bearer {API_KEY}"},
)
with urllib.request.urlopen(req, timeout=60) as r:
    d = json.loads(r.read().decode("utf-8"))

# The key part: is there a tool_calls field, and what is finish_reason
msg = d["choices"][0]["message"]
print("finish_reason:", d["choices"][0].get("finish_reason"))
print("has tool call:", "tool_calls" in msg and bool(msg["tool_calls"]))
print(json.dumps(msg, ensure_ascii=False, indent=1)[:800])

How to read the result:

Raw API result Conclusion Next step
Returns tool_calls normally Your prompt is not at fault; the problem is the framework or model combination Swap the model (Step 5)
Also empty, no tool_calls The problem is the prompt or the tool definition Simplify the prompt, check the tool schema
Returns a 4xx error Auth, format or quota problem Follow the error message; it is usually clear

The value of the isolation test: whichever way it comes out, you rule out half of the possibilities at once. The trouble with tweaking the prompt one piece at a time is that every change alters several variables, so even if the symptom improves you do not know why.

Step 5: Swap the model, then rerun your acceptance tests

If Step 4 shows the model can produce tool calls but the framework still fails, try another model. Prefer the newer generation of the same family (best compatibility, usually a one-line config change). After swapping, restart the service (back to Step 1) and rerun the acceptance tests.

Traps you will hit

Trap 1: You edited the prompt but nothing changed, because the service was not restarted

You will think the new rule does not work when it was never loaded. Restart any long-running service after every config change until it is a reflex.

Trap 2: Old and new rules fight inside the same prompt

The middle of a system prompt says “answer status questions yourself,” while the end (a newer rule) says “delegate status questions to another tool.” The model reads the first one, answers itself, and the new rule never takes effect. Lesson: when you add rules to a long prompt, scan back for old clauses that contradict them. Also, a new rule placed near the top works better than one at the end, because weaker models follow the opening much more reliably.

Trap 3: Your test question cannot verify anything

One acceptance question asked “where do your behavior rules live?”, but the prompt never defined how to answer that. A wrong answer was not a violation, so the question tested nothing. Lesson: every acceptance question must map to an explicit clause in the spec. If there is no clause, write the clause first and then write the question.

Trap 4: Mistaking rate limiting for an empty response

  • Rate limiting (429): the error is explicit and tells you the limit and how long to wait.
  • Empty response: no error message at all, a perfectly clean log.

A 429 is a quota problem, so do not chase model capability; and the reverse is true too.

Appendix: two quota ideas many people get wrong

1. RPM is a rate, not a total. RPM means Requests Per Minute. “Limit 5” means at most 5 calls within the same minute; it resets every minute and never runs out. Before you rush to pay because a number looks small, check whether the unit is per minute or per day.

2. RPM is usually counted per model. So you can set up a fallback chain: when the main model hits its limit, requests automatically go to another model. Each model has its own quota, so you effectively double the usable volume for free. Always test it: hit the main model with concurrent requests until it is limited, immediately ask again, and confirm the backup really takes over. A correct-looking config file is not proof.

⚠️ Before adding a new model to the fallback chain, verify with the Step 4 script that it can produce tool calls. Otherwise you have added a backup that chats but does nothing, and the next failure will be even harder to trace.

Rescue prompt: what to ask an AI when you are stuck

If you get stuck on any step above, copy this into an AI assistant (ChatGPT or Claude both work) and replace the brackets with your situation:

I am debugging an AI agent. It replies normally but does not perform any action.

My environment:
- Framework/tool: [e.g. Hermes / LangChain / self-written]
- Model: [model name]
- OS: [Windows 11 / macOS / Ubuntu]

Symptom:
[Paste the full log excerpt including the original error text. Do not rewrite it.]

What I have tried:
[List what you did]

Walk me through the investigation one step at a time. Give me only one command
or one check each time and wait for my result before the next step.
I am a beginner, so explain what each command does.

The key points: paste the log verbatim, because every word in an error message is a clue; and ask the AI for “one step at a time,” otherwise it hands you ten commands and you never learn which one fixed it (the same idea as the isolation test).

One-line summary

For a failure with no error message, investigate with an experiment that splits the variables in one move, not by tweaking suspects one at a time. For an AI agent, that experiment is “bypass the framework and call the raw API once.”

Support this work:Support on Ko-fi