fix(celery+lmstudio): PYTHONPATH for forked workers + qwen3.5-9b thinking model support
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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
ca43e653aa
commit
ac2dded35b
@@ -60,7 +60,7 @@ class GenericOpenAIProvider(OpenAIProvider):
|
|||||||
)
|
)
|
||||||
create_kwargs = dict(
|
create_kwargs = dict(
|
||||||
model=self._model,
|
model=self._model,
|
||||||
max_tokens=1024,
|
max_tokens=4096,
|
||||||
messages=[
|
messages=[
|
||||||
{"role": "system", "content": system_prompt},
|
{"role": "system", "content": system_prompt},
|
||||||
{"role": "user", "content": user_msg},
|
{"role": "user", "content": user_msg},
|
||||||
@@ -87,7 +87,7 @@ class GenericOpenAIProvider(OpenAIProvider):
|
|||||||
)
|
)
|
||||||
create_kwargs = dict(
|
create_kwargs = dict(
|
||||||
model=self._model,
|
model=self._model,
|
||||||
max_tokens=256,
|
max_tokens=1024,
|
||||||
messages=[
|
messages=[
|
||||||
{"role": "system", "content": system_prompt},
|
{"role": "system", "content": system_prompt},
|
||||||
{"role": "user", "content": user_msg},
|
{"role": "user", "content": user_msg},
|
||||||
|
|||||||
@@ -83,16 +83,17 @@ PROVIDER_DEFAULTS: dict[str, dict] = {
|
|||||||
},
|
},
|
||||||
"lmstudio": {
|
"lmstudio": {
|
||||||
"base_url": "http://host.docker.internal:1234/v1",
|
"base_url": "http://host.docker.internal:1234/v1",
|
||||||
"model": "gemma-4-e4b-it",
|
"model": "qwen/qwen3.5-9b",
|
||||||
"context_chars": 8_000,
|
"context_chars": 32_000,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
# Whether the provider honours response_format={"type": "json_object"}.
|
# Whether the provider honours response_format={"type": "json_object"}.
|
||||||
# Gemini's OpenAI-compat endpoint does NOT support the string form (D-02/D-03).
|
# 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
|
# LMStudio only accepts "json_schema" or "text" (not "json_object") — set False
|
||||||
# GenericOpenAIProvider always wraps the raw response with parse_classification()
|
# so GenericOpenAIProvider omits response_format and relies on parse_classification().
|
||||||
# regardless, so they are left as True (the parameter is still sent).
|
# 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] = {
|
SUPPORTS_JSON_MODE: dict[str, bool] = {
|
||||||
"openai": True,
|
"openai": True,
|
||||||
"anthropic": True,
|
"anthropic": True,
|
||||||
@@ -103,5 +104,5 @@ SUPPORTS_JSON_MODE: dict[str, bool] = {
|
|||||||
"openrouter": True,
|
"openrouter": True,
|
||||||
"mistral": True,
|
"mistral": True,
|
||||||
"ollama": True,
|
"ollama": True,
|
||||||
"lmstudio": True,
|
"lmstudio": False,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ services:
|
|||||||
- CLOUD_CREDS_KEY=${CLOUD_CREDS_KEY}
|
- CLOUD_CREDS_KEY=${CLOUD_CREDS_KEY}
|
||||||
- SECRET_KEY=${SECRET_KEY}
|
- SECRET_KEY=${SECRET_KEY}
|
||||||
- PYTHONDONTWRITEBYTECODE=1
|
- PYTHONDONTWRITEBYTECODE=1
|
||||||
|
- PYTHONPATH=/app
|
||||||
labels:
|
labels:
|
||||||
logging: "promtail"
|
logging: "promtail"
|
||||||
volumes:
|
volumes:
|
||||||
@@ -134,6 +135,7 @@ services:
|
|||||||
- MINIO_BUCKET=${MINIO_BUCKET}
|
- MINIO_BUCKET=${MINIO_BUCKET}
|
||||||
- REDIS_URL=${REDIS_URL}
|
- REDIS_URL=${REDIS_URL}
|
||||||
- PYTHONDONTWRITEBYTECODE=1
|
- PYTHONDONTWRITEBYTECODE=1
|
||||||
|
- PYTHONPATH=/app
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
- "host.docker.internal:host-gateway"
|
- "host.docker.internal:host-gateway"
|
||||||
command: celery -A celery_app beat --loglevel=info --schedule /tmp/celerybeat-schedule
|
command: celery -A celery_app beat --loglevel=info --schedule /tmp/celerybeat-schedule
|
||||||
|
|||||||
Reference in New Issue
Block a user