anthropic-a2ui 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,83 @@
1
+ """anthropic-a2ui: integración de A2UI con el SDK de Anthropic.
2
+
3
+ Paquete que compone ``a2ui-agent-sdk`` con ``anthropic`` para que Claude
4
+ genere respuestas conformes al protocolo A2UI. No envuelve el SDK de
5
+ Anthropic: se usa ``anthropic`` directamente y se plugan las piezas de A2UI.
6
+
7
+ API pública:
8
+
9
+ - ``ClaudeA2uiPromptBuilder``: genera el system prompt con esquema + ejemplos.
10
+ - ``create_a2ui_tool``: define la tool ``send_a2ui_json_to_client``.
11
+ - ``ClaudeStreamParser``: parsea el stream de Anthropic y emite
12
+ ``ResponsePart`` de A2UI.
13
+ - ``to_a2ui_part``, ``A2uiPart``, ``MIME_A2UI``: helpers de transporte.
14
+ - ``SUPPORTED_VERSIONS``, ``DEFAULT_VERSION``: constantes del protocolo.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from .parts import A2uiPart, MIME_A2UI, parse_a2ui_part_json, to_a2a_datapart, to_a2ui_part
20
+ from .prompt_builder import (
21
+ DEFAULT_VERSION,
22
+ SUPPORTED_VERSIONS,
23
+ ClaudeA2uiPromptBuilder,
24
+ )
25
+ from .repair import (
26
+ find_orphans,
27
+ patch_catalog_schema,
28
+ repair_childlists,
29
+ repair_functions,
30
+ repair_icons,
31
+ repair_orphans,
32
+ )
33
+ from .retry import (
34
+ A2uiConversation,
35
+ A2uiConversationAsync,
36
+ ConversationTurn,
37
+ RetryResult,
38
+ create_a2ui_response_format,
39
+ generate_a2ui,
40
+ generate_a2ui_async,
41
+ parse_json_response,
42
+ )
43
+ from .stream_parser import ClaudeStreamParser
44
+ from .tool import (
45
+ TOOL_DESCRIPTION,
46
+ TOOL_NAME,
47
+ create_a2ui_tool,
48
+ make_parser_for_tool,
49
+ validate_tool_input,
50
+ )
51
+ from .version import __version__
52
+
53
+ __all__ = [
54
+ "A2uiConversation",
55
+ "A2uiConversationAsync",
56
+ "A2uiPart",
57
+ "ConversationTurn",
58
+ "MIME_A2UI",
59
+ "RetryResult",
60
+ "SUPPORTED_VERSIONS",
61
+ "DEFAULT_VERSION",
62
+ "TOOL_NAME",
63
+ "TOOL_DESCRIPTION",
64
+ "ClaudeA2uiPromptBuilder",
65
+ "ClaudeStreamParser",
66
+ "create_a2ui_response_format",
67
+ "create_a2ui_tool",
68
+ "find_orphans",
69
+ "generate_a2ui",
70
+ "generate_a2ui_async",
71
+ "make_parser_for_tool",
72
+ "parse_a2ui_part_json",
73
+ "parse_json_response",
74
+ "patch_catalog_schema",
75
+ "repair_childlists",
76
+ "repair_functions",
77
+ "repair_icons",
78
+ "repair_orphans",
79
+ "to_a2a_datapart",
80
+ "to_a2ui_part",
81
+ "validate_tool_input",
82
+ "__version__",
83
+ ]
@@ -0,0 +1,6 @@
1
+ """Internos de anthropic-a2ui.
2
+
3
+ Reservado para compatibilidad futura. La adaptación de tipos de Anthropic
4
+ se hace con ``getattr`` en ``stream_parser.py`` para no acoplarse a versiones
5
+ del SDK.
6
+ """
@@ -0,0 +1,108 @@
1
+ """Helpers de transporte para envolver JSON de A2UI en DataParts.
2
+
3
+ Esta pieza es la menos acoplada a Anthropic: sirve igual si el JSON viene de
4
+ tool use, de tags ``<a2ui-json>`` o de ``response_format``. Produce la
5
+ envoltura estándar que los renderers de A2UI esperan (MIME
6
+ ``application/a2ui+json``), compatible con A2A.
7
+
8
+ El módulo no depende de ``a2a-sdk`` para evitar arrastrar el transporte al
9
+ importar ``anthropic-a2ui``; en su lugar define ``A2uiPart`` como un dataclass
10
+ ligero. Si el caller ya usa ``a2a-sdk`` puede convertirlo fácilmente, y se
11
+ ofrece ``to_a2a_datapart`` que intenta el bridge si ``a2a`` está instalado.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ from dataclasses import dataclass, field
18
+ from typing import Any, Optional
19
+
20
+ MIME_A2UI = "application/a2ui+json"
21
+
22
+
23
+ @dataclass(frozen=True, slots=True)
24
+ class A2uiPart:
25
+ """Envoltura estándar de un mensaje A2UI para transporte.
26
+
27
+ Attributes:
28
+ mime: Tipo MIME bajo el que se etiqueta el payload. El protocolo A2UI
29
+ define ``application/a2ui+json`` como estándar para los DataParts.
30
+ data: El JSON A2UI ya parseado (lista de mensajes o mensaje único). Se
31
+ conserva como objeto para que el receptor lo use directamente; si hace
32
+ falta serializar, usar ``part.to_json_string()``.
33
+ """
34
+
35
+ mime: str = MIME_A2UI
36
+ data: Any = field(default=None)
37
+
38
+ def to_json_string(self, **kwargs: Any) -> str:
39
+ """Serializa ``data`` a cadena JSON compacta.
40
+
41
+ Args:
42
+ **kwargs: Argumentos extra para ``json.dumps`` (por ejemplo
43
+ ``indent=2`` para pretty-print).
44
+ """
45
+ kwargs.setdefault("ensure_ascii", False)
46
+ return json.dumps(self.data, **kwargs)
47
+
48
+ def to_dict(self) -> dict[str, Any]:
49
+ """Convierte el part a un dict plano apto para serialización genérica.
50
+
51
+ Útil para enviar por JSON-RPC, SSE o cualquier canal que no sea A2A.
52
+ """
53
+ return {"mimeType": self.mime, "data": self.data}
54
+
55
+
56
+ def to_a2ui_part(payload: Any, *, mime: str = MIME_A2UI) -> A2uiPart:
57
+ """Envuelve un payload A2UI (dict o lista) en un ``A2uiPart``.
58
+
59
+ Args:
60
+ payload: El JSON A2UI ya parseado. Puede ser un único mensaje (dict) o
61
+ una lista de mensajes; el protocolo admite ambos.
62
+ mime: Tipo MIME a usar. Por defecto el estándar del protocolo.
63
+
64
+ Returns:
65
+ Un ``A2uiPart`` listo para entregar al renderer o al transporte.
66
+ """
67
+ return A2uiPart(mime=mime, data=payload)
68
+
69
+
70
+ def to_a2a_datapart(part: A2uiPart) -> Any:
71
+ """Intenta convertir un ``A2uiPart`` a ``a2a.types.DataPart``.
72
+
73
+ Esta función es opcional y solo funciona si ``a2a-sdk`` está instalado. Si
74
+ no lo está, devuelve el dict plano (``part.to_dict()``) para que el caller
75
+ lo envuelva como prefiera.
76
+
77
+ Returns:
78
+ Un ``DataPart`` de ``a2a`` si está disponible, o un dict con
79
+ ``{"mimeType": ..., "data": ...}`` si no.
80
+ """
81
+ try:
82
+ from a2a.types import DataPart # type: ignore
83
+ except Exception:
84
+ return part.to_dict()
85
+ dp = DataPart(**part.to_dict()) # type: ignore[call-arg]
86
+ return dp
87
+
88
+
89
+ def parse_a2ui_part_json(raw: str | bytes) -> Any:
90
+ """Parsea una cadena JSON de A2UI y devuelve el objeto.
91
+
92
+ Es un atajo sobre ``json.loads`` que acepta ``str`` o ``bytes`` y que lanza
93
+ ``ValueError`` (en vez de ``json.JSONDecodeError``) para que el caller pueda
94
+ capturar de forma agnóstica al framework de serialización.
95
+
96
+ Args:
97
+ raw: Cadena o bytes con JSON A2UI.
98
+
99
+ Returns:
100
+ El objeto parseado (dict o lista).
101
+
102
+ Raises:
103
+ ValueError: Si el contenido no es JSON válido.
104
+ """
105
+ try:
106
+ return json.loads(raw)
107
+ except json.JSONDecodeError as exc:
108
+ raise ValueError(f"JSON A2UI inválido: {exc}") from exc
@@ -0,0 +1,198 @@
1
+ """Generación del system prompt para que Claude emita respuestas A2UI.
2
+
3
+ Esta pieza delega en ``A2uiSchemaManager`` de ``a2ui-agent-sdk`` para montar
4
+ el esquema del catálogo y los ejemplos few-shot, y expone una API pensada
5
+ para el flujo de Anthropic: construir el prompt, pasar a ``system`` de
6
+ ``client.messages.stream(...)``, y opcionalmente podar componentes para
7
+ ahorrar tokens.
8
+
9
+ El paquete no redefine el formato del prompt; el ``A2uiSchemaManager`` es la
10
+ fuente canónica del protocolo. Aquí solo se adapta el punto de entrada y se
11
+ ofrece conveniencia (defaults razonables, validación de versiones soportadas).
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from typing import Any, Callable, Optional
17
+
18
+ from a2ui.basic_catalog.provider import BasicCatalog
19
+ from a2ui.schema.catalog import CatalogConfig
20
+ from a2ui.schema.manager import A2uiSchemaManager
21
+
22
+ # Versiones de protocolo que a2ui-agent-sdk 0.2.x soporta. Se mantiene aquí
23
+ # para dar errores claros antes de llegar al A2uiSchemaManager (que lanza un
24
+ # ValueError genérico).
25
+ SUPPORTED_VERSIONS = ("0.8", "0.9", "0.9.1")
26
+ DEFAULT_VERSION = "0.9"
27
+
28
+
29
+ class ClaudeA2uiPromptBuilder:
30
+ """Construye el system prompt para que Claude genere respuestas A2UI.
31
+
32
+ Envuelve a ``A2uiSchemaManager`` con defaults orientados a Anthropic: el
33
+ rol del asistente, descripción del flujo y activación de esquema +
34
+ ejemplos. Permite reutilizar una instancia para varias llamamas y podar el
35
+ catálogo a un subconjunto de componentes para reducir tokens.
36
+
37
+ Attributes:
38
+ version: Versión del protocolo A2UI (``"0.9"`` por defecto).
39
+ catalogs: Lista de ``CatalogConfig`` activos.
40
+ manager: El ``A2uiSchemaManager`` subyacente, expuesto para uso avanzado.
41
+
42
+ Example:
43
+ ```python
44
+ builder = ClaudeA2uiPromptBuilder()
45
+ system = builder.build(role_description="Construye formularios de contacto.")
46
+ # system -> string listo para pasar a client.messages.stream(system=system)
47
+ ```
48
+ """
49
+
50
+ def __init__(
51
+ self,
52
+ catalogs: Optional[list[CatalogConfig]] = None,
53
+ version: str = DEFAULT_VERSION,
54
+ accepts_inline_catalogs: bool = False,
55
+ schema_modifiers: Optional[
56
+ list[Callable[[dict[str, Any]], dict[str, Any]]]
57
+ ] = None,
58
+ ) -> None:
59
+ """Inicializa el builder.
60
+
61
+ Args:
62
+ catalogs: Catálogos de componentes. Si es ``None`` se usa el
63
+ ``BasicCatalog`` (Button, Text, TextField, etc.) bundled en
64
+ ``a2ui-agent-sdk``.
65
+ version: Versión del protocolo. Debe ser una de
66
+ ``SUPPORTED_VERSIONS``.
67
+ accepts_inline_catalogs: Si el cliente acepta catálogos inline.
68
+ schema_modifiers: Funciones para modificar el esquema antes de
69
+ inyectarlo en el prompt (caso avanzado).
70
+
71
+ Raises:
72
+ ValueError: Si ``version`` no está en ``SUPPORTED_VERSIONS``.
73
+ """
74
+ if version not in SUPPORTED_VERSIONS:
75
+ raise ValueError(
76
+ f"Versión no soportada: {version!r}. Versiones válidas: {SUPPORTED_VERSIONS}"
77
+ )
78
+ self.version = version
79
+ if catalogs is None:
80
+ catalogs = [BasicCatalog.get_config(version=version)]
81
+ self.catalogs = list(catalogs)
82
+ self.manager = A2uiSchemaManager(
83
+ version=version,
84
+ catalogs=self.catalogs,
85
+ accepts_inline_catalogs=accepts_inline_catalogs,
86
+ schema_modifiers=schema_modifiers,
87
+ )
88
+
89
+ def build(
90
+ self,
91
+ role_description: str,
92
+ workflow_description: str = "",
93
+ ui_description: str = "",
94
+ allowed_components: Optional[list[str]] = None,
95
+ allowed_messages: Optional[list[str]] = None,
96
+ include_schema: bool = True,
97
+ include_examples: bool = True,
98
+ validate_examples: bool = False,
99
+ include_icon_list: bool = True,
100
+ include_function_list: bool = True,
101
+ ) -> str:
102
+ """Genera el system prompt listo para pasar a Anthropic.
103
+
104
+ Args:
105
+ role_description: Descripcion del rol del agente (inyecta al inicio).
106
+ workflow_description: Descripcion del flujo de trabajo (opcional).
107
+ ui_description: Descripcion extra de la UI (opcional).
108
+ allowed_components: Subconjunto de nombres de componentes que el
109
+ agente puede usar. Si es ``None``, todos los del catalogo.
110
+ allowed_messages: Subconjunto de tipos de mensaje.
111
+ include_schema: Si incluir el JSON Schema en el prompt.
112
+ include_examples: Si incluir ejemplos few-shot.
113
+ validate_examples: Si validar los ejemplos contra el esquema.
114
+ include_icon_list: Si anadir la lista de iconos validos al final del
115
+ prompt. Evita que Claude invente nombres de iconos no permitidos.
116
+ Por defecto ``True``.
117
+
118
+ Returns:
119
+ Cadena con el system prompt completo.
120
+ """
121
+ prompt = self.manager.generate_system_prompt(
122
+ role_description=role_description,
123
+ workflow_description=workflow_description,
124
+ ui_description=ui_description,
125
+ client_ui_capabilities=None,
126
+ allowed_components=allowed_components,
127
+ allowed_messages=allowed_messages,
128
+ include_schema=include_schema,
129
+ include_examples=include_examples,
130
+ validate_examples=validate_examples,
131
+ )
132
+ if include_icon_list:
133
+ icons = _extract_valid_icons(self.get_catalog())
134
+ if icons:
135
+ prompt += (
136
+ "\n\n--- ICONOS VALIDOS ---\n"
137
+ "El componente Icon solo acepta estos nombres (no inventes "
138
+ "otros):\n"
139
+ + ", ".join(icons)
140
+ + "\n"
141
+ )
142
+ if include_function_list:
143
+ functions = _extract_valid_functions(self.get_catalog())
144
+ if functions:
145
+ prompt += (
146
+ "\n\n--- FUNCIONES VALIDAS ---\n"
147
+ "Las unicas funciones que puedes usar en FunctionCall son estas "
148
+ "(no inventes otras como ternary, if, switch, etc.):\n"
149
+ + ", ".join(functions)
150
+ + "\n"
151
+ )
152
+ return prompt
153
+
154
+ def get_catalog(self) -> Any:
155
+ """Devuelve el ``A2uiCatalog`` seleccionado (con poda aplicada si la hubo).
156
+
157
+ Expuesto para que el caller pueda obtener un validador o podar el
158
+ catálogo de forma independiente al prompt.
159
+ """
160
+ return self.manager.get_selected_catalog()
161
+
162
+ def get_validator(self) -> Any:
163
+ """Atajo para obtener el ``A2uiValidator`` del catalogo seleccionado."""
164
+ return self.get_catalog().validator
165
+
166
+
167
+ def _extract_valid_icons(catalog: Any) -> list[str]:
168
+ """Extrae la lista de iconos validos del catalogo.
169
+
170
+ Recorre el schema del componente ``Icon`` y devuelve los valores del
171
+ ``enum`` en su propiedad ``name``.
172
+ """
173
+ components = catalog.catalog_schema.get("components", {})
174
+ icon_def = components.get("Icon")
175
+ if not icon_def:
176
+ return []
177
+ for sub in icon_def.get("allOf", []):
178
+ if "properties" not in sub:
179
+ continue
180
+ name_prop = sub["properties"].get("name")
181
+ if not name_prop:
182
+ continue
183
+ for branch in name_prop.get("oneOf", []):
184
+ if "enum" in branch:
185
+ return branch["enum"]
186
+ if "enum" in name_prop:
187
+ return name_prop["enum"]
188
+ return []
189
+
190
+
191
+ def _extract_valid_functions(catalog: Any) -> list[str]:
192
+ """Extrae la lista de funciones validas del catalogo.
193
+
194
+ Devuelve los nombres de las funciones definidas en el catalogo, que son
195
+ las unicas que se pueden usar en ``FunctionCall``.
196
+ """
197
+ functions = catalog.catalog_schema.get("functions", {})
198
+ return list(functions.keys())
File without changes