From 08fb99c6f8061b690942f5df20d73a7bd54aec0a Mon Sep 17 00:00:00 2001 From: Yiorgis Gozadinos Date: Fri, 19 Dec 2025 14:49:33 +0200 Subject: [PATCH] Add prompt customization and domain preable --- CHANGELOG.md | 7 ++ docs/configuration/index.md | 6 + docs/configuration/prompts.md | 108 ++++++++++++++++++ haiku_rag_slim/haiku/rag/config/__init__.py | 2 + haiku_rag_slim/haiku/rag/config/models.py | 7 ++ .../haiku/rag/graph/research/graph.py | 25 ++-- haiku_rag_slim/haiku/rag/qa/__init__.py | 11 +- haiku_rag_slim/haiku/rag/utils.py | 15 +++ mkdocs.yml | 1 + 9 files changed, 173 insertions(+), 9 deletions(-) create mode 100644 docs/configuration/prompts.md diff --git a/CHANGELOG.md b/CHANGELOG.md index d0840676..08444bf3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ # Changelog ## [Unreleased] +### Added + +- **Prompt Customization**: Configure agent prompts via `prompts` config section + - `domain_preamble`: Prepended to all agent prompts for domain context + - `qa`: Full replacement for QA agent prompt + - `synthesis`: Full replacement for research synthesis prompt + ### Changed - **Embeddings**: Migrated to pydantic-ai's embeddings module diff --git a/docs/configuration/index.md b/docs/configuration/index.md index b807cbd7..5bf63ecf 100644 --- a/docs/configuration/index.md +++ b/docs/configuration/index.md @@ -114,6 +114,11 @@ agui: cors_methods: ["GET", "POST", "OPTIONS"] cors_headers: ["*"] +prompts: + domain_preamble: "" # Prepended to all agent prompts + qa: null # Custom QA agent prompt (null = use default) + synthesis: null # Custom research synthesis prompt (null = use default) + processing: converter: docling-local # docling-local or docling-serve chunker: docling-local # docling-local or docling-serve @@ -189,3 +194,4 @@ For detailed configuration of specific topics, see: - **[Search and Question Answering](qa-research.md)** - Search settings, question answering, and research workflows - **[Document Processing](processing.md)** - Document conversion, chunking, and file monitoring - **[Storage](storage.md)** - Database, remote storage, and vector indexing +- **[Prompts](prompts.md)** - Customize agent prompts for your domain diff --git a/docs/configuration/prompts.md b/docs/configuration/prompts.md new file mode 100644 index 00000000..30574c3c --- /dev/null +++ b/docs/configuration/prompts.md @@ -0,0 +1,108 @@ +# Prompt Customization + +Customize the prompts used by haiku.rag's AI agents to better match your domain and use case. + +## Configuration + +```yaml +prompts: + # Prepended to all agent prompts + domain_preamble: | + You are answering questions about our internal documentation. + Technical terms like "time travel" refer to database versioning features. + + # Full replacement for QA agent prompt (optional) + qa: null + + # Full replacement for research synthesis prompt (optional) + synthesis: null +``` + +## Domain Preamble + +The `domain_preamble` field is prepended to **all** agent prompts (QA, research planning, search, evaluation, and synthesis). Use this to: + +- Add domain context that clarifies terminology +- Set the tone or personality of responses +- Specify what the knowledge base contains + +**Example:** + +```yaml +prompts: + domain_preamble: | + You are a technical support assistant for Acme Corp products. + The knowledge base contains product documentation, FAQs, and troubleshooting guides. + Always be helpful and professional. +``` + +## Custom QA Prompt + +Replace the default QA agent prompt entirely by setting `prompts.qa`. The prompt should instruct the agent how to: + +1. Use the `search_documents` tool to find relevant content +2. Interpret search results with scores and metadata +3. Cite sources using chunk IDs +4. Handle insufficient information + +**Example:** + +```yaml +prompts: + qa: | + You are a concise technical assistant. Answer questions using only the knowledge base. + + Process: + 1. Search for relevant documents using the search_documents tool + 2. Review results and their relevance scores + 3. Provide a brief, direct answer based on retrieved content + + Guidelines: + - Use only information from search results + - Include chunk IDs in cited_chunks for sources you use + - If information is insufficient, say so clearly + - Be concise - avoid unnecessary elaboration +``` + +## Custom Synthesis Prompt + +Replace the research report synthesis prompt by setting `prompts.synthesis`. This controls how the multi-agent research workflow generates its final report. + +The prompt should produce a `ResearchReport` with: `title`, `executive_summary`, `main_findings`, `conclusions`, `recommendations`, `limitations`, and `sources_summary`. + +**Example:** + +```yaml +prompts: + synthesis: | + Generate a research report based on the gathered evidence. + + Output format: + - title: 5-12 word title + - executive_summary: 3-5 sentence overview + - main_findings: 4-8 bullet points of key findings + - conclusions: 2-4 bullet points + - recommendations: 2-5 actionable recommendations + - limitations: 1-3 limitations or gaps + - sources_summary: Brief description of sources used + + Guidelines: + - Base all content strictly on collected evidence + - Be specific and objective + - Avoid meta-commentary like "This report covers..." +``` + +## Programmatic Configuration + +```python +from haiku.rag.config import AppConfig +from haiku.rag.config.models import PromptsConfig + +config = AppConfig( + prompts=PromptsConfig( + domain_preamble="You are answering questions about our product documentation.", + qa=None, # Use default QA prompt + synthesis=None, # Use default synthesis prompt + ) +) +``` diff --git a/haiku_rag_slim/haiku/rag/config/__init__.py b/haiku_rag_slim/haiku/rag/config/__init__.py index e4efd7c6..0b233601 100644 --- a/haiku_rag_slim/haiku/rag/config/__init__.py +++ b/haiku_rag_slim/haiku/rag/config/__init__.py @@ -16,6 +16,7 @@ from haiku.rag.config.models import ( MonitorConfig, OllamaConfig, ProcessingConfig, + PromptsConfig, ProvidersConfig, QAConfig, RerankingConfig, @@ -35,6 +36,7 @@ __all__ = [ "MonitorConfig", "OllamaConfig", "ProcessingConfig", + "PromptsConfig", "ProvidersConfig", "QAConfig", "RerankingConfig", diff --git a/haiku_rag_slim/haiku/rag/config/models.py b/haiku_rag_slim/haiku/rag/config/models.py index 50b4ffd9..6c17fd39 100644 --- a/haiku_rag_slim/haiku/rag/config/models.py +++ b/haiku_rag_slim/haiku/rag/config/models.py @@ -162,6 +162,12 @@ class AGUIConfig(BaseModel): cors_headers: list[str] = ["*"] +class PromptsConfig(BaseModel): + domain_preamble: str = "" + qa: str | None = None + synthesis: str | None = None + + class AppConfig(BaseModel): environment: str = "production" storage: StorageConfig = Field(default_factory=StorageConfig) @@ -175,3 +181,4 @@ class AppConfig(BaseModel): search: SearchConfig = Field(default_factory=SearchConfig) providers: ProvidersConfig = Field(default_factory=ProvidersConfig) agui: AGUIConfig = Field(default_factory=AGUIConfig) + prompts: PromptsConfig = Field(default_factory=PromptsConfig) diff --git a/haiku_rag_slim/haiku/rag/graph/research/graph.py b/haiku_rag_slim/haiku/rag/graph/research/graph.py index f81849b6..c1dab7b7 100644 --- a/haiku_rag_slim/haiku/rag/graph/research/graph.py +++ b/haiku_rag_slim/haiku/rag/graph/research/graph.py @@ -31,7 +31,7 @@ from haiku.rag.graph.research.prompts import ( SYNTHESIS_PROMPT, ) from haiku.rag.graph.research.state import ResearchDeps, ResearchState -from haiku.rag.utils import get_model +from haiku.rag.utils import build_prompt, get_model def format_context_for_prompt(context: ResearchContext) -> str: @@ -76,6 +76,18 @@ def build_research_graph( Configured Research graph """ model_config = config.research.model + + # Build prompts with system_context if configured + plan_prompt = build_prompt( + PLAN_PROMPT + + "\n\nUse the gather_context tool once on the main question before planning.", + config, + ) + search_prompt = build_prompt(SEARCH_PROMPT, config) + decision_prompt = build_prompt(DECISION_PROMPT, config) + synthesis_prompt = build_prompt( + config.prompts.synthesis or SYNTHESIS_PROMPT, config + ) g = GraphBuilder( state_type=ResearchState, deps_type=ResearchDeps, @@ -98,10 +110,7 @@ def build_research_graph( plan_agent = Agent( model=get_model(model_config, config), output_type=ResearchPlan, - instructions=( - PLAN_PROMPT - + "\n\nUse the gather_context tool once on the main question before planning." - ), + instructions=plan_prompt, retries=3, output_retries=3, deps_type=ResearchDependencies, @@ -178,7 +187,7 @@ def build_research_graph( agent = Agent( model=get_model(model_config, config), output_type=ToolOutput(RawSearchAnswer, max_retries=3), - instructions=SEARCH_PROMPT, + instructions=search_prompt, retries=3, deps_type=ResearchDependencies, ) @@ -281,7 +290,7 @@ def build_research_graph( agent = Agent( model=get_model(model_config, config), output_type=EvaluationResult, - instructions=DECISION_PROMPT, + instructions=decision_prompt, retries=3, output_retries=3, deps_type=ResearchDependencies, @@ -438,7 +447,7 @@ def build_research_graph( agent = Agent( model=get_model(model_config, config), output_type=ResearchReport, - instructions=SYNTHESIS_PROMPT, + instructions=synthesis_prompt, retries=3, output_retries=3, deps_type=ResearchDependencies, diff --git a/haiku_rag_slim/haiku/rag/qa/__init__.py b/haiku_rag_slim/haiku/rag/qa/__init__.py index 63ee6419..77d7bed7 100644 --- a/haiku_rag_slim/haiku/rag/qa/__init__.py +++ b/haiku_rag_slim/haiku/rag/qa/__init__.py @@ -1,6 +1,8 @@ from haiku.rag.client import HaikuRAG from haiku.rag.config import AppConfig, Config from haiku.rag.qa.agent import QuestionAnswerAgent +from haiku.rag.qa.prompts import QA_SYSTEM_PROMPT +from haiku.rag.utils import build_prompt def get_qa_agent( @@ -13,11 +15,18 @@ def get_qa_agent( Args: client: HaikuRAG client instance. config: Configuration to use. Defaults to global Config. - system_prompt: Optional custom system prompt. + system_prompt: Optional custom system prompt (overrides config). Returns: A configured QuestionAnswerAgent instance. """ + # Determine the base prompt: explicit > config > default + if system_prompt is None: + system_prompt = config.prompts.qa or QA_SYSTEM_PROMPT + + # Prepend system_context if configured + system_prompt = build_prompt(system_prompt, config) + return QuestionAnswerAgent( client=client, model_config=config.qa.model, diff --git a/haiku_rag_slim/haiku/rag/utils.py b/haiku_rag_slim/haiku/rag/utils.py index 67c15d6b..060b3367 100644 --- a/haiku_rag_slim/haiku/rag/utils.py +++ b/haiku_rag_slim/haiku/rag/utils.py @@ -398,6 +398,21 @@ def get_default_data_dir() -> Path: return data_path +def build_prompt(base_prompt: str, config: "AppConfig") -> str: + """Build a prompt with domain_preamble prepended if configured. + + Args: + base_prompt: The base prompt to use + config: AppConfig with prompts.domain_preamble + + Returns: + Prompt with domain_preamble prepended if configured + """ + if config.prompts.domain_preamble: + return f"{config.prompts.domain_preamble}\n\n{base_prompt}" + return base_prompt + + async def is_up_to_date() -> tuple[bool, Version, Version]: """Check whether haiku.rag is current. diff --git a/mkdocs.yml b/mkdocs.yml index dff5c1d2..4bf04854 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -65,6 +65,7 @@ nav: - Search and Question Answering: configuration/qa-research.md - Document Processing: configuration/processing.md - Storage: configuration/storage.md + - Prompts: configuration/prompts.md - CLI: cli.md - Python: python.md - Custom Pipelines: custom-pipelines.md