Agent 流式對話接口 SSE 實時輸出 iTick 金融 AI 文檔 SDK & 開發工具 iTick Agent 流式對話接口文檔,基於 SSE 提供實時 AI 回覆輸出,支援思考過程展示、心跳保活、thread_id 會話保持及多語言請求示例。
Agent 流式對話接口文檔
基於 SSE(Server-Sent Events)的流式對話接口,按 Token 逐字輸出 AI 回覆內容,支援思考過程展示和心跳保活。
接口信息
| 項目 | 說明 |
|---|---|
| 請求方式 | POST |
| 接口路徑 | /agent/stream/v1 |
| 通信協議 | SSE(text/event-stream) |
| 認證方式 | Header token 欄位校驗 |
請求參數
Headers
| 參數名 | 類型 | 必填 | 說明 |
|---|---|---|---|
token | string | 是 | 使用者認證令牌,用於驗權和配額管理 |
Body(JSON)
{
"input": {
"messages": [
{
"type": "human",
"content": "你好,請幫我分析一下..."
}
]
},
"config": {
"configurable": {
"thread_id": "會話ID(可選,不傳則自動生成UUID)"
}
}
}
| 欄位 | 類型 | 必填 | 說明 |
|---|---|---|---|
input.messages | array | 是 | 對話消息列表,支援多輪歷史 |
input.messages[].type | string | 是 | 消息角色:human / user / ai / assistant |
input.messages[].content | string | 是 | 消息文字內容 |
config.configurable.thread_id | string | 否 | 會話執行緒 ID,不傳則自動生成 UUID |
回應格式
回應為 SSE 流,每行以 data: 開頭,包含 JSON 數據。
思考內容事件
模型在推理過程中輸出的思考內容,由 thinking 和 response 標籤包裹:
{"type": "think", "content": " thinking使用者的問題是..."}
{"type": "think", "content": " response"}
- 首次
think事件內容以thinking開頭,表示進入思考階段 - 思考結束後,發送
content: " response"表示思考結束,轉入正式回覆
正式回覆事件
{
"type": "content",
"content": "你好!",
"node": "agent",
"run_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
| 欄位 | 類型 | 說明 |
|---|---|---|
type | string | 固定值 "content" |
content | string | AI 回覆的 Token 片段 |
node | string | LangGraph 節點名稱 |
run_id | string | 當前運行 ID(UUID) |
心跳包
每隔約 1 秒,如果模型仍在處理中,會發送空的心跳數據以維持連接:
data:
流結束標記
data: [DONE]
錯誤事件
模型安全策略攔截:
{
"type": "error",
"code": 3,
"message": "模型生成內容被安全策略阻止,請嘗試調整輸入內容。"
}
通用異常:
{
"error": "ExceptionClassName",
"message": "錯誤詳情"
}
Token 認證失敗:
{
"type": "package permission not enough",
"code": 2002
}
| 錯誤碼 | 說明 |
|---|---|
2002 | token 無效、不存在或配額已用完 |
3 | 內容被安全策略攔截 |
認證邏輯
- 從請求 Header 中取得
token。 - 透過
TokenAuthManager校驗 token 是否在 Redis 中存在有效授權記錄。 - 校驗 token 是否過期(
expire_time)。 - 校驗 token 的 credits 配額是否耗盡(基於輸入/輸出 token 用量估算)。
任一校驗失敗都會回傳 code: 2002 錯誤。
請求示例
cURL(快速測試)
curl -X POST https://agent.itick.org/agent/stream/v1 \
-H "Content-Type: application/json" \
-H "token: your_token_here" \
-d '{
"input": {
"messages": [
{"type": "human", "content": "你好,請介紹一下自己"}
]
},
"config": {
"configurable": {
"thread_id": "session-001"
}
}
}'
Go
package main
import (
"bufio"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
)
const (
apiURL = "https://agent.itick.org/agent/stream/v1"
token = "your_token_here"
)
type RequestBody struct {
Input struct {
Messages []Message `json:"messages"`
} `json:"input"`
Config struct {
Configurable struct {
ThreadID string `json:"thread_id"`
} `json:"configurable"`
} `json:"config"`
}
type Message struct {
Type string `json:"type"`
Content string `json:"content"`
}
func main() {
body := RequestBody{}
body.Input.Messages = []Message{
{Type: "human", Content: "你好,請介紹一下自己"},
}
body.Config.Configurable.ThreadID = "session-001"
jsonBody, _ := json.Marshal(body)
req, _ := http.NewRequest("POST", apiURL, strings.NewReader(string(jsonBody)))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("token", token)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("請求失敗:", err)
return
}
defer resp.Body.Close()
reader := bufio.NewReader(resp.Body)
for {
line, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
break
}
fmt.Println("讀取失敗:", err)
break
}
line = strings.TrimSpace(line)
if line == "" {
continue
}
if strings.HasPrefix(line, "data: ") {
dataStr := line[6:]
if dataStr == "[DONE]" {
fmt.Println("=== 流結束 ===")
break
}
fmt.Println(dataStr)
}
}
}
Python
import json
import requests
url = "https://agent.itick.org/agent/stream/v1"
headers = {
"Content-Type": "application/json",
"token": "your_token_here"
}
body = {
"input": {
"messages": [
{"type": "human", "content": "你好,請介紹一下自己"}
]
},
"config": {
"configurable": {
"thread_id": "session-001"
}
}
}
response = requests.post(url, headers=headers, json=body, stream=True)
for line in response.iter_lines():
if not line:
continue
line = line.decode("utf-8")
if line.startswith("data: "):
data_str = line[6:]
if data_str == "[DONE]":
print("=== 流結束 ===")
break
data = json.loads(data_str)
print(data)
Java
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
public class AgentStreamClient {
private static final String API_URL = "https://agent.itick.org/agent/stream/v1";
private static final String TOKEN = "your_token_here";
public static void main(String[] args) throws Exception {
String jsonBody = """
{
"input": {
"messages": [
{"type": "human", "content": "你好,請介紹一下自己"}
]
},
"config": {
"configurable": {
"thread_id": "session-001"
}
}
}
""";
URL url = new URI(API_URL).toURL();
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("token", TOKEN);
conn.setDoOutput(true);
conn.setConnectTimeout(10000);
conn.setReadTimeout(300000);
try (OutputStream os = conn.getOutputStream()) {
os.write(jsonBody.getBytes("UTF-8"));
os.flush();
}
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream(), "UTF-8"))) {
String line;
while ((line = reader.readLine()) != null) {
if (line.isEmpty()) {
continue;
}
if (line.startsWith("data: ")) {
String dataStr = line.substring(6);
if ("[DONE]".equals(dataStr)) {
System.out.println("=== 流結束 ===");
break;
}
System.out.println(dataStr);
}
}
}
conn.disconnect();
}
}
Node.js
const API_URL = "https://agent.itick.org/agent/stream/v1";
const TOKEN = "your_token_here";
async function streamChat() {
const response = await fetch(API_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
"token": TOKEN,
},
body: JSON.stringify({
input: {
messages: [{ type: "human", content: "你好,請介紹一下自己" }]
},
config: {
configurable: { thread_id: "session-001" },
},
}),
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value, { stream: true });
const lines = text.split("\n");
for (const line of lines) {
if (line.startsWith("data: ")) {
const dataStr = line.slice(6);
if (dataStr === "[DONE]") {
console.log("=== 流結束 ===");
return;
}
if (dataStr) {
console.log(JSON.parse(dataStr));
}
}
}
}
}
streamChat().catch(console.error);
注意事項
- 連接保活:接口每 1 秒發送心跳包,防止代理(如 Nginx)超時斷開連接。Nginx 需配置
proxy_buffering off或X-Accel-Buffering: no。 - 用戶端斷開檢測:服務端會實時檢測用戶端是否斷開,一旦斷開會立即取消 LangGraph 任務,節省 LLM 算力。
- 多輪對話:
messages陣列支援傳入完整的歷史消息,服務端會自動將dict格式的消息轉換為 LangChain 消息物件。 - thread_id:建議用戶端維護
thread_id以保持多輪對話的上下文連續性;不傳則每次生成新會話。