Skip to content

Bug: prepare_request() mutates caller's messages list via shallow copy #2437

Description

@truecallerabreham

Describe the bug

prepare_request() methods in Cohere, Mistral, and xAI providers mutate the caller's messages list on the first call (no retry needed).

To reproduce

import instructor
from openai import OpenAI

client = instructor.from_openai(OpenAI())

messages = [{"role": "user", "content": "hello"}]

try:
    client.chat.completions.create(
        model="gpt-4o",
        messages=messages,
        response_model=str,
    )
except Exception:
    pass

print(len(messages))  # Expected: 1, Actual: 2+ (system message prepended)

Expected behavior

messages should remain unchanged after prepare_request() runs.

Root cause

prepare_request() uses kwargs.copy() (shallow copy) or _convert_messages(kwargs) which returns a dict with a shared messages list. Then .insert(), .append(), or direct dict key assignment mutates the caller's data.

Affected providers:

  • Cohere: _convert_messages_to_cohere_v2 (shallow copy)
  • Mistral: tools, json_schema, json_instructions modes (shallow copy + .insert())
  • xAI: tools, parallel_tools, json_schema, json_instructions modes (shallow copy + .insert())

Fix

new_kwargs = {**kwargs, 'messages': [dict(m) for m in kwargs.get('messages', [])]}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions