> ## Documentation Index
> Fetch the complete documentation index at: https://docs.morphllm.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Dynamic Tool Loading

> Load Kimi K3 tool schemas only when an agent needs them

An agent with hundreds of tools should not send every schema on every turn.

Kimi K3 can add tools at any point in a conversation. Put the new definitions in a `system` message under `tools`. They become available from that position forward and calls come back in the standard OpenAI `tool_calls` response field.

<Note>
  Dynamic loading is available on Kimi K3 (`morph-kimik3` and `morph-kimik3-fast`) through `/v1/chat/completions`. Other models still require tool definitions in the request's top-level `tools` array.
</Note>

## Quick Start

This request declares `Calculator` after the user turn, then requires K3 to call an available tool:

```bash theme={null}
curl "https://api.morphllm.com/v1/chat/completions" \
  -H "Authorization: Bearer $MORPH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "morph-kimik3",
    "messages": [
      {"role": "user", "content": "Use Calculator to compute 23 * 47."},
      {
        "role": "system",
        "tools": [
          {
            "type": "function",
            "function": {
              "name": "Calculator",
              "description": "Evaluate one arithmetic expression",
              "parameters": {
                "type": "object",
                "properties": {
                  "expr": {"type": "string"}
                },
                "required": ["expr"]
              }
            }
          }
        ]
      }
    ],
    "tool_choice": "required",
    "max_tokens": 128
  }'
```

The response uses the same shape as a tool declared at the top level:

```json theme={null}
{
  "choices": [
    {
      "message": {
        "role": "assistant",
        "tool_calls": [
          {
            "id": "call_...",
            "type": "function",
            "function": {
              "name": "Calculator",
              "arguments": "{\"expr\":\"23 * 47\"}"
            }
          }
        ]
      },
      "finish_reason": "tool_calls"
    }
  ]
}
```

Your application validates the arguments, executes the function, and returns its result in a `tool` message.

## Load tools on demand

Keep a small discovery tool in the top-level `tools` array. When K3 calls it, search your registry and append the matching definitions as a dynamic declaration.

```python theme={null}
import json
from openai import OpenAI

client = OpenAI(
    api_key="YOUR_API_KEY",
    base_url="https://api.morphllm.com/v1",
)

search_tools = {
    "type": "function",
    "function": {
        "name": "search_tools",
        "description": "Find tools relevant to a task",
        "parameters": {
            "type": "object",
            "properties": {"query": {"type": "string"}},
            "required": ["query"],
        },
    },
}

messages = [{
    "role": "user",
    "content": "Find the unpaid invoice for Acme and email its owner.",
}]

# 1. K3 discovers which capabilities it needs.
response = client.chat.completions.create(
    model="morph-kimik3",
    messages=messages,
    tools=[search_tools],
    tool_choice="required",
)
assistant = response.choices[0].message
messages.append(assistant.model_dump(exclude_none=True))

call = assistant.tool_calls[0]
loaded_tools = search_registry(json.loads(call.function.arguments)["query"])
messages.append({
    "role": "tool",
    "tool_call_id": call.id,
    "content": json.dumps({
        "loaded": [tool["function"]["name"] for tool in loaded_tools],
    }),
})

# 2. Append the complete schemas at this point in the conversation.
messages.append({"role": "system", "tools": loaded_tools})

# 3. K3 can now call the discovered tools. Keep search_tools available too.
response = client.chat.completions.create(
    model="morph-kimik3",
    messages=messages,
    tools=[search_tools],
)
```

`messages[].tools` is a K3 extension to the OpenAI message schema. The Python SDK sends the extra dictionary field at runtime, but static type checkers and generated TypeScript types may not recognize it. Widen that message type locally or send the JSON request directly.

## Message rules

A dynamic declaration must:

* use `role: "system"`;
* contain a non-empty `tools` array of standard OpenAI function definitions; and
* omit the `content` key entirely.

Do not send `content: null`. A declaration containing both `content` and `tools` returns HTTP 400.

Static and dynamic tools can coexist. Keep universal tools such as `search_tools` in the top-level `tools` array, then append task-specific definitions in system messages. Each dynamic declaration extends the tools already available at that point in the conversation.

## Choosing static or dynamic tools

Use top-level tools when the set is small and stable. Use dynamic loading when the full registry is large, tenant-specific, permission-dependent, or expensive to place in every request.

Dynamic loading changes how schemas reach the model. It does not change execution security. Validate arguments, authorize the action for the current user, require approval for sensitive operations, and make side-effecting tools idempotent before executing a returned call.

## See Also

* [Open Source Models](/sdk/components/fast-models) — model IDs and standard tool calling
* [Prompt Caching](/sdk/components/caching) — cached-input behavior and usage fields
* [Agent Runs](/sdk/components/agent-programs) — keep a multi-turn K3 run on the worker holding its cache
* [Moonshot's dynamic tool loading guide](https://platform.kimi.ai/docs/guide/use-dynamic-tool-loading) — the upstream K3 request format
