wide-flower-24692
02/17/2026, 5:39 AMexecute_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"}tall-airline-16756
02/17/2026, 5:39 AMtall-airline-16756
02/17/2026, 5:40 AMexecute_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:
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:
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 Documentationwide-flower-24692
02/17/2026, 5:46 AMtall-airline-16756
02/17/2026, 5:47 AMexecute_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.wide-flower-24692
02/17/2026, 5:49 AMtall-airline-16756
02/17/2026, 5:49 AMsteps 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.
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:
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?