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 플러그인을 설치합니다.

go get github.com/firebase/genkit/go/plugins/dotprompt

그런 다음 Open을 사용하여 프롬프트를 로드합니다.

import "github.com/firebase/genkit/go/plugins/dotprompt"
dotprompt.SetDirectory("prompts")
prompt, err := dotprompt.Open("greeting")

프롬프트의 Generate 메서드를 호출하여 템플릿을 렌더링하고 이를 한 번에 모델 API에 전달할 수 있습니다.

ctx := context.Background()

// Default to the project in GCLOUD_PROJECT and the location "us-central1".
vertexai.Init(ctx, nil)

// The .prompt file specifies vertexai/gemini-1.5-flash, which is
// automatically defined by Init(). However, if it specified a model that
// isn't automatically loaded (such as a specific version), you would need
// to define it here:
// vertexai.DefineModel("gemini-1.0-pro-002", &ai.ModelCapabilities{
// 	Multiturn:  true,
// 	Tools:      true,
// 	SystemRole: true,
// 	Media:      false,
// })

type GreetingPromptInput struct {
	Location string `json:"location"`
	Style    string `json:"style"`
	Name     string `json:"name"`
}
response, err := prompt.Generate(
	ctx,
	&dotprompt.PromptRequest{
		Variables: GreetingPromptInput{
			Location: "the beach",
			Style:    "a fancy pirate",
			Name:     "Ed",
		},
	},
	nil,
)
if err != nil {
	return err
}

fmt.Println(response.Text())

또는 템플릿을 문자열로 렌더링합니다.

renderedPrompt, err := prompt.RenderText(map[string]any{
	"location": "a restaurant",
	"style":    "a pirate",
})

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

Picoschema로 입력/출력 스키마 정의

Dotprompt에는 Picoschema라고 하는 축약형 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

위 스키마는 다음 JSON 스키마와 같습니다.

{
  "properties": {
    "metadata": {
      "properties": {
        "updatedAt": {
          "type": "string",
          "description": "ISO timestamp of last update"
        },
        "approvedBy": {
          "type": "integer",
          "description": "id of approver"
        }
      },
      "type": "object"
    },
    "title": {
      "type": "string"
    },
    "subtitle": {
      "type": "string"
    },
    "draft": {
      "type": "boolean",
      "description": "true when in draft state"
    },
    "date": {
      "type": "string",
      "description": "the date of publication e.g. '2024-04-09'"
    },
    "tags": {
      "items": {
        "type": "string"
      },
      "type": "array",
      "description": "relevant tags for article"
    },
    "authors": {
      "items": {
        "properties": {
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string"
          }
        },
        "type": "object",
        "required": ["name"]
      },
      "type": "array"
    }
  },
  "type": "object",
  "required": ["title", "date", "tags", "authors"]
}

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

// Make sure you set up the model you're using.
vertexai.DefineModel("gemini-1.5-flash", nil)

response, err := prompt.Generate(
	context.Background(),
	&dotprompt.PromptRequest{
		Variables: GreetingPromptInput{
			Location: "the beach",
			Style:    "a fancy pirate",
			Name:     "Ed",
		},
		Model: "vertexai/gemini-1.5-flash",
		Config: &ai.GenerationCommonConfig{
			Temperature: 1.0,
		},
	},
	nil,
)

멀티메시지 프롬프트

기본적으로 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}}

멀티모달 프롬프트

텍스트와 함께 이미지와 같은 멀티모달 입력을 지원하는 모델의 경우 {{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일 수 있습니다. 코드에서 다음과 같습니다.

dotprompt.SetDirectory("prompts")
describeImagePrompt, err := dotprompt.Open("describe_image")
if err != nil {
	return err
}

imageBytes, err := os.ReadFile("img.jpg")
if err != nil {
	return err
}
encodedImage := base64.StdEncoding.EncodeToString(imageBytes)
dataURI := "data:image/jpeg;base64," + encodedImage

type DescribeImagePromptInput struct {
	PhotoUrl string `json:"photo_url"`
}
response, err := describeImagePrompt.Generate(
	context.Background(),
	&dotprompt.PromptRequest{Variables: DescribeImagePromptInput{
		PhotoUrl: dataURI,
	}},
	nil,
)

프롬프트 변형

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

변형을 만들려면 [name].[variant].prompt 파일을 만듭니다. 예를 들어 프롬프트에 Gemini 1.5 Flash를 사용하고 있었지만 Gemini 1.5 Pro가 더 잘 작동하는지 확인하려고 한다면 다음과 같은 두 개의 파일을 만들 수 있습니다.

  • my_prompt.prompt: '기준' 프롬프트
  • my_prompt.geminipro.prompt: 'geminipro'라는 변형

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

describeImagePrompt, err := dotprompt.OpenVariant("describe_image", "geminipro")

프롬프트 로더는 해당 이름의 변형을 로드하려고 시도한 후 존재하지 않으면 기준으로 돌아갑니다. 즉 애플리케이션에 적합한 기준에 따라 조건부 로딩을 사용할 수 있습니다.

var myPrompt *dotprompt.Prompt
var err error
if isBetaTester(user) {
	myPrompt, err = dotprompt.OpenVariant("describe_image", "geminipro")
} else {
	myPrompt, err = dotprompt.Open("describe_image")
}

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