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 và nhập hàm prompt từ thư viện @genkit-ai/dotprompt:

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

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

Sau đó, hãy tải câu lệnh bằng 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());

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

Dotprompt bao gồm một định dạng định nghĩa giản đồ nhỏ gọn, được tối ưu hoá cho 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 giao diện TypeScript sau đây:

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 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 đị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

Sử dụng giản đồ có thể sử dụng lại

Ngoài việc xác định trực tiếp giản đồ trong tệp .prompt, bạn có thể tham khảo một giản đồ được đăng ký với defineSchema theo tên. Cách đăng ký giản đồ:

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

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

Trong câu lệnh, bạn có thể cung cấp tên của giản đồ đã đăng ký:

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

Thư viện Dotprompt sẽ tự động phân giải tên thành thư viện cơ bản giản đồ Zod đã đăng ký. Sau đó, bạn có thể sử dụng giản đồ này để nhập mạnh đầu ra của Dotprompt:

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

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:

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

Kết quả có cấu trúc

Bạn có thể đặt giản đồ định dạng và đầu ra của câu lệnh để chuyển đổi thành 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.

Khi tạo câu lệnh bằng kết quả có cấu trúc, hãy sử dụng trình trợ giúp output() để truy xuất và xác thực URL đó:

const createMenuPrompt = await prompt('create_menu');

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

console.log(menu.output());

Sự tuân thủ của đầu ra đạt được bằng cách chèn các lệnh bổ sung vào . Theo mặc định, ký tự này được nối vào cuối thư cuối cùng được tạo bằng câu lệnh. Bạn có thể đặt lại vị trí theo cách thủ công bằng cách sử dụng {{section "output"}} của chúng tôi.

This is a prompt that manually positions output instructions.

== Output Instructions

{{section "output"}}

== Other Instructions

This will come after the output instructions.

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

Nhật ký và lời nhắc nhiều lượt

Dotprompt hỗ trợ các lời nhắc nhiều lượt bằng cách chuyển tuỳ chọn history vào Phương thức generate:

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

Theo mặc định, lịch sử sẽ được chèn trước thông báo cuối cùng được tạo bởi lời nhắc. Tuy nhiên, bạn có thể định vị nhật ký theo cách thủ công bằng cách sử dụng {{history}} trình trợ giúp:

{{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.

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à:

const describeImagePrompt = await prompt('describe_image');

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

console.log(result.text());

Từng phần

Từng phần là các mẫu có thể sử dụng lại, có thể đưa vào mọi câu lệnh. Từng phần có thể đặc biệt hữu ích đối với những câu lệnh liên quan có chung hành vi.

Khi tải một thư mục lời nhắc, mọi tệp có tiền tố _ đều được coi là từng phần. Vì vậy, một tệp _personality.prompt có thể chứa:

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

Sau đó, bạn có thể đưa câu lệnh này vào các câu lệnh khác:

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

Các phần được chèn bằng cú pháp {{>NAME_OF_PARTIAL args...}}. Nếu không đối số được cung cấp cho một phần, nó thực thi với cùng một ngữ cảnh như lời nhắc của cha mẹ.

Các phần chấp nhận cả hai đối số có tên như trên hoặc một đối số vị trí duy nhất thể hiện ngữ cảnh. Điều này có thể hữu ích, ví dụ: hiển thị thành viên của một danh sách.

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

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

Xác định các phần trong mã

Bạn cũng có thể xác định các phần trong mã bằng cách sử dụng definePartial:

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

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

Các phần được xác định bằng mã có sẵn trong tất cả câu lệnh.

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.gemini15pro.prompt: một biến thể có tên là "gemini15pro"

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

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

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.

Xác định trình trợ giúp tuỳ chỉnh

Bạn có thể xác định các trình trợ giúp tuỳ chỉnh để xử lý và quản lý dữ liệu bên trong một câu lệnh. Trợ lý được đăng ký trên toàn cầu bằng defineHelper:

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

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

Sau khi xác định trình trợ giúp, bạn có thể sử dụng trình trợ giúp trong bất kỳ câu lệnh nào:

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

HELLO, {{shout name}}!!!

Để biết thêm thông tin về các đối số được chuyển đến trình trợ giúp, hãy xem Tài liệu về tay điều khiển về cách tạo trình trợ giúp tuỳ chỉnh.

Những cách khác để tải và xác định câu lệnh

Dấu chấm được tối ưu hoá cho tổ chức trong thư mục lời nhắc. Tuy nhiên, có là một số cách khác để tải và xác định lời nhắc:

  • loadPromptFile: Tải lời nhắc từ một tệp trong thư mục lời nhắc.
  • loadPromptUrl: Tải câu lệnh từ một URL.
  • defineDotprompt: Xác định câu lệnh trong mã.

Ví dụ:

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?`
);