Skip to content

Commit 1e221d0

Browse files
authored
Initial implementation of prompt builder. (#777)
1 parent 5bb3422 commit 1e221d0

22 files changed

Lines changed: 3239 additions & 193 deletions

examples/simple_chat/lib/chat_session.dart

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,16 @@ class ChatSession extends ChangeNotifier {
1919
_transport = AiClientTransport(aiClient: aiClient);
2020

2121
// 2. Initialize Catalog & Controller
22-
final Catalog catalog = BasicCatalogItems.asCatalog();
22+
final Catalog catalog = BasicCatalogItems.asCatalog(
23+
systemPromptFragments: [
24+
'''
25+
When you need additional information from the user, try to use the component '${BasicCatalogItems.choicePicker.name}' to ask for it.
26+
''',
27+
'''
28+
If there is no way to itemize all the options, either use the component '${BasicCatalogItems.textField.name}' or add option 'Other' to the '${BasicCatalogItems.choicePicker.name}'.
29+
''',
30+
],
31+
);
2332
_surfaceController = SurfaceController(catalogs: [catalog]);
2433

2534
// 3. Initialize Conversation
@@ -68,11 +77,18 @@ class ChatSession extends ChangeNotifier {
6877

6978
final promptBuilder = PromptBuilder.chat(
7079
catalog: catalog,
71-
instructions:
72-
'You are a helpful assistant who chats with a user. '
73-
'Your responses should contain acknowledgment of the user message.',
80+
systemPromptFragments: [
81+
'You are a helpful assistant who chats with a user.',
82+
PromptFragments.acknowledgeUser(),
83+
PromptFragments.requireAtLeastOneSubmitElement(
84+
prefix: PromptBuilder.defaultImportancePrefix,
85+
),
86+
PromptFragments.uiGenerationRestriction(
87+
prefix: PromptBuilder.defaultImportancePrefix,
88+
),
89+
],
7490
);
75-
_transport.addSystemMessage(promptBuilder.systemPrompt);
91+
_transport.addSystemMessage(promptBuilder.systemPromptJoined());
7692
}
7793

7894
void _addSurfaceMessage(String surfaceId) {

examples/travel_app/lib/src/ai_client/google_generative_ai_client.dart

Lines changed: 13 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class GoogleGenerativeAiClient implements AiClient {
5353
/// [apiKey] is the API key to use for authentication.
5454
GoogleGenerativeAiClient({
5555
required this.catalog,
56-
this.systemInstruction,
56+
this.systemInstruction = const [],
5757
this.outputToolName = 'provideFinalOutput',
5858
this.serviceFactory = defaultGenerativeServiceFactory,
5959
this.additionalTools = const [],
@@ -65,7 +65,7 @@ class GoogleGenerativeAiClient implements AiClient {
6565
final Catalog catalog;
6666

6767
/// The system instruction to use for the AI model.
68-
final String? systemInstruction;
68+
final List<String> systemInstruction;
6969

7070
/// The name of an internal pseudo-tool used to retrieve the final structured
7171
/// output from the AI.
@@ -472,35 +472,19 @@ class GoogleGenerativeAiClient implements AiClient {
472472
var toolUsageCycle = 0;
473473
const maxToolUsageCycles = 40; // Safety break for tool loops
474474

475-
// Build system instruction if provided
476-
final parts = <google_ai.Part>[];
477-
if (systemInstruction != null) {
478-
parts.add(google_ai.Part(text: systemInstruction));
479-
}
480-
parts.add(
481-
google_ai.Part(
482-
text:
483-
'Current Date: '
484-
'${DateTime.now().toIso8601String().split('T').first}\n'
485-
'You do not have the ability to execute code. If you need to '
486-
'perform calculations, do them yourself.',
487-
),
475+
final promptBuilder = PromptBuilder.custom(
476+
catalog: catalog,
477+
systemPromptFragments: systemInstruction,
478+
allowedOperations: SurfaceOperations.createAndUpdate(dataModel: true),
479+
clientDataModel: clientDataModel,
488480
);
489-
parts.add(google_ai.Part(text: BasicCatalogEmbed.basicCatalogRules));
490-
final String catalogJson = A2uiMessage.a2uiMessageSchema(
491-
catalog,
492-
).toJson(indent: ' ');
493-
if (clientDataModel != null) {
494-
final String dataString = const JsonEncoder.withIndent(
495-
' ',
496-
).convert(clientDataModel);
497-
parts.add(google_ai.Part(text: 'Client Data Model:\n$dataString'));
498-
}
499-
parts.add(google_ai.Part(text: 'A2UI Message Schema:\n$catalogJson'));
500481

501-
final systemInstructionContent = parts.isNotEmpty
502-
? [google_ai.Content(role: 'user', parts: parts)]
503-
: <google_ai.Content>[];
482+
final systemInstructionContent = [
483+
google_ai.Content(
484+
role: 'user',
485+
parts: [google_ai.Part(text: promptBuilder.systemPromptJoined())],
486+
),
487+
];
504488

505489
while (toolUsageCycle < maxToolUsageCycles) {
506490
if (cancellationSignal?.isCancelled ?? false) {

examples/travel_app/lib/src/travel_planner_page.dart

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -301,10 +301,10 @@ class _ChatInput extends StatelessWidget {
301301

302302
String? _imagesJson;
303303

304-
final prompt =
305-
'''
306-
Today is ${DateTime.timestamp()}
307-
304+
// TODO(polina-c): construct examples automatically after improving catalog API.
305+
final prompt = <String>[
306+
PromptFragments.currentDate(),
307+
'''
308308
# Instructions
309309
310310
You are a helpful travel agent assistant that communicates by creating and
@@ -403,25 +403,9 @@ user helpful information in InformationCard and TravelCarousel. Always add new
403403
surfaces when doing this and do not update or delete existing ones. That way,
404404
the user can return to the main booking flow once they have done some research.
405405
406-
## Controlling the UI
407-
408-
You can control the UI by outputting valid A2UI JSON messages wrapped in markdown code blocks.
409-
Supported messages are: `createSurface` and `updateComponents`.
410-
411-
To show a new UI:
412-
1. Output a `createSurface` message to define the surface ID and catalog.
413-
2. Output an `updateComponents` message to populate the surface with components.
414-
415-
To update an existing UI (e.g. adding items to an itinerary):
416-
1. Output an `updateComponents` message with the existing `surfaceId` and the new component definitions.
417-
418-
Properties:
419-
- `createSurface`: requires `surfaceId`, `catalogId` (use the catalog ID provided in system instructions), and `sendDataModel: true`.
420-
- `updateComponents`: requires `surfaceId` and a list of `components`. One component MUST have `id: "root"`.
406+
## Updating UI
421407
422-
IMPORTANT:
423-
- Do not use tools or function calls for UI generation. Use JSON text blocks.
424-
- Ensure all JSON is valid and fenced with ```json ... ```.
408+
Update surfaces to modify existing UI, for example to add items to an itinerary.
425409
426410
## Images
427411
@@ -515,4 +499,8 @@ Here is an example of creating a trip planner UI.
515499
```
516500
517501
When updating or showing UIs, **ALWAYS** use the JSON messages as described above. Prefer to collect and show information by creating a UI for it.
518-
''';
502+
''',
503+
PromptFragments.uiGenerationRestriction(
504+
prefix: PromptBuilder.defaultImportancePrefix,
505+
),
506+
];

packages/genui/lib/genui.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ export 'src/interfaces/transport.dart';
2323
export 'src/model/a2ui_client_capabilities.dart';
2424
export 'src/model/a2ui_message.dart';
2525
export 'src/model/a2ui_schemas.dart';
26-
export 'src/model/basic_catalog_embed.dart';
2726
export 'src/model/catalog.dart';
2827
export 'src/model/catalog_item.dart';
2928
export 'src/model/chat_message.dart';

packages/genui/lib/src/catalog/basic_catalog.dart

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,10 @@ abstract final class BasicCatalogItems {
103103
/// This typically includes controls like play/pause, seek, and volume.
104104
static final CatalogItem video = video_item.video;
105105

106+
static final String basicCatalogRules = _basicCatalogRules;
107+
106108
/// Creates a catalog containing all core catalog items.
107-
static Catalog asCatalog() {
109+
static Catalog asCatalog({List<String> systemPromptFragments = const []}) {
108110
return Catalog(
109111
[
110112
audioPlayer,
@@ -128,6 +130,58 @@ abstract final class BasicCatalogItems {
128130
],
129131
functions: BasicFunctions.all,
130132
catalogId: basicCatalogId,
133+
systemPromptFragments: [basicCatalogRules, ...systemPromptFragments],
131134
);
132135
}
133136
}
137+
138+
/// The text content of basic_catalog_rules.txt.
139+
const String _basicCatalogRules = r'''
140+
**REQUIRED PROPERTIES:** You MUST include ALL required properties for every component, even if they are inside a template or will be bound to data.
141+
- For 'Text', you MUST provide 'text'. If dynamic, use { "path": "..." }.
142+
- For 'Image', you MUST provide 'url'. If dynamic, use { "path": "..." }.
143+
- For 'Button', you MUST provide 'action'.
144+
- For 'TextField', 'CheckBox', etc., you MUST provide 'label'.
145+
146+
**EXAMPLES:**
147+
148+
1. Create a surface:
149+
```json
150+
{
151+
"version": "v0.9",
152+
"createSurface": {
153+
"surfaceId": "main",
154+
"catalogId": "https://a2ui.org/specification/v0_9/standard_catalog.json",
155+
"sendDataModel": true
156+
}
157+
}
158+
```
159+
160+
2. Update components:
161+
```json
162+
{
163+
"version": "v0.9",
164+
"updateComponents": {
165+
"surfaceId": "main",
166+
"components": [
167+
{
168+
// The root component MUST have id "root"
169+
"id": "root",
170+
"component": "Column",
171+
"justify": "start",
172+
"children": [
173+
"headerText",
174+
"content"
175+
]
176+
}
177+
]
178+
}
179+
}
180+
```
181+
182+
**IMPORTANT:**
183+
- One of the components sent in one of the `updateComponents` MUST have id "root", or nothing will be displayed.
184+
- Do NOT nest `components` inside `createSurface`. Use `updateComponents` to add components to a surface.
185+
- `createSurface` ONLY sets up the surface (ID and catalog). It does NOT take content.
186+
- To show a UI, you typically send a `createSurface` message (if the surface doesn't exist), followed by an `updateComponents` message.
187+
''';

0 commit comments

Comments
 (0)