跳至主要内容

REFERENCE

SDK 範例

@asgard-js/core 與 @asgard-js/react 的用法與可以直接操作的範例。

停止生成

run 是在伺服器端獨立執行的。這代表停止生成不是「切斷本地連線」——早期只呼叫 AbortController.abort() 的做法只是停止觀看,agent 其實還在跑、還在燒 token、還在寫對話紀錄, 而 UI 卻宣稱已經停了。

現在停止是送一個請求要後端暫停那個 run,而且非同步完成。

  1. 兩個時刻​

    stopGeneration() resolve 只代表請求被受理;run 真正停下來,是 SSE 串流走到終止事件的那一刻—— 和一個正常結束的 run 走的是同一個事件。這兩個時刻之間,channel 處於 isStopping。

    await stopGeneration();  // ← 受理(不是「已停止」)
    // … isStopping === true …
    // ← 串流上的終止事件抵達,此時才回到可送出狀態
  2. 內建 footer 已經處理好整段流程,不需要接任何線。下面的規則只有在你用 renderFooter 換掉 footer、或自己做送出入口時才需要在意。

  3. 1. 每個送出入口都要同時擋 isStopping​

    不是只擋 isConnecting。舊的 run 還沒真的結束,這時送出會讓兩個 run 同時寫進同一份對話紀錄。 使用者的草稿要保留,不要清掉。

    2. 只有 canStop 為真時才顯示停止控制​

    isConnecting 一詞四義——使用者自己的回合、RESET_CHANNEL 歡迎訊息、transcript 重新加入、 隱形的 nudge——其中只有第一種可以停。canStop 精確地只表示那一種。

    function CustomFooter() {
    const {
    sendMessage, isConnecting, isStopping,
    canStop, canForceStop, stopGeneration,
    } = useAsgardContext();

    const [value, setValue] = useState("");
    const canSend = !isConnecting && !isStopping && value.trim().length > 0;

    const onStop = (): void => {
    // DOM handler 不能 reject。失敗時 SDK 會把狀態回捲成 idle,
    // 控制項只是變回可按——想自己呈現錯誤就改成 await。
    void stopGeneration?.({ force: canForceStop }).catch(() => undefined);
    };

    if (canStop || isStopping) {
    return (
    <button onClick={onStop} disabled={isStopping && !canForceStop}>
    {canForceStop ? "強制停止" : isStopping ? "停止中…" : "停止"}
    </button>
    );
    }

    return (
    <button onClick={() => sendMessage?.({ text: value })} disabled={!canSend}>
    送出
    </button>
    );
    }
  4. 狀態旗標​

    旗標意義
    isConnectingchannel 正忙——四種原因之一,不等於「可停止」
    canStop目前是使用者發起的 run,可以停
    isStopping停止已受理,終止事件尚未抵達
    canForceStop受理後約 10 秒仍未收到終止事件,可升級為強制停止(隱含 isStopping)

    canForceStop 是給沒有回應的 agent 的退路,正常情況不該走到。再按一次會以 force: true 重呼端點,告訴後端放棄這個 run。

  5. ChannelBusyError 防線​

    除了 UI 擋,core 的 sendMessage() 也會在 run 進行中直接 reject:

    import { ChannelBusyError, isChannelBusyError } from "@asgard-js/core";

    try {
    await sendMessage({ text });
    } catch (err) {
    if (isChannelBusyError(err)) {
    // 這則訊息沒有被送出,也沒有在對話裡留下任何痕跡
    }
    }

    它在推出樂觀的 user 泡泡之前就拒絕,所以被擋下的訊息不會在對話串裡留下殘影。

  6. 對話會活過一次停止​

    紀錄保留、被中止的回合由後端回捲,下一則訊息延續同一段對話——不需要重建 channel。

  7. 也看看​

準備好開始了嗎?

想知道 Asgard 怎麼用在你的團隊?跟我們聊聊。

聯絡我們