Skip to main content

chat-layer

Working with OpenBench chat layer - ChatEngine, A2UI v0.10 builder, content renderers, SSE + REST transport, and ChatLayer L2 orchestrator. Use when building chat interfaces, streaming A2UI JSONL, rendering rich content (charts, forms, files), or composing chat workflows. Use when this capability is needed.

Zur Installation springen

Quellinformationen

Repository
tomevault-io/skills-registry
Letzte Quellaktivität
28. April 2026 um 22:53
Erkannte Sprache von SKILL.md
Englisch
Sterne
1
Forks
0

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

Datei-Explorer
2 Dateien

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
chat-layer
description
Working with OpenBench chat layer - ChatEngine, A2UI v0.10 builder, content renderers, SSE + REST transport, and ChatLayer L2 orchestrator. Use when building chat interfaces, streaming A2UI JSONL, rendering rich content (charts, forms, files), or composing chat workflows. Use when this capability is needed.
metadata
{"author":"ai-kitchen-inc"}
# Chat Layer OpenBench chat layer provides a complete backend for interactive chat UIs powered by **A2UI v0.10** (Google's declarative JSON streaming UI protocol). ## A2UI v0.10 Protocol Four message types, every message has `"version": "v0.10"`: | Message | Purpose | |---------|---------| | `createSurface` | Initialize surface with `surfaceId` + `catalogId` | | `updateComponents` | Add/replace components (flat adjacency list) | | `updateDataModel` | Update data at JSON Pointer path | | `deleteSurface` | Remove a surface | Components are flat objects: `{"id": "root", "component": "Column", "children": ["t1", "c1"]}` Root component must have `id: "root"`. Properties are NOT nested. Standard catalog: 18 components. Custom (OpenBench): 6 extensions. ## Architecture ``` src/openbench/chat/ ├── engine.py # ChatEngine (Chainable) -- main orchestrator ├── session.py # ChatSession, ChatMessage, Attachment ├── a2ui/ │ ├── builder.py # A2UIMessageBuilder -- A2UI v0.10 JSONL generator │ ├── catalog.py # Custom catalog (ObChart, ObFileCard, ObCodeBlock, ObMarkdown) │ └── schema.py # A2UI v0.10 message types and validation ├── renderers/ │ ├── base.py # ContentRenderer ABC + Registry │ ├── text.py # TextRenderer (markdown, code) │ ├── chart.py # ChartRenderer (bar, line, pie, scatter) │ ├── code.py # CodeRenderer (syntax-highlighted code) │ ├── form.py # FormRenderer (dynamic forms) │ ├── file.py # FileRenderer (file preview/download) │ ├── media.py # MediaRenderer (images, video, audio) │ ├── list.py # ListRenderer (ordered/unordered lists) │ ├── tabs.py # TabsRenderer (tabbed content) │ ├── modal.py # ModalRenderer (modal overlays) │ ├── table.py # TableRenderer (structured tables) │ └── callout.py # CalloutRenderer (styled callout boxes) ├── transport/ │ ├── agui.py # AGUIHandler -- AG-UI SSE event streaming │ └── agui_actions.py # AGUIActionHandler -- REST for A2UI actions └── layer.py # ChatLayer (L2) + ChatFactory ``` ## ChatEngine Main orchestrator. Chainable -- composable with all OpenBench layers. ```python from openbench.chat import ChatEngine, ChatSession from openbench.intelligence.base import BaseAgent agent = BaseAgent(goal="Answer questions about Q4 sales") engine = ChatEngine(agent=agent) # Single turn result = engine.invoke({"content": "Show Q4 sales by region"}) # result = {"messages": [...], "session": ChatSession, "metadata": {...}} # Streaming (A2UI v0.10 JSONL) for line in engine.stream({"content": "Show Q4 sales"}): print(line) # Each line is a valid A2UI v0.10 JSON message ``` ## Composition with Layers ```python from openbench.core import DataLayer, OutputLayer from openbench.chat import ChatLayer # Chat with RAG pipeline workflow = DataLayer(sources=[pdf_source]) | ChatLayer(agent=rag_agent) # Chat with transcript output workflow = ChatLayer(agent=agent) | OutputLayer(generators=[transcript_gen]) # Full E2E workflow = DataLayer(sources=[pdf]) | ChatLayer(agent=agent) | OutputLayer(generators=[...]) ``` ## ChatSession ```python from openbench.chat import ChatSession, ChatMessage session = ChatSession() session.add_user_message("Hello", attachments=[...]) session.add_assistant_message("Hi!", surfaces=[...]) # Serialize / deserialize data = session.to_dict() restored = ChatSession.from_dict(data) # Context window for LLM recent = session.get_context_window(max_messages=50) ``` ## Content Renderers Auto-detect and render agent output as A2UI components: ```python from openbench.chat.renderers.text import TextRenderer from openbench.chat.renderers.chart import ChartRenderer # Renderers auto-detect content type text_renderer = TextRenderer() text_renderer.detect("Hello world") # True components = text_renderer.render("# Title\nBody text", surface_id="s1") # Returns [A2UIComponent(id="...", component="Text", properties={"text": "...", "variant": "h2"})] chart_renderer = ChartRenderer() chart_renderer.detect({"type": "bar", "data": [...]}) # True components = chart_renderer.render({"type": "bar", "data": [...]}, surface_id="s1") # Returns [A2UIComponent(id="...", component="ObChart", properties={"chartType": "bar", ...})] ``` ## A2UI Builder Build A2UI v0.10 JSONL messages: ```python from openbench.chat.a2ui import A2UIMessageBuilder builder = A2UIMessageBuilder() # Build complete surface (createSurface + updateComponents + updateDataModel) messages = builder.build_surface( surface_id="s1", components=[...], # A2UIComponent list (one must have id="root") data_model={"/chart/data": [...]} ) # Returns: # [ # {"version":"v0.10","createSurface":{"surfaceId":"s1","catalogId":"..."}}, # {"version":"v0.10","updateComponents":{"surfaceId":"s1","components":[...]}}, # {"version":"v0.10","updateDataModel":{"surfaceId":"s1","path":"/chart/data","value":[...]}} # ] # Serialize to JSONL jsonl = builder.to_jsonl(messages) ``` ## AG-UI Transport (Primary) ```python from fastapi import FastAPI, Request from openbench.chat import ChatEngine from openbench.chat.transport import AGUIHandler, AGUIActionHandler app = FastAPI() engine = ChatEngine(agent=my_agent) agui_handler = AGUIHandler(engine=engine) action_handler = AGUIActionHandler(engine=engine) @app.post("/awp") async def chat_stream(request: Request): return await agui_handler.handle(request) @app.post("/chat/action") async def chat_action(request: Request): return await action_handler.handle(request) ``` ## AG-UI Protocol ``` Client Server | | |-- POST /awp -->| AG-UI RunAgentInput | {threadId, runId, | | messages: [...], | | forwardedProps: {sessionId}} | | | |<-- data: {"type":"RUN_STARTED", | AG-UI: run begins | "threadId":"...", | | "runId":"..."} | | | |<-- data: {"type":"STEP_STARTED", | AG-UI: processing input | "stepName":"Processing input"}| |<-- data: {"type":"STEP_FINISHED"} | | | |<-- data: {"type":"STEP_STARTED", | AG-UI: thinking (streaming) | "stepName":"Thinking"} | | | |<-- data: {"type":"TEXT_MESSAGE_START", | Text streaming begins | "messageId":"msg-xxx", | | "role":"assistant"} | | | |<-- data: {"type":"TEXT_MESSAGE_CONTENT",| Token deltas (progressive) | "messageId":"msg-xxx", | | "delta":"The revenue..."} | | ... (more deltas) | | | |<-- data: {"type":"TEXT_MESSAGE_END", | Text streaming complete | "messageId":"msg-xxx"} | | | |<-- data: {"type":"STEP_FINISHED"} | | | |<-- data: {"type":"STEP_STARTED", | (only if rich content) | "stepName":"Rendering"} | |<-- data: {"type":"CUSTOM", | AG-UI: A2UI messages | "name":"a2ui", | | "value":{"version":"v0.10", | | "createSurface":{...}}} | |<-- data: {"type":"STEP_FINISHED"} | | | |<-- data: {"type":"RUN_FINISHED", | AG-UI: run complete | "result":{...}} | | | |-- POST /chat/action -->| User action (A2UI event) | {"name":"submit_form", | | "surfaceId":"s1", | | "sourceComponentId":"btn", | | "context":{...}} | |<-- [response messages] ---| JSON array ``` ## Progressive Token Streaming AGUIHandler streams text token-by-token using an `asyncio.Queue` bridge pattern: ``` Sync Thread (BaseAgent) Async Event Loop (AGUIHandler) ┌─────────────────────┐ ┌──────────────────────────┐ │ llm.generate_stream()│ │ _event_stream(): │ │ ├─ chunk "The" │──queue──>│ ├─ TextMessageContent │──SSE──> Browser │ ├─ chunk " answer"│──queue──>│ ├─ TextMessageContent │──SSE──> Browser │ ├─ chunk " is..." │──queue──>│ ├─ TextMessageContent │──SSE──> Browser │ └─ DONE sentinel │──queue──>│ └─ break loop │ └─────────────────────┘ └──────────────────────────┘ ``` - `ChatEngine._execute_agent(content, config, attachments, on_chunk)` accepts the callback - Agent calls `on_chunk(delta)` for each token - `on_chunk` puts deltas into `asyncio.Queue` via `loop.call_soon_threadsafe()` - SSE generator reads from queue and emits `TextMessageContentEvent` - Text-only responses: 2 steps (Processing input + Thinking) - Rich content responses: 3 steps (+ Rendering response with A2UI surfaces) ## Custom A2UI Catalog 6 custom components beyond the 18 standard A2UI v0.10 components: | Component | Properties | |-----------|------------| | `ObChart` | `chartType`, `data`, `options`, `width`, `height` | | `ObFileCard` | `fileName`, `fileUrl`, `fileSize`, `mimeType`, `previewUrl` | | `ObCodeBlock` | `code`, `language`, `showLineNumbers`, `maxHeight` | | `ObMarkdown` | `content`, `allowHtml` | | `ObTable` | `headers`, `rows`, `striped`, `compact` | | `ObCallout` | `content`, `variant`, `title` | Note: `AudioPlayer` and `Video` are standard A2UI components -- no custom wrappers needed. ## Anti-Patterns **DO NOT:** - Import `fastapi` at module level -- use lazy imports (it's an optional dep) - Create ChatEngine without an agent -- it requires `Agent | FrameworkAdapter` - Skip `detect()` before `render()` on ContentRenderers -- always check first - Send raw strings over SSE -- always use A2UI v0.10 JSONL format - Use `surfaceUpdate` or `beginRendering` -- those are NOT real A2UI message types - Use `"type"` on components -- the correct field is `"component"` - Nest properties inside a `"properties"` dict -- A2UI properties are flat on the component object - Forget `"version": "v0.10"` on every A2UI message - Forget `catalogId` on `createSurface` messages - Forget to include a component with `id: "root"` in every surface ## Cross-References - **Intelligence Layer**: Agents used in ChatEngine -> see `intelligence-layer` skill - **Data Layer**: RAG sources used with ChatLayer -> see `data-layer` skill - **Output Layer**: Transcript generators used after ChatLayer -> see `output-layer` skill - **Composing Workflows**: ChatLayer L2 composition -> see `composing-workflows` skill - **Chat UI**: @openbench/chat-ui React SDK -> see `chat-ui` skill - **Adapters**: External framework agents in ChatEngine -> see `adapters` skill - **Testing**: Mock agent and renderer tests -> see `testing-openbench` skill ## Best Practices 1. Use renderers to auto-detect content -- don't hardcode A2UI components 2. Always stream for real-time UIs -- `engine.stream()` over `engine.invoke()` 3. Keep sessions serializable -- use `to_dict()` / `from_dict()` for persistence 4. Register custom renderers via `ContentRendererRegistry` for extensibility 5. Use `ChatLayer` for L2 composition, `ChatEngine` for direct usage 6. Validate JSONL output against A2UI v0.10 JSON schemas For architecture details, see `docs/CHAT_UI_ARCHITECTURE.md` ---
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen