DotPrompt는 애플리케이션에 프롬프트를 포함할 필요 없이 구성 기반 구문을 사용하여 프롬프트를 작성할 수 있는 간단한 라이브러리입니다. Fluid 템플릿 언어를 통해 프롬프트에 대한 템플릿을 지원하므로 동일한 프롬프트를 재사용하고 런타임 시 다른 값을 전달할 수 있습니다.
프롬프트 파일은 단순히 .prompt
확장자로 끝나는 모든 파일입니다. 실제 파일 자체는 YAML 구성 파일이며 확장을 통해 라이브러리는 의도된 목적에 맞게 파일을 빠르게 식별할 수 있습니다.
Rider 및 IntelliJ와 같은 도구에서 비정상적인 동작을 일으키는 .prompt
파일에 알려진 문제가 있습니다. 터미널 플러그인을 비활성화하거나 다른 편집기를 사용하여 파일을 수정하면 이 문제를 해결할 수 있습니다.
프롬프트 파일의 내용에는 일부 최상위 식별 속성, 구성 정보, 마지막으로 프롬프트가 포함되어 있습니다.
전체 프롬프트 파일은 다음과 같습니다.
이름: 예제모델: gpt-4oconfig: 출력 형식: 텍스트 온도: 0.9 최대토큰: 500 입력:매개변수: 주제: 문자열 스타일?: 문자열기본: 주제: 소셜 미디어 프롬프트: 시스템: | 귀하는 주어진 주제와 그것이 사회 사용자에게 미치는 영향에 대해 설명적인 응답을 제공하는 유용한 연구 조교입니다. | 우리가 사회로서 기술에 참여하는 방식에 대한 {{ 주제 }}의 영향을 설명하세요. {% if style -%} {{ style }} {% endif -%}fewShots 스타일로 답변해 주실 수 있나요? - 사용자: 블루투스란 무엇입니까? 응답: 블루투스는 고정 장치와 모바일 장치 간에 근거리에서 데이터를 교환하고 개인 영역 네트워크를 구축하는 데 사용되는 근거리 무선 기술 표준입니다. - 사용자: 머신러닝은 기존 프로그래밍과 어떻게 다릅니까? 응답: 머신러닝을 사용하면 알고리즘이 명시적으로 프로그래밍하지 않고도 데이터에서 학습하고 시간이 지남에 따라 개선될 수 있습니다. - 사용자: 일상생활에서 AI의 예를 들어주실 수 있나요? 응답: AI는 음성 명령을 이해하고 응답하는 Siri, Alexa와 같은 가상 비서에 사용됩니다.
name
구성에서 선택 사항이며, 제공되지 않은 경우 파일 이름에서 확장자를 뺀 이름을 가져옵니다. 따라서 gen-lookup-code.prompt
라는 파일은 gen-lookup-code
이름을 갖게 됩니다. 이는 프롬프트 자체 생성에서는 역할을 수행하지 않지만(향후 업데이트에서는 그럴 수 있음) 로깅할 때 프롬프트 소스를 식별하고 프롬프트 관리자에서 프롬프트를 선택할 수 있습니다.
이 속성을 사용하면 파일이 로드될 때 이름이 소문자로 변환되고 공백이 하이픈으로 대체됩니다. 따라서 My cool Prompt
의 이름은 my-cool-prompt
됩니다. 이는 코드에서 이름에 쉽게 액세스할 수 있도록 하기 위해 수행됩니다.
이는 구성의 또 다른 선택적 항목이지만 어떤 모델(또는 Azure Open AI의 배포)을 사용해야 하는지 프롬프트 파일 사용자에게 정보를 제공합니다. 지정하지 않으면 null이 될 수 있으므로 소비자는 사용하기 전에 확인해야 합니다. 예를 들어:
var 모델 = PromptFile.Model ?? "내 기본값";
하지만 이 옵션을 사용하면 프롬프트 엔지니어가 최상의 결과를 제공하기 위해 어떤 모델을 사용할 것인지 매우 명확하게 알 수 있습니다.
config
섹션에는 클라이언트가 LLM 호출에서 각 호출에 대한 옵션을 설정하는 데 사용할 수 있도록 제공되는 몇 가지 최상위 항목이 있습니다. outputFormat
속성은 LLM이 요청에 응답하는 방식에 따라 text
또는 json
값을 사용합니다. json
지정하는 경우 일부 LLM에서는 예상 출력도 JSON임을 명시하는 시스템 또는 사용자 프롬프트가 필요합니다. 라이브러리가 프롬프트에서 JSON
이라는 용어를 감지하지 못하면 응답이 JSON 형식이 되도록 요청하는 작은 명령문을 시스템 프롬프트에 추가합니다.
input
섹션에는 프롬프트에 제공되는 매개변수에 대한 세부정보가 포함되어 있습니다. 이는 필수 사항이 아니며 전달되는 값이 전혀 없는 프롬프트를 만들 수 있습니다. 하지만 그렇게 한다면 이것이 당신에게 필요한 것입니다.
input
아래에는 키-값 쌍 목록이 포함된 parameters
섹션이 있습니다. 여기서 키는 매개변수의 이름이고 값은 매개변수의 유형입니다. 매개변수 이름 뒤에 물음표(예: style?
)를 붙이면 선택적 매개변수로 간주되며 값을 제공하지 않아도 오류가 발생하지 않습니다.
지원되는 유형은 다음과 같습니다.
매개변수 유형 | 닷넷 유형 | C#에 해당 |
---|---|---|
끈 | 시스템.문자열 | 끈 |
부울 | 시스템.부울 | 부울 |
날짜시간 | System.DateTimeOffset | System.DateTimeOffset |
숫자 | 시스템.바이트 시스템.SByte 시스템.UInt16 시스템.Int16 시스템.UInt32 시스템.Int32 시스템.UInt64 시스템.Int64 시스템.싱글 시스템.더블 시스템.십진수 | 바이트 s바이트 유쇼 짧은 단위 정수 울롱 긴 뜨다 더블 소수 |
물체 | 시스템.객체 | 물체 |
처음 4개는 제공된 대로 사용됩니다. 프롬프트에 전달된 개체에는 프롬프트에서 사용되도록 호출되는 ToString
메서드가 있습니다.
datetime
유형은 기본 ToString
표현으로 표시되거나 Fluid의 필터를 사용하여 형식을 지정하고 시간대를 변경하는 등의 작업을 수행할 수 있습니다.
지정된 유형을 따르지 않는 매개변수에 대한 값을 제공하면 오류가 발생합니다.
또한 input
에는 default
섹션이 있습니다. 이 섹션에서는 모든 매개변수에 대한 기본값을 지정할 수 있습니다. 따라서 애플리케이션에 매개변수가 제공되지 않으면 기본값이 대신 사용됩니다.
prompts
섹션에는 시스템 및 사용자 프롬프트에 대한 템플릿이 포함되어 있습니다. 사용자 프롬프트는 필수이지만 시스템 프롬프트는 지정할 필요가 없습니다.
system
프롬프트와 user
프롬프트는 모두 문자열 값이며 YAML이 지원하는 방식으로 정의할 수 있습니다. 위의 예에서는 캐리지 리턴이 유지되는 여러 줄 문자열을 사용하고 있습니다.
YAML은 블록 스칼라를 통해 여러 줄 문자열 값을 훌륭하게 지원합니다. 이를 통해 리터럴 문자열과 접힌 문자열을 모두 지원합니다. 리터럴 문자열을 사용하면 입력 문자열의 새 줄 문자가 유지되고 문자열은 작성된 대로 정확하게 유지됩니다. 접으면 새 줄 문자가 접혀지고 공백 문자로 대체되므로 여러 줄에 걸쳐 매우 긴 문자열을 쓸 수 있습니다. 접힘을 사용하여 두 개의 새 줄 문자를 사용하면 줄 바꿈이 문자열에 추가됩니다.
# 접힌 예: > 배는 벽돌과 거의 같은 방식으로 하늘에 매달려 있습니다.# 생산:# 배는 벽돌이 그렇지 않은 것과 거의 같은 방식으로 하늘에 매달려 있습니다.
# 리터럴예: | 배는 벽돌과 거의 같은 방식으로 하늘에 매달려 있습니다.# 생산:# 배는 벽돌과 거의 같은 방식으로# 하늘에 매달려 있습니다.
프롬프트 구문은 Shopify에서 만든 Liquid를 기반으로 하는 Fluid 템플릿 언어를 사용합니다. 이 템플릿 언어를 사용하면 템플릿 파서에 전달되는 값에 따라 변경될 수 있는 사용자 프롬프트를 정의할 수 있습니다.
위의 예에서는 전달되는 값의 자리 표시자이며 템플릿으로 바로 대체되는 {{ topic }}
볼 수 있습니다. style
매개변수에 값이 있는 경우에만 이 섹션을 포함하도록 구문 분석기에 지시하는 {% if style -%} ... {% endif -%}
섹션도 있습니다. 마커 끝에 있는 -%}
에는 파서에게 빈 줄을 축소해야 함을 알려주는 하이픈 기호가 포함되어 있습니다.
온라인에서 Fluid를 사용하여 템플릿을 작성하는 방법에 대한 훌륭한 튜토리얼이 있습니다.
프롬프트를 생성하면 템플릿이 대체되지 않고 생성된 출력만 제공됩니다. 즉, 다양한 입력 값을 사용하여 원하는 만큼 프롬프트를 생성할 수 있습니다.
fewShots
는 프롬프트 작성자가 솔루션에 퓨샷 프롬프트 기술을 제공할 수 있도록 하는 섹션입니다. 프롬프트를 구성할 때 시스템 프롬프트, 사용자 프롬프트와 함께 이러한 프롬프트를 포함해야 합니다. 이는 LLM이 사용자 프롬프트에 응답하는 방법에 대한 예를 제공합니다. OpenAI 또는 Azure OpenAI를 사용하는 경우 모든 메시지를 생성하는 확장 메서드(나중에 참조)를 사용할 수 있습니다.
프롬프트 파일에 직접 액세스할 수 있습니다. 파일이 두 개 밖에 없거나 신속하게 테스트하고 싶다면 이 방법이 매우 간단합니다.
DotPrompt 사용;var PromptFile = PromptFile.FromFile("path/to/prompt-file.prompt");var systemPrompt = PromptFile.GetSystemPrompt(null);var userPrompt = PromptFile.GetUserPrompt(new Dictionary<string, object>{{ " topic", "bluetooth" },{ "style", "중고차 세일즈맨" }});
프롬프트 파일에 위의 예가 포함되어 있으면 다음이 생성됩니다.
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 프롬프트Manager = 새로운 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");var 완성 = 대기 client.CompleteChatAsync([new SystemChatMessage(systemPrompt),new UserChatMessage(userPrompt)],new ChatCompletionOptions(ResponseFormat = 프롬프트File.OutputFormat == OutputFormat.Json ? ChatResponseFormat.JsonObject : ChatResponseFormat.Text,Temperature = 프롬프트File.Config.Temperature,MaxTokens = 프롬프트파일.Config.MaxTokens));
또는 OpenAI에서 제공하는 확장 방법을 사용합니다.
System.ClientModel 사용;Azure.AI.OpenAI 사용;DotPrompt 사용;DotPrompt.Extensions.OpenAi 사용;var openAiClient = new(new Uri("https://endpoint"), new ApiKeyCredential("abc123"));var 프롬프트Manager = new PromptManager();var PromptFile = PromptManager.GetPromptFile("example");var PromptValues = new Dictionary<string, object>{{ "topic", "bluetooth" },{ "style", "중고차 판매원" }};var client = openAiClient.GetChatClient(promptFile.Model ?? "default-model");var 완성 = 클라이언트 대기 .CompleteChatAsync(promptFile.ToOpenAiChatMessages(promptValues),promptFile.ToOpenAiChatCompletionOptions());var response = 완성.값;Console.WriteLine(response.Content[0].Text);
이제 프롬프트를 수정해야 하는 경우 프롬프트 파일을 변경하고 코드를 그대로 두면 됩니다(매개변수가 변경되지 않는다고 가정).
위의 내용은 DotPrompt를 사용하여 디스크에서 프롬프트 파일을 읽는 방법을 보여줍니다. 하지만 클라우드 스토리지 서비스나 데이터베이스와 같이 더 중앙에 메시지를 표시하려는 상황이 있다면 어떻게 될까요? 프롬프트 관리자는 IPromptStore
인스턴스를 인수로 사용할 수 있습니다. 위의 모든 예에서는 포함된 FilePromptStore
를 사용하고 있지만 직접 구축할 수도 있습니다. 인터페이스를 구현하기만 하면 완료됩니다.
예를 들어 Azure Storage 테이블 저장소를 사용하여 프롬프트 세부 정보를 보관하는 간단한 구현은 다음과 같습니다.
/// <summary>/// Azure Storage 테이블에 대한 IPromptStore 구현/// </summary>public class AzureTablePromptStore : IPromptStore{/// <summary>/// 테이블 저장소에서 프롬프트를 로드합니다./// < /summary>public IEnumerable<PromptFile> Load(){var tableClient = GetTableClient();var 프롬프트Entities = tableClient.Query<PromptEntity>(e => e.PartitionKey == "DotPromptTest");var PromptFiles = PromptEntities.Select(pe => pe.ToPromptFile()).ToList();return PromptFiles;}/// <summary>/// 테이블 클라이언트 가져오기 /// </summary>private static TableClient GetTableClient(){// 여기의 구성 항목을 원하는 값으로 바꾸거나 // 엔트라 기반 인증을 사용하도록 전환var 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>/// Gets, 레코드에 대한 파티션 키 설정/// </summary>public string PartitionKey { get ; 세트; } = string.Empty;/// <summary>/// 레코드에 대한 행 키를 가져오고 설정합니다./// </summary>public string RowKey { get; 세트; } = string.Empty;/// <summary>/// 항목의 타임스탬프를 가져오고 설정합니다./// </summary>public DateTimeOffset? 타임스탬프 { get; 세트; }/// <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>공개로 반환합니다. PromptFile ToPromptFile(){varparameters = new Dictionary<string, string>();var defaults = new Dictionary<string, object>();// 매개변수 값이 있으면 이를 사전으로 변환합니다.if (!string.IsNullOrEmpty(Parameters)){varentityParameters = (JsonObject)JsonNode.Parse(Parameters)!;foreach (entityParameters의 var (prop, propType)){parameters.Add(prop, propType?.AsValue().ToString () ?? string.Empty);}}// 기본값이 있으면 사전으로 변환합니다.if (!string.IsNullOrEmpty(기본값)){varentityDefaults = (JsonObject)JsonNode.Parse(Default)!;foreach (entityDefaults의 var (prop, defaultValue)){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{Parameters = 매개변수,Default = 기본값}},Prompts = new Prompts{System = SystemPrompt,User = UserPrompt}};return 프롬프트파일;}}
그리고 이를 사용하기 위해 다음을 수행합니다.
var PromptManager = new PromptManager(new AzureTablePromptStore());var PromptFile = PromptManager.GetPromptFile("example");
아직 여기서 할 수 있는 작업 범위가 있으며 우리가 보고 있는 항목 중 일부는 다음과 같습니다.
추가 구성 옵션
추가 프롬프트 기술
피드백을 받습니다. 보고 싶은 게 있나요? 우리에게 알려주세요