feat(config): increase reasoning cache defaults (#17)
parent
3727105b65
commit
b72d12f708
|
|
@ -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
|
|
||||||
|
|
@ -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,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue