DotPrompt — это простая библиотека, которая позволяет создавать подсказки с использованием синтаксиса на основе конфигурации без необходимости встраивания их в ваше приложение. Он поддерживает создание шаблонов для подсказок с помощью языка шаблонов Fluid, что позволяет повторно использовать одно и то же приглашение и передавать разные значения во время выполнения.
Файл подсказки — это любой файл, заканчивающийся расширением .prompt
. Фактический файл сам по себе является файлом конфигурации YAML, а расширение позволяет библиотеке быстро идентифицировать файл по назначению.
Существует известная проблема с файлами .prompt
, приводящая к необычному поведению таких инструментов, как Rider и IntelliJ. Эту проблему можно обойти, отключив плагин Терминала или используя другой редактор для изменения файлов.
Содержимое файла подсказки содержит некоторые идентификационные свойства верхнего уровня, за которыми следует информация о конфигурации и, наконец, подсказки.
Полный файл приглашения будет выглядеть так.
имя: Пример модели: gpt-4oconfig: выходной формат: текст температура: 0,9 МаксТокены: 500 ввод: параметры: тема: стиль строки?: stringdefault: тема: подсказки в социальных сетях: система: | Вы — полезный научный сотрудник, который предоставит описательные ответы по заданной теме и ее влиянию на общество. Пользователь: | Объясните влияние {{ theme }} на то, как мы взаимодействуем с технологиями в обществе {% if style -%} Можете ли вы ответить в стиле {{ style }} {% endif -%}fewShots: - пользователь: Что такое ответ Bluetooth: Bluetooth — это стандарт беспроводной технологии ближнего действия, который используется для обмена данными между стационарными и мобильными устройствами на коротких расстояниях и построения персональных сетей. - пользователь: Чем машинное обучение отличается от традиционного программирования? Ответ: Машинное обучение позволяет алгоритмам учиться на данных и совершенствоваться с течением времени без явного программирования. - пользователь: Можете ли вы привести пример использования ИИ в повседневной жизни? Ответ: ИИ используется в виртуальных помощниках, таких как Siri и Alexa, которые понимают голосовые команды и реагируют на них.
name
в конфигурации не является обязательным, если оно не указано, то имя берется из имени файла без расширения. Таким образом, файл с именем gen-lookup-code.prompt
получит имя gen-lookup-code
. Это не играет роли в создании самих подсказок (хотя будущие обновления могут сыграть такую роль), но позволяет вам идентифицировать источник подсказки при входе в систему и выбирать подсказку в диспетчере подсказок.
Если вы используете это свойство, то при загрузке файла имя преобразуется в нижний регистр, а пробелы заменяются дефисами. Таким образом, имя My cool Prompt
станет my-cool-prompt
. Это сделано для того, чтобы имя было легко доступно из кода.
Это еще один необязательный элемент конфигурации, но он предоставляет пользователю файла подсказки информацию о том, какую модель (или развертывание для Azure Open AI) ему следует использовать. Поскольку это значение может быть нулевым, если оно не указано, потребитель должен обязательно проверить это перед использованием. Например:
вар модель = PromptFile.Model ?? «мой-по умолчанию»;
Однако использование этой опции позволяет инженеру подсказки четко указать, какую модель он намеревается использовать для обеспечения наилучших результатов.
В разделе config
есть некоторые элементы верхнего уровня, которые предоставляются клиенту для использования в вызовах LLM для установки параметров для каждого вызова. Свойство outputFormat
принимает значение text
или json
в зависимости от того, как LLM должен отвечать на запрос. Если указан json
, некоторые LLM требуют, чтобы в системном или пользовательском приглашении было указано, что ожидаемый результат также является JSON. Если библиотека не обнаруживает термин JSON
в приглашении, она добавит небольшой оператор к системному приглашению, запрашивая ответ в формате JSON.
Раздел input
содержит подробную информацию о параметрах, предоставляемых в подсказках. Это необязательно, и вы можете создавать запросы, в которых вообще не передаются никакие значения. Но если да, то это то, что вам нужно.
Под input
находится раздел parameters
, который содержит список пар ключ-значение, где ключ — это имя параметра, а значение — его тип. Если вы добавите к имени параметра вопросительный знак (например, style?
), то он считается необязательным параметром и не будет вызывать ошибку, если вы не укажете для него значение.
Поддерживаемые типы:
Тип параметра | Тип Дотнет | Эквивалент С# |
---|---|---|
нить | Система.Строка | нить |
логическое значение | System.Boolean | логическое значение |
дата и время | System.DateTimeOffset | System.DateTimeOffset |
число | Система.Байт System.SByte Система.UInt16 System.Int16 Система.UInt32 System.Int32 Система.UInt64 System.Int64 Система.Одиночный Система.Двойной System.Decimal | байт сбайт сокращать короткий uint интервал улонг длинный плавать двойной десятичный |
объект | Система.Объект | объект |
Первые 4 используются как предусмотрено. У объектов, передаваемых в приглашение, будет вызываться метод ToString
который будет использоваться в приглашении.
Тип datetime
может отображаться либо с представлением ToString
по умолчанию, либо вы можете использовать фильтры Fluid, чтобы указать его формат, изменить часовой пояс и многое другое.
Если вы укажете значение параметра, которое не соответствует указанному типу, будет выдана ошибка.
Также во input
находится раздел default
. В этом разделе можно указать значения по умолчанию для любого из параметров. Поэтому, если параметр не указан в вашем приложении, вместо него будет использоваться значение по умолчанию.
Раздел prompts
содержит шаблоны системных и пользовательских подсказок. Хотя требуется приглашение пользователя, вам не нужно указывать системное приглашение.
И system
, и user
запросы представляют собой строковые значения и могут быть определены любым способом, поддерживаемым YAML. В приведенном выше примере используется многострочная строка, в которой сохраняются возвраты каретки.
YAML имеет отличную поддержку многострочных строковых значений посредством блочных скаляров. Благодаря этому он поддерживает как литеральные , так и свернутые строки. В случае строк-литералов символы новой строки во входной строке сохраняются, и строка остается точно такой, как она написана. При сворачивании символы новой строки сворачиваются и заменяются символом пробела, что позволяет писать очень длинные строки в несколько строк. При использовании складки, если вы используете два символа новой строки, к строке добавляется новая строка.
# Сложенный пример: > Корабли висят в небе почти так же, как кирпичи не висят# Производит:# Корабли висят в небе почти так же, как кирпичи не висят
# Буквальный пример: | Корабли висят в небе почти так же, как не висят кирпичи# Производит:# Корабли висят# в небе почти так же#, как не висят кирпичи
В синтаксисе подсказок используется язык шаблонов Fluid, который сам по себе основан на Liquid, созданном Shopify. Этот язык шаблонов позволяет нам определять пользовательские подсказки, которые могут меняться в зависимости от значений, передаваемых в анализатор шаблонов.
В приведенном выше примере вы можете увидеть {{ topic }}
который является заполнителем для передаваемого значения и будет подставлен прямо в шаблон. Существует также раздел {% if style -%} ... {% endif -%}
который сообщает синтаксическому анализатору включать этот раздел только в том случае, если параметр style
имеет значение. -%}
в конце маркера содержит символ дефиса, который сообщает синтаксическому анализатору, что он должен свернуть пустые строки.
В Интернете есть отличное руководство по написанию шаблонов с помощью Fluid.
Когда вы создаете приглашение, оно не заменяет шаблон, а дает вам только сгенерированный результат. Это означает, что вы можете генерировать приглашение столько раз, сколько захотите, с разными входными значениями.
fewShots
— это раздел, позволяющий составителю подсказки предоставить методы подсказки в несколько раз для решения. При создании приглашения вы должны включить их вместе с системным приглашением, а затем с приглашением пользователя. Это дает примеры того, как LLM должен реагировать на приглашение пользователя. Если вы используете OpenAI или Azure OpenAI, вы можете использовать методы расширения (см. ниже), которые создадут за вас все сообщения.
Доступ к файлам подсказок можно получить напрямую. Если у вас всего пара файлов или вы хотите быстро их протестировать, это довольно простой способ сделать это.
с помощью DotPrompt;var PromptFile = PromptFile.FromFile("путь/к/prompt-file.prompt");var systemPrompt = PromptFile.GetSystemPrompt(null);var userPrompt = PromptFile.GetUserPrompt(new Dictionary<string, object>{{ " тема", "блютуз" },{ "стиль", "продавец подержанных автомобилей" }});
Если бы файл приглашения содержал приведенный выше пример, он выдал бы следующее.
System Prompt:
You are a helpful research assistant who will provide descriptive responses for a given topic and how it impacts society
User Prompt:
Explain the impact of bluetooth on how we engage with technology as a society
Can you answer in the style of a used car salesman
Это может привести к ответу от LLM, который выглядит следующим образом (извините)
Дамы и господа, соберитесь и позвольте мне рассказать вам о чуде современной технологии, которая произвела революцию в том, как мы подключаемся к нашим гаджетам — я говорю о Bluetooth! Bluetooth — это невоспетый герой, секретный соус, который делает нашу жизнь более удобной, более связанной и, безусловно, более высокотехнологичной. Представьте себе: бесперебойную беспроводную связь между вашими любимыми устройствами. Больше никаких запутанных шнуров и беспорядка. Это как VIP-пропуск в первый ряд будущего!
...
Менеджер подсказок является предпочтительным методом обработки файлов подсказок. Это позволяет вам загружать их из определенного места, затем получать доступ по имени, а затем использовать их в своем приложении.
По умолчанию менеджер подсказок обращается к файлам в локальной папке prompts
, хотя при желании вы можете указать другой путь.
// Загрузка из местоположения по умолчанию каталога `prompts`var PromptManager = new PromptManager();var PromptFile = PromptManager.GetPromptFile("example");// Использовать другую папкуvar PromptManager = new PromptManager("another-location"); var PromptFile = PromptManager.GetPromptFile("example");// Список всех загруженных приглашенийvar PromptNames = PromptManager.ListPromptFileNames();
Менеджер подсказок реализует интерфейс IPromptManager
, поэтому, если вы хотите использовать его через контейнер DI или шаблон IoC, вы можете легко предоставить макетную версию для тестирования.
Диспетчер подсказок также может использовать экземпляр IPromptStore
, который позволяет вам создать собственное хранилище, которое может не быть файловым (см. Создание собственного хранилища подсказок). Это также позволяет предоставить макетируемый интерфейс, чтобы вы могли писать модульные тесты, которые не зависят от механизма хранения.
Использование диспетчера подсказок для чтения подсказки и последующего использования ее при вызове конечной точки Azure OpenAI.
NB. В этом примере предполагается, что существует каталог prompts
, в котором доступен файл подсказки.
использование System.ClientModel; использование Azure.AI.OpenAI; использование DotPrompt;var openAiClient = new(new Uri("https://endpoint"), new ApiKeyCredential("abc123"));var PromptManager = new PromptManager();var PromptFile = PromptManager.GetPromptFile("example");// Методы системного приглашения и пользовательского приглашения принимают словари, содержащие значения, необходимые для // шаблона. Если ничего не требуется, вы можете просто передать null.var systemPrompt = PromptFile.GetSystemPrompt(null);var userPrompt = PromptFile.GetUserPrompt(new Dictionary<string, object>{{ "topic", "bluetooth" },{ "style" , "продавец подержанных автомобилей" }});var client = openAiClient.GetChatClient(promptFile.Model ?? "default-model");varcomplete = await client.CompleteChatAsync([new SystemChatMessage(systemPrompt),new UserChatMessage(userPrompt)],new ChatCompletionOptions(ResponseFormat = PromptFile.OutputFormat == OutputFormat.Json ? ChatResponseFormat.JsonObject: ChatResponseFormat.Text ,Температура = PromptFile.Config.Temperature,MaxTokens = PromptFile.Config.MaxTokens));
Или используйте методы расширения, предоставляемые OpenAI.
использование System.ClientModel; использование Azure.AI.OpenAI; использование DotPrompt; использование DotPrompt.Extensions.OpenAi;var openAiClient = new(new Uri("https://endpoint"), new ApiKeyCredential("abc123"));var PromptManager = новый PromptManager();var PromptFile = PromptManager.GetPromptFile("пример");var PromptValues = новый словарь<string, object>{{ "topic", "bluetooth" }, { "style", "продавец подержанных автомобилей" }};var client = openAiClient.GetChatClient(promptFile.Model ?? "default-model"); вар завершение = ожидание client.CompleteChatAsync(promptFile.ToOpenAiChatMessages(promptValues),promptFile.ToOpenAiChatCompletionOptions());var ответ = завершение.Value;Console.WriteLine(response.Content[0].Text);
И теперь, если нам нужно изменить приглашение, мы можем просто изменить файл приглашения и оставить наш код в покое (при условии, что параметры не изменяются).
Выше показано, как вы можете использовать DotPrompt для чтения файлов подсказок с диска. Но что, если у вас возникла ситуация, когда вам нужно, чтобы ваши подсказки находились в более централизованном месте, например в облачном хранилище или базе данных? Что ж, диспетчер подсказок может принять экземпляр IPromptStore
в качестве аргумента. Во всех приведенных выше примерах используется включенный FilePromptStore
, но вы также можете создать свой собственный. Осталось реализовать интерфейс, и все готово.
В качестве примера приведем простую реализацию, которая использует хранилище таблиц хранилища Azure для хранения подсказок.
/// <summary>/// Реализация IPromptStore для таблиц хранилища Azure /// </summary>public class AzureTablePromptStore : IPromptStore{/// <summary>/// Загружает запросы из хранилища таблиц /// < /summary>public IEnumerable<PromptFile> Load() {var tableClient = GetTableClient();var PromptEntities = tableClient.Query<PromptEntity>(e => e.PartitionKey == "DotPromptTest");var PromptFiles = PromptEntities.Select(pe => pe.ToPromptFile()).ToList();return PromptFiles;}/// <summary >/// Получает клиент таблицы/// </summary>private static TableClient GetTableClient(){// Замените здесь элементы конфигурации на свои значение или переключиться на использование // аутентификации на основе Entra. client = new TableServiceClient(new Uri($"https://{Configuration.StorageAccountName}.table.core.windows.net/"),new TableSharedKeyCredential(Configuration.StorageAccountName, Configuration. StorageAccountKey));var tableClient = client.GetTableClient("prompts");tableClient.CreateIfNotExists();return tableClient;}}/// <summary>/// Представляет запись, хранящуюся в таблице хранения /// </summary>public class PromptEntity : ITableEntity{ /// <summary>/// Получает, устанавливает ключ раздела для записи /// </summary>public string PartitionKey { получать; набор; } = string.Empty;/// <summary>/// Получает, устанавливает ключ строки для записи/// </summary>public string RowKey { get; набор; } = string.Empty;/// <summary>/// Получает, устанавливает временную метку записи/// </summary>public DateTimeOffset? Временная метка {получить; набор; }/// <summary>/// Получает, устанавливает значение ETag для записей /// </summary>public ETag ETag { get; набор; }/// <summary>/// Получает, устанавливает модель для использования/// </summary>публичную строку? Модель {получить; набор; }/// <summary>/// Получает, устанавливает формат вывода/// </summary>public string OutputFormat { get; набор; } = string.Empty;/// <summary>/// Получает, устанавливает максимальное количество токенов /// </summary>public int MaxTokens { get; набор; }/// <summary>/// Получает, устанавливает информацию о параметрах, которая хранится в виде строкового значения JSON/// </summary>public string Параметры { get; набор; } = string.Empty;/// <summary>/// Получает, устанавливает значения по умолчанию, которые хранятся в виде строкового значения JSON/// </summary>public string Default { get; набор; } = string.Empty;/// <summary>/// Получает, устанавливает шаблон системного приглашения/// </summary>public string SystemPrompt { get; набор; } = string.Empty;/// <summary>/// Получает, устанавливает шаблон приглашения пользователя/// </summary>public string UserPrompt { get; набор; } = string.Empty;/// <summary>/// Возвращает запись сущности приглашения в экземпляр <see cref="PromptFile"/> /// </summary>/// <returns></returns>public PromptFile ToPromptFile(){var options = new Dictionary<string, string>();var defaults = new Dictionary<string, object>();// Если есть значения параметров, то преобразовать их в словарьif (!string.IsNullOrEmpty(Parameters)){varentityParameters = (JsonObject)JsonNode.Parse(Parameters)!;foreach (var (prop, propType) inentityParameters){parameters.Add(prop, propType?. AsValue().ToString() ?? string.Empty);}}// Если есть значения по умолчанию, преобразуйте их в словарьif (!string.IsNullOrEmpty(Default)){varentityDefaults = (JsonObject)JsonNode.Parse(Default)!;foreach (var (prop, defaultValue) вentityDefaults){defaults.Add(prop, defaultValue?.AsValue(). GetValue<object>() ?? string.Empty);}}// Генерируем новый файл приглашенияvar PromptFile = new PromptFile {Name = RowKey, Model = Model, Config = new PromptConfig {OutputFormat = Enum.Parse <OutputFormat> (OutputFormat, true), MaxTokens = MaxTokens, Input = new InputSchema {Параметры = параметры, по умолчанию = значения по умолчанию}}, Prompts = новые подсказки {System = SystemPrompt, User = UserPrompt}}; return подсказкаФайл;}}
И затем, чтобы использовать это, мы должны сделать следующее
вар PromptManager = новый PromptManager (новый AzureTablePromptStore()); вар PromptFile = PromptManager.GetPromptFile («пример»);
Здесь еще есть над чем поработать, и некоторые из пунктов, которые мы рассматриваем, включают
Дополнительные возможности конфигурации
Дополнительные техники подсказок
Открыт для обратной связи. Есть ли что-нибудь, что вы хотели бы увидеть? Дайте нам знать