The agent side of A2UI for Elixir, built on a2ui_core.
An agent registers the catalogs it can write for, negotiates them with each
renderer, puts the matching schemas in the model's prompt and reads the
model's output back as validated A2UI messages, from a complete response or
chunk by chunk while it streams.
It supports protocol versions v0.9, v0.9.1 and v1.0, and two inference
formats. In direct JSON, the default, the model writes message lists between
<a2ui-json> and </a2ui-json> tags. In Express (A2UIAgent.Express) it
writes a compact notation between <a2ui> and </a2ui> tags, which the agent
compiles into messages. An Express prompt can also be packaged as skills
(A2UIAgent.SkillGenerator).
Installation
def deps do
[
{:a2ui_agent, "~> 0.1.0"}
]
endUsage
An outline of the flow; Serving an agent walks
through it with examples that run. Here example_turn is an example
conversation turn for the prompt, capabilities the capabilities the
renderer sent, and model_output and chunk what the model wrote.
alias A2UIAgent.{CatalogConfig, CatalogTransformer, Generator, RequestProcessor}
alias A2UIAgent.DirectJson.Parser
# Once, when the agent starts.
basic =
CatalogConfig.new(A2UICore.Catalog.Basic.catalog("v1.0"),
transformers: [CatalogTransformer.component_pruning(["Column", "Text", "Button"])]
)
generator = Generator.new([basic], examples: [example_turn])
# Per request, with the capabilities the renderer sent.
{:ok, processor} = Generator.create_processor(generator, capabilities)
{:ok, snippet} = RequestProcessor.prompt(processor)
# A complete response...
{:ok, parts} = RequestProcessor.parse_response(processor, model_output)
#=> [{:text, "Here is your form."}, {:a2ui, [%{"version" => "v1.0", ...}, ...]}]
# ...or a streamed one, chunk by chunk.
parser = RequestProcessor.parser(processor)
{:ok, parser, parts} = Parser.parse_chunk(parser, chunk)snippet goes into the model's system prompt. Each {:a2ui, messages} part
holds messages that a renderer holding the negotiated catalogs accepts
(A2UIAgent.PayloadValidator). Send them to the renderer over whatever
transport connects the two.
Catalogs can also be loaded from files (A2UIAgent.CatalogConfig.from_path/2)
or sent by the renderer itself (inlineCatalogs, with
accepts_inline_catalogs: true).
For Express, pass format: A2UIAgent.Express to Generator.new/2 (every
request) or Generator.create_processor/3 (one request). Express responses
are parsed once they are complete.
Besides A2A (below), A2UIAgent.MCP and A2UIAgent.AGUI carry A2UI over
MCP and AG-UI. Components can also be built in Elixir (A2UIAgent.Builder), and macros
(A2UIAgent.Macro, A2UIAgent.MacroExpander) give the model components of
your own that expand into the catalog's before they reach the renderer.
Serving over A2A
A2UIAgent.A2A.Plug serves an agent over the JSON-RPC binding of
A2A: message/send answers with the finished task,
message/stream with server-sent events.
defmodule MyAgent do
@behaviour A2UIAgent.A2A.Agent
alias A2UIAgent.A2A.Request
# No catalog negotiated: answer in text.
@impl true
def run(%Request{processor: nil, text: text}), do: {:ok, MyModel.reply(text)}
# `events` holds the renderer's A2UI messages, such as actions.
def run(%Request{processor: processor, text: text, events: events}) do
{:ok, prompt} = A2UIAgent.RequestProcessor.prompt(processor)
# A string, or an enumerable of chunks as the model streams them.
{:ok, MyModel.stream(prompt, text, events)}
end
@impl true
def options do
basic = A2UIAgent.CatalogConfig.new(A2UICore.Catalog.Basic.catalog("v1.0"))
[generator: A2UIAgent.Generator.new([basic])]
end
@impl true
def card do
%{
"name" => "My agent",
"capabilities" => %{"extensions" => [A2UIAgent.A2A.agent_extension("1.0")]}
}
end
end
# In a Phoenix router or a Plug.Router:
forward "/a2a", A2UIAgent.A2A.Plug, agent: MyAgentThe renderer's capabilities, which it sends in the message metadata, select the
catalogs. The model's output goes back as a text part for each run of text and
an A2UI data part for each message. A2UIAgent.A2A.Server does the same
without Plug, and A2UIAgent.A2A holds the part and extension helpers. The
plug needs the optional plug dependency.
License
Apache-2.0. The prompt rules are the A2UI project's text, which the conformance suites compare exactly, under the same license.