Name AI assistant Hesychia in system prompts (#22)
Unit Tests / test (push) Successful in 9s

## Summary
- Closes #20 — name the AI assistant **Hesychia** in system / generation prompts
- Add shared `assistant_identity.py` with `ASSISTANT_NAME` + concise calm/stillness tone
- Prepend identity to chat (`llm_service`), RAG, data analysis, and the views system message
- Document that identity lives in code (not env); add unit coverage

## Test plan
- [x] `uv run python manage.py test chat_backend.tests.test_assistant_identity chat_backend.tests.test_services_llm chat_backend.tests.test_services_data_analysis`
- [ ] Fresh chat: ask "who are you?" → responds as Hesychia
- [ ] Confirm classifiers/moderators/title generator unchanged (not assistant identity)

Related: companion frontend rebrand `chat_web_app#29`Reviewed-on: #22
This commit was merged in pull request #22.
This commit is contained in:
2026-07-26 17:08:08 -07:00
parent 92aa277a37
commit 9984d1c340
7 changed files with 77 additions and 19 deletions
+4
View File
@@ -95,6 +95,10 @@ with `COMPOSE_DATABASE_URL` if needed.
| `GUNICORN_WORKERS` / `GUNICORN_BIND` | 2 / `0.0.0.0:8000` | optional | Entrypoint |
| `SKIP_RAG_INIT` | unset | CI/migrate often `1` | Skip Chroma/Ollama boot work |
Assistant identity (`Hesychia`) lives in code:
`llm_be/chat_backend/services/assistant_identity.py` — prepended to user-facing
generation prompts (chat, RAG, data analysis). Not env-configurable.
Templates: `.env.example` (local), `.env.prod.example` (control-node secret).
Control-node secret path (server-infra on ai-server-4080):
@@ -0,0 +1,13 @@
"""Assistant identity for user-facing LLM prompts.
Hesychia is the product assistant name (hesychia.ai). Keep this concise —
it is prepended to generation prompts, not classifiers/moderators/title gens.
"""
ASSISTANT_NAME = "Hesychia"
ASSISTANT_SYSTEM_PROMPT = (
"You are Hesychia, a helpful AI assistant. "
"Your name evokes quiet, rest, silence, and stillness — "
"respond with calm clarity; keep answers focused and uncluttered."
)
@@ -12,6 +12,7 @@ import docx
import pypdf
from django.conf import settings
from chat_backend.ollama_config import ollama_llm_kwargs
from chat_backend.services.assistant_identity import ASSISTANT_SYSTEM_PROMPT
class AsyncDataAnalysisService:
@@ -30,7 +31,8 @@ class AsyncDataAnalysisService:
def _setup_chain(self):
"""Set up the LLM chain with a prompt tailored for data analysis."""
template = """You are an expert data analyst. Your role is to directly answer a user's question about a dataset or document they have provided.
template = f"""{ASSISTANT_SYSTEM_PROMPT}
For this request, act as an expert data analyst. Your role is to directly answer a user's question about a dataset or document they have provided.
You will be given a summary and a sample of the dataset, or the content of the document.
Based on this information, provide a clear and concise answer to the user's question.
Do not provide Python code or any other code. The user is not a developer and wants a direct answer.
@@ -38,10 +40,10 @@ Even if you don't think the data provides enough evidence for the query, still p
---
Data/Document Content:
{data_summary}
{{data_summary}}
---
User's Question: {query}
User's Question: {{query}}
Answer:"""
self.prompt = ChatPromptTemplate.from_template(template)
+12 -7
View File
@@ -9,6 +9,7 @@ from django.conf import settings
from chat_backend.models import Conversation, Prompt
from chat_backend.ollama_config import ollama_llm_kwargs
from chat_backend.services.assistant_identity import ASSISTANT_SYSTEM_PROMPT
class LLMService(ABC):
@@ -50,11 +51,13 @@ class SyncLLMService(LLMService):
def _setup_chain(self):
"""Setup the conversation chain."""
template = """Continue the conversation based on the following history:
template = f"""{ASSISTANT_SYSTEM_PROMPT}
{history}
Continue the conversation based on the following history:
Latest message: {query}
{{history}}
Latest message: {{query}}
Response:"""
self.prompt = ChatPromptTemplate.from_template(template)
@@ -88,13 +91,15 @@ class AsyncLLMService(LLMService):
def _setup_chain(self):
"""Setup the conversation chain."""
template = """Continue this conversation while maintaining context by providing a single helpful response.
Current context: {context}
template = f"""{ASSISTANT_SYSTEM_PROMPT}
Continue this conversation while maintaining context by providing a single helpful response.
Current context: {{context}}
Last 3 messages:
{recent_history}
{{recent_history}}
Latest message: {query}
Latest message: {{query}}
Instructions:
- Carefully maintain all established context
+13 -8
View File
@@ -24,6 +24,7 @@ from django.core.files.uploadedfile import UploadedFile
from chat_backend.models import Conversation, Prompt, DocumentWorkspace, Document
from pathlib import Path
from chat_backend.services.base_service import BaseService
from chat_backend.services.assistant_identity import ASSISTANT_SYSTEM_PROMPT
from chat_backend.ollama_config import ollama_embeddings_kwargs
@@ -229,13 +230,15 @@ class SyncRAGService(RAGService):
def _setup_chain(self):
"""Setup the RAG chain."""
template = """Answer the question based only on the following context:
{context}
template = f"""{ASSISTANT_SYSTEM_PROMPT}
Answer the question based only on the following context:
{{context}}
Conversation history:
{history}
{{history}}
Question: {question}
Question: {{question}}
"""
self.prompt = ChatPromptTemplate.from_template(template)
@@ -305,13 +308,15 @@ class AsyncRAGService(RAGService):
def _setup_chain(self):
"""Setup the RAG chain."""
template = """Answer the question based only on the following context:
{context}
template = f"""{ASSISTANT_SYSTEM_PROMPT}
Answer the question based only on the following context:
{{context}}
Conversation history:
{history}
{{history}}
Question: {question}
Question: {{question}}
"""
self.prompt = ChatPromptTemplate.from_template(template)
@@ -0,0 +1,28 @@
from django.test import SimpleTestCase
from chat_backend.services.assistant_identity import (
ASSISTANT_NAME,
ASSISTANT_SYSTEM_PROMPT,
)
from chat_backend.services.data_analysis_service import AsyncDataAnalysisService
from chat_backend.services.llm_service import AsyncLLMService, SyncLLMService
class AssistantIdentityTestCase(SimpleTestCase):
def test_assistant_is_named_hesychia(self):
self.assertEqual(ASSISTANT_NAME, "Hesychia")
self.assertIn("Hesychia", ASSISTANT_SYSTEM_PROMPT)
self.assertNotRegex(
ASSISTANT_SYSTEM_PROMPT,
r"(?i)\b(chatgpt|claude|gemini|copilot)\b",
)
def test_user_facing_prompts_include_hesychia(self):
services = [
SyncLLMService(),
AsyncLLMService(),
AsyncDataAnalysisService(),
]
for service in services:
with self.subTest(service=type(service).__name__):
self.assertIn("Hesychia", str(service.prompt))
+2 -1
View File
@@ -44,6 +44,7 @@ import json
import base64
import pandas as pd
import io
from chat_backend.services.assistant_identity import ASSISTANT_SYSTEM_PROMPT
# For email support
from django.core.mail import EmailMultiAlternatives
@@ -714,7 +715,7 @@ class AdminAnalytics(APIView):
prompt = ChatPromptTemplate.from_messages(
[("system", "You are a helpful assistant."), ("user", "{input}")]
[("system", ASSISTANT_SYSTEM_PROMPT), ("user", "{input}")]
)
llm = OllamaLLM(**ollama_llm_kwargs(model=MODEL_NAME))