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

View File

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