Conversation Auto-Naming#
Dify automatically generates a concise title for every new chat conversation using an LLM call against the first user message. There are two distinct entry points into this system:
- Automatic naming at message time β triggered inline during the message pipeline, in a background daemon thread.
- On-demand naming via API β triggered explicitly by a
POST /conversations/<id>/namerequest withauto_generate: true.
Both paths ultimately call the same generator, but they go through different code paths and have different validation rules.
How It Works#
1. Background auto-naming at message time#
MessageCycleManager.generate_conversation_name() fires a 1-second delayed Timer thread on the first message of any non-completion conversation. It checks extras["auto_generate_conversation_name"] (defaults to True) before spawning .
The worker thread, _generate_conversation_name_worker(), adds a Redis cache keyed on conv_name:{conversation_id}:{md5(query)[:16]} (TTL 1 hour) to avoid redundant LLM calls on retries . On LLM failure it falls back to query[:47] + "..." .
2. On-demand rename API#
Three API surfaces expose POST .../conversations/<id>/name:
| Controller | Route |
|---|---|
| Web (end users) | /conversations/<c_id>/name |
| Console (installed apps) | /installed-apps/<id>/conversations/<c_id>/name |
| Service API | /conversations/<c_id>/name |
All three validate with ConversationRenamePayload and delegate to ConversationService.rename().
Core Generator: LLMGenerator.generate_conversation_name()#
Full implementation. Key behaviors:
- Uses
CONVERSATION_TITLE_PROMPT, which instructs the LLM to decompose input into Intention + Subject and return JSON with keysLanguage Type,Your Reasoning, andYour Output. - Truncates queries > 2,000 characters to
query[:300] + "...[TRUNCATED]..." + query[-300:]. - Uses the tenant's default LLM via
ModelManager.for_tenant().get_default_model_instance()withmax_tokens=500, temperature=1. - Parses JSON response; if
"Your Output"is absent/blank, falls back to the raw query . - Caps the resulting name at 75 characters (truncating with
"...") . - Emits a
GENERATE_NAME_TRACEtrace task for observability .
Validation Gap (FIXED)#
The manual naming path has an explicit non-blank guard. In ConversationRenamePayload, the validate_name_requirement model validator rejects name when it is None or whitespace-only (when auto_generate=False). The JSON schema also enforces a non-blank pattern via "pattern": r".*\S.*" .
The auto-naming path previously did not share this protection. In ConversationService.auto_generate_name(), the LLM call is wrapped in contextlib.suppress(Exception), and the result is assigned directly to conversation.name = name:
with contextlib.suppress(Exception):
name = LLMGenerator.generate_conversation_name(...)
conversation.name = name
Within LLMGenerator.generate_conversation_name() itself, the original behavior checked if answer == "": return "" β returning an empty string when the LLM produced an empty response before JSON parsing. This allowed blank or whitespace-only titles to be persisted to the database through the auto-naming path and the background worker path in _generate_conversation_name_worker() .
This has been fixed. The empty check now uses if not answer.strip(): and falls back to answer = query instead of returning an empty string. Empty or whitespace-only LLM responses now result in the original user query being used as the conversation title, preventing blank names from propagating through the system.
Key Files#
| File | Purpose |
|---|---|
api/core/llm_generator/llm_generator.py | generate_conversation_name() β the LLM call, JSON parsing, tracing |
api/core/llm_generator/prompts.py | CONVERSATION_TITLE_PROMPT |
api/core/app/task_pipeline/message_cycle_manager.py | Background auto-naming trigger and Redis cache |
api/services/conversation_service.py | rename() and auto_generate_name() β service layer |
api/controllers/common/controller_schemas.py | ConversationRenamePayload β request validation with non-blank guard |
api/controllers/web/conversation.py | Web API rename endpoint |
api/controllers/console/explore/conversation.py | Console/installed-app rename endpoint |