fix(memhub): turn model thinking off on the SDK write path

Profiles default to disable_thinking, but build_sdk_config never told
the SDK. DeepSeek Flash spent the SDK's 2000 agent_max_tokens thinking
and answered finish_reason=length with no content (EMPTY_RESPONSE).

The provider mapping moves into disable_thinking_params, shared by
attach_disable_thinking and the SDK's llm.extra_body. The SDK sends
extra_body on every chat call, and the coding/working writers build
their LLMProvider from the same llm config.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
alvinfeiandClaude Opus 5.5 committed 2026-10-27 23:37:09 +08:00
1 parent 423cb00dbf
commit ad05303d23
3 files changed
+132 -16

No files matched your search

@@ -36,7 +36,11 @@ from one_memory.hy_graph_admin import (
)
from one_memory.hy_sdk_types import HySdkRuntimeError
from one_memory.local_embedding import LocalEmbedder
from one_memory.model_profiles import ModelProfileSnapshot, ProviderKind
from one_memory.model_profiles import (
ModelProfileSnapshot,
ProviderKind,
disable_thinking_params,
)
_RUNTIME_LOCK = threading.RLock()
BUILTIN_RETRIEVAL_PROFILE_ID = "builtin-retrieval"
@@ -244,14 +248,22 @@ def build_sdk_config(
embedding_provider, embedding_endpoint, embedding_model, dimensions
)
llm: dict[str, Any] = {
"provider": "openai",
"model": profile.model,
"api_key": generation_key,
"base_url": generation_endpoint,
}
# The SDK sends `extra_body` on every chat call, chat and coding/working
# alike. Without it DeepSeek spent the whole agent_max_tokens on thinking
# and answered finish_reason=length with no content.
thinking_off = disable_thinking_params(profile)
if thinking_off:
llm["extra_body"] = thinking_off
return MemoryConfig.from_dict(
{
"llm": {
"provider": "openai",
"model": profile.model,
"api_key": generation_key,
"base_url": generation_endpoint,
},
"llm": llm,
"embedder": {
"provider": "openai",
"model": (
@@ -1136,20 +1136,31 @@ class ModelProfileStore:
)
def disable_thinking_params(
profile: ModelProfile | ModelProfileSnapshot,
) -> dict[str, object]:
"""Provider-specific thinking-off params, empty unless the profile opts in.
The one mapping for every request Core builds itself and for the SDK's
`llm.extra_body`, so a model that thinks on one path thinks on none.
"""
if not profile.disable_thinking:
return {}
if profile.provider_kind is ProviderKind.OPENAI_COMPATIBLE:
if urlparse(profile.base_url or "").hostname == "api.deepseek.com":
return {"thinking": {"type": "disabled"}}
return {"reasoning_effort": "none"}
if profile.provider_kind is ProviderKind.OLLAMA:
return {"think": False}
return {}
def attach_disable_thinking(
profile: ModelProfile | ModelProfileSnapshot,
payload: dict[str, object],
) -> dict[str, object]:
"""Attach provider-specific thinking-off params when the profile opts in."""
if not profile.disable_thinking:
return payload
if profile.provider_kind is ProviderKind.OPENAI_COMPATIBLE:
if urlparse(profile.base_url or "").hostname == "api.deepseek.com":
payload["thinking"] = {"type": "disabled"}
else:
payload["reasoning_effort"] = "none"
elif profile.provider_kind is ProviderKind.OLLAMA:
payload["think"] = False
payload.update(disable_thinking_params(profile))
return payload
@@ -8,6 +8,7 @@ import json
import httpx
from pydantic import SecretStr
from one_memory.hy_sdk_runtime import build_sdk_config
from one_memory.imports.models import (
EvidenceInput,
EvidenceOrigin,
@@ -17,6 +18,7 @@ from one_memory.imports.models import (
from one_memory.imports.storage import EvidenceStore
from one_memory.memory_batch_extraction import MemoryBatchExtractor
from one_memory.memory_qa import _answer_request
from one_memory.model_probe import _structured_request
from one_memory.model_profiles import (
ModelProfileInput,
ModelProfileStore,
@@ -243,3 +245,94 @@ def test_ollama_batch_sends_think_false_only_when_disabled(tmp_path) -> None:
)
assert payloads[0]["think"] is False
assert "reasoning_effort" not in payloads[0]
def _deepseek(store, secrets, *, disable_thinking: bool):
return store.create(
ModelProfileInput(
name=f"deepseek-{disable_thinking}",
provider_kind=ProviderKind.OPENAI_COMPATIBLE,
base_url="https://api.deepseek.com/v1",
model="deepseek-v4-flash",
api_key=SecretStr("sk-test"),
embedding_model="embedding-model",
embedding_provider_kind=ProviderKind.LOCAL_ONNX,
disable_thinking=disable_thinking,
),
secrets,
)
def test_sdk_config_turns_deepseek_thinking_off_like_the_probe(tmp_path) -> None:
"""DeepSeek Flash spent the SDK's 2000 agent tokens thinking: EMPTY_RESPONSE.
The SDK sends `llm.extra_body` on every chat call (chat and the shared
coding/working provider), and the probe must ask the model the same way.
"""
database = tmp_path / "one-memory.sqlite3"
secrets = InMemorySecretStore()
store = ModelProfileStore(database)
profile = _deepseek(store, secrets, disable_thinking=True)
snapshot = store.snapshot(profile.profile_id, profile.revision)
config = build_sdk_config(snapshot, api_key="sk-test", data_dir=tmp_path / "data")
assert config.llm.extra_body == {"thinking": {"type": "disabled"}}
_m, _u, _h, probe = _structured_request(profile, "sk-test")
assert probe["thinking"] == config.llm.extra_body["thinking"]
assert "reasoning_effort" not in probe
def test_sdk_config_sends_no_thinking_fields_unless_the_profile_opts_in(tmp_path) -> None:
database = tmp_path / "one-memory.sqlite3"
secrets = InMemorySecretStore()
store = ModelProfileStore(database)
profile = _deepseek(store, secrets, disable_thinking=False)
snapshot = store.snapshot(profile.profile_id, profile.revision)
config = build_sdk_config(snapshot, api_key="sk-test", data_dir=tmp_path / "data")
assert not config.llm.extra_body
def test_sdk_config_maps_other_providers_through_the_same_table(tmp_path) -> None:
database = tmp_path / "one-memory.sqlite3"
secrets = InMemorySecretStore()
store = ModelProfileStore(database)
openai = store.create(
ModelProfileInput(
name="openai",
provider_kind=ProviderKind.OPENAI_COMPATIBLE,
base_url="https://api.example.com/v1",
model="gpt-test",
api_key=SecretStr("sk-test"),
embedding_model="embedding-model",
embedding_provider_kind=ProviderKind.LOCAL_ONNX,
),
secrets,
)
ollama = store.create(
ModelProfileInput(
name="ollama",
provider_kind=ProviderKind.OLLAMA,
base_url="http://127.0.0.1:11434",
model="qwen",
embedding_model="embedding-model",
embedding_provider_kind=ProviderKind.LOCAL_ONNX,
),
secrets,
)
openai_config = build_sdk_config(
store.snapshot(openai.profile_id, openai.revision),
api_key="sk-test",
data_dir=tmp_path / "data",
)
ollama_config = build_sdk_config(
store.snapshot(ollama.profile_id, ollama.revision),
api_key=None,
data_dir=tmp_path / "data",
)
assert openai_config.llm.extra_body == {"reasoning_effort": "none"}
assert ollama_config.llm.extra_body == {"think": False}