1
1
/*
2
- * Copyright 2023-2024 the original author or authors.
2
+ * Copyright 2023-2025 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
20
20
import java .io .InputStream ;
21
21
import java .nio .charset .Charset ;
22
22
import java .util .HashMap ;
23
- import java .util .HashSet ;
24
23
import java .util .List ;
25
24
import java .util .Map ;
26
25
import java .util .Map .Entry ;
27
26
import java .util .Set ;
28
27
29
- import org .antlr .runtime .Token ;
30
- import org .antlr .runtime .TokenStream ;
31
- import org .stringtemplate .v4 .ST ;
32
- import org .stringtemplate .v4 .compiler .STLexer ;
28
+ import org .springframework .ai .template .TemplateRenderer ;
29
+ import org .springframework .ai .template .st .StTemplateRenderer ;
30
+ import org .springframework .util .Assert ;
33
31
34
32
import org .springframework .ai .chat .messages .Message ;
35
33
import org .springframework .ai .chat .messages .UserMessage ;
36
34
import org .springframework .ai .content .Media ;
37
35
import org .springframework .core .io .Resource ;
38
36
import org .springframework .util .StreamUtils ;
39
37
38
+ /**
39
+ * A template for creating prompts. It allows you to define a template string with
40
+ * placeholders for variables, and then render the template with specific values for those
41
+ * variables.
42
+ */
40
43
public class PromptTemplate implements PromptTemplateActions , PromptTemplateMessageActions {
41
44
45
+ private static final TemplateRenderer DEFAULT_TEMPLATE_RENDERER = StTemplateRenderer .builder ().build ();
46
+
47
+ /**
48
+ * @deprecated will become private in the next release. If you're subclassing this
49
+ * class, re-consider using the built-in implementation together with the new
50
+ * PromptTemplateRenderer interface, designed to give you more flexibility and control
51
+ * over the rendering process.
52
+ */
53
+ @ Deprecated
42
54
protected String template ;
43
55
56
+ /**
57
+ * @deprecated in favor of {@link TemplateRenderer}
58
+ */
59
+ @ Deprecated
44
60
protected TemplateFormat templateFormat = TemplateFormat .ST ;
45
61
46
- private ST st ;
62
+ private final Map < String , Object > variables = new HashMap <>() ;
47
63
48
- private Map < String , Object > dynamicModel = new HashMap <>() ;
64
+ private final TemplateRenderer renderer ;
49
65
50
66
public PromptTemplate (Resource resource ) {
51
- try (InputStream inputStream = resource .getInputStream ()) {
52
- this .template = StreamUtils .copyToString (inputStream , Charset .defaultCharset ());
53
- }
54
- catch (IOException ex ) {
55
- throw new RuntimeException ("Failed to read resource" , ex );
56
- }
57
- try {
58
- this .st = new ST (this .template , '{' , '}' );
59
- }
60
- catch (Exception ex ) {
61
- throw new IllegalArgumentException ("The template string is not valid." , ex );
62
- }
67
+ this (resource , new HashMap <>(), DEFAULT_TEMPLATE_RENDERER );
63
68
}
64
69
65
70
public PromptTemplate (String template ) {
66
- this .template = template ;
67
- // If the template string is not valid, an exception will be thrown
68
- try {
69
- this .st = new ST (this .template , '{' , '}' );
70
- }
71
- catch (Exception ex ) {
72
- throw new IllegalArgumentException ("The template string is not valid." , ex );
73
- }
71
+ this (template , new HashMap <>(), DEFAULT_TEMPLATE_RENDERER );
72
+ }
73
+
74
+ /**
75
+ * @deprecated in favor of {@link PromptTemplate#builder()}.
76
+ */
77
+ @ Deprecated
78
+ public PromptTemplate (String template , Map <String , Object > variables ) {
79
+ this (template , variables , DEFAULT_TEMPLATE_RENDERER );
74
80
}
75
81
76
- public PromptTemplate (String template , Map <String , Object > model ) {
82
+ /**
83
+ * @deprecated in favor of {@link PromptTemplate#builder()}.
84
+ */
85
+ @ Deprecated
86
+ public PromptTemplate (Resource resource , Map <String , Object > variables ) {
87
+ this (resource , variables , DEFAULT_TEMPLATE_RENDERER );
88
+ }
89
+
90
+ PromptTemplate (String template , Map <String , Object > variables , TemplateRenderer renderer ) {
91
+ Assert .hasText (template , "template cannot be null or empty" );
92
+ Assert .notNull (variables , "variables cannot be null" );
93
+ Assert .noNullElements (variables .keySet (), "variables keys cannot be null" );
94
+ Assert .notNull (renderer , "renderer cannot be null" );
95
+
77
96
this .template = template ;
78
- // If the template string is not valid, an exception will be thrown
79
- try {
80
- this .st = new ST (this .template , '{' , '}' );
81
- for (Entry <String , Object > entry : model .entrySet ()) {
82
- add (entry .getKey (), entry .getValue ());
83
- }
84
- }
85
- catch (Exception ex ) {
86
- throw new IllegalArgumentException ("The template string is not valid." , ex );
87
- }
97
+ this .variables .putAll (variables );
98
+ this .renderer = renderer ;
88
99
}
89
100
90
- public PromptTemplate (Resource resource , Map <String , Object > model ) {
101
+ PromptTemplate (Resource resource , Map <String , Object > variables , TemplateRenderer renderer ) {
102
+ Assert .notNull (resource , "resource cannot be null" );
103
+ Assert .notNull (variables , "variables cannot be null" );
104
+ Assert .noNullElements (variables .keySet (), "variables keys cannot be null" );
105
+ Assert .notNull (renderer , "renderer cannot be null" );
106
+
91
107
try (InputStream inputStream = resource .getInputStream ()) {
92
108
this .template = StreamUtils .copyToString (inputStream , Charset .defaultCharset ());
109
+ Assert .hasText (template , "template cannot be null or empty" );
93
110
}
94
111
catch (IOException ex ) {
95
112
throw new RuntimeException ("Failed to read resource" , ex );
96
113
}
97
- // If the template string is not valid, an exception will be thrown
98
- try {
99
- this .st = new ST (this .template , '{' , '}' );
100
- for (Entry <String , Object > entry : model .entrySet ()) {
101
- this .add (entry .getKey (), entry .getValue ());
102
- }
103
- }
104
- catch (Exception ex ) {
105
- throw new IllegalArgumentException ("The template string is not valid." , ex );
106
- }
114
+ this .variables .putAll (variables );
115
+ this .renderer = renderer ;
107
116
}
108
117
109
118
public void add (String name , Object value ) {
110
- this .st .add (name , value );
111
- this .dynamicModel .put (name , value );
119
+ this .variables .put (name , value );
112
120
}
113
121
114
122
public String getTemplate () {
115
123
return this .template ;
116
124
}
117
125
126
+ /**
127
+ * @deprecated in favor of {@link TemplateRenderer}
128
+ */
129
+ @ Deprecated
118
130
public TemplateFormat getTemplateFormat () {
119
131
return this .templateFormat ;
120
132
}
121
133
122
- // Render Methods
134
+ // From PromptTemplateStringActions.
135
+
123
136
@ Override
124
137
public String render () {
125
- validate (this .dynamicModel );
126
- return this .st .render ();
138
+ return this .renderer .apply (template , this .variables );
127
139
}
128
140
129
141
@ Override
130
- public String render (Map <String , Object > model ) {
131
- validate (model );
132
- for (Entry <String , Object > entry : model .entrySet ()) {
133
- if (this .st .getAttribute (entry .getKey ()) != null ) {
134
- this .st .remove (entry .getKey ());
135
- }
142
+ public String render (Map <String , Object > additionalVariables ) {
143
+ Map <String , Object > combinedVariables = new HashMap <>(this .variables );
144
+
145
+ for (Entry <String , Object > entry : additionalVariables .entrySet ()) {
136
146
if (entry .getValue () instanceof Resource ) {
137
- this . st . add (entry .getKey (), renderResource ((Resource ) entry .getValue ()));
147
+ combinedVariables . put (entry .getKey (), renderResource ((Resource ) entry .getValue ()));
138
148
}
139
149
else {
140
- this . st . add (entry .getKey (), entry .getValue ());
150
+ combinedVariables . put (entry .getKey (), entry .getValue ());
141
151
}
142
-
143
152
}
144
- return this .st .render ();
153
+
154
+ return this .renderer .apply (template , combinedVariables );
145
155
}
146
156
147
157
private String renderResource (Resource resource ) {
@@ -153,85 +163,119 @@ private String renderResource(Resource resource) {
153
163
}
154
164
}
155
165
166
+ // From PromptTemplateMessageActions.
167
+
156
168
@ Override
157
169
public Message createMessage () {
158
170
return new UserMessage (render ());
159
171
}
160
172
161
173
@ Override
162
174
public Message createMessage (List <Media > mediaList ) {
163
- return new UserMessage ( render (), mediaList );
175
+ return UserMessage . builder (). text ( render ()). media ( mediaList ). build ( );
164
176
}
165
177
166
178
@ Override
167
- public Message createMessage (Map <String , Object > model ) {
168
- return new UserMessage (render (model ));
179
+ public Message createMessage (Map <String , Object > additionalVariables ) {
180
+ return new UserMessage (render (additionalVariables ));
169
181
}
170
182
183
+ // From PromptTemplateActions.
184
+
171
185
@ Override
172
186
public Prompt create () {
173
187
return new Prompt (render (new HashMap <>()));
174
188
}
175
189
176
190
@ Override
177
191
public Prompt create (ChatOptions modelOptions ) {
178
- return new Prompt ( render (new HashMap <>()), modelOptions );
192
+ return Prompt . builder (). content ( render (new HashMap <>())). chatOptions ( modelOptions ). build ( );
179
193
}
180
194
181
195
@ Override
182
- public Prompt create (Map <String , Object > model ) {
183
- return new Prompt (render (model ));
196
+ public Prompt create (Map <String , Object > additionalVariables ) {
197
+ return new Prompt (render (additionalVariables ));
184
198
}
185
199
186
200
@ Override
187
- public Prompt create (Map <String , Object > model , ChatOptions modelOptions ) {
188
- return new Prompt ( render (model ), modelOptions );
201
+ public Prompt create (Map <String , Object > additionalVariables , ChatOptions modelOptions ) {
202
+ return Prompt . builder (). content ( render (additionalVariables )). chatOptions ( modelOptions ). build ( );
189
203
}
190
204
205
+ // Compatibility
206
+
207
+ /**
208
+ * @deprecated in favor of {@link TemplateRenderer}.
209
+ */
210
+ @ Deprecated
191
211
public Set <String > getInputVariables () {
192
- TokenStream tokens = this .st .impl .tokens ;
193
- Set <String > inputVariables = new HashSet <>();
194
- boolean isInsideList = false ;
195
-
196
- for (int i = 0 ; i < tokens .size (); i ++) {
197
- Token token = tokens .get (i );
198
-
199
- if (token .getType () == STLexer .LDELIM && i + 1 < tokens .size ()
200
- && tokens .get (i + 1 ).getType () == STLexer .ID ) {
201
- if (i + 2 < tokens .size () && tokens .get (i + 2 ).getType () == STLexer .COLON ) {
202
- inputVariables .add (tokens .get (i + 1 ).getText ());
203
- isInsideList = true ;
204
- }
205
- }
206
- else if (token .getType () == STLexer .RDELIM ) {
207
- isInsideList = false ;
208
- }
209
- else if (!isInsideList && token .getType () == STLexer .ID ) {
210
- inputVariables .add (token .getText ());
211
- }
212
- }
212
+ throw new UnsupportedOperationException (
213
+ "The template rendering logic is now provided by PromptTemplateRenderer" );
214
+ }
213
215
214
- return inputVariables ;
216
+ /**
217
+ * @deprecated in favor of {@link TemplateRenderer}.
218
+ */
219
+ @ Deprecated
220
+ protected void validate (Map <String , Object > model ) {
221
+ throw new UnsupportedOperationException ("Validation is now provided by the PromptTemplateRenderer" );
215
222
}
216
223
217
- private Set <String > getModelKeys (Map <String , Object > model ) {
218
- Set <String > dynamicVariableNames = new HashSet <>(this .dynamicModel .keySet ());
219
- Set <String > modelVariables = new HashSet <>(model .keySet ());
220
- modelVariables .addAll (dynamicVariableNames );
221
- return modelVariables ;
224
+ public Builder mutate () {
225
+ return new Builder ().template (this .template ).variables (this .variables ).renderer (this .renderer );
222
226
}
223
227
224
- protected void validate (Map <String , Object > model ) {
228
+ // Builder
229
+
230
+ public static Builder builder () {
231
+ return new Builder ();
232
+ }
233
+
234
+ public static class Builder {
235
+
236
+ private String template ;
237
+
238
+ private Resource resource ;
225
239
226
- Set <String > templateTokens = getInputVariables ();
227
- Set <String > modelKeys = getModelKeys (model );
240
+ private Map <String , Object > variables = new HashMap <>();
228
241
229
- // Check if model provides all keys required by the template
230
- if (!modelKeys .containsAll (templateTokens )) {
231
- templateTokens .removeAll (modelKeys );
232
- throw new IllegalStateException (
233
- "Not all template variables were replaced. Missing variable names are " + templateTokens );
242
+ private TemplateRenderer renderer = DEFAULT_TEMPLATE_RENDERER ;
243
+
244
+ private Builder () {
245
+ }
246
+
247
+ public Builder template (String template ) {
248
+ this .template = template ;
249
+ return this ;
250
+ }
251
+
252
+ public Builder resource (Resource resource ) {
253
+ this .resource = resource ;
254
+ return this ;
255
+ }
256
+
257
+ public Builder variables (Map <String , Object > variables ) {
258
+ this .variables = variables ;
259
+ return this ;
260
+ }
261
+
262
+ public Builder renderer (TemplateRenderer renderer ) {
263
+ this .renderer = renderer ;
264
+ return this ;
265
+ }
266
+
267
+ public PromptTemplate build () {
268
+ if (this .template != null && this .resource != null ) {
269
+ throw new IllegalArgumentException ("Only one of template or resource can be set" );
270
+ }
271
+ else if (this .resource != null ) {
272
+ return new PromptTemplate (this .resource , this .variables , this .renderer );
273
+ }
274
+ else {
275
+ return new PromptTemplate (this .template , this .variables , this .renderer );
276
+ }
234
277
}
278
+
235
279
}
236
280
237
281
}
0 commit comments