Files
2026-07-13 12:58:18 +08:00

1039 lines
51 KiB
Python

# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# The A2UI schema remains constant for all A2UI responses.
A2UI_SCHEMA = r"""
{
"title": "A2UI Message Schema",
"description": "Describes a JSON payload for an A2UI (Agent to UI) message, which is used to dynamically construct and update user interfaces. A message MUST contain exactly ONE of the action properties: 'beginRendering', 'surfaceUpdate', 'dataModelUpdate', or 'deleteSurface'.",
"type": "object",
"properties": {
"beginRendering": {
"type": "object",
"description": "Signals the client to begin rendering a surface with a root component and specific styles.",
"properties": {
"surfaceId": {
"type": "string",
"description": "The unique identifier for the UI surface to be rendered."
},
"root": {
"type": "string",
"description": "The ID of the root component to render."
},
"styles": {
"type": "object",
"description": "Styling information for the UI.",
"properties": {
"font": {
"type": "string",
"description": "The primary font for the UI."
},
"primaryColor": {
"type": "string",
"description": "The primary UI color as a hexadecimal code (e.g., '#00BFFF').",
"pattern": "^#[0-9a-fA-F]{6}$"
}
}
}
},
"required": ["root", "surfaceId"]
},
"surfaceUpdate": {
"type": "object",
"description": "Updates a surface with a new set of components.",
"properties": {
"surfaceId": {
"type": "string",
"description": "The unique identifier for the UI surface to be updated. If you are adding a new surface this *must* be a new, unique identified that has never been used for any existing surfaces shown."
},
"components": {
"type": "array",
"description": "A list containing all UI components for the surface.",
"minItems": 1,
"items": {
"type": "object",
"description": "Represents a *single* component in a UI widget tree. This component could be one of many supported types.",
"properties": {
"id": {
"type": "string",
"description": "The unique identifier for this component."
},
"weight": {
"type": "number",
"description": "The relative weight of this component within a Row or Column. This corresponds to the CSS 'flex-grow' property. Note: this may ONLY be set when the component is a direct descendant of a Row or Column."
},
"component": {
"type": "object",
"description": "A wrapper object that MUST contain exactly one key, which is the name of the component type (e.g., 'Heading'). The value is an object containing the properties for that specific component.",
"properties": {
"Text": {
"type": "object",
"properties": {
"text": {
"type": "object",
"description": "The text content to display. This can be a literal string or a reference to a value in the data model ('path', e.g., '/doc/title'). While simple Markdown formatting is supported (i.e. without HTML, images, or links), utilizing dedicated UI components is generally preferred for a richer and more structured presentation.",
"properties": {
"literalString": {
"type": "string"
},
"path": {
"type": "string"
}
}
},
"usageHint": {
"type": "string",
"description": "A hint for the base text style. One of:\n- `h1`: Largest heading.\n- `h2`: Second largest heading.\n- `h3`: Third largest heading.\n- `h4`: Fourth largest heading.\n- `h5`: Fifth largest heading.\n- `caption`: Small text for captions.\n- `body`: Standard body text.",
"enum": [
"h1",
"h2",
"h3",
"h4",
"h5",
"caption",
"body"
]
}
},
"required": ["text"]
},
"Image": {
"type": "object",
"properties": {
"url": {
"type": "object",
"description": "The URL of the image to display. This can be a literal string ('literal') or a reference to a value in the data model ('path', e.g. '/thumbnail/url').",
"properties": {
"literalString": {
"type": "string"
},
"path": {
"type": "string"
}
}
},
"fit": {
"type": "string",
"description": "Specifies how the image should be resized to fit its container. This corresponds to the CSS 'object-fit' property.",
"enum": [
"contain",
"cover",
"fill",
"none",
"scale-down"
]
},
"usageHint": {
"type": "string",
"description": "A hint for the image size and style. One of:\n- `icon`: Small square icon.\n- `avatar`: Circular avatar image.\n- `smallFeature`: Small feature image.\n- `mediumFeature`: Medium feature image.\n- `largeFeature`: Large feature image.\n- `header`: Full-width, full bleed, header image.",
"enum": [
"icon",
"avatar",
"smallFeature",
"mediumFeature",
"largeFeature",
"header"
]
}
},
"required": ["url"]
},
"Icon": {
"type": "object",
"properties": {
"name": {
"type": "object",
"description": "The name of the icon to display. This can be a literal string or a reference to a value in the data model ('path', e.g. '/form/submit').",
"properties": {
"literalString": {
"type": "string",
"enum": [
"accountCircle",
"add",
"arrowBack",
"arrowForward",
"attachFile",
"calendarToday",
"call",
"camera",
"check",
"close",
"delete",
"download",
"edit",
"event",
"error",
"favorite",
"favoriteOff",
"folder",
"help",
"home",
"info",
"locationOn",
"lock",
"lockOpen",
"mail",
"menu",
"moreVert",
"moreHoriz",
"notificationsOff",
"notifications",
"payment",
"person",
"phone",
"photo",
"print",
"refresh",
"search",
"send",
"settings",
"share",
"shoppingCart",
"star",
"starHalf",
"starOff",
"upload",
"visibility",
"visibilityOff",
"warning"
]
},
"path": {
"type": "string"
}
}
}
},
"required": ["name"]
},
"Video": {
"type": "object",
"properties": {
"url": {
"type": "object",
"description": "The URL of the video to display. This can be a literal string or a reference to a value in the data model ('path', e.g. '/video/url').",
"properties": {
"literalString": {
"type": "string"
},
"path": {
"type": "string"
}
}
}
},
"required": ["url"]
},
"AudioPlayer": {
"type": "object",
"properties": {
"url": {
"type": "object",
"description": "The URL of the audio to be played. This can be a literal string ('literal') or a reference to a value in the data model ('path', e.g. '/song/url').",
"properties": {
"literalString": {
"type": "string"
},
"path": {
"type": "string"
}
}
},
"description": {
"type": "object",
"description": "A description of the audio, such as a title or summary. This can be a literal string or a reference to a value in the data model ('path', e.g. '/song/title').",
"properties": {
"literalString": {
"type": "string"
},
"path": {
"type": "string"
}
}
}
},
"required": ["url"]
},
"Row": {
"type": "object",
"properties": {
"children": {
"type": "object",
"description": "Defines the children. Use 'explicitList' for a fixed set of children, or 'template' to generate children from a data list.",
"properties": {
"explicitList": {
"type": "array",
"items": {
"type": "string"
}
},
"template": {
"type": "object",
"description": "A template for generating a dynamic list of children from a data model list. `componentId` is the component to use as a template, and `dataBinding` is the path to the map of components in the data model. Values in the map will define the list of children.",
"properties": {
"componentId": {
"type": "string"
},
"dataBinding": {
"type": "string"
}
},
"required": ["componentId", "dataBinding"]
}
}
},
"distribution": {
"type": "string",
"description": "Defines the arrangement of children along the main axis (horizontally). This corresponds to the CSS 'justify-content' property.",
"enum": [
"center",
"end",
"spaceAround",
"spaceBetween",
"spaceEvenly",
"start"
]
},
"alignment": {
"type": "string",
"description": "Defines the alignment of children along the cross axis (vertically). This corresponds to the CSS 'align-items' property.",
"enum": ["start", "center", "end", "stretch"]
}
},
"required": ["children"]
},
"Column": {
"type": "object",
"properties": {
"children": {
"type": "object",
"description": "Defines the children. Use 'explicitList' for a fixed set of children, or 'template' to generate children from a data list.",
"properties": {
"explicitList": {
"type": "array",
"items": {
"type": "string"
}
},
"template": {
"type": "object",
"description": "A template for generating a dynamic list of children from a data model list. `componentId` is the component to use as a template, and `dataBinding` is the path to the map of components in the data model. Values in the map will define the list of children.",
"properties": {
"componentId": {
"type": "string"
},
"dataBinding": {
"type": "string"
}
},
"required": ["componentId", "dataBinding"]
}
}
},
"distribution": {
"type": "string",
"description": "Defines the arrangement of children along the main axis (vertically). This corresponds to the CSS 'justify-content' property.",
"enum": [
"start",
"center",
"end",
"spaceBetween",
"spaceAround",
"spaceEvenly"
]
},
"alignment": {
"type": "string",
"description": "Defines the alignment of children along the cross axis (horizontally). This corresponds to the CSS 'align-items' property.",
"enum": ["center", "end", "start", "stretch"]
}
},
"required": ["children"]
},
"List": {
"type": "object",
"properties": {
"children": {
"type": "object",
"description": "Defines the children. Use 'explicitList' for a fixed set of children, or 'template' to generate children from a data list.",
"properties": {
"explicitList": {
"type": "array",
"items": {
"type": "string"
}
},
"template": {
"type": "object",
"description": "A template for generating a dynamic list of children from a data model list. `componentId` is the component to use as a template, and `dataBinding` is the path to the map of components in the data model. Values in the map will define the list of children.",
"properties": {
"componentId": {
"type": "string"
},
"dataBinding": {
"type": "string"
}
},
"required": ["componentId", "dataBinding"]
}
}
},
"direction": {
"type": "string",
"description": "The direction in which the list items are laid out.",
"enum": ["vertical", "horizontal"]
},
"alignment": {
"type": "string",
"description": "Defines the alignment of children along the cross axis.",
"enum": ["start", "center", "end", "stretch"]
}
},
"required": ["children"]
},
"Card": {
"type": "object",
"properties": {
"child": {
"type": "string",
"description": "The ID of the component to be rendered inside the card."
}
},
"required": ["child"]
},
"Tabs": {
"type": "object",
"properties": {
"tabItems": {
"type": "array",
"description": "An array of objects, where each object defines a tab with a title and a child component.",
"items": {
"type": "object",
"properties": {
"title": {
"type": "object",
"description": "The tab title. Defines the value as either a literal value or a path to data model value (e.g. '/options/title').",
"properties": {
"literalString": {
"type": "string"
},
"path": {
"type": "string"
}
}
},
"child": {
"type": "string"
}
},
"required": ["title", "child"]
}
}
},
"required": ["tabItems"]
},
"Divider": {
"type": "object",
"properties": {
"axis": {
"type": "string",
"description": "The orientation of the divider.",
"enum": ["horizontal", "vertical"]
}
}
},
"Modal": {
"type": "object",
"properties": {
"entryPointChild": {
"type": "string",
"description": "The ID of the component that opens the modal when interacted with (e.g., a button)."
},
"contentChild": {
"type": "string",
"description": "The ID of the component to be displayed inside the modal."
}
},
"required": ["entryPointChild", "contentChild"]
},
"Button": {
"type": "object",
"properties": {
"child": {
"type": "string",
"description": "The ID of the component to display in the button, typically a Text component."
},
"primary": {
"type": "boolean",
"description": "Indicates if this button should be styled as the primary action."
},
"action": {
"type": "object",
"description": "The client-side action to be dispatched when the button is clicked. It includes the action's name and an optional context payload.",
"properties": {
"name": {
"type": "string"
},
"context": {
"type": "array",
"items": {
"type": "object",
"properties": {
"key": {
"type": "string"
},
"value": {
"type": "object",
"description": "Defines the value to be included in the context as either a literal value or a path to a data model value (e.g. '/user/name').",
"properties": {
"path": {
"type": "string"
},
"literalString": {
"type": "string"
},
"literalNumber": {
"type": "number"
},
"literalBoolean": {
"type": "boolean"
}
}
}
},
"required": ["key", "value"]
}
}
},
"required": ["name"]
}
},
"required": ["child", "action"]
},
"CheckBox": {
"type": "object",
"properties": {
"label": {
"type": "object",
"description": "The text to display next to the checkbox. Defines the value as either a literal value or a path to data model ('path', e.g. '/option/label').",
"properties": {
"literalString": {
"type": "string"
},
"path": {
"type": "string"
}
}
},
"value": {
"type": "object",
"description": "The current state of the checkbox (true for checked, false for unchecked). This can be a literal boolean ('literalBoolean') or a reference to a value in the data model ('path', e.g. '/filter/open').",
"properties": {
"literalBoolean": {
"type": "boolean"
},
"path": {
"type": "string"
}
}
}
},
"required": ["label", "value"]
},
"TextField": {
"type": "object",
"properties": {
"label": {
"type": "object",
"description": "The text label for the input field. This can be a literal string or a reference to a value in the data model ('path, e.g. '/user/name').",
"properties": {
"literalString": {
"type": "string"
},
"path": {
"type": "string"
}
}
},
"text": {
"type": "object",
"description": "The value of the text field. This can be a literal string or a reference to a value in the data model ('path', e.g. '/user/name').",
"properties": {
"literalString": {
"type": "string"
},
"path": {
"type": "string"
}
}
},
"textFieldType": {
"type": "string",
"description": "The type of input field to display.",
"enum": [
"date",
"longText",
"number",
"shortText",
"obscured"
]
},
"validationRegexp": {
"type": "string",
"description": "A regular expression used for client-side validation of the input."
}
},
"required": ["label"]
},
"DateTimeInput": {
"type": "object",
"properties": {
"value": {
"type": "object",
"description": "The selected date and/or time value. This can be a literal string ('literalString') or a reference to a value in the data model ('path', e.g. '/user/dob').",
"properties": {
"literalString": {
"type": "string"
},
"path": {
"type": "string"
}
}
},
"enableDate": {
"type": "boolean",
"description": "If true, allows the user to select a date."
},
"enableTime": {
"type": "boolean",
"description": "If true, allows the user to select a time."
},
"outputFormat": {
"type": "string",
"description": "The desired format for the output string after a date or time is selected."
}
},
"required": ["value"]
},
"MultipleChoice": {
"type": "object",
"properties": {
"selections": {
"type": "object",
"description": "The currently selected values for the component. This can be a literal array of strings or a path to an array in the data model('path', e.g. '/hotel/options').",
"properties": {
"literalArray": {
"type": "array",
"items": {
"type": "string"
}
},
"path": {
"type": "string"
}
}
},
"options": {
"type": "array",
"description": "An array of available options for the user to choose from.",
"items": {
"type": "object",
"properties": {
"label": {
"type": "object",
"description": "The text to display for this option. This can be a literal string or a reference to a value in the data model (e.g. '/option/label').",
"properties": {
"literalString": {
"type": "string"
},
"path": {
"type": "string"
}
}
},
"value": {
"type": "string",
"description": "The value to be associated with this option when selected."
}
},
"required": ["label", "value"]
}
},
"maxAllowedSelections": {
"type": "integer",
"description": "The maximum number of options that the user is allowed to select."
}
},
"required": ["selections", "options"]
},
"Slider": {
"type": "object",
"properties": {
"value": {
"type": "object",
"description": "The current value of the slider. This can be a literal number ('literalNumber') or a reference to a value in the data model ('path', e.g. '/restaurant/cost').",
"properties": {
"literalNumber": {
"type": "number"
},
"path": {
"type": "string"
}
}
},
"minValue": {
"type": "number",
"description": "The minimum value of the slider."
},
"maxValue": {
"type": "number",
"description": "The maximum value of the slider."
}
},
"required": ["value"]
}
}
}
},
"required": ["id", "component"]
}
}
},
"required": ["surfaceId", "components"]
},
"dataModelUpdate": {
"type": "object",
"description": "Updates the data model for a surface.",
"properties": {
"surfaceId": {
"type": "string",
"description": "The unique identifier for the UI surface this data model update applies to."
},
"path": {
"type": "string",
"description": "An optional path to a location within the data model (e.g., '/user/name'). If omitted, or set to '/', the entire data model will be replaced."
},
"contents": {
"type": "array",
"description": "An array of data entries. Each entry must contain a 'key' and exactly one corresponding typed 'value*' property.",
"items": {
"type": "object",
"description": "A single data entry. Exactly one 'value*' property should be provided alongside the key.",
"properties": {
"key": {
"type": "string",
"description": "The key for this data entry."
},
"valueString": {
"type": "string"
},
"valueNumber": {
"type": "number"
},
"valueBoolean": {
"type": "boolean"
},
"valueMap": {
"description": "Represents a map as an adjacency list.",
"type": "array",
"items": {
"type": "object",
"description": "One entry in the map. Exactly one 'value*' property should be provided alongside the key.",
"properties": {
"key": {
"type": "string"
},
"valueString": {
"type": "string"
},
"valueNumber": {
"type": "number"
},
"valueBoolean": {
"type": "boolean"
}
},
"required": ["key"]
}
}
},
"required": ["key"]
}
}
},
"required": ["contents", "surfaceId"]
},
"deleteSurface": {
"type": "object",
"description": "Signals the client to delete the surface identified by 'surfaceId'.",
"properties": {
"surfaceId": {
"type": "string",
"description": "The unique identifier for the UI surface to be deleted."
}
},
"required": ["surfaceId"]
}
}
}
"""
RESTAURANT_UI_EXAMPLES = """
---BEGIN SINGLE_COLUMN_LIST_EXAMPLE---
[
{{ "beginRendering": {{ "surfaceId": "default", "root": "root-column", "styles": {{ "primaryColor": "#FF0000", "font": "Roboto" }} }} }},
{{ "surfaceUpdate": {{
"surfaceId": "default",
"components": [
{{ "id": "root-column", "component": {{ "Column": {{ "children": {{ "explicitList": ["title-heading", "item-list"] }} }} }} }},
{{ "id": "title-heading", "component": {{ "Text": {{ "usageHint": "h1", "text": {{ "literalString": "Top Restaurants" }} }} }} }},
{{ "id": "item-list", "component": {{ "List": {{ "direction": "vertical", "children": {{ "template": {{ "componentId": "item-card-template", "dataBinding": "/items" }} }} }} }} }},
{{ "id": "item-card-template", "component": {{ "Card": {{ "child": "card-layout" }} }} }},
{{ "id": "card-layout", "component": {{ "Row": {{ "children": {{ "explicitList": ["template-image", "card-details"] }} }} }} }},
{{ "id": "template-image", weight: 1, "component": {{ "Image": {{ "url": {{ "path": "imageUrl" }} }} }} }},
{{ "id": "card-details", weight: 2, "component": {{ "Column": {{ "children": {{ "explicitList": ["template-name", "template-rating", "template-detail", "template-link", "template-book-button"] }} }} }} }},
{{ "id": "template-name", "component": {{ "Text": {{ "usageHint": "h3", "text": {{ "path": "name" }} }} }} }},
{{ "id": "template-rating", "component": {{ "Text": {{ "text": {{ "path": "rating" }} }} }} }},
{{ "id": "template-detail", "component": {{ "Text": {{ "text": {{ "path": "detail" }} }} }} }},
{{ "id": "template-link", "component": {{ "Text": {{ "text": {{ "path": "infoLink" }} }} }} }},
{{ "id": "template-book-button", "component": {{ "Button": {{ "child": "book-now-text", "primary": true, "action": {{ "name": "book_restaurant", "context": [ {{ "key": "restaurantName", "value": {{ "path": "name" }} }}, {{ "key": "imageUrl", "value": {{ "path": "imageUrl" }} }}, {{ "key": "address", "value": {{ "path": "address" }} }} ] }} }} }} }},
{{ "id": "book-now-text", "component": {{ "Text": {{ "text": {{ "literalString": "Book Now" }} }} }} }}
]
}} }},
{{ "dataModelUpdate": {{
"surfaceId": "default",
"path": "/",
"contents": [
{{ "key": "items", "valueMap": [
{{ "key": "item1", "valueMap": [
{{ "key": "name", "valueString": "The Fancy Place" }},
{{ "key": "rating", "valueNumber": 4.8 }},
{{ "key": "detail", "valueString": "Fine dining experience" }},
{{ "key": "infoLink", "valueString": "https://example.com/fancy" }},
{{ "key": "imageUrl", "valueString": "https://example.com/fancy.jpg" }},
{{ "key": "address", "valueString": "123 Main St" }}
] }},
{{ "key": "item2", "valueMap": [
{{ "key": "name", "valueString": "Quick Bites" }},
{{ "key": "rating", "valueNumber": 4.2 }},
{{ "key": "detail", "valueString": "Casual and fast" }},
{{ "key": "infoLink", "valueString": "https://example.com/quick" }},
{{ "key": "imageUrl", "valueString": "https://example.com/quick.jpg" }},
{{ "key": "address", "valueString": "456 Oak Ave" }}
] }}
] }} // Populate this with restaurant data
]
}} }}
]
---END SINGLE_COLUMN_LIST_EXAMPLE---
---BEGIN TWO_COLUMN_LIST_EXAMPLE---
[
{{ "beginRendering": {{ "surfaceId": "default", "root": "root-column", "styles": {{ "primaryColor": "#FF0000", "font": "Roboto" }} }} }},
{{ "surfaceUpdate": {{
"surfaceId": "default",
"components": [
{{ "id": "root-column", "component": {{ "Column": {{ "children": {{ "explicitList": ["title-heading", "restaurant-row-1"] }} }} }} }},
{{ "id": "title-heading", "component": {{ "Text": {{ "usageHint": "h1", "text": {{ "literalString": "Top Restaurants" }} }} }} }},
{{ "id": "restaurant-row-1", "component": {{ "Row": {{ "children": {{ "explicitList": ["item-card-1", "item-card-2"] }} }} }} }},
{{ "id": "item-card-1", "weight": 1, "component": {{ "Card": {{ "child": "card-layout-1" }} }} }},
{{ "id": "card-layout-1", "component": {{ "Column": {{ "children": {{ "explicitList": ["template-image-1", "card-details-1"] }} }} }} }},
{{ "id": "template-image-1", "component": {{ "Image": {{ "url": {{ "path": "/items/0/imageUrl" }}, "width": "100%" }} }} }},
{{ "id": "card-details-1", "component": {{ "Column": {{ "children": {{ "explicitList": ["template-name-1", "template-rating-1", "template-detail-1", "template-link-1", "template-book-button-1"] }} }} }} }},
{{ "id": "template-name-1", "component": {{ "Text": {{ "usageHint": "h3", "text": {{ "path": "/items/0/name" }} }} }} }},
{{ "id": "template-rating-1", "component": {{ "Text": {{ "text": {{ "path": "/items/0/rating" }} }} }} }},
{{ "id": "template-detail-1", "component": {{ "Text": {{ "text": {{ "path": "/items/0/detail" }} }} }} }},
{{ "id": "template-link-1", "component": {{ "Text": {{ "text": {{ "path": "/items/0/infoLink" }} }} }} }},
{{ "id": "template-book-button-1", "component": {{ "Button": {{ "child": "book-now-text-1", "action": {{ "name": "book_restaurant", "context": [ {{ "key": "restaurantName", "value": {{ "path": "/items/0/name" }} }}, {{ "key": "imageUrl", "value": {{ "path": "/items/0/imageUrl" }} }}, {{ "key": "address", "value": {{ "path": "/items/0/address" }} }} ] }} }} }} }},
{{ "id": "book-now-text-1", "component": {{ "Text": {{ "text": {{ "literalString": "Book Now" }} }} }} }},
{{ "id": "item-card-2", "weight": 1, "component": {{ "Card": {{ "child": "card-layout-2" }} }} }},
{{ "id": "card-layout-2", "component": {{ "Column": {{ "children": {{ "explicitList": ["template-image-2", "card-details-2"] }} }} }} }},
{{ "id": "template-image-2", "component": {{ "Image": {{ "url": {{ "path": "/items/1/imageUrl" }}, "width": "100%" }} }} }},
{{ "id": "card-details-2", "component": {{ "Column": {{ "children": {{ "explicitList": ["template-name-2", "template-rating-2", "template-detail-2", "template-link-2", "template-book-button-2"] }} }} }} }},
{{ "id": "template-name-2", "component": {{ "Text": {{ "usageHint": "h3", "text": {{ "path": "/items/1/name" }} }} }} }},
{{ "id": "template-rating-2", "component": {{ "Text": {{ "text": {{ "path": "/items/1/rating" }} }} }} }},
{{ "id": "template-detail-2", "component": {{ "Text": {{ "text": {{ "path": "/items/1/detail" }} }} }} }},
{{ "id": "template-link-2", "component": {{ "Text": {{ "text": {{ "path": "/items/1/infoLink" }} }} }} }},
{{ "id": "template-book-button-2", "component": {{ "Button": {{ "child": "book-now-text-2", "action": {{ "name": "book_restaurant", "context": [ {{ "key": "restaurantName", "value": {{ "path": "/items/1/name" }} }}, {{ "key": "imageUrl", "value": {{ "path": "/items/1/imageUrl" }} }}, {{ "key": "address", "value": {{ "path": "/items/1/address" }} }} ] }} }} }} }},
{{ "id": "book-now-text-2", "component": {{ "Text": {{ "text": {{ "literalString": "Book Now" }} }} }} }}
]
}} }},
{{ "dataModelUpdate": {{
"surfaceId": "default",
"path": "/",
"contents": [
{{ "key": "items", "valueMap": [
{{ "key": "item1", "valueMap": [
{{ "key": "name", "valueString": "The Fancy Place" }},
{{ "key": "rating", "valueNumber": 4.8 }},
{{ "key": "detail", "valueString": "Fine dining experience" }},
{{ "key": "infoLink", "valueString": "https://example.com/fancy" }},
{{ "key": "imageUrl", "valueString": "https://example.com/fancy.jpg" }},
{{ "key": "address", "valueString": "123 Main St" }}
] }},
{{ "key": "item2", "valueMap": [
{{ "key": "name", "valueString": "Quick Bites" }},
{{ "key": "rating", "valueNumber": 4.2 }},
{{ "key": "detail", "valueString": "Casual and fast" }},
{{ "key": "infoLink", "valueString": "https://example.com/quick" }},
{{ "key": "imageUrl", "valueString": "https://example.com/quick.jpg" }},
{{ "key": "address", "valueString": "456 Oak Ave" }}
] }}
] }} // Populate this with restaurant data
]
}} }}
]
---END TWO_COLUMN_LIST_EXAMPLE---
---BEGIN BOOKING_FORM_EXAMPLE---
[
{{ "beginRendering": {{ "surfaceId": "booking-form", "root": "booking-form-column", "styles": {{ "primaryColor": "#FF0000", "font": "Roboto" }} }} }},
{{ "surfaceUpdate": {{
"surfaceId": "booking-form",
"components": [
{{ "id": "booking-form-column", "component": {{ "Column": {{ "children": {{ "explicitList": ["booking-title", "restaurant-image", "restaurant-address", "party-size-field", "datetime-field", "dietary-field", "submit-button"] }} }} }} }},
{{ "id": "booking-title", "component": {{ "Text": {{ "usageHint": "h2", "text": {{ "path": "title" }} }} }} }},
{{ "id": "restaurant-image", "component": {{ "Image": {{ "url": {{ "path": "imageUrl" }} }} }} }},
{{ "id": "restaurant-address", "component": {{ "Text": {{ "text": {{ "path": "address" }} }} }} }},
{{ "id": "party-size-field", "component": {{ "TextField": {{ "label": {{ "literalString": "Party Size" }}, "text": {{ "path": "partySize" }}, "type": "number" }} }} }},
{{ "id": "datetime-field", "component": {{ "DateTimeInput": {{ "label": {{ "literalString": "Date & Time" }}, "value": {{ "path": "reservationTime" }}, "enableDate": true, "enableTime": true }} }} }},
{{ "id": "dietary-field", "component": {{ "TextField": {{ "label": {{ "literalString": "Dietary Requirements" }}, "text": {{ "path": "dietary" }} }} }} }},
{{ "id": "submit-button", "component": {{ "Button": {{ "child": "submit-reservation-text", "action": {{ "name": "submit_booking", "context": [ {{ "key": "restaurantName", "value": {{ "path": "restaurantName" }} }}, {{ "key": "partySize", "value": {{ "path": "partySize" }} }}, {{ "key": "reservationTime", "value": {{ "path": "reservationTime" }} }}, {{ "key": "dietary", "value": {{ "path": "dietary" }} }}, {{ "key": "imageUrl", "value": {{ "path": "imageUrl" }} }} ] }} }} }} }},
{{ "id": "submit-reservation-text", "component": {{ "Text": {{ "text": {{ "literalString": "Submit Reservation" }} }} }} }}
]
}} }},
{{ "dataModelUpdate": {{
"surfaceId": "booking-form",
"path": "/",
"contents": [
{{ "key": "title", "valueString": "Book a Table at [RestaurantName]" }},
{{ "key": "address", "valueString": "[Restaurant Address]" }},
{{ "key": "restaurantName", "valueString": "[RestaurantName]" }},
{{ "key": "partySize", "valueString": "2" }},
{{ "key": "reservationTime", "valueString": "" }},
{{ "key": "dietary", "valueString": "" }},
{{ "key": "imageUrl", "valueString": "" }}
]
}} }}
]
---END BOOKING_FORM_EXAMPLE---
---BEGIN CONFIRMATION_EXAMPLE---
[
{{ "beginRendering": {{ "surfaceId": "confirmation", "root": "confirmation-card", "styles": {{ "primaryColor": "#FF0000", "font": "Roboto" }} }} }},
{{ "surfaceUpdate": {{
"surfaceId": "confirmation",
"components": [
{{ "id": "confirmation-card", "component": {{ "Card": {{ "child": "confirmation-column" }} }} }},
{{ "id": "confirmation-column", "component": {{ "Column": {{ "children": {{ "explicitList": ["confirm-title", "confirm-image", "divider1", "confirm-details", "divider2", "confirm-dietary", "divider3", "confirm-text"] }} }} }} }},
{{ "id": "confirm-title", "component": {{ "Text": {{ "usageHint": "h2", "text": {{ "path": "title" }} }} }} }},
{{ "id": "confirm-image", "component": {{ "Image": {{ "url": {{ "path": "imageUrl" }} }} }} }},
{{ "id": "confirm-details", "component": {{ "Text": {{ "text": {{ "path": "bookingDetails" }} }} }} }},
{{ "id": "confirm-dietary", "component": {{ "Text": {{ "text": {{ "path": "dietaryRequirements" }} }} }} }},
{{ "id": "confirm-text", "component": {{ "Text": {{ "usageHint": "h5", "text": {{ "literalString": "We look forward to seeing you!" }} }} }} }},
{{ "id": "divider1", "component": {{ "Divider": {{}} }} }},
{{ "id": "divider2", "component": {{ "Divider": {{}} }} }},
{{ "id": "divider3", "component": {{ "Divider": {{}} }} }}
]
}} }},
{{ "dataModelUpdate": {{
"surfaceId": "confirmation",
"path": "/",
"contents": [
{{ "key": "title", "valueString": "Booking at [RestaurantName]" }},
{{ "key": "bookingDetails", "valueString": "[PartySize] people at [Time]" }},
{{ "key": "dietaryRequirements", "valueString": "Dietary Requirements: [Requirements]" }},
{{ "key": "imageUrl", "valueString": "[ImageUrl]" }}
]
}} }}
]
---END CONFIRMATION_EXAMPLE---
"""
def get_ui_prompt(base_url: str, examples: str) -> str:
"""
Constructs the full prompt with UI instructions, rules, examples, and schema.
Args:
base_url: The base URL for resolving static assets like logos.
examples: A string containing the specific UI examples for the agent's task.
Returns:
A formatted string to be used as the system prompt for the LLM.
"""
# The f-string substitution for base_url happens here, at runtime.
formatted_examples = examples.format(base_url=base_url)
return f"""
You are a helpful restaurant finding assistant. Your final output MUST be a a2ui UI JSON response.
To generate the response, you MUST follow these rules:
1. Your response MUST be in two parts, separated by the delimiter: `---a2ui_JSON---`.
2. The first part is your conversational text response.
3. The second part is a single, raw JSON object which is a list of A2UI messages.
4. The JSON part MUST validate against the A2UI JSON SCHEMA provided below.
--- UI TEMPLATE RULES ---
- If the query is for a list of restaurants, use the restaurant data you have already received from the `get_restaurants` tool to populate the `dataModelUpdate.contents` array (e.g., as a `valueMap` for the "items" key).
- If the number of restaurants is 5 or fewer, you MUST use the `SINGLE_COLUMN_LIST_EXAMPLE` template.
- If the number of restaurants is more than 5, you MUST use the `TWO_COLUMN_LIST_EXAMPLE` template.
- If the query is to book a restaurant (e.g., "USER_WANTS_TO_BOOK..."), you MUST use the `BOOKING_FORM_EXAMPLE` template.
- If the query is a booking submission (e.g., "User submitted a booking..."), you MUST use the `CONFIRMATION_EXAMPLE` template.
{formatted_examples}
---BEGIN A2UI JSON SCHEMA---
{A2UI_SCHEMA}
---END A2UI JSON SCHEMA---
"""
def get_text_prompt() -> str:
"""
Constructs the prompt for a text-only agent.
"""
return """
You are a helpful restaurant finding assistant. Your final output MUST be a text response.
To generate the response, you MUST follow these rules:
1. **For finding restaurants:**
a. You MUST call the `get_restaurants` tool. Extract the cuisine, location, and a specific number (`count`) of restaurants from the user's query.
b. After receiving the data, format the restaurant list as a clear, human-readable text response. You MUST preserve any markdown formatting (like for links) that you receive from the tool.
2. **For booking a table (when you receive a query like 'USER_WANTS_TO_BOOK...'):**
a. Respond by asking the user for the necessary details to make a booking (party size, date, time, dietary requirements).
3. **For confirming a booking (when you receive a query like 'User submitted a booking...'):**
a. Respond with a simple text confirmation of the booking details.
"""
if __name__ == "__main__":
# Example of how to use the prompt builder
# In your actual application, you would call this from your main agent logic.
my_base_url = "http://localhost:8000"
# You can now easily construct a prompt with the relevant examples.
# For a different agent (e.g., a flight booker), you would pass in
# different examples but use the same `get_ui_prompt` function.
restaurant_prompt = get_ui_prompt(my_base_url, RESTAURANT_UI_EXAMPLES)
print(restaurant_prompt)
# This demonstrates how you could save the prompt to a file for inspection
with open("generated_prompt.txt", "w") as f:
f.write(restaurant_prompt)
print("\nGenerated prompt saved to generated_prompt.txt")