Defaults to None.
+### [`PAPERLESS_AI_LLM_OUTPUT_LANGUAGE=<str>`](#PAPERLESS_AI_LLM_OUTPUT_LANGUAGE) {#PAPERLESS_AI_LLM_OUTPUT_LANGUAGE}
+
+: The language to use for AI suggestions (results may vary by LLM model). If not supplied, defaults to the user's UI language setting or None.
+
+ Defaults to None.
+
#### [`PAPERLESS_AI_LLM_ALLOW_INTERNAL_ENDPOINTS=<bool>`](#PAPERLESS_AI_LLM_ALLOW_INTERNAL_ENDPOINTS) {#PAPERLESS_AI_LLM_ALLOW_INTERNAL_ENDPOINTS}
: If set to false, Paperless blocks AI endpoint URLs that resolve to non-public addresses (e.g., localhost, etc).
config_key: 'PAPERLESS_AI_LLM_ENDPOINT',
category: ConfigCategory.AI,
},
+ {
+ key: 'llm_output_language',
+ title: $localize`LLM Output Language`,
+ type: ConfigOptionType.String,
+ config_key: 'PAPERLESS_AI_LLM_OUTPUT_LANGUAGE',
+ category: ConfigCategory.AI,
+ note: $localize`Language to use for generated AI suggestions. When unset, AI suggestions use the user's display language if explicitly set.`,
+ },
]
export interface PaperlessConfig extends ObjectWithId {
llm_model: string
llm_api_key: string
llm_endpoint: string
+ llm_output_language: string
}
"llm_model": None,
"llm_api_key": None,
"llm_endpoint": None,
+ "llm_output_language": None,
},
)
"KI Title",
)
+ @patch("documents.views.get_ai_document_classification")
+ @override_settings(
+ AI_ENABLED=True,
+ LLM_BACKEND="mock_backend",
+ LLM_OUTPUT_LANGUAGE="fr-fr",
+ )
+ def test_ai_suggestions_configured_language_takes_precedence(
+ self,
+ mock_get_ai_classification,
+ ) -> None:
+ UiSettings.objects.create(user=self.user, settings={"language": "de-de"})
+ mock_get_ai_classification.return_value = {
+ "title": "Titre IA",
+ "tags": [],
+ "correspondents": [],
+ "document_types": [],
+ "storage_paths": [],
+ "dates": [],
+ }
+
+ self.client.force_login(user=self.user)
+ response = self.client.get(
+ f"/api/documents/{self.document.pk}/ai_suggestions/",
+ )
+
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ mock_get_ai_classification.assert_called_once_with(
+ self.document,
+ self.user,
+ "fr-fr",
+ )
+ self.assertEqual(
+ get_llm_suggestion_cache(
+ self.document.pk,
+ backend="mock_backend:fr-fr",
+ ).suggestions["title"],
+ "Titre IA",
+ )
+
@patch("documents.views.get_ai_document_classification")
@override_settings(
AI_ENABLED=True,
if not ai_config.ai_enabled:
return HttpResponseBadRequest("AI is required for this feature")
- output_language = None
- if hasattr(request.user, "ui_settings") and isinstance(
- request.user.ui_settings.settings,
- dict,
+ output_language = ai_config.llm_output_language
+ if (
+ not output_language
+ and hasattr(request.user, "ui_settings")
+ and isinstance(
+ request.user.ui_settings.settings,
+ dict,
+ )
):
output_language = request.user.ui_settings.settings.get("language") or None
llm_cache_backend = (
llm_model: str = dataclasses.field(init=False)
llm_api_key: str = dataclasses.field(init=False)
llm_endpoint: str = dataclasses.field(init=False)
+ llm_output_language: str = dataclasses.field(init=False)
llm_allow_internal_endpoints: bool = dataclasses.field(init=False)
def __post_init__(self) -> None:
self.llm_model = app_config.llm_model or settings.LLM_MODEL
self.llm_api_key = app_config.llm_api_key or settings.LLM_API_KEY
self.llm_endpoint = app_config.llm_endpoint or settings.LLM_ENDPOINT
+ self.llm_output_language = (
+ app_config.llm_output_language or settings.LLM_OUTPUT_LANGUAGE
+ )
self.llm_allow_internal_endpoints = settings.LLM_ALLOW_INTERNAL_ENDPOINTS
@property
--- /dev/null
+# Generated by Django 5.2.6 on 2026-06-02
+
+from django.db import migrations
+from django.db import models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("paperless", "0011_applicationconfiguration_llm_embedding_chunk_size"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="applicationconfiguration",
+ name="llm_output_language",
+ field=models.CharField(
+ blank=True,
+ max_length=32,
+ null=True,
+ verbose_name="Sets the LLM output language",
+ ),
+ ),
+ ]
max_length=256,
)
+ llm_output_language = models.CharField(
+ verbose_name=_("Sets the LLM output language"),
+ blank=True,
+ null=True,
+ max_length=32,
+ )
+
class Meta:
verbose_name = _("paperless application settings")
permissions = [
data["barcode_tag_mapping"] = None
if "language" in data and data["language"] == "":
data["language"] = None
+ if "llm_output_language" in data and data["llm_output_language"] == "":
+ data["llm_output_language"] = None
if "llm_api_key" in data and data["llm_api_key"] is not None:
if data["llm_api_key"] == "":
data["llm_api_key"] = None
LLM_MODEL = os.getenv("PAPERLESS_AI_LLM_MODEL")
LLM_API_KEY = os.getenv("PAPERLESS_AI_LLM_API_KEY")
LLM_ENDPOINT = os.getenv("PAPERLESS_AI_LLM_ENDPOINT")
+LLM_OUTPUT_LANGUAGE = os.getenv("PAPERLESS_AI_LLM_OUTPUT_LANGUAGE")
LLM_ALLOW_INTERNAL_ENDPOINTS = get_bool_from_env(
"PAPERLESS_AI_LLM_ALLOW_INTERNAL_ENDPOINTS",
"true",