{"message": "tried to call AI function `execute_we...
# ask-ai
w
{"message": "tried to call AI function
execute_webhook
with invalid arguments\nTraceback (most recent call last):\n File \"/home/ec2-user/livekit-agent/venv/lib64/python3.11/site-packages/livekit/agents/voice/generation.py\", line 493, in _execute_tools_task\n fnc_args, fnc_kwargs = llm_utils.prepare_function_arguments(\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/home/ec2-user/livekit-agent/venv/lib64/python3.11/site-packages/livekit/agents/llm/utils.py\", line 400, in prepare_function_arguments\n model = model_type.model_validate(args_dict) # can raise ValidationError\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/home/ec2-user/livekit-agent/venv/lib64/python3.11/site-packages/pydantic/main.py\", line 716, in model_validate\n return cls.__pydantic_validator__.validate_python(\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\npydantic_core._pydantic_core.ValidationError: 2 validation errors for ExecuteWebhookArgs\npayload\n Input should be a valid dictionary [type=dict_type, input_value='{\"date\": \"18/02/2026\"}', input_type=str]\n For further information visit https://errors.pydantic.dev/2.12/v/dict_type\nheaders\n Input should be a valid dictionary [type=dict_type, input_value='{}', input_type=str]\n For further information visit https://errors.pydantic.dev/2.12/v/dict_type", "level": "ERROR", "name": "livekit.agents", "exc_info": "Traceback (most recent call last):\n File \"/home/ec2-user/livekit-agent/venv/lib64/python3.11/site-packages/livekit/agents/voice/generation.py\", line 493, in _execute_tools_task\n fnc_args, fnc_kwargs = llm_utils.prepare_function_arguments(\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/home/ec2-user/livekit-agent/venv/lib64/python3.11/site-packages/livekit/agents/llm/utils.py\", line 400, in prepare_function_arguments\n model = model_type.model_validate(args_dict) # can raise ValidationError\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/home/ec2-user/livekit-agent/venv/lib64/python3.11/site-packages/pydantic/main.py\", line 716, in model_validate\n return cls.__pydantic_validator__.validate_python(\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\npydantic_core._pydantic_core.ValidationError: 2 validation errors for ExecuteWebhookArgs\npayload\n Input should be a valid dictionary [type=dict_type, input_value='{\"date\": \"18/02/2026\"}', input_type=str]\n For further information visit https://errors.pydantic.dev/2.12/v/dict_type\nheaders\n Input should be a valid dictionary [type=dict_type, input_value='{}', input_type=str]\n For further information visit https://errors.pydantic.dev/2.12/v/dict_type", "function": "execute_webhook", "arguments": "{\"body\": \"{\\\"date\\\": \\\"18/02/2026\\\"}\", \"headers\": \"{}\", \"method\": \"POST\", \"url\": \"https://ai.goaicrm.com/api/crm/ai/webhook/gbusybydate\", \"payload\": \"{\\\"date\\\": \\\"18/02/2026\\\"}\"}", "speech_id": "speech_856c1a3a46ad", "pid": 1611536, "job_id": "AJ_5MHrybDBhR6j", "room_id": "RM_zegBCzL7AYAX", "timestamp": "2026-02-17T053152.462471+00:00"}
t
Thanks for reaching out, Sujata. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I see you're getting a Pydantic ValidationError because
execute_webhook
received JSON strings instead of Python dictionaries for
payload
and
headers
. I’ll explain what’s happening and give clear options to fix it. What failed • The function was called with
payload
= '{"date": "18/02/2026"}' (a string) and
headers
= '{}' (a string). • The function signature expects
payload
and
headers
to be dictionary objects, so Pydantic rejects the string inputs. What you can do (two straightforward fixes) 1) Preferable: Change the tool signature to accept dicts • Update
execute_webhook
to declare
payload
and
headers
as dict types (e.g.,
Dict[str, Any]
). This makes the expected types explicit and lets the LLM/tooling pass native dicts. Example:
Copy code
from typing import Dict, Any

@function_tool()
async def execute_webhook(
    self,
    context: RunContext,
    url: str,
    method: str = "POST",
    payload: Dict[str, Any] | None = None,
    headers: Dict[str, Any] | None = None,
) -> str:
    # use payload and headers as dicts
    ...
Benefits: better type safety, clearer contract for callers/LLM, avoids runtime JSON parsing. 2) If callers may pass JSON strings: accept strings and parse them inside the function • Change the parameters to
str
and json.loads them to dicts, handling JSONDecodeError. Example:
Copy code
import json

@function_tool()
async def execute_webhook(
    self,
    context: RunContext,
    url: str,
    method: str = "POST",
    payload: str = "{}",
    headers: str = "{}",
) -> str:
    try:
        payload_dict = json.loads(payload) if payload else {}
        headers_dict = json.loads(headers) if headers else {}
    except json.JSONDecodeError as e:
        return f"Error parsing JSON: {e}"
    # continue using payload_dict and headers_dict
    ...
Which to pick • Use Option 1 (dict types) when you control the caller and can ensure callers/LLM will pass dictionaries. • Use Option 2 when you expect external callers or the LLM might send JSON strings; parse defensively. Extra tips • If you keep dict types, add a short description in the tool docstring that
payload
and
headers
must be dictionary objects (this helps the LLM/tooling generate the correct types). • If you log or return errors, include the problematic argument so you can spot whether a string was passed. If you want, tell me which code you control (the tool definition or the LLM prompt/call), and I’ll provide a tailored patch or an updated function-tool definition you can drop into your code. Sources: Tool definition and use | LiveKit Documentation
w
which option is correct elif node_type == "webhook": body = data.get("body", "{}") method = data.get("method", "{}") url = data.get("url", "{}") steps.append( f""" {indent}{prefix} [WEBHOOK ACTION] Prompt: {prompt} Webhook : url: {url} method: {method} body: {body} STRICT EXECUTION RULES: 1. execute_webhook MUST be called using ABOVE values. 2. Speak dialogue FIRST unless explicitly AFTER webhook. 3. Never modify payload keys. """ ) this is my instruction for webhook
t
Thanks — this is helpful. I’ll be direct: Option 1 (having
execute_webhook
accept dicts) is the correct approach if you control the function and want a robust, type-safe contract between your agent and the webhook executor. Why Option 1 is the right fit for your snippet • Your instruction enforces strict execution rules and tells the agent to "execute_webhook MUST be called using ABOVE values." That implies the agent/tool should pass the webhook fields exactly as shown. Passing Python dictionaries preserves structure (so the webhook payload keys remain unchanged) and avoids Pydantic validation errors that occur when strings are passed where dicts are expected. • Dict parameters make it explicit to the LLM/tooling that `body`/`payload` and
headers
are structured data, not just text to be JSON-encoded. What to change in your snippet • Ensure
body
is a dictionary (not a JSON string) when you build the step and when the agent calls the function. • Ensure
method
and
url
remain strings. • Make the instructions explicitly state that
body
must be a dictionary object (not a JSON string) so the LLM knows the expected type. Suggested minimal edits to your instruction (conceptual) • When you set body: parse incoming JSON into a dict (if needed) before placing it into
steps
. • Update the STRICT EXECUTION RULES to say: “execute_webhook MUST be called with
body
as a dictionary object (do not send as JSON string).” Example of how to prepare values before appending the step (conceptual Python) 1. If
data["body"]
might be a JSON string, convert it: ◦ If it’s a dict already, keep it. ◦ If it’s a string, run
json.loads(body)
(with try/except) to get a dict. 1. Put that dict into the step so the LLM will pass a dict to
execute_webhook
. One quick note about your current code lines • You use
method = data.get("method", "{}")
and
url = data.get("url", "{}")
. Defaulting
method
and
url
to
"{}"
(a string that looks like an object) is confusing. Use sensible string defaults, e.g.
method = data.get("method", "POST")
and
url = data.get("url", "")
. If you’d like, paste the exact shape of
data["body"]
(string vs dict) and I’ll give the exact code change to normalize it to a dict and an updated text block for
steps
that makes the expected types crystal clear for the LLM.
w
in my instruction what i change give me correct instruction
t
Thanks — I’ll give you a corrected instruction block you can drop into your
steps
that explicitly requires
body
to be a dictionary (not a JSON string), and a short note on normalizing
data["body"]
before building the instruction. Corrected instruction to append to steps • This block is the replacement for your previous multiline string. It makes the expected types explicit and uses sensible defaults for method/url.
Copy code
elif node_type == "webhook":
    # Normalize body to a dict before building the instruction (see note below)
    body = data.get("body", {})        # must be a dict
    method = data.get("method", "POST")
    url = data.get("url", "")

    steps.append(
f"""
{indent}{prefix} [WEBHOOK ACTION]

Prompt:

{prompt}

Webhook:
url: {url}
method: {method}
body: {body}

STRICT EXECUTION RULES:
1. execute_webhook MUST be called using the ABOVE values.
   - **body MUST be passed as a dictionary object (not a JSON string).**
   - Do not add, remove, or change keys inside `body`.
2. Speak dialogue FIRST unless explicitly instructed to call the webhook before speaking.
3. Do not alter `method` or `url`.
"""
    )
How to normalize
data["body"]
before appending • If
data["body"]
may sometimes be a JSON string, convert it to a dict first. Example:
Copy code
import json

raw_body = data.get("body", {})
if isinstance(raw_body, str):
    try:
        body = json.loads(raw_body)
    except json.JSONDecodeError:
        # fallback: keep it as an empty dict or handle as you prefer
        body = {}
else:
    body = raw_body  # assume it's already a dict
Why this fixes the Pydantic error • Pydantic expects `payload`/`body` and
headers
as dicts. By normalizing and explicitly instructing the agent to pass a dictionary, you prevent the LLM or caller from sending JSON strings that fail validation. One small extra suggestion • If you also pass
headers
, apply the same normalization and add a rule: "`headers` MUST be passed as a dictionary object (not a JSON string)." If you want, I can return the full updated function-tool definition and a short example of the agent call that correctly sends the dicts. Which would be more helpful: the exact tool signature or a sample agent call?