DEV Community

Cover image for How to Build a Streaming AI Chat Endpoint with Tool Events
Germey
Germey

Posted on • Originally published at platform.acedata.cloud

How to Build a Streaming AI Chat Endpoint with Tool Events

A chat UI feels simple until you try to ship the details: streaming text, images in the user message, tool calls, citations, resumable sessions, and a clean fallback for non-streaming clients. The AI Chat v2 endpoint gives you one API surface for that whole shape instead of forcing you to glue several patterns together.

This guide walks through the practical integration path: start with a normal JSON response, add server-side conversation state, then move to NDJSON or SSE streaming so your frontend can render tokens, tool activity, and final usage separately.

What you can do

The endpoint is:

POST https://api.acedata.cloud/aichat2/conversations
Enter fullscreen mode Exit fullscreen mode

You authenticate with:

authorization: Bearer {token}
Enter fullscreen mode Exit fullscreen mode

For the simplest request, send model and question. The response comes back as { answer, id }, which makes it compatible with a conventional request/response chat implementation.

AI Chat v2 also supports more advanced patterns from the same endpoint:

  • stateful: true for server-side multi-turn conversation state
  • id to continue a previous conversation
  • accept: application/x-ndjson for server or CLI streaming
  • accept: text/event-stream for browser-style SSE streaming
  • message[] blocks for text, image URLs, and file URLs
  • structured stream event types such as text_delta, thinking, tool_use, tool_result, card, citation, artifact, ask_user_question, error, and done
  • async: true for background execution
  • action: retrieve with id to query an async or saved conversation result

The model list is broad, but the examples in the documentation use gpt-5.4, so we will keep the examples here on that model.

Start with a plain JSON request

Before adding streaming, make sure the basic call works. This is the smallest useful version:

curl -X POST 'https://api.acedata.cloud/aichat2/conversations' \
  -H 'accept: application/json' \
  -H 'authorization: Bearer {token}' \
  -H 'content-type: application/json' \
  -d '{
    "model": "gpt-5.4",
    "question": "Introduce AceDataCloud in one sentence."
  }'
Enter fullscreen mode Exit fullscreen mode

A JSON response contains an answer and an id:

{
  "answer": "AceDataCloud is a unified API platform that aggregates mainstream AI models and multimodal services, allowing developers to access services like GPT, Claude, Gemini, Midjourney, Suno, Veo, etc., with a single key.",
  "id": "f2f4b3e8-0c0a-4d3a-aaa2-7ff80c0a1c44"
}
Enter fullscreen mode Exit fullscreen mode

This mode is the right first milestone because you can connect it to an existing backend route, verify authentication, and store the returned id before you worry about stream parsing.

Keep conversation state on the server

For multi-turn chat, pass stateful: true. The endpoint returns an id; later requests can bring the same id back instead of resending the entire message history.

First request:

curl -X POST 'https://api.acedata.cloud/aichat2/conversations' \
  -H 'accept: application/json' \
  -H 'authorization: Bearer {token}' \
  -H 'content-type: application/json' \
  -d '{
    "model": "gpt-5.4",
    "stateful": true,
    "question": "Remember a number: 42."
  }'
Enter fullscreen mode Exit fullscreen mode

Follow-up request:

curl -X POST 'https://api.acedata.cloud/aichat2/conversations' \
  -H 'accept: application/json' \
  -H 'authorization: Bearer {token}' \
  -H 'content-type: application/json' \
  -d '{
    "model": "gpt-5.4",
    "stateful": true,
    "id": "f2f4b3e8-0c0a-4d3a-aaa2-7ff80c0a1c44",
    "question": "What number did I just ask you to remember?"
  }'
Enter fullscreen mode Exit fullscreen mode

The documentation notes that stateful defaults to true. If you do not want the server to save a round, explicitly pass stateful: false.

Stream NDJSON on the server

For backends, CLIs, and Node-style stream parsers, NDJSON is often simpler than SSE: each line is one JSON object. Set the accept header to application/x-ndjson.

import json
import requests

url = "https://api.acedata.cloud/aichat2/conversations"

headers = {
    "accept": "application/x-ndjson",
    "authorization": "Bearer {token}",
    "content-type": "application/json",
}

payload = {
    "model": "gpt-5.4",
    "stateful": True,
    "question": "Introduce Hangzhou in three sentences.",
}

with requests.post(url, json=payload, headers=headers, stream=True) as resp:
    answer = ""
    for line in resp.iter_lines():
        if not line:
            continue
        event = json.loads(line)
        if event.get("type") == "text_delta":
            answer += event["content"]
            print(event["delta_answer"], end="", flush=True)
        elif event.get("type") == "done":
            print()
            print("usage =", event.get("usage"))
Enter fullscreen mode Exit fullscreen mode

The key event is text_delta. Concatenating every text_delta.content gives you the same final answer you would have received in application/json mode. The final done event can include usage with token counts and terminal_reason.

Render tool activity without blocking the answer

AI Chat v2 can emit structured events for tool use. For example, the stream can include:

{"type":"tool_use","tool_id":"toolu_01ABCDEF","tool_name":"web_search","input":{"query":"上海 2026 春季展览"},"id":"f2f4b3e8-..."}
{"type":"tool_result","tool_id":"toolu_01ABCDEF","output":"...","is_error":false,"id":"f2f4b3e8-..."}
{"type":"text_delta","content":"目前","delta_answer":"目前","id":"f2f4b3e8-..."}
Enter fullscreen mode Exit fullscreen mode

A good UI treats these as separate channels. Render text_delta into the assistant message, show tool_use and tool_result as a collapsible activity log, and attach citation or card events where they belong. If your product does not expose tool details, you can ignore tool_use, tool_result, card, and citation; the final answer still streams through text_delta.

You can also limit self-invocation with max_turns. Setting a low value such as max_turns: 1 enforces a single response without allowing tool invocation.

Add images and files with message blocks

When a user sends more than plain text, use message instead of question. Each item is a content block.

{
  "model": "gpt-5.4",
  "stateful": true,
  "message": [
    { "type": "text", "text": "How many cats are in this picture?" },
    { "type": "image_url", "image_url": { "url": "https://cdn.acedata.cloud/cats.jpg" } }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Supported block types are text, image_url, and file_url. For older clients, the v2 endpoint still recognizes references: ["https://...", ...]; image suffixes become image_url blocks and other file types become file_url blocks.

When to use async mode

If the request comes from a webhook, monitoring alert, or other background job, pass async: true. The endpoint can return a task-style response with task_id, conversation_id, id, and status: queued. You can later query the result with action: retrieve and the same id, or provide a callback_url that receives { status, answer, usage, error } when the task completes.

That gives you one integration path for interactive chat and unattended jobs, while keeping the request body shape familiar.

The full Ace Data Cloud reference, including event tables and resume flow details, is here: https://platform.acedata.cloud/documents/aichat2-conversations-integration

Top comments (0)