Dotprompt로 프롬프트 관리

Firebase Genkit는 생성형 AI 프롬프트 작성 및 정리에 도움이 되는 Dotprompt 플러그인과 텍스트 형식을 제공합니다.

Dotprompt는 프롬프트가 코드라는 전제 하에 설계되었습니다. 개발자는 dotprompt 파일이라는 특수 형식의 파일에 프롬프트를 작성 및 유지 관리하고, 코드에 사용하는 것과 동일한 버전 제어 시스템을 사용하여 변경사항을 추적하며, 생성형 AI 모델을 호출하는 코드와 함께 배포합니다.

Dotprompt를 사용하려면 먼저 프로젝트 루트에 prompts 디렉터리를 만들고 이 디렉터리에 .prompt 파일을 만듭니다. greeting.prompt라고 하는 간단한 예시를 살펴 보겠습니다.

---
model: vertexai/gemini-1.5-flash
config:
  temperature: 0.9
input:
  schema:
    location: string
    style?: string
    name?: string
  default:
    location: a restaurant
---

You are the world's most welcoming AI assistant and are currently working at {{location}}.

Greet a guest{{#if name}} named {{name}}{{/if}}{{#if style}} in the style of {{style}}{{/if}}.

이 프롬프트를 사용하려면 dotprompt 플러그인을 설치하고 prompt 함수를 @genkit-ai/dotprompt 라이브러리:

import { dotprompt, prompt } from '@genkit-ai/dotprompt';

configureGenkit({ plugins: [dotprompt()] });

그런 다음 prompt('file_name')을 사용하여 프롬프트를 로드합니다.

const greetingPrompt = await prompt('greeting');

const result = await greetingPrompt.generate({
  input: {
    location: 'the beach',
    style: 'a fancy pirate',
  },
});

console.log(result.text());

Dotprompt의 구문은 Handlebars 템플릿 언어를 기반으로 합니다. if, unless, each 도우미를 사용하여 프롬프트에 조건부 부분을 추가하거나 정형 콘텐츠를 통해 반복합니다. 파일 형식은 YAML 전면을 활용하여 템플릿과 함께 프롬프트 인라인에 대한 메타데이터를 제공합니다.

입력/출력 스키마 정의

Dotprompt에는 YAML에 최적화된 간결한 스키마 정의 형식인 스키마의 가장 중요한 속성을 쉽게 정의할 수 있는 피코스키마 LLM에 사용됩니다 다음은 기사의 스키마 예시입니다.

schema:
  title: string # string, number, and boolean types are defined like this
  subtitle?: string # optional fields are marked with a `?`
  draft?: boolean, true when in draft state
  status?(enum, approval status): [PENDING, APPROVED]
  date: string, the date of publication e.g. '2024-04-09' # descriptions follow a comma
  tags(array, relevant tags for article): string # arrays are denoted via parentheses
  authors(array):
    name: string
    email?: string
  metadata?(object): # objects are also denoted via parentheses
    updatedAt?: string, ISO timestamp of last update
    approvedBy?: integer, id of approver
  extra?: any, arbitrary extra data
  (*): string, wildcard field

위의 스키마는 다음 TypeScript 인터페이스와 동일합니다.

interface Article {
  title: string;
  subtitle?: string | null;
  /** true when in draft state */
  draft?: boolean | null;
  /** approval status */
  status?: 'PENDING' | 'APPROVED' | null;
  /** the date of publication e.g. '2024-04-09' */
  date: string;
  /** relevant tags for article */
  tags: string[];
  authors: {
    name: string;
    email?: string | null;
  }[];
  metadata?: {
    /** ISO timestamp of last update */
    updatedAt?: string | null;
    /** id of approver */
    approvedBy?: number | null;
  } | null;
  /** arbitrary extra data */
  extra?: any;
  /** wildcard field */

}

Picoschema는 스칼라 유형 string, integer, number, boolean, any를 지원합니다. 객체, 배열 및 enum의 경우 필드 이름 뒤에 괄호가 표시됩니다.

Picoschema로 정의된 객체에는 ?에 의해 선택사항으로 표시되지 않는 한 필수 속성이 모두 포함되며 추가 속성은 허용되지 않습니다. 속성이 선택사항으로 표시되면 LLM이 필드를 생략하는 대신 null을 반환하도록 편의를 제공하기 위해 null을 허용하도록 설정됩니다.

객체 정의에서 특수 키 (*)를 사용하여 '와일드 카드' 필드 정의를 선언할 수 있습니다. 이렇게 하면 명시적 키에서 제공하지 않는 추가 속성이 일치합니다.

Picoschema는 전체 JSON 스키마의 많은 기능을 지원하지 않습니다. 보다 강력한 스키마가 필요한 경우 JSON 스키마를 대신 제공할 수 있습니다.

output:
  schema:
    type: object
    properties:
      field1:
        type: number
        minimum: 20

재사용 가능한 스키마 활용

.prompt 파일의 스키마를 직접 정의하는 것 외에도 이름으로 defineSchema에 등록된 스키마 스키마를 등록하려면 다음 안내를 따르세요.

import { defineSchema } from '@genkit-ai/core';
import { z } from 'zod';

const MySchema = defineSchema(
  'MySchema',
  z.object({
    field1: z.string(),
    field2: z.number(),
  })
);

프롬프트 내에서 등록된 스키마의 이름을 제공할 수 있습니다.

# myPrompt.prompt
---
model: vertexai/gemini-1.5-flash
output:
  schema: MySchema
---

Dotprompt 라이브러리는 자동으로 이름을 기본 등록된 Zod 스키마입니다. 그런 다음 스키마를 활용하여 출력됩니다.

import { prompt } from "@genkit-ai/dotprompt";

const myPrompt = await prompt("myPrompt");

const result = await myPrompt.generate<typeof MySchema>({...});

// now strongly typed as MySchema
result.output();

프롬프트 메타데이터 재정의

.prompt 파일을 사용하면 모델 구성과 같은 메타데이터를 파일 자체에 삽입할 수 있으며 호출별로 이러한 값을 재정의할 수도 있습니다.

const result = await greetingPrompt.generate({
  model: 'vertexai/gemini-1.5-pro',
  config: {
    temperature: 1.0,
  },
  input: {
    location: 'the beach',
    style: 'a fancy pirate',
  },
});

구조화된 출력

프롬프트의 형식 및 출력 스키마를 설정하여 JSON으로 강제 변환할 수 있습니다.

---
model: vertexai/gemini-1.5-flash
input:
  schema:
    theme: string
output:
  format: json
  schema:
    name: string
    price: integer
    ingredients(array): string
---

Generate a menu item that could be found at a {{theme}} themed restaurant.

구조화된 출력으로 프롬프트를 생성할 때 output() 도우미를 사용하여 다음을 수행합니다. 확인하고 검증합니다.

const createMenuPrompt = await prompt('create_menu');

const menu = await createMenuPrompt.generate({
  input: {
    theme: 'banana',
  },
});

console.log(menu.output());

출력 적합성은 메시지가 표시됩니다. 기본적으로 마지막에 생성된 메일의 끝에 추가됩니다. 표시됩니다. {{section "output"}}를 사용하여 수동으로 위치를 변경할 수 있습니다. 도움이 됩니다.

This is a prompt that manually positions output instructions.

== Output Instructions

{{section "output"}}

== Other Instructions

This will come after the output instructions.

멀티메시지 프롬프트

기본적으로 Dotprompt는 "user" 역할을 가진 단일 메시지를 작성합니다. 일부 프롬프트는 시스템 프롬프트과 같이 여러 메시지를 조합하여 가장 잘 표현됩니다.

{{role}} 도우미를 사용하면 간단하게 멀티메시지 프롬프트를 구성할 수 있습니다.

---
model: vertexai/gemini-1.5-flash
input:
  schema:
    userQuestion: string
---

{{role "system"}}
You are a helpful AI assistant that really loves to talk about food. Try to work
food items into all of your conversations.
{{role "user"}}
{{userQuestion}}

멀티턴 프롬프트 및 기록

Dotprompt는 history 옵션을 generate 메서드:

const result = await multiTurnPrompt.generate({
  history: [
    { role: 'user', content: [{ text: 'Hello.' }] },
    { role: 'model', content: [{ text: 'Hi there!' }] },
  ],
});

기본적으로 확인할 수 있습니다 그러나 {{history}}를 사용하여 기록을 수동으로 배치할 수 있습니다. 도우미:

{{role "system"}}
This is the system prompt.
{{history}}
{{role "user"}}
This is a user message.
{{role "model"}}
This is a model message.
{{role "user"}}
This is the final user message.

멀티모달 프롬프트

텍스트와 함께 이미지와 같은 멀티모달 입력을 지원하는 모델의 경우 {{media}} 도우미를 사용할 수 있습니다.

---
model: vertexai/gemini-1.5-flash
input:
  schema:
    photoUrl: string
---

Describe this image in a detailed paragraph:

{{media url=photoUrl}}

URL은 '인라인' 이미지 사용을 위한 https:// 또는 base64로 인코딩된 data: URI일 수 있습니다. 코드에서는 다음과 같습니다.

const describeImagePrompt = await prompt('describe_image');

const result = await describeImagePrompt.generate({
  input: {
    photoUrl: 'https://example.com/image.png',
  },
});

console.log(result.text());

부분

부분은 프롬프트 안에 포함할 수 있는 재사용 가능한 템플릿입니다. 부분 는 공통된 동작을 공유하는 관련 프롬프트에 특히 유용합니다.

프롬프트 디렉터리를 로드할 때 _ 접두사가 붙은 모든 파일은 있습니다. 따라서 _personality.prompt 파일에는 다음이 포함될 수 있습니다.

You should speak like a {{#if style}}{{style}}{{else}}helpful assistant.{{/else}}.

그런 다음 다른 프롬프트에 포함할 수 있습니다.

---
model: vertexai/gemini-1.5-flash
input:
  schema:
    name: string
    style?: string
---

{{ role "system" }}
{{>personality style=style}}

{{ role "user" }}
Give the user a friendly greeting.

User's Name: {{name}}

부분 수는 {{>NAME_OF_PARTIAL args...}} 문법을 사용하여 삽입됩니다. 답이 '아니요'인 경우 인수가 부분에 제공되고 부모 프롬프트가 표시됩니다.

부분 인수는 위와 같이 이름이 지정된 두 인수 또는 단일 위치 인수를 허용합니다. 컨텍스트를 나타냅니다 이는 다음과 같은 경우에 유용할 수 있습니다. 렌더링할 수 있습니다.

# _destination.prompt
- {{name}} ({{country}})

# chooseDestination.prompt
Help the user decide between these vacation destinations:
{{#each destinations}}
{{>destination this}}{{/each}}

코드에서 부분 정의하기

다음과 같이 definePartial를 사용하여 코드에서 부분 부분을 정의할 수도 있습니다.

import { definePartial } from '@genkit-ai/dotprompt';

definePartial(
  'personality',
  'Talk like a {{#if style}}{{style}}{{else}}helpful assistant{{/if}}.'
);

코드 정의 부분은 모든 프롬프트에서 사용할 수 있습니다.

프롬프트 변형

프롬프트 파일은 텍스트일 뿐이므로 버전 제어 시스템에 커밋하면 시간 경과에 따른 변경사항을 쉽게 비교할 수 있습니다. 종종 프롬프트의 수정된 버전은 기존 버전과 나란히 비교하여 프로덕션 환경에서만 완전히 테스트할 수 있습니다. Dotprompt 변형 기능을 통해 이를 지원합니다.

변형을 만들려면 [name].[variant].prompt 파일을 만듭니다. 예를 들어 프롬프트에 Gemini 1.5 Flash를 사용 중이었으나 Gemini 1.5가 Gemini 1.5인지 확인하고 싶었습니다. Pro가 더 잘 실행되므로 다음 두 파일을 만들 수 있습니다.

  • my_prompt.prompt: '기준' 프롬프트
  • my_prompt.gemini15pro.prompt: 'gemini15pro'라는 변형입니다.

프롬프트 변형을 사용하려면 로드할 때 variant 옵션을 지정합니다.

const myPrompt = await prompt('my_prompt', { variant: 'gemini15pro' });

변형 이름은 생성 trace의 메타데이터에 포함되므로 Genkit trace 검사기에서 변형 간의 실제 성능을 비교하고 대조할 수 있습니다.

커스텀 도우미 정의

커스텀 도우미를 정의하여 프롬프트 내에서 데이터를 처리하고 관리할 수 있습니다. 도우미 defineHelper를 사용하여 전역으로 등록됩니다.

import { defineHelper } from '@genkit-ai/dotprompt';

defineHelper('shout', (text: string) => text.toUpperCase());

도우미가 정의되면 모든 프롬프트에서 사용할 수 있습니다.

---
model: vertexai/gemini-1.5-flash
input:
  schema:
    name: string
---

HELLO, {{shout name}}!!!

도우미에 전달되는 인수에 관한 자세한 내용은 생성에 대한 핸들바 문서 커스텀 도우미를 제공합니다.

프롬프트를 로드하고 정의하는 다른 방법

Dotprompt는 프롬프트 디렉터리의 구성에 최적화되어 있습니다. 하지만 프롬프트를 로드하고 정의하는 몇 가지 다른 방법입니다.

  • loadPromptFile: 프롬프트 디렉터리의 파일에서 프롬프트를 로드합니다.
  • loadPromptUrl: URL에서 프롬프트를 로드합니다.
  • defineDotprompt: 코드에서 메시지를 정의합니다.

예를 들면 다음과 같습니다.

import {
  loadPromptFile,
  loadPromptUrl,
  defineDotprompt,
} from '@genkit-ai/dotprompt';
import path from 'path';
import { z } from 'zod';

// Load a prompt from a file
const myPrompt = await loadPromptFile(
  path.resolve(__dirname, './path/to/my_prompt.prompt')
);

// Load a prompt from a URL
const myPrompt = await loadPromptUrl('https://example.com/my_prompt.prompt');

// Define a prompt in code
const myPrompt = defineDotprompt(
  {
    model: 'vertexai/gemini-1.5-flash',
    input: {
      schema: z.object({
        name: z.string(),
      }),
    },
  },
  `Hello {{name}}, how are you today?`
);