feat(config): increase reasoning cache defaults (#17)

main
Yixing Lao 2026-04-26 19:11:55 +08:00 committed by GitHub
parent 3727105b65
commit b72d12f708
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 115 additions and 81 deletions

View File

@ -1,23 +0,0 @@
# This file was created automatically at ~/.deepseek-cursor-proxy/config.yaml.
# API keys are read from Cursor's Authorization header and forwarded upstream.
# `model` is the fallback when a request has no model; Cursor's requested
# DeepSeek model name is otherwise respected.
base_url: https://api.deepseek.com
model: deepseek-v4-pro
thinking: enabled
reasoning_effort: high
display_reasoning: true
host: 127.0.0.1
port: 9000
ngrok: true
verbose: false
request_timeout: 300
max_request_body_bytes: 20971520
cors: false
reasoning_content_path: reasoning_content.sqlite3
missing_reasoning_strategy: recover
reasoning_cache_max_age_seconds: 604800
reasoning_cache_max_rows: 10000

View File

@ -14,6 +14,23 @@ REASONING_CONTENT_FILE_NAME = "reasoning_content.sqlite3"
TRUE_VALUES = {"1", "true", "yes", "on"}
FALSE_VALUES = {"0", "false", "no", "off"}
MISSING = object()
DEFAULT_HOST = "127.0.0.1"
DEFAULT_PORT = 9000
DEFAULT_UPSTREAM_BASE_URL = "https://api.deepseek.com"
DEFAULT_UPSTREAM_MODEL = "deepseek-v4-pro"
DEFAULT_THINKING = "enabled"
DEFAULT_REASONING_EFFORT = "high"
DEFAULT_CURSOR_DISPLAY_REASONING = True
DEFAULT_NGROK = True
DEFAULT_VERBOSE = False
DEFAULT_REQUEST_TIMEOUT = 300.0
DEFAULT_MAX_REQUEST_BODY_BYTES = 20 * 1024 * 1024
DEFAULT_CORS = False
DEFAULT_MISSING_REASONING_STRATEGY = "recover"
DEFAULT_REASONING_CACHE_MAX_AGE_SECONDS = 30 * 24 * 60 * 60
DEFAULT_REASONING_CACHE_MAX_ROWS = 100_000
DEFAULT_CONFIG_HEADER = (
"# This file was created automatically at ~/.deepseek-cursor-proxy/config.yaml."
)
@ -22,24 +39,24 @@ DEFAULT_CONFIG_TEXT = f"""{DEFAULT_CONFIG_HEADER}
# `model` is the fallback when a request has no model; Cursor's requested
# DeepSeek model name is otherwise respected.
base_url: https://api.deepseek.com
model: deepseek-v4-pro
thinking: enabled
reasoning_effort: high
display_reasoning: true
base_url: {DEFAULT_UPSTREAM_BASE_URL}
model: {DEFAULT_UPSTREAM_MODEL}
thinking: {DEFAULT_THINKING}
reasoning_effort: {DEFAULT_REASONING_EFFORT}
display_reasoning: {str(DEFAULT_CURSOR_DISPLAY_REASONING).lower()}
host: 127.0.0.1
port: 9000
ngrok: true
verbose: false
request_timeout: 300
max_request_body_bytes: 20971520
cors: false
host: {DEFAULT_HOST}
port: {DEFAULT_PORT}
ngrok: {str(DEFAULT_NGROK).lower()}
verbose: {str(DEFAULT_VERBOSE).lower()}
request_timeout: {DEFAULT_REQUEST_TIMEOUT:g}
max_request_body_bytes: {DEFAULT_MAX_REQUEST_BODY_BYTES}
cors: {str(DEFAULT_CORS).lower()}
reasoning_content_path: reasoning_content.sqlite3
missing_reasoning_strategy: recover
reasoning_cache_max_age_seconds: 604800
reasoning_cache_max_rows: 10000
reasoning_content_path: {REASONING_CONTENT_FILE_NAME}
missing_reasoning_strategy: {DEFAULT_MISSING_REASONING_STRATEGY}
reasoning_cache_max_age_seconds: {DEFAULT_REASONING_CACHE_MAX_AGE_SECONDS}
reasoning_cache_max_rows: {DEFAULT_REASONING_CACHE_MAX_ROWS}
"""
@ -144,39 +161,39 @@ def settings_from_config(
def normalize_thinking(value: Any) -> str:
thinking = as_str(value, "enabled").strip().lower()
thinking = as_str(value, DEFAULT_THINKING).strip().lower()
if thinking in {"passthrough", "pass-through", "pass_through"}:
return "pass-through"
if thinking in {"enabled", "disabled"}:
return thinking
return "enabled"
return DEFAULT_THINKING
def normalize_missing_reasoning_strategy(value: Any) -> str:
strategy = as_str(value, "recover").strip().lower()
strategy = as_str(value, DEFAULT_MISSING_REASONING_STRATEGY).strip().lower()
if strategy in {"recover", "reject"}:
return strategy
return "recover"
return DEFAULT_MISSING_REASONING_STRATEGY
@dataclass(frozen=True)
class ProxyConfig:
host: str = "127.0.0.1"
port: int = 9000
upstream_base_url: str = "https://api.deepseek.com"
upstream_model: str = "deepseek-v4-pro"
thinking: str = "enabled"
reasoning_effort: str = "high"
request_timeout: float = 300.0
max_request_body_bytes: int = 20 * 1024 * 1024
host: str = DEFAULT_HOST
port: int = DEFAULT_PORT
upstream_base_url: str = DEFAULT_UPSTREAM_BASE_URL
upstream_model: str = DEFAULT_UPSTREAM_MODEL
thinking: str = DEFAULT_THINKING
reasoning_effort: str = DEFAULT_REASONING_EFFORT
request_timeout: float = DEFAULT_REQUEST_TIMEOUT
max_request_body_bytes: int = DEFAULT_MAX_REQUEST_BODY_BYTES
reasoning_content_path: Path = field(default_factory=default_reasoning_content_path)
missing_reasoning_strategy: str = "recover"
reasoning_cache_max_age_seconds: int = 7 * 24 * 60 * 60
reasoning_cache_max_rows: int = 10000
cursor_display_reasoning: bool = True
cors: bool = False
verbose: bool = False
ngrok: bool = False
missing_reasoning_strategy: str = DEFAULT_MISSING_REASONING_STRATEGY
reasoning_cache_max_age_seconds: int = DEFAULT_REASONING_CACHE_MAX_AGE_SECONDS
reasoning_cache_max_rows: int = DEFAULT_REASONING_CACHE_MAX_ROWS
cursor_display_reasoning: bool = DEFAULT_CURSOR_DISPLAY_REASONING
cors: bool = DEFAULT_CORS
verbose: bool = DEFAULT_VERBOSE
ngrok: bool = DEFAULT_NGROK
@classmethod
def from_file(
@ -189,32 +206,32 @@ class ProxyConfig:
return cls(
host=as_str(
setting_value(settings, "host"),
"127.0.0.1",
DEFAULT_HOST,
),
port=as_int(
setting_value(settings, "port"),
9000,
DEFAULT_PORT,
),
upstream_base_url=as_str(
setting_value(settings, "base_url"),
"https://api.deepseek.com",
DEFAULT_UPSTREAM_BASE_URL,
).rstrip("/"),
upstream_model=as_str(
setting_value(settings, "model"),
"deepseek-v4-pro",
DEFAULT_UPSTREAM_MODEL,
),
thinking=normalize_thinking(setting_value(settings, "thinking")),
reasoning_effort=as_str(
setting_value(settings, "reasoning_effort"),
"high",
DEFAULT_REASONING_EFFORT,
),
request_timeout=as_float(
setting_value(settings, "request_timeout"),
300.0,
DEFAULT_REQUEST_TIMEOUT,
),
max_request_body_bytes=as_int(
setting_value(settings, "max_request_body_bytes"),
20 * 1024 * 1024,
DEFAULT_MAX_REQUEST_BODY_BYTES,
),
reasoning_content_path=as_path(
setting_value(settings, "reasoning_content_path"),
@ -226,26 +243,26 @@ class ProxyConfig:
),
reasoning_cache_max_age_seconds=as_int(
setting_value(settings, "reasoning_cache_max_age_seconds"),
7 * 24 * 60 * 60,
DEFAULT_REASONING_CACHE_MAX_AGE_SECONDS,
),
reasoning_cache_max_rows=as_int(
setting_value(settings, "reasoning_cache_max_rows"),
10000,
DEFAULT_REASONING_CACHE_MAX_ROWS,
),
cursor_display_reasoning=as_bool(
setting_value(settings, "display_reasoning"),
True,
DEFAULT_CURSOR_DISPLAY_REASONING,
),
cors=as_bool(
setting_value(settings, "cors"),
False,
DEFAULT_CORS,
),
verbose=as_bool(
setting_value(settings, "verbose"),
False,
DEFAULT_VERBOSE,
),
ngrok=as_bool(
setting_value(settings, "ngrok"),
False,
DEFAULT_NGROK,
),
)

View File

@ -8,6 +8,14 @@ import unittest
from unittest.mock import patch
from deepseek_cursor_proxy.config import (
DEFAULT_MISSING_REASONING_STRATEGY,
DEFAULT_NGROK,
DEFAULT_PORT,
DEFAULT_REASONING_CACHE_MAX_AGE_SECONDS,
DEFAULT_REASONING_CACHE_MAX_ROWS,
DEFAULT_THINKING,
DEFAULT_UPSTREAM_MODEL,
DEFAULT_VERBOSE,
ProxyConfig,
default_config_path,
default_reasoning_content_path,
@ -30,6 +38,7 @@ class ConfigTests(unittest.TestCase):
ProxyConfig().reasoning_content_path,
home / ".deepseek-cursor-proxy" / "reasoning_content.sqlite3",
)
self.assertEqual(ProxyConfig().ngrok, DEFAULT_NGROK)
def test_missing_default_config_file_is_populated(self) -> None:
with TemporaryDirectory() as temp_dir:
@ -39,17 +48,37 @@ class ConfigTests(unittest.TestCase):
config = ProxyConfig.from_file(config_path=None)
config_path = default_config_path()
config_text = config_path.read_text(encoding="utf-8")
self.assertTrue(config_path.exists())
self.assertIn(f"model: {DEFAULT_UPSTREAM_MODEL}", config_text)
self.assertIn(
"model: deepseek-v4-pro", config_path.read_text(encoding="utf-8")
f"missing_reasoning_strategy: {DEFAULT_MISSING_REASONING_STRATEGY}",
config_text,
)
self.assertIn(
"missing_reasoning_strategy: recover",
config_path.read_text(encoding="utf-8"),
"reasoning_cache_max_age_seconds: "
f"{DEFAULT_REASONING_CACHE_MAX_AGE_SECONDS}",
config_text,
)
self.assertIn(
f"reasoning_cache_max_rows: {DEFAULT_REASONING_CACHE_MAX_ROWS}",
config_text,
)
self.assertIn(f"ngrok: {str(DEFAULT_NGROK).lower()}", config_text)
self.assertEqual(stat.S_IMODE(config_path.stat().st_mode), 0o600)
self.assertEqual(config.upstream_model, "deepseek-v4-pro")
self.assertEqual(config.missing_reasoning_strategy, "recover")
self.assertEqual(config.upstream_model, DEFAULT_UPSTREAM_MODEL)
self.assertEqual(config.ngrok, DEFAULT_NGROK)
self.assertEqual(
config.missing_reasoning_strategy, DEFAULT_MISSING_REASONING_STRATEGY
)
self.assertEqual(
config.reasoning_cache_max_age_seconds,
DEFAULT_REASONING_CACHE_MAX_AGE_SECONDS,
)
self.assertEqual(
config.reasoning_cache_max_rows, DEFAULT_REASONING_CACHE_MAX_ROWS
)
def test_missing_explicit_config_file_is_not_populated(self) -> None:
with TemporaryDirectory() as temp_dir:
@ -58,7 +87,15 @@ class ConfigTests(unittest.TestCase):
config = ProxyConfig.from_file(config_path=config_path)
self.assertFalse(config_path.exists())
self.assertEqual(config.upstream_model, "deepseek-v4-pro")
self.assertEqual(config.upstream_model, DEFAULT_UPSTREAM_MODEL)
self.assertEqual(config.ngrok, DEFAULT_NGROK)
self.assertEqual(
config.reasoning_cache_max_age_seconds,
DEFAULT_REASONING_CACHE_MAX_AGE_SECONDS,
)
self.assertEqual(
config.reasoning_cache_max_rows, DEFAULT_REASONING_CACHE_MAX_ROWS
)
def test_loads_config_from_user_yaml_file(self) -> None:
with TemporaryDirectory() as temp_dir:
@ -124,10 +161,13 @@ class ConfigTests(unittest.TestCase):
config = ProxyConfig.from_file(config_path=config_path)
self.assertEqual(config.thinking, "enabled")
self.assertEqual(config.missing_reasoning_strategy, "recover")
self.assertEqual(config.port, 9000)
self.assertFalse(config.verbose)
self.assertEqual(config.thinking, DEFAULT_THINKING)
self.assertEqual(
config.missing_reasoning_strategy, DEFAULT_MISSING_REASONING_STRATEGY
)
self.assertEqual(config.port, DEFAULT_PORT)
self.assertEqual(config.ngrok, DEFAULT_NGROK)
self.assertEqual(config.verbose, DEFAULT_VERBOSE)
def test_relative_reasoning_content_path_in_config_is_relative_to_config_file(
self,