OpenBMB/ChatDev · error · ConfigError
tooling must be a list
Error message
tooling must be a list
What it means
The 'tooling' key of an agent model config is present and non-null but is not a JSON/YAML list. Tooling entries are parsed as a list of ToolingConfig objects, so any other shape fails immediately.
Source
Thrown at entity/configs/node/agent.py:370
role = optional_str(mapping, "role", path)
api_key = optional_str(mapping, "api_key", path)
params = optional_dict(mapping, "params", path) or {}
raw_input_mode = optional_str(mapping, "input_mode", path)
input_mode = AgentInputMode.MESSAGES
if raw_input_mode:
try:
input_mode = AgentInputMode(raw_input_mode.strip().lower())
except ValueError as exc:
raise ConfigError(
"model.input_mode must be 'prompt' or 'messages'",
extend_path(path, "input_mode"),
) from exc
tooling_cfg: List[ToolingConfig] = []
if "tooling" in mapping and mapping["tooling"] is not None:
raw_tooling = mapping["tooling"]
if not isinstance(raw_tooling, list):
raise ConfigError("tooling must be a list", extend_path(path, "tooling"))
for idx, item in enumerate(raw_tooling):
tooling_cfg.append(
ToolingConfig.from_dict(item, path=extend_path(path, f"tooling[{idx}]"))
)
thinking_cfg = None
if "thinking" in mapping and mapping["thinking"] is not None:
thinking_cfg = ThinkingConfig.from_dict(mapping["thinking"], path=extend_path(path, "thinking"))
memories_cfg: List[MemoryAttachmentConfig] = []
if "memories" in mapping and mapping["memories"] is not None:
raw_memories = mapping["memories"]
if not isinstance(raw_memories, list):
raise ConfigError("memories must be a list", extend_path(path, "memories"))
for idx, item in enumerate(raw_memories):
memories_cfg.append(
MemoryAttachmentConfig.from_dict(item, path=extend_path(path, f"memories[{idx}]"))
)View on GitHub (pinned to 4fb2db0ea9)
Solutions
- Wrap single tool configs in a list: "tooling": [{...}] not {"tooling": {...}}
- Verify YAML uses dash-prefixed list items under 'tooling'
- Omit 'tooling' or set it to null when no tools are needed
Example fix
// before
"tooling": {"type": "web_search"}
// after
"tooling": [{"type": "web_search"}] Defensive patterns
Strategy: validation
Validate before calling
tooling = cfg.get('model', {}).get('tooling')
if tooling is not None and not isinstance(tooling, list):
cfg['model']['tooling'] = [tooling] Type guard
def has_list_tooling(cfg: dict) -> bool:
t = cfg.get('model', {}).get('tooling')
return t is None or isinstance(t, list) Try / catch
try:
ModelConfig.from_dict(data, path='agent')
except ConfigError as e:
if 'tooling' in e.path and isinstance(data['model'].get('tooling'), dict):
data['model']['tooling'] = [data['model']['tooling']]
ModelConfig.from_dict(data, path='agent')
else:
raise Prevention
- Always author tooling as a list even for one tool
- Linter rule: sequence keys must be lists in config files
- Unit-test config round-trips through from_dict
When it happens
Trigger: Passing tooling as a single dict (one tool instead of a list), a string, or a number: "tooling": {"type": "web_search"} instead of [{...}]. A null/absent tooling key is fine.
Common situations: Wrapping a single tool config directly instead of in a list — the most common shape mistake; hand-editing YAML and dropping the '- ' list marker.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- model.name must be a non-empty string
- model.input_mode must be 'prompt' or 'messages'
- memories must be a list
- content cannot be empty
- role must be 'user' or 'assistant'
AI-assisted analysis of OpenBMB/ChatDev@4fb2db0ea9 (2026-08-27).
Data as JSON: /api/errors/a1fc656ac9c32ac5.
Report an issue: GitHub.