From ac2dded35ba0ae3e1eea86c35a73626282851eb2 Mon Sep 17 00:00:00 2001 From: curo1305 Date: Fri, 5 Jun 2026 00:00:26 +0200 Subject: [PATCH] fix(celery+lmstudio): PYTHONPATH for forked workers + qwen3.5-9b thinking model support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three root causes fixed: 1. docker-compose.yml — celery-worker and celery-beat missing PYTHONPATH=/app ForkPoolWorker processes inherit sys.path with '' (CWD), but fork can change CWD so lazy imports like `from db.session import ...` raised ModuleNotFoundError. Adding PYTHONPATH=/app ensures /app is always explicit in the path. 2. provider_config.py — lmstudio SUPPORTS_JSON_MODE set to False LM Studio only accepts response_format type 'json_schema' or 'text', not 'json_object'. GenericOpenAIProvider now omits response_format for lmstudio and relies on parse_classification() fallback (same path as Gemini). 3. generic_openai_provider.py — max_tokens raised 1024→4096 (classify) / 256→1024 (suggest) Qwen3 thinking models (like qwen/qwen3.5-9b) consume reasoning tokens from the same budget. With max_tokens=1024 the model exhausts the budget in the thinking trace, leaving content=''. 4096 gives room for both thinking and the JSON output. Also updates lmstudio PROVIDER_DEFAULTS model to qwen/qwen3.5-9b (confirmed loaded in LM Studio) and context_chars to 32_000 (Qwen3 context window). Tested: classify(invoice_text, ['Finance','Legal','HR']) → topics=['Finance'] ✓ Backend tests: 376 passed ✓ Co-Authored-By: Claude Sonnet 4.6 --- backend/ai/generic_openai_provider.py | 4 ++-- backend/ai/provider_config.py | 13 +++++++------ docker-compose.yml | 2 ++ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/backend/ai/generic_openai_provider.py b/backend/ai/generic_openai_provider.py index be51272..d18d02e 100644 --- a/backend/ai/generic_openai_provider.py +++ b/backend/ai/generic_openai_provider.py @@ -60,7 +60,7 @@ class GenericOpenAIProvider(OpenAIProvider): ) create_kwargs = dict( model=self._model, - max_tokens=1024, + max_tokens=4096, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_msg}, @@ -87,7 +87,7 @@ class GenericOpenAIProvider(OpenAIProvider): ) create_kwargs = dict( model=self._model, - max_tokens=256, + max_tokens=1024, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_msg}, diff --git a/backend/ai/provider_config.py b/backend/ai/provider_config.py index 020e0f3..244443c 100644 --- a/backend/ai/provider_config.py +++ b/backend/ai/provider_config.py @@ -83,16 +83,17 @@ PROVIDER_DEFAULTS: dict[str, dict] = { }, "lmstudio": { "base_url": "http://host.docker.internal:1234/v1", - "model": "gemma-4-e4b-it", - "context_chars": 8_000, + "model": "qwen/qwen3.5-9b", + "context_chars": 32_000, }, } # Whether the provider honours response_format={"type": "json_object"}. # Gemini's OpenAI-compat endpoint does NOT support the string form (D-02/D-03). -# Ollama and LMStudio accept the parameter but some models ignore it — the -# GenericOpenAIProvider always wraps the raw response with parse_classification() -# regardless, so they are left as True (the parameter is still sent). +# LMStudio only accepts "json_schema" or "text" (not "json_object") — set False +# so GenericOpenAIProvider omits response_format and relies on parse_classification(). +# Ollama accepts the parameter but some models ignore it — left True; the +# GenericOpenAIProvider always wraps the raw response with parse_classification(). SUPPORTS_JSON_MODE: dict[str, bool] = { "openai": True, "anthropic": True, @@ -103,5 +104,5 @@ SUPPORTS_JSON_MODE: dict[str, bool] = { "openrouter": True, "mistral": True, "ollama": True, - "lmstudio": True, + "lmstudio": False, } diff --git a/docker-compose.yml b/docker-compose.yml index 9cbc1cf..3c625d7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -102,6 +102,7 @@ services: - CLOUD_CREDS_KEY=${CLOUD_CREDS_KEY} - SECRET_KEY=${SECRET_KEY} - PYTHONDONTWRITEBYTECODE=1 + - PYTHONPATH=/app labels: logging: "promtail" volumes: @@ -134,6 +135,7 @@ services: - MINIO_BUCKET=${MINIO_BUCKET} - REDIS_URL=${REDIS_URL} - PYTHONDONTWRITEBYTECODE=1 + - PYTHONPATH=/app extra_hosts: - "host.docker.internal:host-gateway" command: celery -A celery_app beat --loglevel=info --schedule /tmp/celerybeat-schedule