{"record":{"id":"58ec4b62382a8063","repo":"zed-industries/zed","slug":"deepseek-does-not-support-custom-tools","errorCode":null,"errorMessage":"DeepSeek does not support custom tools","messagePattern":"DeepSeek does not support custom tools","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/language_models/src/provider/deepseek.rs","lineNumber":366,"sourceCode":"            Err(error) => return async move { Err(error.into()) }.boxed(),\n        };\n        let stream = self.stream_completion(request, cx);\n\n        async move {\n            let mapper = DeepSeekEventMapper::new();\n            Ok(mapper.map_stream(stream.await?).boxed())\n        }\n        .boxed()\n    }\n}\n\npub fn into_deepseek(\n    request: LanguageModelRequest,\n    model: &deepseek::Model,\n    max_output_tokens: Option<u64>,\n) -> Result<deepseek::Request> {\n    if request.contains_custom_tool_input() {\n        anyhow::bail!(\"DeepSeek does not support custom tools\");\n    }\n\n    let thinking = deepseek_thinking(model, request.thinking_allowed);\n    let thinking_enabled = thinking\n        .as_ref()\n        .is_some_and(|thinking| thinking.kind == deepseek::ThinkingType::Enabled);\n\n    let mut messages = Vec::new();\n    let mut current_reasoning: Option<String> = None;\n\n    for message in request.messages {\n        for content in message.content {\n            match content {\n                MessageContent::Text(text) => {\n                    let should_add = if message.role == Role::User {\n                        !text.trim().is_empty()\n                    } else {\n                        !text.is_empty()","sourceCodeStart":348,"sourceCodeEnd":384,"githubUrl":"https://github.com/zed-industries/zed/blob/f4178619acd0d47ea1f76a2025c42962c6d6638c/crates/language_models/src/provider/deepseek.rs#L348-L384","documentation":"into_deepseek rejects any LanguageModelRequest with contains_custom_tool_input() before building the OpenAI-compatible chat request: DeepSeek's API only accepts function tools with JSON schemas, so the freeform custom-tool variant cannot be mapped to a tool entry.","triggerScenarios":"Using a DeepSeek model (deepseek-chat/reasoner via Zed's provider integration) while an agent or extension attaches a custom tool to the request.","commonSituations":"Switching an existing agent from Anthropic/OpenAI to DeepSeek without re-checking tool types; extensions that always register their tool as Custom.","solutions":["Convert the tool to a function tool with a JSON Schema input","Disable the custom tool/extension when the active model is DeepSeek","Use a provider whose API supports custom tools for that workflow","Check the request's tools with contains_custom_tool_input() before dispatch"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"if model.provider() == \"deepseek\" && request.contains_custom_tool_input() {\n    return Err(anyhow!(\"DeepSeek requires function tools; re-register '{}' with a JSON schema\", tool_name));\n}","typeGuard":"fn is_function_only_request(request: &LanguageModelRequest) -> bool {\n    !request.contains_custom_tool_input()\n}","tryCatchPattern":"match into_deepseek(request, model, max_tokens) {\n    Ok(req) => Ok(client.complete(req).await?),\n    Err(e) if e.to_string().contains(\"custom tools\") => { request.tools.clear_of_custom(); into_deepseek(request, model, max_tokens) }\n    Err(e) => Err(e),\n}","preventionTips":["Prefer function tools everywhere; custom tools are a per-provider extension","Validate tool kinds against the selected model's capabilities before sending","Surface a UI-level warning when a custom tool is active on an incompatible model"],"tags":["deepseek","llm","tools","capability-mismatch","api-compat"],"backgroundTag":"llm-provider-unsupported-tool-type","analyzedSha":"f4178619acd0d47ea1f76a2025c42962c6d6638c","analyzedAt":"2026-08-20T19:29:52.058Z","contentChangedAt":"2026-08-20T19:29:52.058Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}