Quản lý câu lệnh bằng Dotprompt

Firebase Genkit cung cấp trình bổ trợ Dotprompt và định dạng văn bản để giúp bạn viết và sắp xếp các câu lệnh dựa trên AI tạo sinh.

Dấu chấm nhắc được thiết kế xung quanh tiền đề lời nhắc là mã. Bạn viết và hãy lưu trữ câu lệnh của mình trong các tệp có định dạng đặc biệt, gọi là tệp dấu chấm, theo dõi những thay đổi này bằng cách sử dụng cùng một hệ thống quản lý phiên bản mà bạn dùng cho rồi triển khai chúng cùng với đoạn mã gọi AI tạo sinh của bạn người mẫu.

Để sử dụng Dotprompt, trước tiên hãy tạo một thư mục prompts trong thư mục gốc của dự án và thì hãy tạo một tệp .prompt trong thư mục đó. Sau đây là một ví dụ đơn giản, có thể gọi 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}}.

Để sử dụng lời nhắc này, hãy cài đặt trình bổ trợ dotprompt:

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

Sau đó, hãy tải câu lệnh bằng Open:

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

Bạn có thể gọi phương thức Generate của lời nhắc để kết xuất và truyền mẫu đó vào API mô hình trong một bước:

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())

Hoặc chỉ kết xuất mẫu thành một chuỗi:

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

Cú pháp của dấu chấm hỏi dựa trên Thanh công cụ ngôn ngữ gợi mở. Bạn có thể sử dụng trình trợ giúp if, unlesseach để thêm các phần có điều kiện trong câu lệnh hoặc lặp lại thông qua nội dung có cấu trúc. Chiến lược phát hành đĩa đơn định dạng tệp sử dụng trình phân cách trước YAML để cung cấp siêu dữ liệu cho lời nhắc cùng dòng với mẫu.

Xác định giản đồ đầu vào/đầu ra bằng Picoschema

Dotprompt bao gồm một định dạng định nghĩa giản đồ nhỏ gọn dựa trên YAML được gọi là Picoschema giúp dễ dàng xác định các thuộc tính quan trọng nhất của giản đồ để sử dụng LLM. Dưới đây là ví dụ về giản đồ cho một bài viết:

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

Giản đồ trên tương đương với giản đồ JSON sau:

{
  "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 hỗ trợ các loại vô hướng string, integer, number, booleanany. Đối với các đối tượng, mảng và enum, chúng được biểu thị bằng một trong dấu ngoặc đơn sau tên trường.

Các đối tượng do Picoschema xác định sẽ có tất cả các thuộc tính theo yêu cầu trừ khi được biểu thị là không bắt buộc muộn nhất vào ? và không cho phép các thuộc tính khác. Khi một thuộc tính được đánh dấu là không bắt buộc, hệ thống cũng được làm rỗng để khiến các LLM trả về giá trị rỗng thay vì bỏ qua một trường.

Trong một định nghĩa đối tượng, bạn có thể dùng khoá đặc biệt (*) để khai báo một "ký tự đại diện" định nghĩa trường. Thông tin này sẽ khớp với mọi cơ sở lưu trú khác không do khoá rõ ràng.

Picoschema không hỗ trợ nhiều tính năng của giản đồ JSON đầy đủ. Nếu bạn yêu cầu giản đồ mạnh mẽ hơn, bạn có thể cung cấp giản đồ JSON:

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

Ghi đè siêu dữ liệu về lời nhắc

Mặc dù tệp .prompt cho phép bạn nhúng siêu dữ liệu (chẳng hạn như cấu hình mô hình) vào tệp, bạn cũng có thể ghi đè các giá trị này cho từng lệnh gọi:

// 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,
)

Lời nhắc gửi nhiều thông báo

Theo mặc định, Dotprompt tạo một tin nhắn duy nhất có vai trò "user". Hơi nhiều tốt nhất là bạn nên thể hiện câu lệnh khi kết hợp nhiều thông điệp, chẳng hạn như lời nhắc của hệ thống.

Trình trợ giúp {{role}} cung cấp một cách thức đơn giản để tạo các lời nhắc gồm nhiều thông báo:

---
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}}

Câu lệnh đa phương thức

Đối với những mô hình hỗ trợ nhập dữ liệu đa phương thức, chẳng hạn như hình ảnh dọc theo văn bản, bạn có thể sử dụng trình trợ giúp {{media}}:

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

Describe this image in a detailed paragraph:

{{media url=photoUrl}}

URL có thể là các URI https:// hoặc data: được mã hoá base64 cho thao tác "nội tuyến" hình ảnh mức sử dụng. Trong mã, URL này sẽ là:

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,
)

Biến thể câu lệnh

Vì tệp lời nhắc chỉ là văn bản nên bạn có thể (và nên!) gắn những tệp này vào hệ thống quản lý phiên bản, cho phép bạn dễ dàng so sánh các thay đổi theo thời gian. Thông thường, bạn chỉ có thể kiểm thử đầy đủ các phiên bản đã tinh chỉnh của những câu lệnh trong một môi trường phát hành công khai song song với các phiên bản hiện có. Hỗ trợ Dotprompt thông qua tính năng biến thể.

Để tạo một biến thể, hãy tạo tệp [name].[variant].prompt. Ví dụ: nếu bạn đang sử dụng Gemini 1.5 Flash trong câu lệnh của mình nhưng muốn biết liệu Gemini 1.5 Pro sẽ hoạt động hiệu quả hơn. Bạn có thể tạo hai tệp:

  • my_prompt.prompt: "đường cơ sở" câu lệnh
  • my_prompt.geminipro.prompt: một biến thể có tên là "geminipro"

Để sử dụng một biến thể lời nhắc, hãy chỉ định biến thể đó khi tải:

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

Trình tải lời nhắc sẽ cố tải biến thể của tên đó rồi quay trở lại cho đường cơ sở nếu không có đường cơ sở nào. Điều này có nghĩa là bạn có thể sử dụng tính năng tải có điều kiện dựa trên dựa trên bất cứ tiêu chí nào phù hợp với ứng dụng của bạn:

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

Tên của biến thể được đưa vào siêu dữ liệu của dấu vết tạo, vì vậy, bạn có thể so sánh và đối chiếu hiệu suất thực tế giữa các biến thể trong dấu vết Genkit Trình kiểm tra.