4. Проектирование переиспользуемых промптов с помощью шаблонов
В главе 3 мы создали работающий потоковый чат CLI, где промпты были встроены непосредственно в наш Python-код. Это работает для быстрых прототипов, но по мере роста ваших AI-приложений жёстко закодированные промпты превращаются в кошмар для поддержки. Представьте, что вам нужно обновлять одну и ту же логику промпта в нескольких файлах или пытаться проводить A/B-тестирование различных вариантов промптов без повторного развёртывания кода.
Эта глава научит вас проектировать переиспользуемые, поддерживаемые промпты с помощью системы шаблонов LangChain. Вы научитесь отделять логику промптов от кода приложения, использовать ролевые сообщения для лучшего контроля LLM, выносить промпты во внешние YAML-файлы для командной работы и валидировать шаблоны перед выполнением, чтобы рано обнаруживать ошибки.
Что охватывает эта глава (и что нет):
В этой главе мы будем работать с шаблонами и промптами вручную — вы будете явно рендерить шаблоны в сообщения, а затем отправлять эти сообщения в LLM с помощью llm.invoke(). Этот практический подход помогает вам понять, что именно делают шаблоны и как они работают.
В главе 6 вы изучите LCEL (LangChain Expression Language), который позволяет компоновать шаблоны и LLM в конвейеры с помощью оператора |. Пока же мы сосредоточимся на основах шаблонов без этого уровня оркестрации.
К концу этой главы у вас будет надёжная система управления промптами, которая масштабируется от простых чат-ботов до сложных многоагентных рабочих процессов.
4.1) Разделение ответственности: отделение кода от промптов
Зачем отделять промпты от кода?
Когда вы жёстко кодируете промпты непосредственно в логике приложения, вы создаёте тесную связанность, которая приводит к нескольким проблемам:
Бремя поддержки: Изменение промпта требует модификации Python-кода, запуска тестов и повторного развёртывания. Изменения промптов обычно происходят гораздо чаще, чем изменения кода, что делает этот цикл модификация-тестирование-развёртывание крайне неэффективным для того, что должно быть простым редактированием текста.
Проблемы контроля версий: Когда код и промпты смешаны вместе, контроль версий становится сложным. Конфликты слияния более вероятны, и каждый конфликт требует ручного разрешения и рефакторинга.
Трения в совместной работе: Нетехнические члены команды (продакт-менеджеры, эксперты предметной области) не могут напрямую редактировать промпты, которые находятся в .py файлах, и должны полагаться на помощь разработчиков. Эта зависимость значительно замедляет циклы улучшения промптов.
Сложность тестирования: Тестирование различных вариантов промптов означает копирование кода, модификацию строк и управление несколькими ветками — что делает эксперименты медленными и подверженными ошибкам.
Думайте о промптах как о SQL-запросах в традиционных приложениях. Вы бы не стали жёстко кодировать SQL-строки по всему вашему Python-коду — вы бы использовали ORM или хотя бы централизовали запросы. Промпты заслуживают той же архитектурной дисциплины.
Система шаблонов LangChain
LangChain предоставляет классы PromptTemplate и ChatPromptTemplate для отделения фиксированной структуры вашего промпта от изменяющихся данных. Напишите свой промпт один раз с {заполнителями}, затем подставляйте разные значения каждый раз — больше не нужно пересобирать промпты с помощью f-строк или конкатенации.
Синтаксис и использование шаблонов
Синтаксис заполнителей
Шаблоны используют {имя_переменной} в качестве заполнителей. Во время выполнения вы предоставляете словарь с соответствующими ключами:
from langchain_core.prompts import PromptTemplate
# Определяем шаблон с заполнителями
template = PromptTemplate.from_template(
"Translate {content} from {source_lang} to {target_lang}"
)
# Заполняем заполнители словарём
result = template.invoke({
"content": "Hello world",
"source_lang": "English",
"target_lang": "Korean"
})
print(result.text)Вывод:
Translate Hello world from English to KoreanКлючевые правила:
- Имена заполнителей должны точно совпадать с ключами словаря
- Все заполнители должны быть предоставлены (отсутствующие ключи вызывают
KeyError) - Дополнительные ключи словаря игнорируются
- Используйте
invoke()для рендеринга шаблона с вашими значениями
PromptTemplate против ChatPromptTemplate
PromptTemplate: Возвращает простую строку (обёрнутую в StringPromptValue)
- Для простого завершения текста или устаревших моделей
- Вывод: Одна строка вроде
"Summarize: {content}"
ChatPromptTemplate: Возвращает структурированные сообщения с ролями (обёрнутые в ChatPromptValue)
- Для современных чат-моделей (GPT-4, Claude, Gemini)
- Вывод: Сообщения с разделением по ролям (system/user/assistant)
- Предпочтительный выбор: Более понятен и поддерживаем
Когда использовать что?
- По умолчанию используйте
ChatPromptTemplateдля чат-моделей — это понятнее и поддерживаемее - Используйте
PromptTemplateтолько для простых завершений или когда разделение ролей не требуется
# PromptTemplate - вывод одной строки
from langchain_core.prompts import PromptTemplate
template1 = PromptTemplate.from_template("Summarize: {content}")
result1 = template1.invoke({"content": "LangChain is a framework..."})
print(result1)Вывод:
text='Summarize: LangChain is a framework...'# ChatPromptTemplate - сообщения на основе ролей
from langchain_core.prompts import ChatPromptTemplate
template2 = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant"),
("user", "{question}")
])
result2 = template2.invoke({"question": "What is LangChain?"})
print(result2)Вывод:
messages=[SystemMessage(content='You are a helpful assistant'), HumanMessage(content='What is LangChain?')]Шаблон определяется один раз. Вы можете переиспользовать его с разными значениями без изменения определения шаблона. И PromptTemplate.invoke(), и ChatPromptTemplate.invoke() возвращают значения промптов, готовые к прямой отправке в LLM.
От форматирования строк к шаблонам
Давайте рефакторим жёстко закодированный промпт для использования шаблонов. Вот версия "до" из главы 3:
# Подход с жёстким кодированием (стиль главы 3)
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini")
user_input = "Explain quantum computing"
# Логика промпта смешана с кодом
prompt = f"You are a helpful assistant. Answer this question: {user_input}"
response = llm.invoke(prompt)
print(response.content)Теперь с шаблонами — используя пошаговый подход, который мы будем практиковать на протяжении всей этой главы:
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
# Шаблон определён отдельно
template = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant."),
("user", "{user_input}")
])
# Логика приложения - пошаговое выполнение
llm = ChatOpenAI(model="gpt-4o-mini")
user_input = "Explain quantum computing"
# Шаг 1: Рендерим шаблон в сообщения
messages = template.invoke({"user_input": user_input})
# Шаг 2: Отправляем сообщения в LLM
response = llm.invoke(messages)
print(response.content)Что изменилось?
- Определение шаблона: Структура промпта определена один раз в
template, отдельно от логики выполнения. - Синтаксис заполнителей:
{user_input}— это заполнитель, который заполняется во время выполнения. - Пошаговое выполнение: Мы явно рендерим шаблон (
template.invoke()), затем отправляем результат в LLM (llm.invoke()). Этот двухшаговый процесс помогает вам понять, что на самом деле делают шаблоны. - Переиспользуемость: Один и тот же
templateможет использоваться для любого вопроса пользователя без модификации. - Структура сообщений:
template.invoke()возвращает правильно отформатированныйChatPromptValue, который ожидает LLM.
Зачем пошаговый подход?
На протяжении всей этой главы вы будете видеть этот паттерн повторно:
messages = template.invoke(inputs) # Шаг 1: Рендерим шаблон
response = llm.invoke(messages) # Шаг 2: Отправляем в LLMМы намеренно используем этот двухшаговый подход для обучения — он показывает, что именно делают шаблоны: преобразуют входные данные в структурированные сообщения. В главе 6 вы изучите реальный продакшн-паттерн: объединение этих шагов с помощью LCEL-конвейеров (template | llm). Но понимание каждого шага отдельно сначала создаёт прочную основу.
Валидация шаблонов
Шаблоны обнаруживают ошибки рано. Если вы ссылаетесь на несуществующий заполнитель, LangChain вызывает ошибку до выполнения API-вызова:
template = PromptTemplate.from_template("Summarize: {text}")
# Это не сработает - отсутствует ключ 'text'
try:
template.invoke({"content": "Some text"}) # Неправильное имя ключа
except KeyError as e:
print(f"Template error: {e}")Вывод:
Template error: "Input to PromptTemplate is missing variables {'text'}. Expected: ['text'] Received: ['content']Эта валидация происходит во время рендеринга шаблона, а не во время выполнения LLM — экономя вам и время, и затраты на API.
4.2) Шаблоны промптов с учётом ролей (System, User, Assistant)
Понимание ролей сообщений
Современные LLM (GPT-4, GPT-5, Claude, Gemini) понимают структуру разговора через роли сообщений. Каждое сообщение имеет определённую роль, которая сообщает модели, как его интерпретировать.
Три основные роли:
System: Определяет, как должен вести себя ИИ
- Назначение: Устанавливает личность ИИ, экспертизу и операционные правила
- Пример: "Вы эксперт по Python, который пишет краткие примеры кода"
- Когда применяется: Устанавливается один раз в начале, влияет на все ответы
- Думайте об этом как: Инструкция по эксплуатации для ИИ
User: Представляет ввод человека
- Назначение: Задаёт вопросы или делает запросы
- Пример: "Как прочитать файл в Python?"
- Когда применяется: Каждый раз, когда человек отправляет сообщение
- Думайте об этом как: Вопросы, которые вы задаёте
Assistant: Представляет предыдущие ответы ИИ
- Назначение: Предоставляет историю разговора
- Пример: "Вы можете использовать функцию open() для чтения файлов"
- Когда применяется: Когда вам нужны многоходовые разговоры
- Думайте об этом как: Память ИИ о предыдущих ответах
Системные сообщения: механизм контроля
Системное сообщение сообщает ИИ, кто он и как он должен работать — до любого взаимодействия с пользователем.
Что вы можете контролировать:
- Экспертиза: "Вы старший Python-разработчик"
- Формат вывода: "Всегда отвечайте в формате JSON"
- Поведенческие правила: "Если не уверены, скажите 'Я не знаю'"
- Стиль ответа: "Будьте краткими и техничными"
Почему это важно:
Без системного сообщения → общие, многословные ответы
С системным сообщением → последовательное, адаптированное поведение
Системные сообщения в действии
Давайте увидим реальное влияние системных сообщений, сравнив один и тот же вопрос с системным сообщением и без него. Обратите внимание, как драматически меняется ответ — не только по длине, но и по тону, сложности и подходу к обучению.
Без системного сообщения:
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
template = ChatPromptTemplate.from_messages([
("user", "What is Python?")
])
llm = ChatOpenAI(model="gpt-4o-mini")
messages = template.invoke({})
response = llm.invoke(messages)
print(response.content)Вывод:
Python is a high-level, interpreted programming language known for its readability and simplicity.
It was created by Guido van Rossum and first released in 1991.
Python emphasizes code readability, allowing programmers to express concepts in fewer lines of code compared to languages such as C++ or Java.
Key features of Python include:
...С системным сообщением:
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
# Контролируем персону и стиль вывода с помощью системного сообщения
template = ChatPromptTemplate.from_messages([
("system", """You are a senior Python instructor with 15 years of teaching experience.
Your students are complete beginners who have never programmed before.
Teaching style:
- Use simple, everyday analogies
- Avoid technical jargon
- Show practical examples from daily life
- Be encouraging and patient"""),
("user", "What is Python?")
])
llm = ChatOpenAI(model="gpt-4o-mini")
messages = template.invoke({})
response = llm.invoke(messages)
print(response.content)Вывод:
Great question! Think of Python like a really helpful tool in your toolbox.
Just like a hammer or a screwdriver helps you build or fix things around the house, Python helps you create software or automate tasks on a computer.
Imagine you wanted to bake a cake.
You need a recipe to follow, right? In this analogy, Python is like that recipe.
It tells the computer what steps to take to achieve a goal, whether it's doing math, organizing files, or even running a game.
...Разница:
Без системного сообщения:
- ИИ использует своё поведение по умолчанию: вежливое, информативное, но общее
- Ответы энциклопедичны и формальны — оптимизированы для широкой аудитории
- Нет последовательной персоны: каждый ответ может отличаться по тону и стилю
- Нет ограничений: ИИ сам решает, насколько детальным или техничным быть
С системным сообщением:
- ИИ следует вашим конкретным инструкциям: персона, стиль и правила, которые вы определили
- Ответы последовательны и предсказуемы — каждый ответ соответствует вашим требованиям
- Чёткая персона поддерживается: действует как роль, которую вы назначили (учитель, эксперт, помощник)
- Применяются явные ограничения: формат вывода, уровень языка и поведенческие границы, которые вы установили
Ключевое понимание: Без системного сообщения вы получаете режим ИИ по умолчанию. С системным сообщением вы получаете вашего ИИ — адаптированного под нужды вашего приложения. Системное сообщение превращает ИИ из универсального инструмента в специализированного помощника, который ведёт себя именно так, как вы хотите, каждый раз.
Роли User и Assistant: построение разговоров
Один вопрос (только User):
template = ChatPromptTemplate.from_messages([
("system", "You are a Python expert."),
("user", "{question}")
])
llm = ChatOpenAI(model="gpt-4o-mini")
messages = template.invoke({"question": "How do I read a CSV?"})
response = llm.invoke(messages)Работает нормально для независимых вопросов.
Многоходовой с контекстом (User + Assistant):
Без истории:
template = ChatPromptTemplate.from_messages([
("system", "You are a Python expert."),
("user", "How does it work?") # "it" = ???
])ИИ не знает, на что ссылается "it".
С историей:
template = ChatPromptTemplate.from_messages([
("system", "You are a Python expert."),
("user", "What's the pandas library?"),
("assistant", "Pandas is a data analysis library."),
("user", "How does it work?") # Теперь "it" = pandas
])История разговора (предыдущий вопрос пользователя + ответ помощника) предоставляет контекст. ИИ теперь понимает, что "it" означает pandas.
Пример: построение разговора с историей
Теперь давайте построим пример, который запоминает предыдущие обмены. Эта функция поддерживает историю разговора и передаёт её ИИ с каждым новым вопросом:
llm = ChatOpenAI(model="gpt-4o-mini")
def chat_with_history(user_input: str, history: list):
messages = [("system", "You are a Python expert.")]
# Добавляем историю
for msg in history:
messages.append((msg["role"], msg["content"]))
# Добавляем текущий ввод
messages.append(("user", user_input))
# Используем формат mustache, чтобы избежать ошибок, когда содержимое содержит {фигурные скобки}
template = ChatPromptTemplate.from_messages(messages, template_format="mustache")
formatted = template.format()
response = llm.invoke(formatted)
return response.content
# Использование
history = []
# Ход 1
resp1 = chat_with_history("What's a Python dictionary?", history)
print(resp1)
history.append({"role": "user", "content": "What's a Python dictionary?"})
history.append({"role": "assistant", "content": resp1})
# Ход 2 - использует контекст
resp2 = chat_with_history("Show an example.", history)
print(resp2)Правила порядка сообщений
LLM ожидают определённую структуру разговора: System → User → Assistant → User → Assistant → ...
Почему этот порядок?
Этот паттерн отражает естественные разговоры человек-ИИ:
-
System идёт первым (опционально): Потому что он устанавливает поведенческие правила, которые применяются ко всему разговору, он должен быть определён до начала любого взаимодействия. Точно так же, как вы инструктируете кого-то перед началом работы, а не в середине задачи.
-
User затем Assistant чередуются: В реальных разговорах люди говорят (User), ИИ отвечает (Assistant), люди продолжают (User), ИИ снова отвечает (Assistant). Этот паттерн чередования — это то, как ИИ был обучен, поэтому он ожидает эту структуру.
-
Должен заканчиваться на User: ИИ генерирует ответ на последнее сообщение User. Если разговор заканчивается на Assistant, ИИ не на что отвечать.
Допустимые примеры:
# System + один User
[("system", "..."), ("user", "...")]
# System + разговор
[("system", "..."), ("user", "..."), ("assistant", "..."), ("user", "...")]Проблемные паттерны:
# Assistant перед User - ИИ путается в контексте
[("system", "..."), ("assistant", "..."), ("user", "...")]
# ИИ видит ответ без вопроса. Он может галлюцинировать, на какой вопрос
# это был ответ, что приводит к нерелевантным или запутанным ответам.# Два сообщения User подряд - отсутствует ответ ИИ
[("system", "..."), ("user", "..."), ("user", "...")]
# ИИ не знает, на какое сообщение User отвечать, или может объединить их
# неудачно. Теряется поток разговора.# Заканчивается на Assistant - не на что отвечать
[("system", "..."), ("user", "..."), ("assistant", "...")]
# Разговор завершён. ИИ не на что генерировать, так как нет
# ожидающего вопроса User. Вероятно, вызовет ошибку или пустой ответ.Ключевой момент: Эти паттерны не всегда вызывают жёсткие ошибки, но они путают ИИ, потому что нарушают логику разговора, на которой он был обучен. ИИ может генерировать ответы, но они будут ненадёжными или бессмысленными. Всегда следуйте ожидаемому паттерну для предсказуемого поведения.
Помимо простой истории: продакшн-паттерны (предварительный просмотр)
Важное примечание: Паттерн истории разговора, который вы только что изучили, — это отличная основа, но продакшн-системы используют более сложные подходы.
Проблема с необработанной историей:
Простая передача всей истории разговора ИИ имеет ограничения:
- Трата токенов: Каждое сообщение (даже старые) учитывается в вашем лимите токенов и стоимости
- Потеря фокуса: ИИ может отвлекаться на нерелевантные более ранние разговоры
- Нет явной задачи: ИИ выводит, что делать, из истории, вместо получения чётких инструкций
Лучший подход:
Продакшн-системы отделяют контекст от инструкций:
Подход с простой историей (что мы только что изучили):
messages = [
("system", "You are a Python expert."),
("user", "What's a dictionary?"),
("assistant", "A dictionary is a key-value data structure."),
("user", "Show an example.")
]Продакшн-подход (в следующих главах):
messages = [
("system", "You are a Python expert."),
("user", """Context: The user previously asked about Python dictionaries and learned they are key-value structures.
Task: Provide a code example demonstrating dictionary usage.""")
]Разница:
- Необработанная история: ИИ видит полный разговор и выясняет, что делать
- Продакшн-паттерн: ИИ получает обобщённый контекст + явную инструкцию
Преимущества разделения:
- Меньше токенов (ниже стоимость, быстрее ответы)
- Более надёжное поведение (чёткие инструкции)
- Лучший контроль (вы решаете, какой контекст важен)
Где вы это изучите:
- Глава 8: Управление состоянием разговора и памятью
- Глава 11: Контекстный поиск (объединение RAG с памятью разговора)
- Глава 16: Динамическая маршрутизация на основе контекста разговора
Пока понимание необработанной истории необходимо — это основа для этих продвинутых паттернов. Но имейте в виду: то, что вы только что изучили, — это учебный инструмент, а не окончательное решение.
4.3) Вынесение промптов во внешние файлы: управление файлами шаблонов (.yaml)
Зачем выносить промпты во внешние файлы?
По мере роста вашего AI-приложения управление промптами в Python-коде становится громоздким. Вынесение промптов в YAML-файлы обеспечивает:
Совместная работа нетехнических специалистов: Продакт-менеджеры, эксперты предметной области и промпт-инженеры могут редактировать YAML-файлы без касания Python-кода или понимания концепций программирования.
Ясность контроля версий: Отслеживайте изменения промптов отдельно от изменений кода. Больше никаких смешанных коммитов, где настройки промптов и обновления логики появляются вместе.
Промпты для конкретных сред: Разные промпты для разработки, стейджинга и продакшна без изменений кода.
A/B-тестирование: Тестируйте варианты промптов, загружая разные файлы — изменения кода не требуются.
Думайте о YAML-файлах промптов как о конфигурационных файлах в традиционных приложениях — они определяют поведение без необходимости изменений кода или повторного развёртывания.
Что такое YAML?
YAML — это человекочитаемый формат данных, обычно используемый для конфигурационных файлов. Если вы никогда не видели YAML раньше, думайте о нём как о более чистой альтернативе JSON — он использует отступы вместо скобок и легче читается и редактируется.
Структура YAML-промптов
LangChain определяет стандартную структуру YAML-файла для промптов. Давайте посмотрим на примеры:
Пример 1: Промпт без переменных
Когда промпт не нуждается в значениях времени выполнения, установите input_variables в пустой список:
# prompts/system_prompt.yaml
_type: prompt
input_variables: []
template: |
You are a helpful assistant.
Please answer in a friendly and encouraging tone.Символ | позволяет вам писать многострочный текст, и переносы строк сохраняются.
Пример 2: Промпт с переменными
Когда промпт нуждается в значениях времени выполнения, перечислите их в input_variables:
# prompts/user_prompt.yaml
_type: prompt
input_variables:
- user_input
template: |
User question: {user_input}
Please provide a clear answer.Во время выполнения заполнитель {user_input} заменяется фактическим значением.
Ключевые компоненты:
_type: prompt: Идентифицирует это как шаблон промптаinput_variables: Перечисляет все заполнители, используемые в шаблоне (пустой список[], если их нет)template: Фактический текст промпта с{заполнителями}
Загрузка и использование YAML-промптов
Базовая загрузка:
Теперь давайте загрузим созданные нами YAML-файлы и используем их с LLM:
from langchain_core.prompts import load_prompt, ChatPromptTemplate
from langchain_openai import ChatOpenAI
# Загружаем промпты из YAML-файлов
system_prompt_template = load_prompt("prompts/system_prompt.yaml")
user_prompt_template = load_prompt("prompts/user_prompt.yaml")
# Объединяем загруженные промпты в чат-шаблон
chat_template = ChatPromptTemplate.from_messages([
("system", system_prompt_template.template),
("user", user_prompt_template.template)
])
llm = ChatOpenAI(model="gpt-4o-mini")
# Шаг 1: Рендерим шаблон со значениями времени выполнения
messages = chat_template.invoke({"user_input": "What is LangChain?"})
# Шаг 2: Отправляем в LLM
response = llm.invoke(messages)
print(response.content)Проверка загруженных шаблонов:
Перед использованием шаблона проверьте, что он загрузился правильно:
from langchain_core.prompts import load_prompt
# Загружаем шаблон
user_prompt_template = load_prompt("prompts/user_prompt.yaml")
# Проверяем, какие переменные он ожидает
print("Input variables:", user_prompt_template.input_variables)
# Смотрим текст шаблона
print("Template:", user_prompt_template.template)Вывод:
Input variables: ['user_input']
Template: User question: {user_input}
Please provide a clear answer.Распространённые ошибки в YAML
Ошибка 1: Несогласованные отступы
YAML требует согласованных отступов (обычно 2 пробела). Каждый уровень должен использовать одинаковое количество пробелов:
Неправильно:
_type: prompt
input_variables:
- user_input # Неправильно: элементы списка должны иметь отступ
- question # Неправильно: смешанные уровни отступовПравильно:
_type: prompt
input_variables:
- user_input # Правильно: оба элемента на одном уровне отступа
- questionОшибка 2: Несоответствие заполнителей
Заполнители в template должны совпадать с input_variables:
Неправильно:
input_variables:
- user_input
template: "Question: {question}" # 'question' нет в input_variables!Правильно:
input_variables:
- user_input
template: "Question: {user_input}"LangChain вызовет ошибку, если заполнители не совпадают с объявленными переменными.
4.4) Предварительный просмотр и валидация шаблонов перед выполнением
Зачем предварительно просматривать шаблоны?
Промпт-инжиниринг — это итеративный процесс. Вы настраиваете формулировки, корректируете структуру, добавляете примеры — и каждая итерация стоит токенов API и времени. Предварительный просмотр шаблонов перед выполнением позволяет вам:
Экономить время и деньги: Обнаруживать ошибки до выполнения дорогих API-вызовов.
Проверять корректность: Убедиться, что переменные заполнены правильно и форматирование соответствует ожиданиям.
Эффективно отлаживать: Видеть точный промпт, отправленный в LLM, со всеми заполненными переменными и применённым форматированием.
Думайте о предварительном просмотре шаблона как о print-отладке — вы проверяете промежуточное состояние перед выполнением, чтобы проверить корректность.
Базовый предварительный просмотр шаблона
Проверка структуры шаблона:
Перед использованием шаблона с LLM проверьте его структуру и предварительно просмотрите, как он рендерится с примерными данными:
from langchain_core.prompts import ChatPromptTemplate
template = ChatPromptTemplate.from_messages([
("system", "You are a {role}."),
("user", "{user_input}")
])
# Предварительный просмотр структуры шаблона
print("Input variables:", template.input_variables)
print("Message count:", len(template.messages))
# Предварительный просмотр с примерными данными
prompt_value = template.invoke({
"role": "Python programming expert",
"user_input": "What is Python?"
})
print("\nPreview:")
for msg in prompt_value.to_messages():
print(f"{msg.type}: {msg.content}")Вывод:
Input variables: ['role', 'user_input']
Message count: 2
Preview:
system: You are a Python programming expert.
human: What is Python?Это показывает точно, что будет отправлено в LLM, позволяя вам проверить промпт перед выполнением.
Валидация шаблонов: обнаружение отсутствующих переменных
Самая распространённая ошибка шаблона — отсутствующие обязательные переменные. Вот переиспользуемая функция валидации, которая обнаруживает отсутствующие переменные:
from langchain_core.prompts import ChatPromptTemplate
def preview_template(template: ChatPromptTemplate, inputs: dict):
"""Предварительный просмотр шаблона с заданными входными данными, обнаружение ошибок."""
try:
prompt_value = template.invoke(inputs)
print("TEMPLATE PREVIEW")
print("=" * 60)
for i, msg in enumerate(prompt_value.to_messages(), 1):
print(f"Message {i} ({msg.type.upper()}):")
print(msg.content)
print("-" * 60)
except KeyError as e:
print(f"ERROR: {e}")
print(f"Required variables: {template.input_variables}")
# Использование
template = ChatPromptTemplate.from_messages([
("system", "You are a {role}."),
("user", "{user_input}")
])
# Допустимые входные данные
preview_template(template, {
"role": "Python programming expert",
"user_input": "What is Python?"
})
# Отсутствующая переменная
preview_template(template, {
"user_input": "What is Python?" # Отсутствует 'role'
})Вывод:
TEMPLATE PREVIEW
============================================================
Message 1 (SYSTEM):
You are a Python programming expert.
------------------------------------------------------------
Message 2 (HUMAN):
What is Python?
------------------------------------------------------------
ERROR: "Input to ChatPromptTemplate is missing variables {'role'}.
Expected: ['role', 'user_input'] Received: ['user_input']
...
Required variables: ['role', 'user_input']Рабочий процесс валидации:
Вот типичный процесс валидации шаблона:
Этот итеративный процесс помогает обнаруживать ошибки до дорогих вызовов LLM.
Контрольный список перед выполнением
Перед отправкой шаблонов в продакшн:
- Все
input_variablesобъявлены в YAML/шаблоне - Примерные данные рендерятся без ошибок
- Многострочные промпты отображаются правильно
- Заполнители точно совпадают с именами переменных
- Тестирование с граничными случаями (пустые строки, длинный текст)
Резюме главы:
Вы научились проектировать поддерживаемые, переиспользуемые промпты с помощью системы шаблонов LangChain:
- Разделение ответственности: Отделяйте промпты от кода для более лёгкой поддержки и итерации
- Шаблоны с учётом ролей: Используйте системные, пользовательские и помощнические сообщения для структурированных взаимодействий с LLM с правильной иерархией инструкций
- Вынесенные промпты: Управляйте промптами в YAML-файлах для совместной работы нетехнических специалистов и контроля версий
- Предварительный просмотр и валидация: Обнаруживайте ошибки рано и проверяйте шаблоны перед выполнением
Следующие шаги:
В главе 5 вы увидите, как шаблоны обеспечивают автономное принятие решений в примерах предварительного просмотра агентов. Затем в главе 6 вы изучите LCEL (LangChain Expression Language) для компоновки этих шаблонов в мощные конвейеры с помощью оператора |.