a2ui-project / a2ui-project/a2ui
Replace CRUD operations with JSON patch
- Langage dominant
- TypeScript
- Étoiles
- 16.4k
- Forks
- 1.3k
- Merge moyen
- 2 j 13 h
- PR mergées (30 j)
- 134
Description
# Background
Currently, A2UI has an underlying data structure which is basically a set of components and a data model. But our messages are for incrementally updating each of them, just imply defining them.
# Problems
- There is no well-defined format to serialize the current state of a surface
- Our CRUD operations are a little irregular. E.g. why `updateComponents` rather than `updateSurface`? Why not include initial components in `CreateSurface` etc (see https://github.com/google/A2UI/issues/1239). If we add more kinds of data, we need to invent new CRUD operations to represent them.
- In practice, most UIs are created and never updated, so it makes sense for the format to emphasize the surface state, rather than incremental updates. Right now, we have an incremental API makes it simple to represent changes to state (a single message) but complex to represent the current state (list of message which may overwrite each other and thus be redundant).
- There is no way to delete components currently, only replace them.
# Recent changes in direction
Since we defined A2UI v0.8 and v0.9, some things have changed about our direction which could motivate this design change.
- There is more emphasis on MCP, which is typically a "generate once" pattern, rather than a "generate and update" pattern. A2UI is kind of awkward for MCP, because they need to include a list of messages, and there is a question about which order they should be in etc (it doesn't matter).
- We are looking at separating transport and inference format - https://docs.google.com/document/d/1mH2DJ_jZZA2vZ9EQlWaU4v8_xCAcmqrox_LlElmJ3ws/edit?tab=t.0#heading=h.c0uts5ftkk58 . LLMs cannot write JSON patch well, and this motivated us to avoid it previously. But, if we are defining a separate LLM format anyway, let's go ahead and use it seeing as it entirely solves the issue of representing incremental data updates.
# Proposal
Let's focus on defining a JSON object which represents the state of a surface including data model and components. Then, when creating new surfaces, we just send the data. To update a surface, we send a JSON patch based on the previous data to apply.
# Pros and Cons
## Pros
- Simplify the transport format
- Simplify the mental model of a Surface snapshot and incremental updates
- Make it easy for us to expand the surface state representation (e.g. add something more than data model and components) without needing to rethink CRUD operations
- Simplify core library implementation. Currently, we have a message processor that understands how to apply our messages to a data model. Instead, we would just have some JSON and a JSON patch library that modifies it.
- Obvious way to represent surface snapshot state to LLMs. Currently, in the most simple a2ui implementations, the LLM would see the history of A2UI messages in its context. This is unnecessarily verbose. If there are many messages updating a surface, it should likely just see a snapshot of surface state. This new structure makes it obvious how to achieve this.
- Ability to represent arbitrarily small updates to state, e.g. update an individual property value
## Cons
- Creates more churn - this is relatively big change to the spec. Though, I believe we can make this update in the core library and renderers would be unaffected.
- Agents will really need full access to the accurate existing UI state in order to generate correct patches. They will probably have this anyway, though.
- JSON patch is hard for humans to read
- To incrementally update surfaces, developers will *need* an inference library to help. They won't be able to wing the JSON patch generation.
# Sketch
### Surface
```
{
"$defs": {
"Surface": {
"type": "object",
"description": "The complete state of an A2UI surface.",
"properties": {
"surfaceId": {
"type": "string",
"description": "Unique identifier for the surface."
},
"catalogId": {
"type": "string",
"description": "The URI of the catalog defining the components used."
},
"components": {
"type": "object",
"description": "A map of ComponentId to component definitions.",
"additionalProperties": {
"$ref": "catalog.json#/$defs/anyComponent"
}
},
"dataModel": {
"type": "object",
"description": "The initial data model for the surface.",
"default": {}
},
"theme": {
"$ref": "catalog.json#/$defs/theme"
},
"sendDataModel": {
"type": "boolean",
"default": false
}
},
"required": ["surfaceId", "catalogId", "components"]
}
}
}
```
### Server to client
```
{
"oneOf": [
{ "$ref": "#/$defs/CreateSurfaceMessage" },
{ "$ref": "#/$defs/UpdateSurfaceMessage" },
{ "$ref": "#/$defs/DeleteSurfaceMessage" }
],
"$defs": {
"CreateSurfaceMessage": {
"type": "object",
"properties": {
"version": { "const": "v0.10" },
"createSurface": {
"type": "object",
"properties": {
"surface": { "$ref": "common_types.json#/$defs/Surface" }
},
"required": ["surface"]
}
},
"required": ["createSurface", "version"]
},
"UpdateSurfaceMessage": {
"type": "object",
"properties": {
"version": { "const": "v0.10" },
"updateSurface": {
"type": "object",
"properties": {
"surfaceId": { "type": "string" },
"patch": {
"type": "array",
"items": { "$ref": "#/$defs/JsonPatchOperation" }
}
},
"required": ["surfaceId", "patch"]
}
},
"required": ["updateSurface", "version"]
},
"JsonPatchOperation": {
"type": "object",
"properties": {
"op": { "enum": ["add", "remove", "replace", "move", "copy", "test"] },
"path": { "type": "string", "description": "JSON Pointer to the surface property." },
"value": { "description": "The value to add or replace." },
"from": { "type": "string", "description": "Required for move/copy." }
},
"required": ["op", "path"]
}
}
}
```
Guide de contribution
Ouvrir le guide de contribution
Piste de recherche
The issue proposes replacing the current CRUD message system with a JSON Patch-based API. Start by examining the existing message definitions and processor in the core library. Understand the current Surface state representation and how updates are applied. Review the linked issue #1239 for context on irregular CRUD operations. The goal is to design a new JSON schema for Surface state and patch operations, ensuring backward compatibility where possible. Focus on the core library changes first, as renderers should be unaffected.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Évaluation
- Stack technique
- json, typescript
- Domaine
- api, backend-api-design
- Type d'issue
- Refactorisation
- Difficulté
- 5/5
- Temps estimé
- Plus d'une semaine
- Activité
- Calme
- Clarté
- Plutôt claire
- Accessibilité débutants
- 35/100