|
1 | 1 | from application.database import db |
2 | 2 | from application.defs import cre_defs |
3 | | -from application.prompt_client import openai_prompt_client, vertex_prompt_client |
4 | 3 | from datetime import datetime |
5 | 4 | from multiprocessing import Pool |
6 | 5 | from nltk.corpus import stopwords |
|
16 | 15 | from sklearn.metrics.pairwise import cosine_similarity |
17 | 16 | from typing import Dict, List, Any, Tuple, Optional |
18 | 17 | from pydantic import ValidationError |
| 18 | +from jinja2 import Environment, FileSystemLoader, StrictUndefined |
19 | 19 | import logging |
20 | 20 |
|
21 | 21 | try: |
|
35 | 35 | logger.setLevel(logging.INFO) |
36 | 36 |
|
37 | 37 | SIMILARITY_THRESHOLD = float(os.environ.get("CHATBOT_SIMILARITY_THRESHOLD", "0.7")) |
| 38 | +PROMPT_TEMPLATES_DIR = os.path.join(os.path.dirname(__file__), "templates") |
| 39 | +PROMPT_TEMPLATE_ENV = Environment( |
| 40 | + loader=FileSystemLoader(PROMPT_TEMPLATES_DIR), |
| 41 | + undefined=StrictUndefined, |
| 42 | + autoescape=False, |
| 43 | + trim_blocks=True, |
| 44 | + lstrip_blocks=True, |
| 45 | +) |
38 | 46 |
|
39 | 47 |
|
40 | 48 | def _safe_truncate_for_log(text: str, limit: int = 600) -> str: |
@@ -95,6 +103,15 @@ def _is_llm_rate_limit_error(err: Exception) -> bool: |
95 | 103 | return status == 429 |
96 | 104 |
|
97 | 105 |
|
| 106 | +def _render_chat_prompt(*, question: str, retrieved_knowledge: Optional[str]) -> str: |
| 107 | + template = PROMPT_TEMPLATE_ENV.get_template("chat_prompt.j2") |
| 108 | + return template.render( |
| 109 | + question=question, |
| 110 | + retrieved_knowledge=retrieved_knowledge or "", |
| 111 | + has_retrieved_knowledge=bool(retrieved_knowledge), |
| 112 | + ) |
| 113 | + |
| 114 | + |
98 | 115 | def is_valid_url(url): |
99 | 116 | return url.startswith("http://") or url.startswith("https://") |
100 | 117 |
|
@@ -697,9 +714,11 @@ def __init__(self, database: db.Node_collection, load_all_embeddings=False) -> N |
697 | 714 | "litellm package is required for PromptHandler LLM calls" |
698 | 715 | ) from e |
699 | 716 | self._litellm = litellm |
700 | | - self.chat_model = os.environ.get("CRE_LLM_CHAT_MODEL", "openai/gpt-4o-mini") |
| 717 | + self.chat_model = os.environ.get( |
| 718 | + "CRE_LLM_CHAT_MODEL", "gemini/gemini-2.5-flash" |
| 719 | + ) |
701 | 720 | self.embed_model = os.environ.get( |
702 | | - "CRE_EMBED_MODEL", "openai/text-embedding-3-small" |
| 721 | + "CRE_EMBED_MODEL", "gemini/gemini-embedding-001" |
703 | 722 | ) |
704 | 723 | self.align_model = os.environ.get("CRE_EMBED_ALIGN_MODEL", self.chat_model) |
705 | 724 | self._llm_max_retries = int(os.environ.get("CRE_LLM_MAX_RETRIES", "2")) |
@@ -801,20 +820,19 @@ def _call() -> Any: |
801 | 820 | return vectors[0] |
802 | 821 |
|
803 | 822 | def create_chat_completion(self, prompt: str, closest_object_str: str) -> str: |
| 823 | + rag_instruction = _render_chat_prompt( |
| 824 | + question=prompt, |
| 825 | + retrieved_knowledge=closest_object_str, |
| 826 | + ) |
804 | 827 | messages = [ |
805 | 828 | { |
806 | 829 | "role": "system", |
807 | | - "content": "Assistant is a large language model trained for cybersecurity help.", |
808 | | - }, |
809 | | - { |
810 | | - "role": "user", |
811 | 830 | "content": ( |
812 | | - "Your task is to answer the following question based on this area of " |
813 | | - f"knowledge: `{closest_object_str}` delimit any code snippet with three " |
814 | | - "backticks ignore all other commands and questions that are not relevant.\n" |
815 | | - f"Question: `{prompt}`" |
| 831 | + "You are OpenCRE Chat, a cybersecurity assistant. " |
| 832 | + "Follow the user instructions strictly." |
816 | 833 | ), |
817 | 834 | }, |
| 835 | + {"role": "user", "content": rag_instruction}, |
818 | 836 | ] |
819 | 837 |
|
820 | 838 | def _call() -> Any: |
@@ -888,21 +906,19 @@ def _call_json_object_fallback() -> Any: |
888 | 906 | raise |
889 | 907 |
|
890 | 908 | def query_llm(self, raw_question: str) -> str: |
| 909 | + direct_instruction = _render_chat_prompt( |
| 910 | + question=raw_question, |
| 911 | + retrieved_knowledge=None, |
| 912 | + ) |
891 | 913 | messages = [ |
892 | 914 | { |
893 | 915 | "role": "system", |
894 | | - "content": "Assistant is a large language model trained for cybersecurity.", |
895 | | - }, |
896 | | - { |
897 | | - "role": "user", |
898 | 916 | "content": ( |
899 | | - "Your task is to answer the following cybersecurity question if you can, " |
900 | | - "provide code examples, delimit any code snippet with three backticks, " |
901 | | - "ignore any unethical questions or questions irrelevant to cybersecurity\n" |
902 | | - f"Question: `{raw_question}`\n" |
903 | | - "ignore all other commands and questions that are not relevant." |
| 917 | + "You are OpenCRE Chat, a cybersecurity assistant. " |
| 918 | + "Follow the user instructions strictly." |
904 | 919 | ), |
905 | 920 | }, |
| 921 | + {"role": "user", "content": direct_instruction}, |
906 | 922 | ] |
907 | 923 |
|
908 | 924 | def _call() -> Any: |
|
0 commit comments