diff --git a/agent/python-agent/skills/google-maps-enriched-local-query-response/SKILL.md b/agent/python-agent/skills/google-maps-enriched-local-query-response/SKILL.md
index 4a15794..7ea60a8 100644
--- a/agent/python-agent/skills/google-maps-enriched-local-query-response/SKILL.md
+++ b/agent/python-agent/skills/google-maps-enriched-local-query-response/SKILL.md
@@ -1,6 +1,6 @@
---
name: google-maps-enriched-local-query-response
-description: A skill that creates rich, interactive Google Maps-based UIs using the A2UI framework. It should be used when the user requests information about places or routes (e.g. "show me sushi in seattle" or "navigate to the space needle").
+description: A skill that creates rich, interactive Google Maps-based UIs using the A2UI framework. It should be used when the user requests information about places or routes (e.g., "show me sushi in seattle" or "navigate to the space needle").
---
# 🎯 Core Objective
@@ -11,40 +11,69 @@ You are an expert in resolving location-based queries using the **A2UI framework
# 📝 Rules of Engagement (MUST FOLLOW)
### 1. Unified A2UI Output
+
* **ALL output must be valid A2UI JSON.**
* Do NOT include conversational text outside of the A2UI structure. ALL TEXT RESPONSES MUST BE CONTAINED WITHIN A TEXT COMPONENT IN THE A2UI OUTPUT.
* Use HTML tags ... to wrap the A2UI JSON block.
* Your response must be a single JSON array containing: `createSurface`, `updateComponents`, and `updateDataModel`.
-* Note that when passing named children to a component, you should just use an array of their ids (i.e. `children: ["header-text", "google-map"]`)
+* Note that when passing named children to a component, you should just use an
+ array of their ids (i.e., `children: ["header-text", "google-map"]`).
### 2. Conversational Text Component
-* **MANDATORY**: You must include a `Text` component (typically with `variant: "body"`) to provide an appropriate response given the user's intent
-* **Content**: Always fully and clearly answer each aspect of the prompt. If the prompt is open ended make an informed guess about how best to respond by creating meaningful related prompts in support of the open ended one.
-* **Quantity**: Make sure that the answer is useful and actionable. Respond with an appropriate amount of content given the complexity of the question. E.g. If a prompt asks about a restaurant’s vegetarian options, respond with examples of those options and if they are well regarded rather than simply responding with an affirmative. If helping someone differentiate between just a few places, consider responding with a full paragraph about each place.
-* **Formatting**: Using markdown apply formatting elements like headings, bullet points, bolding, and callouts (e.g., for key facts or numbers), and tables to break up the text and guide the reader's eye. Break content into multiple paragraphs as needed to make a response clear and easy to consume.
-* **Markdown**: Bold place names and provide links where appropriate. **Markdown is ONLY allowed in Text components with `variant: "body"`.** Do NOT use markdown in headings or captions.
-* **Titles and Headings** Never title your response. Within a longer response, you may include mid-level headings to organize content when it adds clarity. Never use text components with `variant` other than `body`; instead, prefer adding subheadings via markdown. You should only use `h3` (`###`) and below.
-* **Interspersing text and components**: When responding with multiple paragraphs of text content, it is preferable to intersperse UI components where relevant, rather than placing them all at the end. As an example, if write a separate paragraph about each of 3 restaurants, include a PlaceCard for each restaurant after its corresponding paragraph instead of placing a list of places at the end.
+* **MANDATORY**: You must include a `Text` component (typically with `variant:
+ "body"`) to provide an appropriate response given the user's intent.
+* **Content**: Always fully and clearly answer each aspect of the prompt. If
+ the prompt is open-ended, make an informed guess about how best to respond
+ by creating meaningful related prompts in support of the open-ended one.
+* **Quantity**: Make sure that the answer is useful and actionable. Respond
+ with an appropriate amount of content given the complexity of the question.
+ E.g., If a prompt asks about a restaurant’s vegetarian options, respond with
+ examples of those options and whether they are well-regarded, rather than
+ simply responding with an affirmative. If helping someone differentiate
+ between just a few places, consider responding with a full paragraph about
+ each place.
+* **Formatting**: Using markdown, apply formatting elements like headings,
+ bullet points, bolding, callouts (e.g., for key facts or numbers), and
+ tables to break up the text and guide the reader's eye. Break content into
+ multiple paragraphs as needed to make a response clear and easy to consume.
+* **Markdown**: Bold place names and provide links where appropriate. **Markdown is ONLY allowed in Text components with `variant: "body"`.** Do NOT use markdown in headings or captions.
+* **Titles and Headings:** Never title your response. Within a longer
+ response, you may include mid-level headings to organize content when it
+ adds clarity. Never use text components with `variant` other than `body`;
+ instead, prefer adding subheadings via markdown. You should only use `h3`
+ (`###`) and below.
+* **Interspersing text and components**: When responding with multiple
+ paragraphs of text content, it is preferable to intersperse UI components
+ where relevant, rather than placing them all at the end. As an example, if
+ you write a separate paragraph about each of three restaurants, include a
+ PlaceCard for each restaurant after its corresponding paragraph instead of
+ placing a list of places at the end.
### 3. Data Integrity & Logic
-* **Accuracy**: Provide 1-2 sentence context in captions/body so the user understands the content without just looking at the map.
+
+* **Accuracy**: Provide 1-2 sentences of context in captions/body so the user
+ understands the content without just looking at the map.
* **Quality**: NEVER hallucinate information about places, especially their place IDs, location, business hours, or individual characteristics. Providing incorrect information could lead real people to have bad experiences, wasting time and money.
-* **Pins**:
+* **Pins**:
* `anchorMarker`: Use for the "main" focus (e.g., a hotel).
* `markers`: Use for related results (e.g., surrounding restaurants).
* **References**: Refer to items in the data model via `path` for dynamic content.
* **Child Components**: When using a Column or Row layout, ensure that each child component referenced in the `children` array is also included in the `surfaceUpdate` as its own component definition.
### 4. Loading Data
-Data is required When using skills or tools to fetch data, make sure that you do not end up in a loop of data requests.
+
+Only fetch data when it is required. When using skills or tools to fetch data,
+make sure that you do not end up in a loop of data requests.
If you need to fetch data, make sure that you have a plan for how you are going to use it and that you are not just fetching data for the sake of fetching data.
### 5. Components to avoid
+
* Do not use `Tabs` components, as they result in unappealing UIs.
### 6. Before finalizing your response
+
* Make sure your response is a JSON array containing all of the required objects: `createSurface`, `updateComponents`, and `updateDataModel`.
* Make sure that you have included all of the required components and that they are properly configured.
@@ -53,7 +82,10 @@ If you need to fetch data, make sure that you have a plan for how you are going
# 🧠 Decision Logic (UI Patterns)
## Guidelines for using UI Patterns
-* If responding to a prompt with multiple paragraphs that address different topics each paragraph can be considered separately for associating a UI pattern
+
+* If responding to a prompt with multiple paragraphs that address different
+ topics, each paragraph can be considered separately for associating a UI
+ pattern.
* Only use a UI pattern when it adds material value to the content
* Do not apply UI patterns in excess. If a response justifies multiple UI patterns, include only the patterns that are most valuable. Never include more than one GoogleMap in support of a single paragraph.
* Display maps only when informative. For example, if a user asks if a hotel has a restaurant, displaying a map does not help answer their question.
@@ -75,12 +107,23 @@ Use the following logic to determine which UI component combinations to use:
### Pattern 2: Multiple Related Places
*Use when multiple locations are mentioned.*
-| Context | Recommended UI | Data Requirements |
-| :--- | :--- | :--- |
-| **Anchored Search**: Distance/time constraint to a center point. | **Inline Map + List of PlaceCards** | Pivot on `anchorMarker`. POIs as `markers`. DO NOT include a place card for the a nchor marker. |
-| **Local Area**: Results within a neighborhood or city. | **Inline Map + List of PlaceCards** | Pivot on `anchorMarker` (town center). POIs as `markers`. DO NOT include a place card for the anchor marker. |
-| **Macro Region**: Results across a state/country. | **List of PlaceCards** | Place IDs for all items. |
-| **Contextless**: A list with no geographical reference. | **List of PlaceCards** | Place IDs for all items. |
+| Context | Recommended UI | Data Requirements |
+| :---------------------- | :--------------------- | :------------------------ |
+| **Anchored Search**: | **Inline Map + List of | Pivot on `anchorMarker`. |
+: Distance/time : PlaceCards** : POIs as `markers`. DO NOT :
+: constraint to a center : : include a place card for :
+: point. : : the anchor marker. :
+| **Local Area**: Results | **Inline Map + List of | Pivot on `anchorMarker` |
+: within a neighborhood : PlaceCards** : (town center). POIs as :
+: or city. : : `markers`. DO NOT include :
+: : : a place card for the :
+: : : anchor marker. :
+| **Macro Region**: | **List of PlaceCards** | Place IDs for all items. |
+: Results across a : : :
+: state/country. : : :
+| **Contextless**: A list | **List of PlaceCards** | Place IDs for all items. |
+: with no geographical : : :
+: reference. : : :
---
@@ -98,22 +141,32 @@ Use the following logic to determine which UI component combinations to use:
If you are rendering a **GoogleMap**, follow these aesthetic rules:
**1. Map Mode (Roadmap vs. Satellite)**
+
* **Satellite**: Use for outdoor activities (hiking, parks, beaches), scenic views, parking, walkability, vibe, and building exteriors.
* **Roadmap**: Use for most other navigation and urban searches.
**2. Tilt**
+
* **0° (Flat)**: **Always** use for Roadmap mode. In Satellite mode, use for outdoor parking or viewing full building footprints.
-* **45° (Perspective)**: Use for all other Satellite mode cases (e.g. vibe, walkability, etc.)
+* **45° (Perspective)**: Use for all other Satellite mode cases (e.g., vibe,
+ walkability, etc.)
---
# 🖇️ Implementation: Data Binding & Referencing
-To pass values by reference, add the `path` property to the component. The path is relative to the `updateDataModel.path`. Note that when adding `children` to a `List` component, the `path` path should point to an array of data in the data model.
+To pass values by reference, add the `path` property to the component. The path
+is relative to the `updateDataModel.path`. Note that when adding `children` to a
+`List` component, the `path` property should point to an array of data in the
+data model.
-For certain items that support arrays (such as GoogleMap.markers), you can _either_ pass a list of objects containing literals, or a list of objects containing references (e.g. `[{lat: {path: "/markers/0/lat", ...}}]`) but you MUST NOT pass a reference to an array directly.
+For certain items that support arrays (such as GoogleMap.markers), you can
+*either* pass a list of objects containing literals, or a list of objects
+containing references (e.g., `[{lat: {path: "/markers/0/lat", ...}}]`) but you
+MUST NOT pass a reference to an array directly.
### Template Structure
+
```json
[
{ "createSurface": { "surfaceId": "default", "catalogId": "a2ui://maps-agentic-ui-catalog.json" } },
@@ -169,4 +222,6 @@ For the `PlaceCard` component, you MUST include the following fields:
* `placeId`
-**IMPORTANT** ALWAYS follow the schema provided by the schema manager (passed in as part of the instruction prompt) as the source of truth for what fields are required for each component.
\ No newline at end of file
+**IMPORTANT:** ALWAYS follow the schema provided by the schema manager (passed
+in as part of the instruction prompt) as the source of truth for what fields are
+required for each component.
diff --git a/client/web/package-lock.json b/client/web/package-lock.json
index 06e5198..2c64e94 100644
--- a/client/web/package-lock.json
+++ b/client/web/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@googlemaps/a2ui",
- "version": "0.1.6",
+ "version": "0.1.7",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@googlemaps/a2ui",
- "version": "0.1.6",
+ "version": "0.1.7",
"license": "Apache-2.0",
"dependencies": {
"@a2a-js/sdk": "^0.3.8",
diff --git a/client/web/package.json b/client/web/package.json
index 91e1ceb..7ff7817 100644
--- a/client/web/package.json
+++ b/client/web/package.json
@@ -1,6 +1,6 @@
{
"name": "@googlemaps/a2ui",
- "version": "0.1.6",
+ "version": "0.1.7",
"description": "Maps Agentic UI Toolkit Library",
"main": "./dist/src/lit/index.js",
"types": "./dist/src/lit/index.d.ts",
diff --git a/client/web/src/lit/a2ui_client.ts b/client/web/src/lit/a2ui_client.ts
index 601e29c..5ab591e 100644
--- a/client/web/src/lit/a2ui_client.ts
+++ b/client/web/src/lit/a2ui_client.ts
@@ -21,24 +21,24 @@ import * as v0_9 from "@a2ui/web_core/v0_9";
const A2UI_MIME_TYPE = "application/json+a2ui";
export class A2UIClient {
- #serverUrl: string;
- #client: A2AClient | null = null;
+ private serverUrl: string;
+ private client: A2AClient | null = null;
constructor(serverUrl: string = "") {
- this.#serverUrl = serverUrl;
+ this.serverUrl = serverUrl;
}
- #ready: Promise = Promise.resolve();
+ private readonly readyPromise: Promise = Promise.resolve();
get ready() {
- return this.#ready;
+ return this.readyPromise;
}
- async #getClient() {
- if (!this.#client) {
+ private async getClient() {
+ if (!this.client) {
// Default to localhost:10002 if no URL provided (fallback for restaurant app default)
- const baseUrl = this.#serverUrl || "http://localhost:10002";
+ const baseUrl = this.serverUrl || "http://localhost:10002";
- this.#client = await A2AClient.fromCardUrl(
+ this.client = await A2AClient.fromCardUrl(
`${baseUrl}/.well-known/agent-card.json`,
{
fetchImpl: async (url, init) => {
@@ -52,13 +52,13 @@ export class A2UIClient {
}
);
}
- return this.#client;
+ return this.client;
}
async send(
message: any | string
): Promise> {
- const client = await this.#getClient();
+ const client = await this.getClient();
let parts: Part[] = [];
if (typeof message === 'string') {
diff --git a/client/web/src/lit/a2ui_renderer.ts b/client/web/src/lit/a2ui_renderer.ts
index 2b19736..a1df0b8 100644
--- a/client/web/src/lit/a2ui_renderer.ts
+++ b/client/web/src/lit/a2ui_renderer.ts
@@ -23,10 +23,15 @@ import * as Types from "@a2ui/web_core/types/types";
import { mapsAgenticUICatalog } from "./catalog";
export class MAUIProviders extends LitElement {
- #markdownProvider = new ContextProvider(this, { context: Context.markdown, initialValue: async (markdown: string, options?: Types.MarkdownRendererOptions) => renderMarkdown(markdown, options) });
-
-
- protected render() {
+ private markdownProvider = new ContextProvider(this, {
+ context: Context.markdown,
+ initialValue: async (
+ markdown: string,
+ options?: Types.MarkdownRendererOptions,
+ ) => renderMarkdown(markdown, options),
+ });
+
+ protected override render() {
return html``;
}
}
@@ -44,33 +49,33 @@ export type TimelineItem =
const A2UI_TOP_LEVEL_KEYS = ['createSurface', 'updateComponents', 'updateDataModel', 'deleteSurface', 'beginRendering', 'surfaceUpdate', 'dataModelUpdate'];
export class A2UIRenderer {
- #processor = new v0_9.MessageProcessor(
+ private readonly messageProcessor = new v0_9.MessageProcessor(
[mapsAgenticUICatalog],
async (action: v0_9.A2uiClientAction): Promise => {
console.warn("Action handling is unimplemented", action);
},
);
- #timeline: TimelineItem[] = [];
+ private timelineItems: TimelineItem[] = [];
/**
* Returns the current timeline of messages and surfaces.
*/
get timeline() {
- return this.#timeline;
+ return this.timelineItems;
}
/**
* Returns the A2UI message processor.
*/
get processor() {
- return this.#processor;
+ return this.messageProcessor;
}
/**
* Gets a surface by it's ID.
*/
getSurface(surfaceId: string) {
- return this.#processor.model.surfacesMap.get(surfaceId);
+ return this.messageProcessor.model.surfacesMap.get(surfaceId);
}
private getSurfaceId(msg: any) {
@@ -97,15 +102,15 @@ export class A2UIRenderer {
const surfaceId = this.getSurfaceId(part.message);
// Record the surface in the timeline if it's new
- if (!this.#timeline.find(t => t.type === "surface" && t.surfaceId === surfaceId) &&
+ if (!this.timelineItems.find(t => t.type === "surface" && t.surfaceId === surfaceId) &&
!newItems.find(t => t.type === "surface" && t.surfaceId === surfaceId)) {
newItems.push({ type: "surface", surfaceId });
}
}
}
- this.#timeline = [...this.#timeline, ...newItems];
- this.#processor.processMessages(uiMessages);
+ this.timelineItems = [...this.timelineItems, ...newItems];
+ this.messageProcessor.processMessages(uiMessages);
return newItems;
}
@@ -114,6 +119,6 @@ export class A2UIRenderer {
* Adds a user message to the timeline.
*/
addUserMessage(text: string) {
- this.#timeline = [...this.#timeline, { type: "user", text }];
+ this.timelineItems = [...this.timelineItems, { type: "user", text }];
}
}
diff --git a/client/web/src/lit/custom-components/google_map.ts b/client/web/src/lit/custom-components/google_map.ts
index 1b71768..3971bdf 100644
--- a/client/web/src/lit/custom-components/google_map.ts
+++ b/client/web/src/lit/custom-components/google_map.ts
@@ -91,6 +91,14 @@ declare global {
}
}
+interface ResolvedMarker {
+ lat: number;
+ lng: number;
+ label: string;
+ placeId?: string;
+ collisionBehavior?: google.maps.CollisionBehavior;
+}
+
/** A2UI Custom Component for GoogleMap */
@customElement("a2ui-googlemap")
export class GoogleMap extends A2uiLitElement {
@@ -99,14 +107,14 @@ export class GoogleMap extends A2uiLitElement {
Map3DElement;
}
- protected createController() {
+ protected override createController() {
return new A2uiController(this, GoogleMapApi);
}
- #markers: HTMLElement[] = [];
- #prevCenter: { lat: number; lng: number } | null = null;
- #prevMarkers: any = null;
- #prevRoutes: any = null;
+ private markers: HTMLElement[] = [];
+ private prevCenter: { lat: number; lng: number } | null = null;
+ private prevMarkers: unknown = null;
+ private prevRoutes: unknown = null;
static styles = [
unsafeCSS(structuralStyles),
@@ -135,7 +143,7 @@ export class GoogleMap extends A2uiLitElement {
return { lat: lat as number, lng: lng as number };
}
- #resolveMarkers(): any[] {
+ private resolveMarkers(): ResolvedMarker[] {
const props = this.controller.props;
if (!props || !props.markers) return [];
@@ -158,7 +166,7 @@ export class GoogleMap extends A2uiLitElement {
return [];
}
- #create3DMarkerElement({ position, placeId, label, zIndex, collisionBehavior }: {
+ private create3DMarkerElement({ position, placeId, label, zIndex, collisionBehavior }: {
position?: google.maps.LatLngLiteral,
placeId?: string | null,
label?: string | null,
@@ -177,7 +185,7 @@ export class GoogleMap extends A2uiLitElement {
return marker;
}
- updated(changedProperties: PropertyValues): void {
+ override updated(changedProperties: PropertyValues): void {
super.updated(changedProperties);
const props = this.controller.props;
if (!props) return;
@@ -186,7 +194,7 @@ export class GoogleMap extends A2uiLitElement {
const markers = props.markers;
const routes = props.routes;
- if (center && (!this.#prevCenter || this.#prevCenter.lat !== center.lat || this.#prevCenter.lng !== center.lng)) {
+ if (center && (!this.prevCenter || this.prevCenter.lat !== center.lat || this.prevCenter.lng !== center.lng)) {
console.log('updating camera');
this.map3dElement.flyCameraTo({
endCamera: {
@@ -196,53 +204,53 @@ export class GoogleMap extends A2uiLitElement {
altitudeMode: (google as any).maps.maps3d.AltitudeMode.RELATIVE_TO_GROUND
}
});
- this.#prevCenter = { lat: center.lat, lng: center.lng };
+ this.prevCenter = { lat: center.lat, lng: center.lng };
}
- if (markers !== this.#prevMarkers || routes !== this.#prevRoutes) {
- this.#prevMarkers = markers;
- this.#prevRoutes = routes;
- this.#updateMarkers();
+ if (markers !== this.prevMarkers || routes !== this.prevRoutes) {
+ this.prevMarkers = markers;
+ this.prevRoutes = routes;
+ this.updateMarkers();
}
}
- async #updateMarkers() {
+ private async updateMarkers() {
const props = this.controller.props;
if (!props || !this.map3dElement) return;
// Clear existing markers
- this.#markers.forEach(marker => marker.remove());
- this.#markers = [];
+ this.markers.forEach(marker => marker.remove());
+ this.markers = [];
- const markers = this.#resolveMarkers();
+ const markers = this.resolveMarkers();
const anchorMarker = props.anchorMarker;
const destination = props.destination;
const routes = props.routes || [];
// Add markers from props.markers
for (const { lat, lng, label, placeId } of markers) {
- const marker = this.#create3DMarkerElement({
+ const marker = this.create3DMarkerElement({
position: { lat, lng },
placeId,
label,
});
this.map3dElement.appendChild(marker);
- this.#markers.push(marker);
+ this.markers.push(marker);
}
// Add destination marker if available
if (destination) {
- const marker = this.#create3DMarkerElement({
+ const marker = this.create3DMarkerElement({
position: { lat: destination.lat as number, lng: destination.lng as number },
label: 'Destination',
});
this.map3dElement.appendChild(marker);
- this.#markers.push(marker);
+ this.markers.push(marker);
}
// Add anchor marker if available and no routes
if (anchorMarker && !routes.length) {
- const marker = this.#create3DMarkerElement({
+ const marker = this.create3DMarkerElement({
position: { lat: anchorMarker.lat as number, lng: anchorMarker.lng as number },
placeId: anchorMarker.placeId as string,
label: anchorMarker.label as string,
@@ -257,32 +265,32 @@ export class GoogleMap extends A2uiLitElement {
marker.append(pin as any);
}
this.map3dElement.appendChild(marker);
- this.#markers.push(marker);
+ this.markers.push(marker);
}
// Add pins for each route origin and destination
for (const route of routes) {
- const originMarker = this.#create3DMarkerElement({
+ const originMarker = this.create3DMarkerElement({
position: { lat: route.origin.lat as number, lng: route.origin.lng as number },
label: route.origin.label as string || "Origin",
collisionBehavior: google.maps.CollisionBehavior.OPTIONAL_AND_HIDES_LOWER_PRIORITY,
placeId: route.origin.placeId as string,
});
this.map3dElement.appendChild(originMarker);
- this.#markers.push(originMarker);
+ this.markers.push(originMarker);
- const destMarker = this.#create3DMarkerElement({
+ const destMarker = this.create3DMarkerElement({
position: { lat: route.destination.lat as number, lng: route.destination.lng as number },
label: route.destination.label as string || "Destination",
collisionBehavior: google.maps.CollisionBehavior.OPTIONAL_AND_HIDES_LOWER_PRIORITY,
placeId: route.destination.placeId as string,
});
this.map3dElement.appendChild(destMarker);
- this.#markers.push(destMarker);
+ this.markers.push(destMarker);
}
}
- render() {
+ override render() {
const props = this.controller.props;
if (!props) return nothing;
@@ -322,13 +330,13 @@ export class GoogleMap extends A2uiLitElement {
max-tilt=${mode === 'roadmap' ? '0' : nothing}
heading="${heading}"
map-id="2d6e1a27a57efe3c9479f6fc"
- internal-usage-attribution-ids="gmp_web_a2ui_v0.0.2_exp"
+ internal-usage-attribution-ids="gmp_web_maui_v0.1.7_exp"
>${routes.map((route: any) => html`
`)}
diff --git a/client/web/src/lit/custom-components/place_card.ts b/client/web/src/lit/custom-components/place_card.ts
index fc0fd70..782fa4e 100644
--- a/client/web/src/lit/custom-components/place_card.ts
+++ b/client/web/src/lit/custom-components/place_card.ts
@@ -43,7 +43,7 @@ declare global {
/** A2UI Custom Component for PlaceCard */
@customElement('a2ui-placecard')
export class PlaceCard extends A2uiLitElement {
- protected createController() {
+ protected override createController() {
return new A2uiController(this, PlaceCardApi);
}
@@ -61,7 +61,7 @@ export class PlaceCard extends A2uiLitElement {
`,
];
- render() {
+ override render() {
const props = this.controller.props;
if (!props) return nothing;
@@ -79,7 +79,7 @@ export class PlaceCard extends A2uiLitElement {
+ internal-usage-attribution-ids="gmp_web_maui_v0.1.7_exp">