Skip to content

vllm.renderers.cohere

Cohere prompt renderer.

Templates the Cohere Command-family prompt formats (cmd3 / cmd4) using the cohere_melody Rust bindings instead of Jinja. Enabled by passing --tokenizer-mode cohere on the engine; tokenization itself still flows through the cached HuggingFace tokenizer.

The renderer's client surface is chat_template_kwargs (vLLM's generic passthrough for template-time inputs). It is fed by two different code paths, which speak different vocabularies:

  1. :class:vllm.entrypoints.cohere.serving.CohereServingChatV2 -- translates a native Cohere Chat v2 request <https://docs.cohere.com/reference/chat>__ into chat_template_kwargs.
  2. :class:OpenAIServingChat -- forwards whatever the client puts in request.chat_template_kwargs directly, without translation.

Accordingly the renderer accepts keys from both vocabularies.

Cohere Chat v2 request fields (populated automatically by CohereServingChatV2; a direct chat_template_kwargs caller may also pass them):

  • documents: list of documents (v2 documents)
  • tools: list of tools (v2 tools)
  • safety_mode: v2 safety_mode (CONTEXTUAL / STRICT / NONE) -- forwarded to melody's cmd3 safety_mode slot; lowercased.
  • citation_options: v2 citation_options. Its .mode (ENABLED / DISABLED / FAST / ACCURATE / OFF) is normalized into melody's cmd3 citation_quality (on / off) and cmd4 grounding (enabled / disabled / unknown) slots. cmd4 has no fast/accurate distinction at the prompt-template layer -- both mean grounding-on.
  • response_format: v2 response_format (json_object / json_schema) -- mapped onto melody's json_mode / json_schema config slots.
  • thinking: v2 thinking; its .type (enabled / disabled) becomes melody's reasoning_type.

Melody template-config knobs (accepted for direct-passthrough callers; not part of the Cohere Chat v2 surface):

  • cohere_format: renderer selector, cmd3 or cmd4 (default cmd4).
  • template_id: pick one of melody's built-in template variants.
  • available_tools: melody's own name for tools; takes precedence over tools when both are set.
  • reasoning_type: direct enabled / disabled toggle; overrides derivation from thinking.type.
  • dev_instruction: developer-instruction override.
  • json_mode / json_schema: direct structured-output toggles; override derivation from response_format.
  • cmd3-only: citation_quality (on / off), skip_preamble.
  • cmd4-only: grounding (enabled / disabled / unknown; overrides derivation from citation_options.mode), platform_instruction.

Rejected inputs: template_jinja and template are Cohere's own inlets for raw Jinja template source. They are valid at Cohere's API surface but not at vLLM's -- raw template source must flow through the standard chat_template request field (guarded by --trust-request-chat-template), which this renderer forwards to melody as template_jinja. Setting these keys explicitly in chat_template_kwargs raises ValueError so client misconfiguration surfaces loudly.

Everything else in chat_template_kwargs is forwarded verbatim to melody as additional_template_fields and becomes accessible as Jinja variables inside the template. This matches vLLM's documented contract for chat_template_kwargs ("kwargs accessible by the template"), so e.g. chat_template_kwargs={"reasoning_effort": "low"} resolves {{ reasoning_effort }} inside cmd3 / cmd4 templates.

Citations produced by Cohere models are surfaced through the Cohere-scoped CohereChatMessage.citations / CohereDeltaMessage.citations fields (see :mod:vllm.entrypoints.cohere.cohere_chat_message), populated by the cohere2 reasoning parser. The base OpenAI ChatMessage / DeltaMessage keep their declared schemas unchanged; the response envelope declares them as SerializeAsAny[...] so the subclass fields survive JSON serialization.

Classes:

  • CohereRenderer

    Renderer that templates Cohere prompts via the melody Rust bindings.

  • MelodyContentType

    Wire-format discriminator for melody content blocks.

CohereRenderer

Bases: BaseRenderer[HfTokenizer]

Renderer that templates Cohere prompts via the melody Rust bindings.

Tokenization is delegated to the standard HF tokenizer; only the chat-template step is replaced with cohere_melody.render_cmd3 / render_cmd4. Enabled via --tokenizer-mode cohere.

Source code in vllm/renderers/cohere.py
class CohereRenderer(BaseRenderer[HfTokenizer]):
    """Renderer that templates Cohere prompts via the melody Rust bindings.

    Tokenization is delegated to the standard HF tokenizer; only the
    chat-template step is replaced with ``cohere_melody.render_cmd3`` /
    ``render_cmd4``. Enabled via ``--tokenizer-mode cohere``.
    """

    def __init__(
        self,
        config: VllmConfig,
        tokenizer: HfTokenizer | None,
    ) -> None:
        # Match HfRenderer in not mutating the cached tokenizer instance
        tokenizer = copy.copy(tokenizer)
        super().__init__(config, tokenizer)

        # Lazy import to keep `cohere_melody` an optional dependency
        self._melody = _try_import_melody()
        # ``render_cmd3`` / ``render_cmd4`` are pure CPU work; cache the
        # thread-pool wrapper once so the async path doesn't allocate a new
        # adapter on every request.
        self._render_async = make_async(self._render, executor=self._executor)

    def _render(self, fmt: str, config_dict: dict[str, Any]) -> str:
        if fmt == "cmd3":
            return self._melody.render_cmd3(config_dict)
        return self._melody.render_cmd4(config_dict)

    def render_messages(
        self,
        messages: list[ChatCompletionMessageParam],
        params: ChatParams,
    ) -> tuple[list[ConversationMessage], DictPrompt]:
        conversation, mm_data, mm_uuids = parse_chat_messages(
            messages,
            self.model_config,
            content_format="openai",
            media_io_kwargs=params.media_io_kwargs,
            mm_processor_kwargs=params.mm_processor_kwargs,
        )

        chat_template_kwargs = dict(params.chat_template_kwargs)
        fmt, config_dict = _build_render_config(
            conversation, chat_template_kwargs, params.chat_template
        )
        prompt_text = self._render(fmt, config_dict)
        prompt = parse_dec_only_prompt(prompt_text)

        if mm_data is not None:
            prompt["multi_modal_data"] = mm_data
        if mm_uuids is not None:
            prompt["multi_modal_uuids"] = mm_uuids

        return conversation, prompt

    async def render_messages_async(
        self,
        messages: list[ChatCompletionMessageParam],
        params: ChatParams,
    ) -> tuple[list[ConversationMessage], DictPrompt]:
        conversation, mm_data, mm_uuids = await parse_chat_messages_async(
            messages,
            self.model_config,
            content_format="openai",
            media_io_kwargs=params.media_io_kwargs,
            mm_processor_kwargs=params.mm_processor_kwargs,
        )

        chat_template_kwargs = dict(params.chat_template_kwargs)
        fmt, config_dict = _build_render_config(
            conversation, chat_template_kwargs, params.chat_template
        )
        prompt_text = await self._render_async(fmt, config_dict)
        prompt = parse_dec_only_prompt(prompt_text)

        if mm_data is not None:
            prompt["multi_modal_data"] = mm_data
        if mm_uuids is not None:
            prompt["multi_modal_uuids"] = mm_uuids

        return conversation, prompt

MelodyContentType

Bases: str, Enum

Wire-format discriminator for melody content blocks.

These strings are what the cmd3 / cmd4 Jinja templates check against in message.content[0].type (e.g. the thinking branch in cmd4-v1.jinja). Keep new values in sync with melody's template schema.

Source code in vllm/renderers/cohere.py
class MelodyContentType(str, Enum):
    """Wire-format discriminator for melody content blocks.

    These strings are what the cmd3 / cmd4 Jinja templates check against
    in ``message.content[0].type`` (e.g. the ``thinking`` branch in
    ``cmd4-v1.jinja``). Keep new values in sync with melody's template
    schema.
    """

    TEXT = "text"
    THINKING = "thinking"
    IMAGE = "image"
    DOCUMENT = "document"

_build_render_config(conversation, chat_template_kwargs, chat_template=None)

Build the render_cmd3 / render_cmd4 config dict.

chat_template is the standard vLLM per-request template override (from ChatCompletionRequest.chat_template / the server-side --chat-template flag). When non-None it is forwarded to melody as template_jinja so a single vLLM-native path drives template source through the trust guard.

Returns (format, config_dict) where format is either "cmd3" or "cmd4".

Source code in vllm/renderers/cohere.py
def _build_render_config(
    conversation: list[ConversationMessage],
    chat_template_kwargs: dict[str, Any],
    chat_template: str | None = None,
) -> tuple[str, dict[str, Any]]:
    """Build the ``render_cmd3`` / ``render_cmd4`` config dict.

    ``chat_template`` is the standard vLLM per-request template override
    (from ``ChatCompletionRequest.chat_template`` / the server-side
    ``--chat-template`` flag). When non-``None`` it is forwarded to melody
    as ``template_jinja`` so a single vLLM-native path drives template
    source through the trust guard.

    Returns ``(format, config_dict)`` where ``format`` is either ``"cmd3"``
    or ``"cmd4"``.
    """
    fmt = chat_template_kwargs.get("cohere_format", _DEFAULT_FORMAT)
    if fmt not in _VALID_FORMATS:
        raise ValueError(
            f"Invalid cohere_format={fmt!r}; expected one of {_VALID_FORMATS}"
        )

    # Reject Cohere-only inlets for raw Jinja source. These keys are
    # valid at Cohere's own API surface but not at vLLM's: callers must
    # use the standard vLLM ``chat_template`` request field so the
    # ``--trust-request-chat-template`` guard applies uniformly.
    for cohere_only_key in ("template_jinja", "template"):
        if chat_template_kwargs.get(cohere_only_key) is not None:
            raise ValueError(
                f"chat_template_kwargs.{cohere_only_key!r} is not accepted "
                "in vLLM (it is a Cohere-only field); pass the template "
                "body via the standard 'chat_template' request field "
                "instead (requires --trust-request-chat-template)."
            )

    config: dict[str, Any] = {
        "messages": _conversation_to_melody_messages(
            conversation,
            chat_template_kwargs.get(MESSAGES_CITATIONS_KEY),
        ),
    }

    # ``template_id`` is a selector for one of melody's built-in template
    # variants (not raw source), so it is safe to accept from the client.
    if template_id := chat_template_kwargs.get("template_id"):
        config["template_id"] = template_id
    # Raw template source flows exclusively through the standard vLLM
    # ``chat_template`` field so it is subject to the
    # ``--trust-request-chat-template`` guard in OnlineRenderer.
    if chat_template:
        config["template_jinja"] = chat_template
    # Only support Jinja with vllm
    config["use_jinja"] = True

    # Documents
    documents = chat_template_kwargs.get("documents") or []
    if documents:
        config["documents"] = [_document_to_melody(d) for d in documents]

    # Tools - prefer explicit ``available_tools``, fall back to OpenAI ``tools``
    tools = (
        chat_template_kwargs.get("available_tools")
        or chat_template_kwargs.get("tools")
        or []
    )
    if tools:
        config["available_tools"] = [_tool_to_melody(t) for t in tools]

    # Reasoning toggle (cmd3 + cmd4)
    if (rt := chat_template_kwargs.get("reasoning_type")) is not None:
        config["reasoning_type"] = str(rt)
    elif "thinking" in chat_template_kwargs:
        # Cohere v2 ``thinking: {type: enabled|disabled}`` shorthand
        thinking = chat_template_kwargs["thinking"]
        t = thinking.get("type") if isinstance(thinking, dict) else thinking
        if t in ("enabled", "disabled"):
            config["reasoning_type"] = t

    if (di := chat_template_kwargs.get("dev_instruction")) is not None:
        config["dev_instruction"] = str(di)

    # JSON / structured outputs
    if (rf := chat_template_kwargs.get("response_format")) is not None:
        rf = rf.model_dump() if hasattr(rf, "model_dump") else dict(rf)
        rf_type = rf.get("type")
        if rf_type == "json_object":
            config["json_mode"] = True
        elif rf_type in ("json_schema", "json"):
            schema = rf.get("schema") or rf.get("json_schema")
            if isinstance(schema, dict) and "schema" in schema:
                schema = schema["schema"]
            if schema is not None:
                config["json_schema"] = (
                    schema if isinstance(schema, str) else json.dumps(schema)
                )
    if (js := chat_template_kwargs.get("json_schema")) is not None:
        config["json_schema"] = js if isinstance(js, str) else json.dumps(js)
    if "json_mode" in chat_template_kwargs:
        config["json_mode"] = bool(chat_template_kwargs["json_mode"])

    if fmt == "cmd3":
        if (sm := chat_template_kwargs.get("safety_mode")) is not None:
            config["safety_mode"] = str(sm).lower()
        # citation_quality: ``on`` / ``off``
        cq = chat_template_kwargs.get("citation_quality")
        if cq is None and (co := chat_template_kwargs.get("citation_options")):
            mode = co.get("mode") if isinstance(co, dict) else None
            if mode is not None:
                cq = "on" if str(mode).lower() != "off" else "off"
        if cq is not None:
            config["citation_quality"] = str(cq).lower()
        if "skip_preamble" in chat_template_kwargs:
            config["skip_preamble"] = bool(chat_template_kwargs["skip_preamble"])
    else:  # cmd4
        # cmd4 uses ``grounding`` rather than safety_mode/citation_quality.
        # melody's cmd4 only accepts ``unknown`` / ``enabled`` / ``disabled``,
        # so the Cohere v2 ``citation_options.mode`` values
        # (``FAST`` / ``ACCURATE`` / ``OFF``) have to be normalized -- a
        # raw lowercased passthrough would raise from
        # ``render_cmd4`` (cmd4 has no fast/accurate distinction at the
        # prompt-template layer; both request grounding-on).
        if (gr := chat_template_kwargs.get("grounding")) is not None:
            config["grounding"] = _normalize_cmd4_grounding(gr)
        elif co := chat_template_kwargs.get("citation_options"):
            mode = co.get("mode") if isinstance(co, dict) else None
            if mode is not None:
                config["grounding"] = _normalize_cmd4_grounding(mode)
        if (pi := chat_template_kwargs.get("platform_instruction")) is not None:
            config["platform_instruction"] = str(pi)

    # Anything we didn't explicitly interpret above is forwarded to melody
    # as a Jinja template variable. This matches vLLM's documented contract
    # for ``chat_template_kwargs`` ("kwargs accessible by the template")
    # and lets callers write e.g. ``{"reasoning_effort": "low"}`` directly
    # without a nested ``additional_template_fields`` wrapper.
    extra = {
        k: v
        for k, v in chat_template_kwargs.items()
        if k not in _RENDERER_CONSUMED_KEYS
    }
    if extra:
        config["additional_template_fields"] = extra

    return fmt, config

_content_blocks(content)

Convert OpenAI ConversationMessage.content into melody content blocks.

The chat_utils content_format="openai" produces a list of {type: text, text: ...} dicts already; we pass those through with minimal coercion. Plain string content is wrapped in a single text block. Image / multimodal placeholder dicts ({"type": "image"}) are forwarded as-is so that templates can reference image placeholders that the upstream tokenizer expands separately.

Source code in vllm/renderers/cohere.py
def _content_blocks(content: Any) -> list[dict[str, Any]]:
    """Convert OpenAI ``ConversationMessage.content`` into melody content blocks.

    The chat_utils ``content_format="openai"`` produces a list of
    ``{type: text, text: ...}`` dicts already; we pass those through with
    minimal coercion. Plain string content is wrapped in a single text block.
    Image / multimodal placeholder dicts (``{"type": "image"}``) are
    forwarded as-is so that templates can reference image placeholders that
    the upstream tokenizer expands separately.
    """
    if content is None:
        return []
    if isinstance(content, str):
        return [{"type": MelodyContentType.TEXT, "text": content}]
    blocks: list[dict[str, Any]] = []
    for part in content:
        if isinstance(part, str):
            blocks.append({"type": MelodyContentType.TEXT, "text": part})
            continue
        if not isinstance(part, dict):
            raise TypeError(f"Unexpected content part: {part!r}")
        part_type = part.get("type", MelodyContentType.TEXT)
        if part_type in ("text", "input_text", "output_text", "refusal"):
            blocks.append(
                {"type": MelodyContentType.TEXT, "text": part.get("text", "")}
            )
        elif part_type == MelodyContentType.THINKING:
            blocks.append(
                {
                    "type": MelodyContentType.THINKING,
                    "thinking": part.get("thinking", ""),
                }
            )
        elif part_type == MelodyContentType.IMAGE:
            blocks.append(
                {
                    "type": MelodyContentType.IMAGE,
                    "image": {
                        "template_placeholder": part.get(
                            "template_placeholder", "<image>"
                        ),
                    },
                }
            )
        elif part_type == MelodyContentType.DOCUMENT:
            doc = part.get("document")
            if isinstance(doc, dict):
                blocks.append({"type": MelodyContentType.DOCUMENT, "document": doc})
            else:
                # Fall back to wrapping arbitrary string as text.
                blocks.append({"type": MelodyContentType.TEXT, "text": json.dumps(doc)})
        elif part_type == "tool_reference":
            # Tool references are rendered by name; emit as text so the
            # renderer downstream is content-format agnostic.
            blocks.append(
                {
                    "type": MelodyContentType.TEXT,
                    "text": part.get("name") or part.get("text", ""),
                }
            )
        else:
            # Unknown block type: render as text fallback.
            text = part.get("text") or part.get(part_type) or ""
            text_str = text if isinstance(text, str) else json.dumps(text)
            blocks.append({"type": MelodyContentType.TEXT, "text": text_str})
    return blocks

_conversation_to_melody_messages(conversation, messages_citations=None)

Convert vLLM ConversationMessages into melody's message dict shape.

messages_citations is an optional index-keyed lookup of per-message citations from the request, populated by CohereServingChatV2 under the :data:MESSAGES_CITATIONS_KEY chat_template_kwargs entry. Values must already be in melody's FilterCitation dict shape (see CohereServingChatV2._sdk_citation_to_melody). Index is the position in the input conversation list, which currently maps 1-to-1 with CohereChatV2Request.messages for text-only Cohere requests. This mapping breaks silently if parse_chat_messages ever expands a single input message into multiple entries (e.g. multi-modal splitting), which is not the case today.

Source code in vllm/renderers/cohere.py
def _conversation_to_melody_messages(
    conversation: list[ConversationMessage],
    messages_citations: dict[int, list[dict[str, Any]]] | None = None,
) -> list[dict[str, Any]]:
    """Convert vLLM ``ConversationMessage``s into melody's message dict shape.

    ``messages_citations`` is an optional index-keyed lookup of
    per-message citations from the request, populated by
    ``CohereServingChatV2`` under the
    :data:`MESSAGES_CITATIONS_KEY` chat_template_kwargs entry. Values
    must already be in melody's ``FilterCitation`` dict shape (see
    ``CohereServingChatV2._sdk_citation_to_melody``). Index is the
    position in the *input* ``conversation`` list, which currently maps
    1-to-1 with ``CohereChatV2Request.messages`` for text-only Cohere
    requests. This mapping breaks silently if ``parse_chat_messages``
    ever expands a single input message into multiple entries (e.g.
    multi-modal splitting), which is not the case today.
    """
    out: list[dict[str, Any]] = []
    for i, msg in enumerate(conversation):
        role = _role_to_melody(msg.get("role", "user"))
        content_blocks = _content_blocks(msg.get("content"))

        # Treat reasoning content as a thinking block on assistant turns
        # so multi-turn reasoning is preserved in the rendered prompt.
        #
        # Cohere models output thought as either a ``thinking``
        # block (reasoning models) or a ``tool_plan`` field (older non-
        # reasoning Command models), but vLLM's ConversationMessage has a
        # single unified ``reasoning`` field that drops that difference.
        # The cmd3 / cmd4 jinja templates render ``thinking`` blocks in both
        # the case of tool calls and regular thinking blocks, so this is fine
        # from the renderer's perspective.
        reasoning = msg.get("reasoning") or msg.get("reasoning_content")
        if role == "chatbot" and reasoning:
            content_blocks.insert(
                0,
                {"type": MelodyContentType.THINKING, "thinking": reasoning},
            )

        tool_calls = [_normalize_tool_call(tc) for tc in (msg.get("tool_calls") or [])]

        out_msg: dict[str, Any] = {
            "role": role,
            "content": content_blocks,
            "tool_calls": tool_calls,
        }
        tool_call_id = msg.get("tool_call_id")
        if tool_call_id:
            out_msg["tool_call_id"] = tool_call_id
        if messages_citations and (cites := messages_citations.get(i)):
            out_msg["citations"] = cites
        out.append(out_msg)
    return out

_document_to_melody(doc)

Coerce a Cohere v2 document into melody's Document (dict) shape.

Source code in vllm/renderers/cohere.py
def _document_to_melody(doc: Any) -> dict[str, Any]:
    """Coerce a Cohere v2 document into melody's ``Document`` (dict) shape."""
    match doc:
        case str():
            return {"text": doc}
        # Cohere v2 wraps documents in {id, data: {...}}; the ``**rest``
        # capture keeps the outer ``id`` (and any other outer keys) so we
        # can lift them onto the payload without losing them.
        case {"data": dict() as data, **rest}:
            payload = dict(data)
            if (outer_id := rest.get("id")) is not None and "id" not in payload:
                payload["id"] = outer_id
            return payload
        case dict():
            return dict(doc)
        case _:
            raise TypeError(f"Unsupported document type: {type(doc).__name__}")

_normalize_cmd4_grounding(value)

Coerce a user-facing grounding/citation mode to melody's vocab.

Raises ValueError rather than letting an unrecognized value slip through to render_cmd4 and surface as a generic Invalid config: grounding from melody.

Source code in vllm/renderers/cohere.py
def _normalize_cmd4_grounding(value: Any) -> str:
    """Coerce a user-facing grounding/citation mode to melody's vocab.

    Raises ``ValueError`` rather than letting an unrecognized value
    slip through to ``render_cmd4`` and surface as a generic
    ``Invalid config: grounding`` from melody.
    """
    out = _CMD4_GROUNDING_FROM_MODE.get(value.lower())
    if out is None:
        raise ValueError(
            f"Unrecognized cmd4 grounding value: {value!r}. Expected one "
            f"of ENABLED / DISABLED / FAST / ACCURATE / OFF (from "
            f"citation_options.mode), or the direct melody values "
            f"enabled / disabled / unknown."
        )
    return out

_normalize_tool_call(tc)

Normalize an OpenAI tool call dict into melody's tool call shape.

melody expects {id, name, parameters: <json str>} whereas OpenAI delivers {id, type, function: {name, arguments: <json|str>}}.

Source code in vllm/renderers/cohere.py
def _normalize_tool_call(tc: dict[str, Any] | Any) -> dict[str, Any]:
    """Normalize an OpenAI tool call dict into melody's tool call shape.

    melody expects ``{id, name, parameters: <json str>}`` whereas OpenAI
    delivers ``{id, type, function: {name, arguments: <json|str>}}``.
    """
    # Pydantic objects
    if hasattr(tc, "model_dump"):
        tc = tc.model_dump()
    if not isinstance(tc, dict):
        raise TypeError(f"Unexpected tool_call value: {tc!r}")

    fn = tc.get("function") or {}
    name = fn.get("name") or tc.get("name") or ""
    args = fn.get("arguments")
    if args is None:
        args = tc.get("arguments", {})
    # melody expects a JSON-encoded string
    if not isinstance(args, str):
        args = json.dumps(args, ensure_ascii=False)
    return {
        "id": tc.get("id") or "",
        "name": name,
        "parameters": args,
    }

_role_to_melody(role)

Map an OpenAI role to the role string melody expects.

The cmd3 / cmd4 jinja templates only recognize system, user, assistant/chatbot, and tool: any other role is silently dropped by the template's role-dispatch chain (no fallback branch), which produces a malformed prompt without any error. We therefore refuse unknown roles up front rather than letting them disappear.

Aliases:

  • assistant -> chatbot (Cohere's historical assistant role name used by the templates).
  • developer -> system (OpenAI's developer role is documented as high-priority instructions, which maps onto the system slot in Cohere's prompt format).
Source code in vllm/renderers/cohere.py
def _role_to_melody(role: str) -> str:
    """Map an OpenAI role to the role string melody expects.

    The cmd3 / cmd4 jinja templates only recognize ``system``, ``user``,
    ``assistant``/``chatbot``, and ``tool``: any other role is silently
    dropped by the template's role-dispatch chain (no fallback branch),
    which produces a malformed prompt without any error. We therefore
    refuse unknown roles up front rather than letting them disappear.

    Aliases:

    * ``assistant`` -> ``chatbot`` (Cohere's historical assistant role
      name used by the templates).
    * ``developer`` -> ``system`` (OpenAI's ``developer`` role is
      documented as high-priority instructions, which maps onto the
      ``system`` slot in Cohere's prompt format).
    """
    role = role.lower()
    if role == "assistant":
        return "chatbot"
    if role == "developer":
        return "system"
    if role in _MELODY_ROLES:
        return role
    raise ValueError(
        f"Unsupported message role for the cohere renderer: {role!r}. "
        "Expected one of: system, developer, user, assistant, chatbot, tool."
    )

_tool_to_melody(tool)

Coerce a chat completions tool definition into melody's Tool shape.

Accepts either the raw OpenAI tool wrapper {type:"function", function: {name, description, parameters}} or a flat {name, description, parameters} dict (which is what melody itself expects).

Source code in vllm/renderers/cohere.py
def _tool_to_melody(tool: Any) -> dict[str, Any]:
    """Coerce a chat completions tool definition into melody's ``Tool`` shape.

    Accepts either the raw OpenAI tool wrapper ``{type:"function", function:
    {name, description, parameters}}`` or a flat ``{name, description,
    parameters}`` dict (which is what melody itself expects).
    """
    if hasattr(tool, "model_dump"):
        tool = tool.model_dump()
    if not isinstance(tool, dict):
        raise TypeError(f"Unsupported tool type: {type(tool).__name__}")
    if "function" in tool and isinstance(tool["function"], dict):
        fn = tool["function"]
    else:
        fn = tool
    return {
        "name": fn.get("name", ""),
        "description": fn.get("description", "") or "",
        "parameters": fn.get("parameters") or {},
    }