博客 / 詳情

返回

前端AI流式輸出深度思考<think>標籤分割

假如我們有這麼一段ai回覆的內容

let text = "<think>\n好的,用户想了解如何判斷手機攝像頭是否是湊數的,希望我用直白易懂的語言來解釋。這個問題需要從實用角度出發,幫助用户識別那些功能冗餘或性能低下的攝像頭。\n我看到的搜索結果提供了很多有價值的信息。根據,湊數攝像頭通常是指那些功能可以被其他攝像頭或算法替代的鏡頭,比如低像素的景深或微距鏡頭。進一步解釋瞭如何通過反證法來判斷:如果去掉某個攝像頭後,其他攝像頭通過算法也能實現相同功能,那這個攝像頭就很可能是湊數的。\n\n</think>\n\n1.先看像素​:遇到200萬像素的鏡頭,心裏先打個問號。\n2.再看名稱​:如果鏡頭名稱是“景深”、“人像風格”等,而非明確表示焦段(如超廣角、3x長焦),就要多留意。\n3.思考能否被替代​:這個鏡頭做的事,​主攝拍照後裁剪或者裝個APP能不能搞定?如果能,它大概率是湊數的。\n4.關注核心鏡頭​:​主攝的素質是根本。一顆優秀的主攝遠比一堆湊數鏡頭重要。其次看超廣角和長焦的規格是否紮實。記住一個簡單的道理​:手機廠商增加一顆真正的、高質量的攝像頭,成本會顯著上升。如果一款手機攝像頭數量很多但價格不高,你就需要多警惕那些低規格的鏡頭了。"

有些AI-Markdown組件是不支持自動識別<think></think>標籤的深度思考的,所以我們需要手動分割<think>標籤,用來區分深度思考和正文內容。我在這裏封裝了一個行數,可以在流式輸出中調用,自動分割

/**
 * ai回覆的思考和正文分塊
 * @param {string} chunk 流式文字
 * @param {object} state ai的content
 * @return 返回分塊後的內容 { buffer: '',thinkParts: [] }
 */
export const processStreamedOutput = (chunk, state) => {
    // 將新的塊添加到緩衝區
    state.buffer += chunk;

    // 處理所有完整的 <think> 標籤(完整保留標籤)
    const thinkRegex = /<think>[\s\S]*?<\/think>/g; // 修改後的正則表達式

    while (true) {
        const match = thinkRegex.exec(state.buffer);
        if (!match) break;

        // 保留完整的 <think>...</think> 標籤
        state.thinkParts.push(match[0]);

        // 更新緩衝區,移除已處理的部分
        thinkRegex.lastIndex = 0; // 重置正則狀態
        state.buffer = state.buffer.replace(match[0], '');
    }

    // 檢查是否存在未閉合的 <think> 標籤
    const openTagIndex = state.buffer.indexOf('<think>');
    if (openTagIndex !== -1) {
        // 保留 <think> 標籤及其後的所有內容到緩衝區
        state.buffer = state.buffer.substring(openTagIndex);
    }

    return state;
};

使用示例:

// 當前ai的對話內容,比如 chatList.at(-1).content,其中content的結構是 { buffer: '',thinkParts: [] }
let chat = { buffer: '',thinkParts: [] }
// 分割
let data = processStreamedOutput(text, chat)
// 結果
console.log(data)


支持在流式對話中的連續調用,每次輸出都可以調用該函數進行一次分割
默認將text添加到buffer緩衝字段,如果深度思考的 <think></think> 標籤出現則説明當前深度思考結束,將包含<think></think>的深度思考內容添加到 thinkParts 數組內,後續正文的內容依舊添加到buffer緩衝字段
在頁面使用的時候需要兩個個輔助函數實現

    /**
     * 深度思考取值
     * @param {number} type 1取深度思考 2取正文
     * @param {object} content AI的content內容
     * @returns 深度思考或正文的正確內容
     */
    const getMarkdown = (type, content) => {
        let {
            thinkParts,
            buffer
        } = content;
        if (type == 1) {
            if (buffer.includes('<think>')) return buffer;
            let state = thinkParts.some((item) => item.includes('<think>'));
            if (state) return thinkParts.at(-1);
            return '';
        } else {
            if (!buffer.includes('<think>')) {
                if (thinkParts.length) {
                    return buffer || '';
                } else {
                    return buffer || '已暫停生成';
                }
            }
        }
    };
    /**
     * 是否有深度思考
     * @param {object} content AI的content內容
     * @returns 是否存在深度思考標籤
     */
    const isThink = (content) => {
        let {
            thinkParts,
            buffer
        } = content;
        let state = thinkParts.some((item) => item.includes('<think>'));
        if (buffer.includes('<think>') || state) {
            return true;
        } else {
            return false;
        }
    };

頁面使用

<!-- 深度思考 -->
<view class="ai-think-chunk" v-show="isThink(msg.content)">
        <markdown-view :theme-color="'#252B3A'" :markdown="getMarkdown(1, msg.content)" />
</view>
<!-- 正文內容 -->
<markdown-view :theme-color="'#252B3A'" :markdown="getMarkdown(2, msg.content)" />

如果深度思考標籤存在,則直接顯示深度思考,並且取深度思考的內容
正文內容直接取值就好了,因為在分割深度思考的那一步已經做了區分了,另外加上getMarkdown輔助函數做內容判斷,如果正文能夠取到,説明正文一定是有內容的

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.