Skip to content

feat: add enable_thinking support for OpenAI-compatible providers - #2181

Merged
endxxxx merged 7 commits into
MemTensor:dev-v2.0.30from
RerankerGuo:feat/issue-2149-enable-thinking
Aug 13, 2026
Merged

feat: add enable_thinking support for OpenAI-compatible providers#2181
endxxxx merged 7 commits into
MemTensor:dev-v2.0.30from
RerankerGuo:feat/issue-2149-enable-thinking

Conversation

@RerankerGuo

Copy link
Copy Markdown
Contributor

Description

Fixes #2149

Adds enable_thinking configuration parameter for OpenAI-compatible providers (Qwen, DeepSeek, MiniMax, etc.) to control whether the model produces <think> reasoning blocks before the actual response.

Models like Qwen3 and DeepSeek-R1 support an enable_thinking parameter in the chat completion body. When thinking is enabled, output contains <think>...</think> tags before the actual response, which can break JSON parsing in structured-output tasks (capture summarization, L3 abstraction, skill crystallization).

Changes

  1. Added enable_thinking field to OpenAILLMConfigbool | None defaulting to None:
    • None (default): provider's default behavior preserved (backward compatible)
    • True: explicitly pass enable_thinking=true
    • False: explicitly pass enable_thinking=false (protects JSON output tasks)
  2. OpenAILLM.generate() — extracted _build_request_body() helper; passes enable_thinking to request body when configured
  3. OpenAILLM.generate_stream() — same enable_thinking injection
  4. AzureLLM.generate() / generate_stream() — added getattr-gated support for enable_thinking (future-proof)
  5. Per-call overridekwargs["enable_thinking"] takes precedence over config-level setting
  6. Comprehensive tests in tests/llms/test_enable_thinking.py: default, config-level, kwarg-level, False-value, param-preservation

Before (provider default)

After (configurable)

Type of change

  • Bug fix (non-breaking — prevents JSON output breakage when thinking is unwanted)
  • New feature (non-breaking — adds configurable parameter)

How Has This Been Tested?

  • python3 -m py_compile src/memos/configs/llm.py
  • python3 -m py_compile src/memos/llms/openai.py
  • python3 -m py_compile tests/llms/test_enable_thinking.py
  • Logic verification: enable_thinking=None → param omitted; True/False → param present; kwarg override wins
  • Zero behavior change when enable_thinking is not set (backward compatible)

Checklist

@Memtensor-AI Memtensor-AI added area:core MOS 编排层 / 框架底座 / 跨模块问题 area:model llm + embedder + reranker status:in-progress Someone or AI is working on it | 人工或 AI 正在处理 labels Jul 29, 2026
@Memtensor-AI

Memtensor-AI commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

🤖 Open Code Review

Target: PR #2181
Task: 6974f526c003764f
Base: dev-v2.0.30
Head: feat/issue-2149-enable-thinking

🔍 OpenCodeReview found 1 issue(s) in this PR.


1. src/memos/llms/openai.py (L27)

If extra_body is a non-None, non-dict value (e.g., a list or string passed via kwargs) and enable_thinking is not None, the dict-unpack {**(extra_body or {}), ...} will raise a TypeError at runtime because extra_body or {} returns the truthy non-dict object, and **non_dict is invalid.

Although extra_body is None by default in the config, callers can pass arbitrary values via kwargs. Adding a type guard makes the failure explicit and early.

Suggested fix:

def _merge_enable_thinking(extra_body: Any, enable_thinking: bool | None) -> Any:
    if enable_thinking is None:
        return extra_body
    if extra_body is not None and not isinstance(extra_body, dict):
        raise TypeError(f"extra_body must be a dict or None, got {type(extra_body).__name__!r}")
    return {**(extra_body or {}), "enable_thinking": enable_thinking}

Generated by cloud-assistant via Open Code Review.

@Memtensor-AI

Copy link
Copy Markdown
Collaborator

⚠️ Automated Test Results: INCONCLUSIVE

Automated tests inconclusive (auto-generated test defect); treated as non-blocking. Manual review recommended. Details: The test helper _make_config() passes a provider='openai' field to OpenAILLMConfig, but that config class forbids extra inputs, causing all 8 tests to fail at construction time. [advisory, non-gating] AI-generated tests on branch test/auto-gen-afe461593e5cdcda-20260729091129: 13/58 passed, 45 failed — these do NOT affect the PR verdict; review the branch manually.

Branch: feat/issue-2149-enable-thinking

@RerankerGuo
RerankerGuo force-pushed the feat/issue-2149-enable-thinking branch from 07ba039 to 59e98be Compare July 30, 2026 00:54
@Memtensor-AI

Copy link
Copy Markdown
Collaborator

⚠️ Automated Test Results: INCONCLUSIVE

Automated tests inconclusive (auto-generated test defect); treated as non-blocking. Manual review recommended. Details: The new test file passes a provider='openai' field to OpenAILLMConfig, but that field is not permitted by the Pydantic model, causing all 8 tests to fail at construction time before exercising the actual enable_thinking logic. [advisory, non-gating] AI-generated tests on branch test/auto-gen-58710bc92f74b814-20260730090707: 98/98 passed — these do NOT affect the PR verdict; review the branch manually.

Branch: feat/issue-2149-enable-thinking

@Memtensor-AI

Copy link
Copy Markdown
Collaborator

⚠️ Automated Test Results: INCONCLUSIVE

Automated tests inconclusive (auto-generated test defect); treated as non-blocking. Manual review recommended. Details: The AI-generated test passes an unsupported provider='openai' field into OpenAILLMConfig, which rejects extra inputs via Pydantic's extra_forbidden validation. [advisory, non-gating] AI-generated tests on branch test/auto-gen-89dcf9c3ff4eea6b-20260731100701: 158/158 passed — these do NOT affect the PR verdict; review the branch manually.

Branch: feat/issue-2149-enable-thinking

Closes MemTensor#2149

Adds enable_thinking configuration parameter for OpenAI-compatible
providers (qwen, deepseek, minimax) to control whether the model
produces <think> reasoning blocks before the actual response.

Changes:
- Added enable_thinking field (bool | None) to OpenAILLMConfig
  - None (default): preserve provider's default behavior
  - True: explicitly enable thinking mode
  - False: explicitly disable thinking mode (prevents JSON breaking)
- OpenAILLM.generate / generate_stream now pass enable_thinking
  to API calls when configured, via enable_thinking body param
- AzureLLM.generate / generate_stream also support the parameter
  (gated by getattr for backward compatibility with older configs)
- Per-call override supported via kwargs (enable_thinking=True/False)
- Added tests covering default, config-level, and kwarg-level
  enable_thinking behavior in test_enable_thinking.py

This is a non-breaking change: when enable_thinking is unset,
request bodies are identical to previous versions.

Test: python3 -m py_compile src/memos/configs/llm.py
Test: python3 -m py_compile src/memos/llms/openai.py
Test: python3 -m py_compile tests/llms/test_enable_thinking.py
@RerankerGuo
RerankerGuo force-pushed the feat/issue-2149-enable-thinking branch from 08760c2 to 333852d Compare August 5, 2026 05:24
@Memtensor-AI

Copy link
Copy Markdown
Collaborator

✅ Automated Test Results: PASSED

All tests passed (14/14 executed). memos_python_core/changed-repo-python: 14/14. Duration: 5s [advisory, non-gating] AI-generated tests on branch test/auto-gen-03457e832d3ea1fa-20260805133404: 87/129 passed, 42 failed — these do NOT affect the PR verdict; review the branch manually.

Branch: feat/issue-2149-enable-thinking

@Memtensor-AI Memtensor-AI added status:ready Ready for implementation; waiting for assignee or AI dispatch | 可进入实现,等待认领或派发 and removed status:in-progress Someone or AI is working on it | 人工或 AI 正在处理 labels Aug 5, 2026
@endxxxx

endxxxx commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Thanks for adding enable_thinking support. I tested this PR against multiple versions of the official openai-python SDK and found a compatibility issue in the current implementation.

Problem

The PR currently passes enable_thinking as a top-level SDK argument:

client.chat.completions.create(
    ...,
    enable_thinking=False,
)

However, enable_thinking is a provider-specific extension used by OpenAI-compatible services such as Qwen and DeepSeek. It is not a declared parameter of the official OpenAI Python SDK's Completions.create() method.

I tested the following SDK versions:

openai-python Top-level enable_thinking extra_body
1.77.0 ❌ TypeError ✅ Works
1.97.0 ❌ TypeError ✅ Works
2.48.0 ❌ TypeError ✅ Works

The exact error is:

TypeError: Completions.create() got an unexpected keyword argument 'enable_thinking'

Version 1.77.0 is especially relevant because it is the minimum version currently allowed by MemOS:

openai = ">=1.77.0,<2.0.0"

Therefore, using an older official SDK does not make the current implementation work. It might only work with a vendor-specific fork or wrapper that accepts arbitrary keyword arguments.

The current tests mock chat.completions.create, so they verify that the keyword reaches the mock but do not exercise the real SDK method signature. This is why the issue is not detected.

Suggested implementation

Provider-specific parameters should be merged into the SDK-supported extra_body argument:

def _merge_enable_thinking(extra_body, enable_thinking):
    if enable_thinking is None:
        return extra_body

    return {
        **(extra_body or {}),
        "enable_thinking": enable_thinking,
    }

Then use:

client.chat.completions.create(
    ...,
    extra_body=_merge_enable_thinking(extra_body, enable_thinking),
)

The SDK will merge extra_body into the final HTTP request, so the provider still receives:

{
  "enable_thinking": false
}

This approach also:

  • preserves existing extra_body parameters;
  • supports both True and False;
  • omits the parameter when configured as None;
  • allows per-call overrides to take precedence;
  • works for streaming and non-streaming requests;
  • works with official openai-python 1.77.0, 1.97.0, and 2.48.0.

Azure configuration

AzureLLM also accesses self.config.extra_body and attempts to support enable_thinking, but AzureLLMConfig currently declares neither field.

I suggest adding:

class AzureLLMConfig(BaseLLMConfig):
    # Existing fields...

    extra_body: Any = Field(
        default=None,
        description="Extra request body parameters",
    )
    enable_thinking: bool | None = Field(
        default=None,
        description=(
            "Enable or disable thinking mode. "
            "When None, preserve the provider's default behavior."
        ),
    )

Without this, Pydantic rejects an Azure configuration containing enable_thinking.

@Memtensor-AI Memtensor-AI added status:in-progress Someone or AI is working on it | 人工或 AI 正在处理 and removed status:ready Ready for implementation; waiting for assignee or AI dispatch | 可进入实现,等待认领或派发 labels Aug 11, 2026
@Memtensor-AI

Copy link
Copy Markdown
Collaborator

✅ Automated Test Results: PASSED

All tests passed (19/19 executed). memos_python_core/changed-repo-python: 19/19. Duration: 6s

Branch: feat/issue-2149-enable-thinking

Exercise the independent Azure streaming request-body path with a per-call enable_thinking override and an existing vendor extra_body option.

Test: .venv/bin/python -m pytest tests/llms/test_enable_thinking.py tests/configs/test_llm.py -q\nTest: .venv/bin/python -m pytest tests/llms -q --ignore=tests/llms/test_hf.py\nTest: .venv/bin/python -m pytest tests/configs -q
@RerankerGuo

RerankerGuo commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @endxxxx for testing the official SDK versions and for pushing 355d7583.

I kept that production fix unchanged and verified the compatibility boundary directly:

  • openai-python 1.77.0 rejects top-level enable_thinking and accepts extra_body;
  • the local SDK (1.109.1) has the same signature behavior;
  • the config-level value, per-call override, existing extra_body, and True/False/None behavior are covered;
  • OpenAI generate/stream and Azure generate serialize enable_thinking into the final HTTP body;
  • 1e3411f7 adds the remaining Azure streaming HTTP-body regression, including a per-call False override while preserving another vendor-specific extra_body option.

Verification:

  • .venv/bin/python -m pytest tests/llms/test_enable_thinking.py tests/configs/test_llm.py -q -> 20 passed
  • .venv/bin/python -m pytest tests/configs -q -> 37 passed
  • .venv/bin/python -m pytest tests/llms -q --ignore=tests/llms/test_hf.py -> 28 passed
  • Ruff check and format check pass for all four PR files
  • the full LLM directory only requires excluding test_hf.py because this local environment does not install the optional torch dependency

CI update for run 31556416899:

  • all 8 macOS and all 4 Windows jobs passed;
  • all 4 Ubuntu jobs stopped before build, Ruff, or tests in scripts/check_dependencies.py on the unchanged src/memos/embedders/cache.py top-level cachetools import;
  • this PR does not modify cache.py, pyproject.toml, poetry.lock, or the dependency checker;
  • Open Code Review passed with one non-blocking finding.

I have kept the unrelated core/optional dependency issue out of this PR.

@Memtensor-AI

Copy link
Copy Markdown
Collaborator

✅ Automated Test Results: PASSED

All tests passed (20/20 executed). memos_python_core/changed-repo-python: 20/20. Duration: 5s [advisory, non-gating] AI-generated tests on branch test/auto-gen-7f506e4e6f76a1a9-20260812102405: 35/35 passed — these do NOT affect the PR verdict; review the branch manually.

Branch: feat/issue-2149-enable-thinking

@endxxxx
endxxxx changed the base branch from main to dev-v2.0.30 August 13, 2026 08:35
@Memtensor-AI

Copy link
Copy Markdown
Collaborator

✅ Automated Test Results: PASSED

All tests passed (20/20 executed). memos_python_core/changed-repo-python: 20/20. Duration: 6s [advisory, non-gating] AI-generated tests on branch test/auto-gen-6974f526c003764f-20260813165549: 172/172 passed — these do NOT affect the PR verdict; review the branch manually.

Branch: feat/issue-2149-enable-thinking

@Memtensor-AI Memtensor-AI added status:ready Ready for implementation; waiting for assignee or AI dispatch | 可进入实现,等待认领或派发 and removed status:in-progress Someone or AI is working on it | 人工或 AI 正在处理 labels Aug 13, 2026
@endxxxx
endxxxx merged commit 616e744 into MemTensor:dev-v2.0.30 Aug 13, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core MOS 编排层 / 框架底座 / 跨模块问题 area:model llm + embedder + reranker status:ready Ready for implementation; waiting for assignee or AI dispatch | 可进入实现,等待认领或派发

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] openai_compatible provider: support enableThinking parameter for thinking-capable models (Qwen3, DeepSeek-R1)

6 participants