From be7828d7b2f7bf4df51a08ed13f619d0f3d9cbf1 Mon Sep 17 00:00:00 2001 From: Ryan Westfall Date: Sun, 26 Jul 2026 18:31:25 -0500 Subject: [PATCH] Name AI assistant Hesychia in system prompts (#20) Closes #20 Introduce a shared assistant identity module and prepend it to user-facing generation prompts so the model self-identifies as Hesychia. --- README.md | 4 +++ .../services/assistant_identity.py | 13 +++++++++ .../services/data_analysis_service.py | 8 ++++-- llm_be/chat_backend/services/llm_service.py | 19 ++++++++----- llm_be/chat_backend/services/rag_services.py | 21 ++++++++------ .../tests/test_assistant_identity.py | 28 +++++++++++++++++++ llm_be/chat_backend/views.py | 3 +- 7 files changed, 77 insertions(+), 19 deletions(-) create mode 100644 llm_be/chat_backend/services/assistant_identity.py create mode 100644 llm_be/chat_backend/tests/test_assistant_identity.py diff --git a/README.md b/README.md index 817f351..72c8c83 100644 --- a/README.md +++ b/README.md @@ -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): diff --git a/llm_be/chat_backend/services/assistant_identity.py b/llm_be/chat_backend/services/assistant_identity.py new file mode 100644 index 0000000..542bf38 --- /dev/null +++ b/llm_be/chat_backend/services/assistant_identity.py @@ -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." +) diff --git a/llm_be/chat_backend/services/data_analysis_service.py b/llm_be/chat_backend/services/data_analysis_service.py index 9af493f..4da2926 100644 --- a/llm_be/chat_backend/services/data_analysis_service.py +++ b/llm_be/chat_backend/services/data_analysis_service.py @@ -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) diff --git a/llm_be/chat_backend/services/llm_service.py b/llm_be/chat_backend/services/llm_service.py index e2c6d65..220f013 100644 --- a/llm_be/chat_backend/services/llm_service.py +++ b/llm_be/chat_backend/services/llm_service.py @@ -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} + + Continue the conversation based on the following history: - {history} + {{history}} - Latest message: {query} + 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 diff --git a/llm_be/chat_backend/services/rag_services.py b/llm_be/chat_backend/services/rag_services.py index 31ea3c0..74dafc2 100644 --- a/llm_be/chat_backend/services/rag_services.py +++ b/llm_be/chat_backend/services/rag_services.py @@ -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) diff --git a/llm_be/chat_backend/tests/test_assistant_identity.py b/llm_be/chat_backend/tests/test_assistant_identity.py new file mode 100644 index 0000000..86976b1 --- /dev/null +++ b/llm_be/chat_backend/tests/test_assistant_identity.py @@ -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)) diff --git a/llm_be/chat_backend/views.py b/llm_be/chat_backend/views.py index 8c371df..02874c3 100644 --- a/llm_be/chat_backend/views.py +++ b/llm_be/chat_backend/views.py @@ -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))