|
| 1 | +// Copyright 2025 Google LLC |
| 2 | +// |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +// you may not use this file except in compliance with the License. |
| 5 | +// You may obtain a copy of the License at |
| 6 | +// |
| 7 | +// https://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +// |
| 9 | +// Unless required by applicable law or agreed to in writing, software |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +// See the License for the specific language governing permissions and |
| 13 | +// limitations under the License. |
| 14 | + |
| 15 | +package cel |
| 16 | + |
| 17 | +import ( |
| 18 | + _ "embed" |
| 19 | + "sort" |
| 20 | + "strings" |
| 21 | + "text/template" |
| 22 | + |
| 23 | + "github.com/google/cel-go/common" |
| 24 | + "github.com/google/cel-go/common/operators" |
| 25 | + "github.com/google/cel-go/common/overloads" |
| 26 | +) |
| 27 | + |
| 28 | +//go:embed templates/authoring.tmpl |
| 29 | +var authoringPrompt string |
| 30 | + |
| 31 | +// AuthoringPrompt creates a prompt template from a CEL environment for the purpose of AI-assisted authoring. |
| 32 | +func AuthoringPrompt(env *Env) (*Prompt, error) { |
| 33 | + funcMap := template.FuncMap{ |
| 34 | + "split": func(str string) []string { return strings.Split(str, "\n") }, |
| 35 | + } |
| 36 | + tmpl := template.New("cel").Funcs(funcMap) |
| 37 | + tmpl, err := tmpl.Parse(authoringPrompt) |
| 38 | + if err != nil { |
| 39 | + return nil, err |
| 40 | + } |
| 41 | + return &Prompt{ |
| 42 | + Persona: defaultPersona, |
| 43 | + FormatRules: defaultFormatRules, |
| 44 | + GeneralUsage: defaultGeneralUsage, |
| 45 | + tmpl: tmpl, |
| 46 | + env: env, |
| 47 | + }, nil |
| 48 | +} |
| 49 | + |
| 50 | +// Prompt represents the core components of an LLM prompt based on a CEL environment. |
| 51 | +// |
| 52 | +// All fields of the prompt may be overwritten / modified with support for rendering the |
| 53 | +// prompt to a human-readable string. |
| 54 | +type Prompt struct { |
| 55 | + // Persona indicates something about the kind of user making the request |
| 56 | + Persona string |
| 57 | + |
| 58 | + // FormatRules indicate how the LLM should generate its output |
| 59 | + FormatRules string |
| 60 | + |
| 61 | + // GeneralUsage specifies additional context on how CEL should be used. |
| 62 | + GeneralUsage string |
| 63 | + |
| 64 | + // tmpl is the text template base-configuration for rendering text. |
| 65 | + tmpl *template.Template |
| 66 | + |
| 67 | + // env reference used to collect variables, functions, and macros available to the prompt. |
| 68 | + env *Env |
| 69 | +} |
| 70 | + |
| 71 | +type promptInst struct { |
| 72 | + *Prompt |
| 73 | + |
| 74 | + Variables []*common.Doc |
| 75 | + Macros []*common.Doc |
| 76 | + Functions []*common.Doc |
| 77 | + UserPrompt string |
| 78 | +} |
| 79 | + |
| 80 | +// Render renders the user prompt with the associated context from the prompt template |
| 81 | +// for use with LLM generators. |
| 82 | +func (p *Prompt) Render(userPrompt string) string { |
| 83 | + var buffer strings.Builder |
| 84 | + vars := make([]*common.Doc, len(p.env.Variables())) |
| 85 | + for i, v := range p.env.Variables() { |
| 86 | + vars[i] = v.Documentation() |
| 87 | + } |
| 88 | + sort.SliceStable(vars, func(i, j int) bool { |
| 89 | + return vars[i].Name < vars[j].Name |
| 90 | + }) |
| 91 | + macs := make([]*common.Doc, len(p.env.Macros())) |
| 92 | + for i, m := range p.env.Macros() { |
| 93 | + macs[i] = m.(common.Documentor).Documentation() |
| 94 | + } |
| 95 | + funcs := make([]*common.Doc, 0, len(p.env.Functions())) |
| 96 | + for _, f := range p.env.Functions() { |
| 97 | + if _, hidden := hiddenFunctions[f.Name()]; hidden { |
| 98 | + continue |
| 99 | + } |
| 100 | + funcs = append(funcs, f.Documentation()) |
| 101 | + } |
| 102 | + sort.SliceStable(funcs, func(i, j int) bool { |
| 103 | + return funcs[i].Name < funcs[j].Name |
| 104 | + }) |
| 105 | + inst := &promptInst{ |
| 106 | + Prompt: p, |
| 107 | + Variables: vars, |
| 108 | + Macros: macs, |
| 109 | + Functions: funcs, |
| 110 | + UserPrompt: userPrompt} |
| 111 | + p.tmpl.Execute(&buffer, inst) |
| 112 | + return buffer.String() |
| 113 | +} |
| 114 | + |
| 115 | +const ( |
| 116 | + defaultPersona = `You are a software engineer with expertise in networking and application security |
| 117 | +authoring boolean Common Expression Language (CEL) expressions to ensure firewall, |
| 118 | +networking, authentication, and data access is only permitted when all conditions |
| 119 | +are satisified.` |
| 120 | + |
| 121 | + defaultFormatRules = `Output your response as a CEL expression. |
| 122 | +
|
| 123 | +Write the expression with the comment on the first line and the expression on the |
| 124 | +subsequent lines. Format the expression using 80-character line limits commonly |
| 125 | +found in C++ or Java code.` |
| 126 | + |
| 127 | + defaultGeneralUsage = `CEL supports Protocol Buffer and JSON types, as well as simple types and aggregate types. |
| 128 | +
|
| 129 | +Simple types include bool, bytes, double, int, string, and uint: |
| 130 | +
|
| 131 | +* double literals must always include a decimal point: 1.0, 3.5, -2.2 |
| 132 | +* uint literals must be positive values suffixed with a 'u': 42u |
| 133 | +* byte literals are strings prefixed with a 'b': b'1235' |
| 134 | +* string literals can use either single quotes or double quotes: 'hello', "world" |
| 135 | +* string literals can also be treated as raw strings that do not require any |
| 136 | + escaping within the string by using the 'R' prefix: R"""quote: "hi" """ |
| 137 | +
|
| 138 | +Aggregate types include list and map: |
| 139 | +
|
| 140 | +* list literals consist of zero or more values between brackets: "['a', 'b', 'c']" |
| 141 | +* map literal consist of colon-separated key-value pairs within braces: "{'key1': 1, 'key2': 2}" |
| 142 | +* Only int, uint, string, and bool types are valid map keys. |
| 143 | +* Maps containing HTTP headers must always use lower-cased string keys. |
| 144 | +
|
| 145 | +Comments start with two-forward slashes followed by text and a newline.` |
| 146 | +) |
| 147 | + |
| 148 | +var ( |
| 149 | + hiddenFunctions = map[string]bool{ |
| 150 | + overloads.DeprecatedIn: true, |
| 151 | + operators.OldIn: true, |
| 152 | + operators.OldNotStrictlyFalse: true, |
| 153 | + operators.NotStrictlyFalse: true, |
| 154 | + } |
| 155 | +) |
0 commit comments