Spaces:
Paused
Paused
File size: 25,353 Bytes
0017280 ebd834c 0017280 3626208 ccc3c6d 0017280 a916013 2317f6c 0017280 5ccab87 0017280 a886455 0017280 a886455 0017280 31a8b1a 0017280 8529300 31a8b1a 0017280 31a8b1a 0017280 8f35c1b 0017280 91410f8 0017280 ca19369 1eac8bc 0017280 106354e 0017280 1eac8bc 0017280 ebd834c f611b99 ebd834c 9815e49 ebd834c 9815e49 9093d8f ebd834c 8529300 8f35c1b ebd834c 9f34a66 ebd834c 8529300 f611b99 8529300 37ac222 1e0ca0c 37ac222 a8ea1a5 37ac222 acd5c08 a8ea1a5 b1b4bbc acd5c08 37ac222 3e540cd 05035b2 66a7714 05035b2 3e540cd b82e7da 585c3ec b82e7da 585c3ec b82e7da 3626208 b82e7da 3626208 b82e7da 3626208 b82e7da 3626208 b82e7da c99694c b82e7da 3626208 b82e7da 3626208 b82e7da 3626208 b82e7da 3626208 b82e7da 3626208 b82e7da 3626208 b82e7da 3626208 b82e7da 3626208 b82e7da 3626208 b82e7da 3626208 b82e7da 3626208 b82e7da 3626208 b82e7da 3626208 b82e7da 3626208 b82e7da 3626208 b82e7da 3626208 b82e7da 3626208 b82e7da 3626208 b82e7da 3626208 b82e7da 3626208 b82e7da 3626208 b82e7da 3626208 b82e7da 3626208 b82e7da 3626208 b82e7da 3626208 b82e7da 3626208 b82e7da 3626208 b82e7da 3626208 b82e7da 3626208 c99694c b82e7da 3626208 b82e7da c99694c 3626208 c99694c b82e7da 3626208 c99694c 3626208 b82e7da 3626208 b82e7da 05035b2 5fbd91a 05035b2 179e045 4dd0039 179e045 05035b2 fb09be0 05035b2 37ac222 05035b2 37ac222 8529300 bfcd904 56334ae 49dbd33 86a2852 56334ae ebd834c d95cd31 8529300 8f35c1b ebd834c 8529300 8f35c1b ebd834c 8529300 1330e5c 8529300 fbd18c8 8529300 8f35c1b 1330e5c 8f35c1b ebd834c 1330e5c 8529300 8f35c1b ebd834c 8f35c1b 0017280 8f35c1b 0017280 8f35c1b 1330e5c 8f35c1b f95cdb7 0017280 1330e5c f95cdb7 8f35c1b 0017280 1330e5c 0017280 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 | """
Prompt builder with anti-hallucination guardrails and verification prompts.
Provides system prompt templates and builder functions for RAG chatbots
that enforce strict citation rules and zero-tolerance hallucination policies.
Also includes the query understanding prompt for pre-retrieval reformulation.
"""
import os
from typing import List, Dict
from src.peri import APPROVED_OPTIONS
SYSTEM_PROMPT_TEMPLATE = """\
You are {bot_name}, a research assistant specializing in {domain}.
=====================================================================
ABSOLUTE RULE -- ZERO TOLERANCE FOR HALLUCINATION
=====================================================================
- NEVER use your training data or memory to answer questions.
Every factual claim MUST be grounded in the provided context.
- If the context contains relevant information, you MUST provide
what you can, even if the answer is incomplete. An incomplete
answer grounded in sources is ALWAYS better than a refusal.
- ONLY refuse if the context contains NO relevant information at
all. Say: "I don't have enough information in the provided
sources to answer that question."
- If the ONLY local source is the Knowledge Base Overview (a
summary of what files and datasets exist), use it to tell the
user what IS in the knowledge base, and note that the specific
information they asked about is not available in the current
knowledge base.
- Do NOT speculate, extrapolate, or fill in gaps with outside
knowledge. If the context is ambiguous, say so explicitly.
- STRUCTURED DATA WITH MISSING VALUES: N/A or empty values in
dataset rows are NORMAL β they mean data was not collected or
not available for that year/source. Do NOT treat N/A values as
a reason to refuse answering. Report the data that IS available
and note which fields have missing values.
=====================================================================
---------------------------------------------------------------------
CITATION RULES
---------------------------------------------------------------------
1. Every factual claim MUST carry a numbered endnote like [1], [2],
etc. that maps to a reference at the end of your response.
Do NOT output the literal text "[N]" β always use actual numbers.
2. Direct quote anchoring: when you reproduce three or more
consecutive words from a source, wrap them in quotation marks
and attach the endnote number immediately after.
3. Paraphrased claims still require a numbered endnote.
---------------------------------------------------------------------
REFERENCE LIST FORMAT
---------------------------------------------------------------------
At the end of every response, include a "References" section.
IMPORTANT: Do NOT use chunk IDs (CHUNK-LOCAL-001, CHUNK-SQL-001, CHUNK-WEB-001, etc.)
in the reference list. Extract the ACTUAL file name, path, and page
number from the "From:" and "Path:" lines in the context.
For local sources, use the file name and path:
[1] Chenoweth_Stephan_2011.pdf (p. 23)
-- knowledge_base/NAVCO 2.0/Chenoweth_Stephan_2011.pdf
For web sources, use author, year, title, and URL/DOI:
[2] Nepstad, S.E. (2011). "Nonviolent Revolutions"
DOI: https://doi.org/10.xxxx/yyyy
Source priority:
Local sources (user's documents) > Web sources (search results)
Organize references into two groups when both are present:
**Local Sources (Primary)**
**Web Sources (Supplementary)**
---------------------------------------------------------------------
RESPONSE LENGTH
---------------------------------------------------------------------
Keep your response length proportional to the available evidence.
Short context = short answer. Do NOT pad responses.
{kb_overview_section}=====================================================================
CONTEXT (use ONLY this to answer)
=====================================================================
{context}
=====================================================================
"""
VERIFICATION_PROMPT_TEMPLATE = """\
--- RESPONSE TO VERIFY ---
{response}
--- END RESPONSE ---
--- CONTEXT ---
{context}
--- END CONTEXT ---
--- PHRASE-LEVEL FLAGS ---
{phrase_flags}
--- END PHRASE FLAGS ---
--- SIMILARITY FLAGS ---
{similarity_flags}
--- END SIMILARITY FLAGS ---
Run through this 10-point verification checklist and report results:
1. Does every factual claim have a numbered citation endnote (e.g. [1], [2])?
2. For each cited claim, can you find supporting evidence in the context?
- For prose/document sources: look for the specific sentence or passage.
- For SQL/tabular data ([CHUNK-SQL-NNN] rows): the response may
legitimately SUMMARIZE across multiple rows (e.g., "scores ranged
from 3 to 5"). Verify that the individual data points exist in the
SQL rows, even if no single row contains the exact summary statement.
Aggregation, averaging, and range descriptions across SQL rows are
VALID as long as the underlying numbers are present in the data.
3. Are there any claims that appear fabricated or hallucinated?
4. Are direct quotes accurately reproduced from the context?
5. Does the References section exist and list all cited sources?
6. Is the source priority respected (local documents = SQL results > web sources)?
7. Are any phrase-level flags confirmed as hallucinations?
8. Do similarity flags indicate unsupported semantic drift?
9. Is the response length proportional to the available evidence?
10. Do the citation numbers match the sources? Each citation should refer to
a source in the References section that actually supports the claim.
For SQL data, a single citation may cover an entire dataset/table.
Return your analysis as JSON in the following format:
{{
"errors": [
{{"check": 1, "description": "...", "severity": "high|medium|low"}}
],
"error_count": 0,
"pass": true,
"summary": "Brief overall assessment"
}}
If there are no errors, return an empty "errors" list,
"error_count": 0, and "pass": true.
"""
QUERY_UNDERSTANDING_PROMPT_TEMPLATE = """\
You are a query reformulation assistant for a {domain} research chatbot.
Your job is to analyze the user's question and if the question is clear enough, rewrite it as an optimized search query
RULES for SEARCH:
- Expand abbreviations and acronyms (e.g., "HR" -> "human rights")
- Resolve pronouns using conversation history (e.g., "they" -> the entity from prior turn)
- Add relevant domain-specific keywords that would match document content
- Keep the reformulated search_query concise (15-40 words)
- Preserve the user's original intent -- do NOT change what they're asking about
- If the query is already clear and specific, return it with minimal changes
- Also produce a display_query: a clear, complete natural-language question
that captures the user's full intent (including any clarification context).
This is the question the AI will actually answer, so it should read like
a well-formed question a person would ask.
- USE THE KNOWLEDGE BASE OVERVIEW below to standardize your query: use the
exact dataset names, column names, file names, and terminology that appear
in the knowledge base. This ensures better retrieval matching.
(e.g., if the KB has a column "pts_a" for Political Terror Scale, rewrite
"terror scores" as "pts_a Political Terror Scale scores")
{sql_routing_block}
{kb_overview_block}
USER'S QUESTION: {query}
Respond in JSON:
{{
"search_query": "keyword-optimized query for vector search",
"sql_query": "SQLite SELECT query",
"display_query": "clear natural-language question for the AI to answer",
"reasoning": "one sentence explaining your choice"
}}
"""
_SQL_ROUTING_INSTRUCTIONS = """\
You have access to structured SQL tables in addition to vector search.Provide a valid SQLite SELECT query
in the "sql_query" field. When route is "vector", you SHOULD still include
a "sql_query" if the data tables might answer the question as a fallback.
Use exact equality (=) for text columns. The system will automatically
retry with fuzzy matching (LIKE) if the exact query returns no results.
Use only table/column names from the schema below.
{schema_text}
"""
STATA_PROMPT_TEMPLATE = {
"do": {"rewrite": "Rewrite the following Stata do-file for improved optimization and clarity:",
"suggestfix": "Consider any lines in the following do-file that may result in an error. "
"Check that the syntax is correct."
"Return any corrections in the form of 'current line -> corrected line'."
"If there are no errors, return 'No errors found'."
"Do-file:",
"explain": "Explain how the following do-file works:"
},
"code": {"rewrite": f"Rewrite the following Stata code for improved optimization and clarity:",
"suggestfix": f"Consider this Stata code. Check for any errors. "
f"Return any corrections in the form of 'current line -> corrected line'.",
"explain": f"Explain how the following Stata code works:"},
"error": {"suggestfix": "Consider the following error message, and relevant code. What is the error? "
"How do I fix it?"
"Return any corrections in the form of 'current line -> corrected line'.",
"explain": "Explain what the following stata error code or message means. If code is provided, "
"describe the error in context of the code."}
}
CATEGORIZER_PROMPT_TEMPLATE = """\
You are a query-routing classifier for a {domain} research chatbot.
Your task is to read the user's query and classify it into one or more categories. Include all applicable classes in a list
even if it is a single category and return ONLY valid JSON.
Classification rules:
- Classify as "pillar_1" when the user shows interest in understanding whether the selected value chain aligns with the government's political incentives.
- Classify as "pillar_2" when the user shows interest in understanding to what degree decisions are impacted by the lobbying of particular groups or by elite influence
- Classify as "pillar_3" when the user shows interest in understanding if the value chain/or investment intervention can be feasibly implemented given the broader institutional and policy environment
- Classify as "complete" if the input query indicates interest in all three pillars
- Classify as "qual" when shows interest in understanding if there are any political risks that could undermine the selected value chain or investment intervention
- If user input indicates the issue is resolved output "proceed" as the action, otherwise output "unresolved".
Output:
{{
"category": "pillar_1"or"pillar_2"or"pillar_3"or"complete"or"qual",
"action":"proceed"or"unresolved"
}}
USER'S QUESTION: {query}
Return only the JSON object. Do not provide explanations, markdown, or additional text.
"""
INIT_PROMPT_TEMPLATE = """\
You are a query-routing classifier for a {domain} research chatbot.
Your task is to read the user's query and extract the country, value chain, and investment area in which the PERI analysis is to be run. Return ONLY valid JSON.
Place all output values in a list and if not specified in the user query return None
Match labels included in the user queries with options included in this items list and add the item that is the closest match. Only output None if a label is not explicitly mentioned in the user query.
If no match is found return the label in the input
{approved_options}
Output:
{{
"country": ["Malawi"] or None,
"value_chain":["coffee"] or None,
"investment":["credit"] or None
}}
USER'S QUESTION: {query}
Return only the JSON object. Do not provide explanations, markdown, or additional text.
"""
VALUE_CHAIN_PROMPT_TEMPLATE = """\
You are an expert policy and public expenditure analyst for a {domain} research chatbot.
Your task is to review retrieved documents from a knowledge base and extract evidence related to government commitment to a specified agricultural value chain.
IMPORTANT RULES
1. NEVER use your training data or memory to answer questions.
Every factual claim MUST be grounded in the provided context.
2. If the context contains relevant information, you MUST provide
what you can, even if the answer is incomplete. An incomplete
answer grounded in sources is ALWAYS better than a refusal.
3. ONLY refuse if the context contains NO relevant information at
all. If evidence is not found, return "NOT_FOUND".
4. Every extracted value must be accompanied by:
- supporting quotation
- source document name
- page/section reference when available
5. Multiple documents may be provided.
6. Consolidate information across all documents.
7. STRUCTURED DATA WITH MISSING VALUES: N/A or empty values in
dataset rows are NORMAL β they mean data was not collected or
not available for that year/source. Do NOT treat N/A values as
a reason to refuse answering. Report the data that IS available
and note which fields have missing values.
8. Return output strictly as valid JSON.
---------------------------------------------------------------------
CITATION RULES
---------------------------------------------------------------------
1. Every factual claim MUST carry a numbered endnote like [1], [2],
etc. that maps to a reference at the end of your response.
Do NOT output the literal text "[N]" β always use actual numbers.
2. Direct quote anchoring: when you reproduce three or more
consecutive words from a source, wrap them in quotation marks
and attach the endnote number immediately after.
3. Paraphrased claims still require a numbered endnote.
---------------------------------------------------------------------
REFERENCE LIST FORMAT
---------------------------------------------------------------------
At the end of every response, include a "References" section.
IMPORTANT: Do NOT use chunk IDs (CHUNK-LOCAL-001, CHUNK-SQL-001, CHUNK-WEB-001, etc.)
in the reference list. Extract the ACTUAL file name, path, and page
number from the "From:" and "Path:" lines in the context.
For local sources, use the file name and path:
[1] Chenoweth_Stephan_2011.pdf (p. 23)
-- knowledge_base/NAVCO 2.0/Chenoweth_Stephan_2011.pdf
For web sources, use author, year, title, and URL/DOI:
[2] Nepstad, S.E. (2011). "Nonviolent Revolutions"
DOI: https://doi.org/10.xxxx/yyyy
Source priority:
Local sources (user's documents) > Web sources (search results)
Organize references into two groups when both are present:
**Local Sources (Primary)**
**Web Sources (Supplementary)**
---------------------------------------------------------------------
RESPONSE LENGTH
---------------------------------------------------------------------
Keep your response length proportional to the available evidence.
Short context = short answer. Do NOT pad responses.
--------------------------------------------------------
TARGET VALUE CHAIN
--------------------------------------------------------
Value Chain: {value_chain}
--------------------------------------------------------
EXTRACTION TASK
--------------------------------------------------------
{extraction_block}
"""
FINANCIAL_COMMITMENT_BLOCK = """\
Use ONLY the information contained in:
{context}
TASK
Search for:
- budget allocations
- expenditure allocations
- public investment programs
- projects
- financing commitments
Identify:
A. Budget allocation at least five years prior to current date
B. Most recent budget allocation
Extract:
- budget_year (datetime year)
- budget_value (float or int)
- currency (float or int)
- responsible_institution (string)
If multiple values exist, report all candidate values.
store the extracted information as a list of dictionaries for each candidate set of values
--------------------------------------------------------
BUDGET INFORMATION OUTPUT FORMAT
--------------------------------------------------------
[{{
"budget_year": "",
"budget_value": "",
"currency": "",
"responsible_institution": "",
"evidence": [
{{
"quote": "",
"source_document": "",
"page_or_section": ""
}}
]
}}]
"""
STRATEGIC_COMMITMENT_BLOCK = """\
Use ONLY the information contained in:
{context}
TASKS:
a) Return a list of all national development plans that contain any mention of the value chain
b) Return a list of all agricultural strategy documents that contain any mention of the value chain
c) Return a list of all standalone strategies for the value chain
d) Return a list of all documents containing donor funded flagship projects that have any mention of the value chain
Prioritize documents published in the last five years.
Definitions
National Development Plan:
Country's primary medium-term development plan.
Agricultural Strategy:
Most recent agricultural strategy, investment plan, transformation plan,
or equivalent.
Standalone Value Chain Strategy:
Strategy primarily dedicated to the target value chain.
Flagship Project:
Major donor-supported initiative focused on the value chain.
--------------------------------------------------------
STRATEGIC COMMITMENT OUTPUT
--------------------------------------------------------
include a quote with the exact mention along with the publication year in a tuple as illustrated below
{{
"development_plan": [{{
"quote": "",
"document": "",
"page_or_section": "",
"year": "",
}}],
"agricultural_strategy": [{{
"quote": "",
"document": "",
"page_or_section": "",
"year": "",
}}],
"standalone_strategy": [{{
"quote": "",
"document": "",
"page_or_section": "",
"year": "",
}}],
"flagship_project": [{{
"quote": "",
"document": "",
"page_or_section": "",
"year": "",
}}],
}}
"""
INSTITUTIONAL_COMMITMENT_BLOCK = """\
Use ONLY the information contained in:
{context}
TASKS:
A. Return a list of documents indicating whether the value chain has a
dedicated government agency or parastatal
B. Return a list of documents indicating whether the value chain has a
dedicated research institute or research center
C. Return a list of documents indicating whether the value chain has a
dedicated ministry department, directorate, authority, unit, board, or council
--------------------------------------------------------
OUTPUT FORMAT
--------------------------------------------------------
include a quote with the exact mention as illustrated below.
{{
"dedicated_agency": [{{
"agency_name": "",
"document": "",
"page_or_section": "",
"quote": "",
}}],
"dedicated_research_institute": [{{
"institution_name": "",
"document": "",
"page_or_section": "",
"quote": "",
}}],
"dedicated_ministerial_department": [{{
"department_name": "",
"document": "",
"page_or_section": "",
"quote": "",
}}],
}}
"""
FINANCIAL_COMMITMENT_PROMPT = """\
Search for budget allocations, expenditure allocations, projects, programs,
or public investments associated with {value_chain}.
"""
STRATEGIC_COMMITMENT_PROMPT = """\
Extract all agricultural strategies, development plans, donor strategies,
and {value_chain} specific strategies in the last 10 years.
"""
INSTITUTIONAL_COMMITMENT_PROMPT = """\
Search for government agencies or parastatals, research institutes or centers,
ministry departments, directorates, authorities, or units, related to {value_chain}
"""
VC_RAG = {
"financial_commitment":FINANCIAL_COMMITMENT_PROMPT,
"strategic_commitment":STRATEGIC_COMMITMENT_PROMPT,
"institutional_commitment":INSTITUTIONAL_COMMITMENT_PROMPT,
}
VC_EXTRACT = {
"financial_commitment":FINANCIAL_COMMITMENT_BLOCK,
"strategic_commitment":STRATEGIC_COMMITMENT_BLOCK,
"institutional_commitment":INSTITUTIONAL_COMMITMENT_BLOCK,
}
def build_vc_analyzer_prompt(cfg:Dict,
retrieval_result:Dict,
value_chain:str,
domain:str
):
extraction_block = VC_EXTRACT[retrieval_result['var']].format(context=retrieval_result["context"])
return VALUE_CHAIN_PROMPT_TEMPLATE.format(
domain=domain,
value_chain=value_chain,
extraction_block=extraction_block
)
def dict_to_string(d, indent=0):
result = ""
spacing = " " * indent
for key, value in d.items():
if isinstance(value, dict):
result += f"{spacing*2}{key}:\n\n" + dict_to_string(value, indent + 1)
elif isinstance(value, list):
result += f"{spacing*2}{key}: " + ", ".join(value) + "\n\n" + spacing
else:
result += f"{spacing}{key}: {value}\n\n"
return result
def build_stata_prompt(query:str,
subcmd:str,
dofx:str,
opts:List
) -> str:
for i in ["rewrite", "suggestfix"]:
if opts[i]:
return f"{STATA_PROMPT_TEMPLATE[subcmd][i]}\n{query} {dofx}"
return f"{STATA_PROMPT_TEMPLATE[subcmd]['explain']}\n{query} {dofx}"
def build_categorizer_prompt(query:str,
domain:str
):
return CATEGORIZER_PROMPT_TEMPLATE.format(
domain=domain,
query=query,
)
def build_init_prompt(query:str,
domain:str
):
return INIT_PROMPT_TEMPLATE.format(
domain=domain,
query=query,
approved_options=APPROVED_OPTIONS
)
def build_query_understanding_prompt(
query: str, domain: str,
sql_schema_summary: str = "",
kb_overview: str = "",
) -> str:
"""Build the query understanding prompt for pre-retrieval reformulation.
Args:
query: The user's raw question.
domain: Knowledge domain from config.
history: Recent conversation messages for context resolution.
sql_schema_summary: Compact SQL schema summary (empty if no tables).
kb_overview: High-level KB overview for routing awareness (optional).
Returns:
Formatted query understanding prompt string.
"""
if sql_schema_summary:
sql_routing_block = _SQL_ROUTING_INSTRUCTIONS.format(
schema_text=sql_schema_summary
)
else:
sql_routing_block = '(No SQL tables available β always use route "vector" and omit sql_query.)\n'
if kb_overview:
kb_overview_block = (
"KNOWLEDGE BASE CONTENTS (use this to understand what is available):\n"
+ kb_overview
+ "\n"
)
else:
kb_overview_block = ""
return QUERY_UNDERSTANDING_PROMPT_TEMPLATE.format(
domain=domain,
query=query,
sql_routing_block=sql_routing_block,
kb_overview_block=kb_overview_block,
)
def build_prompt(
context: str, bot_name: str, domain: str, kb_overview: str = "",
) -> str:
"""Build the system prompt with anti-hallucination guardrails.
Args:
context: The retrieved context chunks to ground the response.
bot_name: Display name of the chatbot.
domain: Knowledge domain the bot specializes in.
kb_overview: High-level KB overview for general awareness (optional).
Returns:
Formatted system prompt string.
"""
if kb_overview:
overview_section = (
"---------------------------------------------------------------------\n"
"KNOWLEDGE BASE OVERVIEW (general awareness β do NOT cite this\n"
"section directly; use it to understand the broader context)\n"
"---------------------------------------------------------------------\n"
f"{kb_overview}\n\n"
)
else:
overview_section = ""
# Sanitize context to prevent prompt injection via delimiter mimicry
safe_context = context.replace("=" * 69, "\u2261" * 69)
return SYSTEM_PROMPT_TEMPLATE.format(
bot_name=bot_name,
domain=domain,
context=safe_context,
kb_overview_section=overview_section,
)
def build_verification_prompt(
response: str,
context: str,
phrase_flags: list[str],
similarity_flags: list[str],
) -> str:
"""Build the verification prompt for auditing an AI response.
Args:
response: The AI-generated response to verify.
context: The original context the response should be grounded in.
phrase_flags: List of phrase-level flag descriptions.
similarity_flags: List of similarity-based flag descriptions.
Returns:
Formatted verification prompt string.
"""
phrase_str = (
"\n".join(f"- {flag}" for flag in phrase_flags)
if phrase_flags
else "No phrase-level flags detected."
)
similarity_str = (
"\n".join(f"- {flag}" for flag in similarity_flags)
if similarity_flags
else "No similarity flags detected."
)
return VERIFICATION_PROMPT_TEMPLATE.format(
response=response,
context=context,
phrase_flags=phrase_str,
similarity_flags=similarity_str,
)
|