File size: 4,517 Bytes
cc2ed2f
 
 
 
 
 
 
 
 
 
 
 
 
8871df9
 
 
cc2ed2f
 
 
 
 
 
 
8871df9
 
 
 
 
 
 
 
cc2ed2f
8871df9
 
 
cc2ed2f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8871df9
cc2ed2f
8871df9
 
 
 
cc2ed2f
 
 
 
 
 
 
 
 
 
8871df9
 
cc2ed2f
 
 
 
 
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
"""PromptBuilder — формирование chat-template input для модели.

Соответствует разделу 2.4 пояснительной записки. Один и тот же билдер
используется при обучении (формирование SFT-примеров) и при инференсе
(формирование запроса к загруженной модели), что гарантирует совпадение
формата train- и inference-time промптов.

Помимо схемы и вопроса, билдер опционально принимает BusinessVocabulary
(раздел 3.6 ВКР). Бизнес-термины подмешиваются в системное сообщение,
а не конкатенируются к пользовательскому вопросу — это согласуется с
тем, как современные instruction-tuned модели интерпретируют роли
сообщений в chat-template.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from src.business.vocabulary import BusinessVocabulary


BASE_SYSTEM_PROMPT = (
    "Ты — ассистент, который преобразует вопросы на русском языке в корректные SQL-запросы. "
    "Тебе даётся схема базы данных в виде CREATE TABLE statements и пример нескольких строк. "
    "Сгенерируй один SQL-запрос, который отвечает на вопрос пользователя. "
    "Возвращай ТОЛЬКО SQL без объяснений, без markdown, без префиксов."
)


def build_user_message(schema: str, question: str) -> str:
    """Пользовательская часть промпта в формате ``### Schema / ### Question / ### SQL:``."""
    return f"### Schema:\n{schema}\n\n### Question:\n{question}\n\n### SQL:\n"


def build_system_message(vocabulary: "BusinessVocabulary | None" = None) -> str:
    """Собирает системное сообщение.

    Если передан непустой бизнес-словарь, к базовому промпту добавляется
    блок с определениями терминов, фильтрами и правилами компании. Это
    позволяет адаптировать систему к терминологии конкретной организации
    без повторного дообучения модели.
    """
    if vocabulary is None or not vocabulary:
        return BASE_SYSTEM_PROMPT
    context = vocabulary.render_system_context()
    if not context:
        return BASE_SYSTEM_PROMPT
    return BASE_SYSTEM_PROMPT + "\n\n" + context


def build_chat_messages(
    schema: str,
    question: str,
    vocabulary: "BusinessVocabulary | None" = None,
) -> list[dict]:
    """Сообщения для ``tokenizer.apply_chat_template``.

    Параметры
    ---------
    schema : str
        Текстовое представление схемы (CREATE TABLE + sample rows).
    question : str
        Вопрос пользователя на русском языке.
    vocabulary : BusinessVocabulary, optional
        Бизнес-словарь компании. Если передан — добавляется в системное
        сообщение, не нарушая структуры пользовательской реплики.
    """
    return [
        {"role": "system", "content": build_system_message(vocabulary)},
        {"role": "user", "content": build_user_message(schema, question)},
    ]


def build_training_example(
    schema: str,
    question: str,
    sql: str,
    vocabulary: "BusinessVocabulary | None" = None,
) -> list[dict]:
    """Полный диалог с эталонной репликой ассистента для Supervised
    Fine-Tuning (раздел 2.4 ВКР).
    """
    msgs = build_chat_messages(schema, question, vocabulary)
    msgs.append({"role": "assistant", "content": sql.strip()})
    return msgs


# Обратная совместимость. Имя SYSTEM_PROMPT использовалось в старом коде
# и в тестах; сохраняем алиас, чтобы не ломать импорты.
SYSTEM_PROMPT = BASE_SYSTEM_PROMPT