Harnix

Streaming

Khung SSE state|tool|token|approval|question|done|error, keepalive, kết nối lại.

Sau khi gửi một message (widget, server-to-server, hoặc POST /v2/conversations/{id}/messages), bạn có một run_id. Theo dõi câu trả lời theo thời gian thực bằng:

GET https://api.harnix.vn/v2/runs/{run_id}/stream
Authorization: Bearer <token>

(hoặc X-API-Key: <key> khi gọi server-to-server). Response là một luồng Server-Sent Events, mỗi khung là một dòng data: <json> kết bằng một dòng trống.

EventSource không mang được Authorization

EventSource của trình duyệt không cho gắn header tuỳ ý, nên không dùng được để mang Bearer <token>. Dùng fetch và tự đọc ReadableStream của response (ví dụ bên dưới), hoặc một client SSE hỗ trợ header tuỳ chỉnh.

Các loại khung

typedataKhi nào
state{ "state": "running", "reason": "claimed" }Run đổi trạng thái.
token{ "delta": "Xin chào" }Một mẩu văn bản model vừa sinh ra.
tool{ "call_id": "...", "name": "knowledge_search", "phase": "start" | "end", "is_error"?: true }Một tool bắt đầu/kết thúc.
approval{ "run_id": "...", "expires_at": "...", "calls": [...] }Agent muốn gọi một tool cần end-user xác nhận; run dừng chờ. Không phải khung cuối.
question{ "run_id": "...", "call_id": "...", "expires_at": "...", "questions": [...] }Agent gọi ask_user để hỏi lại end-user; run dừng ở waiting_input. Không phải khung cuối.
done{ "result": {...}, "citations": [...] }Run kết thúc thành công. Luôn là khung cuối.
error{ "message": "..." }Run thất bại hoặc bị huỷ. Luôn là khung cuối.

done.data.citations luôn là một mảng ([] nếu không có trích dẫn nào) — cùng giá trị với done.data.result.citations, chỉ đặt ở vị trí dễ đọc hơn cho UI. Khung error không có key citations.

Khung approval — hành động chờ xác nhận

Khi agent gọi một tool của connector cần xác nhận (mặc định: mọi tool server không đánh dấu chỉ-đọc), run dừng lại và stream phát một khung approval:

{
  "type": "approval",
  "data": {
    "run_id": "0e2b...",
    "expires_at": "2026-10-09T08:05:00+00:00",
    "calls": [
      {
        "call_id": "call_1",
        "server_name": "Cửa hàng",
        "name": "create_order",
        "title": "Tạo đơn hàng",
        "arguments": { "sku": "A-100", "quantity": 2 }
      }
    ]
  }
}

calls mang tham số thật agent sắp gửi — đó chính là thứ người dùng đồng ý hay từ chối; title là tên dễ đọc nếu server khai, server_name là tên connector bạn đặt trên console. Hiện chúng cho người dùng (lời trợ lý ngay trước đó là lời giải thích) rồi trả lời bằng POST /v2/runs/{run_id}/approval trước expires_at (mặc định 5 phút sau khi dừng).

Sau khung approval, stream không đóng: khi có quyết định, run chạy tiếp và cùng kết nối đó phát tiếp các khung state (pending rồi running), tool và token như bình thường. Một tool bị từ chối hoặc hết hạn kết thúc bằng tool với phase: "end" và is_error: true; trợ lý tự nói với người dùng rằng hành động không được thực hiện, rồi run kết thúc bằng done như thường lệ.

Khung question — câu hỏi chờ người dùng trả lời

Khi agent gọi tool tích hợp ask_user để hỏi lại người dùng, run dừng lại ở trạng thái waiting_input và stream phát một khung question:

{
  "type": "question",
  "data": {
    "run_id": "0e2b...",
    "call_id": "call_1",
    "expires_at": "2026-10-10T12:10:00+00:00",
    "questions": [
      {
        "header": "Chi nhánh",
        "question": "Bạn muốn nhận đơn hàng tại chi nhánh nào?",
        "multi_select": false,
        "options": [
          { "label": "Quận 1", "description": "Mở cửa đến 22:00" },
          { "label": "Quận 3", "description": "Mở cửa đến 21:00" }
        ]
      }
    ]
  }
}

Mỗi câu hỏi gồm tiêu đề ngắn (header, tối đa 12 ký tự), câu hỏi (question, tối đa 500 ký tự), cờ chọn một hay nhiều (multi_select), và danh sách 2–4 lựa chọn (options). Hiển thị chúng cho người dùng rồi trả lời bằng POST /v2/runs/{run_id}/answer — hoặc gửi một tin nhắn văn bản tiếp theo vào cuộc hội thoại — trước expires_at (mặc định 10 phút sau khi dừng).

Tương tự như approval, sau khung question stream không đóng: khi người dùng trả lời (hoặc hết hạn), run tiếp tục và cùng kết nối đó phát tiếp các khung state (pending rồi running), tool (chứa kết quả của lượt hỏi ask_user), token và done như bình thường.

Giữa các khung, server có thể gửi dòng chú thích : keepalive (không phải một khung — bỏ qua mọi dòng bắt đầu bằng :) để giữ kết nối sống qua một proxy trung gian trong lúc model đang nghĩ lâu.

Ví dụ

curl -N https://api.harnix.vn/v2/runs/0e2b.../stream \
  -H "Authorization: Bearer $TOKEN"

Kết nối lại

Một run đã kết thúc trả về toàn bộ nội dung ngay từ Postgres — không có gì mất kể cả khi bạn kết nối trễ, hay kết nối lại giữa chừng. Nếu kết nối rớt trong lúc run vẫn đang chạy, gọi lại đúng URL đó: server phát lại mọi khung state/tool bạn đã bỏ lỡ (không phát lại token delta cũ — chỉ nội dung cuối cùng qua khung done), rồi tiếp tục stream trực tiếp. Một hành động vẫn đang chờ xác nhận được phát lại thành khung approval; một câu hỏi vẫn đang chờ trả lời được phát lại thành khung question; một hành động hay câu hỏi đã được giải quyết thì không — bạn thấy kết quả qua khung tool tương ứng.

Streaming tạm thời không khả dụng

Nếu hạ tầng streaming (Redis) tạm thời không kết nối được, endpoint trả 503 với mã redis_unavailable. Bản thân run không bị ảnh hưởng — nó vẫn chạy và ghi lại đầy đủ. Đọc lại câu trả lời bằng GET /v2/conversations/{id} sau khi thử lại stream, hoặc poll conversation đó vài giây một lần.

Huỷ một run đang chạy

POST /v2/runs/{run_id}/cancel — xem Idempotency & huỷ. Huỷ được cả một run đang chờ xác nhận hoặc đang chờ trả lời câu hỏi.

Bước tiếp theo

  • Cuộc hội thoại — đọc lại lịch sử sau khi run kết thúc, cùng pending_approval và pending_question khi mở lại một cuộc hội thoại đang chờ.
  • Lỗi — run_not_found, run_not_waiting, redis_unavailable.

Trên trang này