ניהול הנחיות באמצעות Dotprompt

Firebase Genkit מספק את הפלאגין Dotprompt ואת פורמט הטקסט שיעזרו לכם לכתוב ולארגן את ההנחיות של ה-AI הגנרטיבי.

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

אפשר להפעיל את method‏ 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 כולל פורמט קומפקטי של הגדרת סכימה שמבוססת על YAML, שנקרא Picoschema, כדי להקל על ההגדרה של המאפיינים החשובים ביותר של סכימה לשימוש ב-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 כוללים את כל המאפיינים הנדרשים, אלא אם הם מסומנים כאופציונליים באמצעות ?, והם לא מאפשרים להוסיף מאפיינים. כשמאפיין מסומן כאופציונלי, הוא הופך גם לאפשרי לקבל ערך null כדי לספק גמישות רבה יותר ל-LLMs להחזיר ערך 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". חלק מההנחיות הכי טוב להציג כשיילוב של כמה הודעות, כמו הנחיה של מערכת.

בעזרת ה-helper‏ {{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 יכולה להיות מזהי URI מסוג https:// או data: בקידוד base64 לשימוש בתמונות 'מוטבעות'. בקוד, זה ייראה כך:

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

וריאציות של הנחיות

קובצי ה-prompt הם פשוט טקסט, ולכן אפשר (וצריך!) לבצע עליהם commit במערכת לניהול גרסאות, כדי שתוכלו להשוות בקלות בין השינויים לאורך זמן. לרוב, אפשר לבדוק גרסאות משופרות של הנחיות רק בסביבת ייצור לצד גרסאות קיימות. Dotprompt תומך בכך באמצעות התכונה וריאנטים שלו.

כדי ליצור וריאנט, צריך ליצור קובץ [name].[variant].prompt. לדוגמה, אם השתמשתם ב-Gemini 1.5 Flash בהנחיה אבל רציתם לבדוק אם הביצועים של Gemini 1.5 Pro טובים יותר, תוכלו ליצור שני קבצים:

  • my_prompt.prompt: ההנחיה 'baseline'
  • 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")
}

שם הווריאנט נכלל במטא-נתונים של נתוני המעקב אחרי היצירה, כך שתוכלו להשוות בין הביצועים בפועל של הווריאנטים השונים ב-Genkit trace inspector.