iT邦幫忙

2025 iThome 鐵人賽

DAY 22
0
生成式 AI

打造基於 MCP 協議與 n8n 工作流的會議處理 Agent系列 第 22

Day 22 智慧對話機制 — 澄清意圖與會議脈絡問答

  • 分享至 

  • xImage
  •  

昨天我們完成了歷史會議記錄的載入與管理功能,但是當使用者的指令不夠明確時,系統沒辦法主動確認意圖,以及使用者無法針對已完成的會議記錄進行補充提問,因為它缺乏「對話脈絡」的概念。

因此今天的目標是建立智慧澄清對話機制,讓 AI 在指令不明確時主動向使用者確認意圖,並且實作會議脈絡問答功能,允許使用者針對歷史會議進行後續的補充提問。

今天的目標與挑戰

  • 在「新會議處理」分頁與「歷史會議記錄」分頁建立對話式輸入介面
  • 建立對話脈絡還原,讓使用者點選歷史會議後,能在專屬視窗中進行後續提問
  • 建立脈絡問答 Prompt
  • 建立智慧澄清機制,在處理新會議時加入「意圖分析」,若指令模糊則主動提問
  • 設計脈絡感知 Prompt
  • 整合至 Gradio 介面,將新的雙向互動模式整合至現有 UI

Step 1:建立歷史會議的「對話脈絡還原」

這個功能的核心是讓 AI 「記住」我們正在談論哪一場會議。當我們在介面上點選了一筆歷史紀錄,系統就必須將該紀錄的完整內容載入到一個暫存的「對話狀態」中,接下來的所有問答都將圍繞這個狀態展開。

1-1 更新 meeting_history.py 新增脈絡生成功能

為了讓 AI 能快速理解一場會議的重點,我在 meeting_history.py 中新增了一個輔助函式 create_context_summary。它的作用是將一筆完整的會議紀錄,打包成一份結構清晰、適合 AI 閱讀的文字摘要。

在原有的程式碼裡的,新增以下兩個函式

    # 儲存完整會議記錄以供脈絡使用
    def get_full_record_by_session(self, session_id: str) -> dict | None:
        """根據 Session ID 取得最完整的單筆紀錄"""
        records = self.get_all_records()
        for record in records:
            if record.get("session_id") == session_id:
                return record
        return None
    
    # 建立脈絡摘要
    def create_context_summary(self, session_id: str) -> dict | None:
        """為指定的 session_id 建立 AI 可讀的上下文摘要"""
        record = self.get_full_record_by_session(session_id)
        if not record:
            return None

        # 將紀錄打包成一個字典格式的 context
        context = {
            "session_id": session_id,
            "timestamp": record.get("timestamp", "未知"),
            "project_name": record.get("project_name", "未指定"),
            "meeting_type": record.get("meeting_type", "未分類"),
            "summary": record.get("summary", "無摘要"),
            "tasks": record.get("tasks", "無任務"),
            "participants": record.get("participants", []),
            "deadline_date": record.get("deadline_date", "未設定")
        }
        return context

函式說明

  1. create_context_summary :它會接收一個 session_id,並找到對應的完整會議紀錄,然後將其中最重要的資訊(像是專案名稱、摘要、行動任務等)整理成一個乾淨的 dict 物件,這個物件就是我們接下來要餵給 AI 的「對話脈絡」。

1-2 更新 app.py 串接脈絡問答流程

接下來,我在 app.py 中進行了較大幅度的修改,以支援新的問答介面。

app.py 中新增與修改的關鍵內容

# 新增一個專門用於上下文問答的 Prompt
def build_context_qa_prompt(context):
    return f"""你是一個精確的會議問答助理。你的任務是根據下方提供的「會議脈絡」,準確回答使用者的提問。

[會議脈絡]
- 專案名稱:{context.get('project_name')}
- 會議類型:{context.get('meeting_type')}
- 會議時間:{context.get('timestamp')}
- 參與者:{', '.join(context.get('participants')) if context.get('participants') else '未記錄'}
- 完成期限:{context.get('deadline_date')}

[會議摘要]
{context.get('summary')}

[行動任務]
{context.get('tasks')}

[你的規則]
1. 嚴格根據上方提供的「會議脈絡」、「摘要」和「任務」來回答問題。
2. 絕對不可以編造或提供脈絡以外的資訊。
3. 如果問題的答案在脈絡中找不到,請明確回答「根據目前提供的會議內容,我無法回答這個問題。」
4. 回答盡量簡潔、直接。
"""

# 新增一個專門處理上下文問答的 API 呼叫函式
def answer_with_context(question, context):
    system_prompt = build_context_qa_prompt(context)
    return call_lm_studio_api(system_prompt, question, temperature=0.3)

# ...

# 處理歷史會議問答的主要邏輯
def process_history_qa(question: str, context_state: dict, chat_history: list):
    # ... (省略錯誤處理)
    try:
        # 呼叫新的問答函式
        answer = answer_with_context(question, context_state)
        # ... (處理 API 回傳結果)
        bot_message = answer.get("content", "抱歉,我無法處理您的請求。")
    except Exception as e:
        # ... (錯誤處理)

    chat_history.append((question, bot_message))
    return "", chat_history

# 載入歷史會議紀錄的函式
def load_history_record(evt: gr.SelectData):
    try:
        row_index = evt.index[^0]
        # ... (從 history_manager 取得紀錄)
        record = all_records[row_index]
        session_id = record.get("session_id")

        # 使用剛才建立的函式來生成上下文
        context_summary = history_manager.create_context_summary(session_id)
        
        # ... (產生要在介面上顯示的 Markdown 內容)

        # 建立一個初始對話,引導使用者開始提問
        initial_chat = [
            (None, f"已為您載入 {record.get('project_name', '該')} 會議的紀錄,請問有什麼想了解的嗎?")
        ]
        
        # 將上下文存入 gr.State,並更新對話視窗
        return output_markdown, row_index, context_summary, initial_chat
    except Exception as e:
        # ... (錯誤處理)

# ... (Gradio 介面佈局)

# 將 UI 事件與後端函式綁定
history_table.select(
    fn=load_history_record,
    inputs=None,
    outputs=[history_output, selected_record_index, context_state, history_qa_chatbot]
)

history_qa_submit.click(
    fn=process_history_qa,
    inputs=[history_qa_input, context_state, history_qa_chatbot],
    outputs=[history_qa_input, history_qa_chatbot]
)

# ...

函式說明

  1. build_context_qa_prompt:我設計了一個新的 Prompt,明確指示 AI 扮演「會議問答助理」,並將會議的詳細資料作為上下文提供給它,同時設立嚴格規則,防止 AI 亂答。
  2. load_history_record:這個函式與「歷史會議記錄」表格的 select 事件綁定。當使用者點選一筆紀錄時,它會觸發,並呼叫 history_manager.create_context_summary 來準備好「對話脈絡」。這個脈絡被儲存在一個名為 context_stategr.State 元件中,它就像一個隱藏的記憶體,專門存放當前對話的上下文。
  3. process_history_qa:當使用者在歷史問答框中輸入問題並送出時,此函式會被觸發。它會從 context_state 中讀取先前儲存的會議脈絡,連同使用者的問題一起傳遞給 answer_with_context 函式,最後將 AI 的回答更新到對話視窗上。

Step 2:建立「智慧澄清」對話機制

這個機制改變了 AI 處理任務的流程,從「接收指令 → 執行」變成了「接收指令 → 理解與確認 → 執行」。

2-1 設計「意圖分析」專用 Prompt

要讓 AI 學會「提問」,關鍵在於要設計一個能夠分析指令清晰度的 Prompt。

app.py 中新增意圖分析 Prompt

def build_intent_analysis_prompt():
    return """你是一個專業的指令分析專家。你的任務是分析使用者的會議處理指令,判斷意圖是否明確,並決定是否需要進一步澄清。

[指令分析標準]
1. **明確指令的特徵**:
   - 明確指出要執行的動作(摘要、任務提取、參與者辨識等)
   - 提及需要特別注意的重點(時間、人員、專案等)
   - 指令完整且無歧義

2. **需要澄清的情況**:
   - 指令過於簡短或模糊(如「處理一下」、「分析」)
   - 缺少關鍵資訊(如未說明要提取什麼)
   - 包含多個動作但未明確優先順序
   - 使用模糊的詞彙(如「大概」、「可能」、「差不多」)

[分析步驟]
1. **識別動作**:使用者想要執行什麼動作
2. **評估完整性**:執行該動作需要的資訊是否充足
3. **判斷明確度**:指令是否清晰無歧義

[輸出格式]
請嚴格按照以下 JSON 格式回傳:

如果指令明確,回傳:
{
  "is_clear": true,
  "intent": "使用者的明確意圖描述",
  "actions": ["動作1", "動作2"],
  "proceed": true
}

如果需要澄清,回傳:
{
  "is_clear": false,
  "unclear_points": ["不明確的地方1", "不明確的地方2"],
  "clarification_question": "請問您希望我執行以下哪些動作?",
  "suggestions": [
    "選項1:生成會議摘要與提取任務",
    "選項2:只提取行動任務",
    "選項3:完整分析包含摘要、任務、參與者與時間"
  ],
  "proceed": false
}"""

Prompt 設計要點

  • 角色定位:將 AI 設定為「專業的指令分析專家」。
  • 明確規則:提供了清晰與模糊指令的具體範例。
  • 嚴格的 JSON 輸出:這個非常重要!透過 proceed 這個布林值欄位,程式邏輯可以輕易地判斷下一步該執行任務還是該提問。clarification_questionsuggestions 則直接提供了與使用者互動所需的內容。

2-2 改造 app.py 的主處理流程

我重構了 process_new_meeting 函式,將意圖分析作為第一道關卡

# 新增一個全域變數來管理澄清對話的狀態
clarification_context = {
    "is_in_clarification": False,
    "original_instruction": None,
    "last_suggestions": []
}

def process_new_meeting(audio_filepath, command_text, chat_history, progress=gr.Progress(track_tqdm=True)):
    global clarification_context

    # ... (省略初步的輸入檢查)

    # 如果目前不是在澄清模式中
    if not clarification_context["is_in_clarification"]:
        # 儲存原始指令
        clarification_context["original_instruction"] = command_text
        
        # 步驟 1: 進行意圖分析
        intent_result = analyze_intent(command_text)
        
        # 如果分析結果是清晰的 (proceed: true)
        if intent_result.get("proceed", False):
            # 直接執行會議處理流程
            return execute_meeting_processing(audio_filepath, command_text, chat_history, progress)
        # 如果分析結果是模糊的 (proceed: false)
        else:
            # 進入澄清模式
            clarification_context["is_in_clarification"] = True
            clarification_context["last_suggestions"] = intent_result.get("suggestions", [])
            
            # 從 AI 的分析結果中取得問題和建議選項
            clarification_msg = intent_result.get("clarification_question", "我不太確定您的意思,可以說得更詳細一點嗎?")
            suggestions = intent_result.get("suggestions", [])
            if suggestions:
                clarification_msg += "\n\n您可以試著這樣說,或直接回覆數字:\n"
                for i, suggestion in enumerate(suggestions, 1):
                    clarification_msg += f"{i}. {suggestion}\n"

            chat_history.append((command_text, clarification_msg))
            return "", chat_history, gr.DataFrame(value=get_recent_records_for_display(limit=5))

    # 如果目前已經在澄清模式中
    else:
        # 使用者的回覆 + 原始指令 = 完整的指令
        mapped_instruction = map_option_to_instruction(command_text, clarification_context["last_suggestions"])
        full_instruction = f"{clarification_context['original_instruction']},補充說明:{mapped_instruction}"
        
        # 步驟 2: 再次進行意圖分析
        intent_result = analyze_intent(full_instruction)
        
        # 如果這次指令清晰了
        if intent_result.get("proceed", False):
            # 結束澄清模式並執行任務
            clarification_context["is_in_clarification"] = False
            return execute_meeting_processing(clarification_context.get("audio_filepath"), full_instruction, chat_history, progress)
        # 如果指令還是不清楚
        else:
            # 繼續提問... (邏輯同上)

程式碼說明

  1. 我使用一個全域 dict clarification_context 來作為澄清模式的狀態管理器,記錄當前是否在提問、以及原始的指令是什麼。
  2. 當使用者第一次下指令時,process_new_meeting 會先呼叫 analyze_intent
  3. 如果回傳的 JSON 中 "proceed"false,系統就進入澄清模式。它會將 AI 生成的 clarification_questionsuggestions 顯示在聊天視窗,然後等待使用者回覆。
  4. 當使用者回覆後(例如輸入「1」或更詳細的說明),函式會再次被觸發。這次因為 clarification_context["is_in_clarification"]True,程式會進入 else 區塊,將使用者的補充說明與原始指令結合,再次進行意圖分析。這個過程會一直循環,直到 AI 認為指令足夠清晰為止。

Step 3:測試與驗證

現在來測試新實作的功能。

3-1 測試澄清流程

  1. 啟動 Gradio 前端介面
python app.py
  1. 測試模糊指令

    • 上傳一個測試音訊
    • 輸入模糊指令:「幫我處理一下」
    • 觀察 AI 是否會主動詢問並提供選項

    澄清流程 1

    指令:幫我處理一下

    澄清流程 2

    指令:3

    澄清流程 3
    澄清流程 4

  2. 測試明確指令

    • 輸入明確指令:「請生成會議摘要與提取行動任務,特別注意時間、人員等相關資訊」
    • 確認 AI 直接執行處理流程

    澄清流程 5

3-2 測試歷史會議問答

  1. 載入歷史會議

    • 切換到「歷史會議記錄」分頁
    • 點擊表格中的任一筆記錄
      測試歷史會議問答 1
  2. 測試提問

    • 輸入問題:「這個會議主要在討論什麼事情?」
    • 輸入問題:「你可以更具體說明任務1該怎麼做嗎?」
    • 確認 AI 能根據會議脈絡回答

    測試歷史會議問答 2

    提問:這個會議主要在討論什麼事情?

    測試歷史會議問答 3

    提問:這場會議有誰出席?

    測試歷史會議問答 4

    提問:你可以更具體說明任務1該怎麼做嗎?

    測試歷史會議問答 5

    測試歷史會議問答 6


今天的成果總結

完成項目

  • 在「歷史會議記錄」分頁成功實作了針對單一會議的脈絡問答功能
  • 新增了專門用於上下文問答的 build_context_qa_prompt,確保 AI 回答的準確性
  • 建立了智慧澄清機制,讓 AI 在指令不夠明確時,能夠主動向使用者提問確認
  • 設計了 build_intent_analysis_prompt 意圖分析專用 Prompt,作為澄清機制的判斷核心
  • 升級了 Gradio 的互動邏輯,成功支援了載入歷史脈絡與澄清對話兩種新的雙向溝通流程

心得

終於打破了「人說,AI 做」的單向模式,我成功賦予了 AI 「提問」與「記憶」的能力,整個系統從一個被動的執行工具,轉變為一個能與使用者進行有效溝通的「智慧助理」。

我體會到好的互動體驗來自於對「不確定性」的管理,現在透過「意圖分析 → 澄清 → 執行」這個新流程,可以將不確定性轉化為一個確認的機會,不僅大幅提升了任務執行的成功率,也讓使用者感覺 AI 變得更「聰明」、更「體貼」了。

同時,脈絡問答功能的加入,也讓儲存下來的會議記錄不再是冰冷的文字,而是一份可以隨時查閱、探勘的動態知識庫,極大地提升了實用的價值。

🎯 明天計劃

強化對話體驗,實作多輪澄清對話、增加明確的脈絡視覺提示,並精煉核心 Prompt,讓 AI 的理解與互動能力更上一層樓。


上一篇
Day 21 Gradio 介面更新 — 加入歷史會議列表與即時處理狀態
系列文
打造基於 MCP 協議與 n8n 工作流的會議處理 Agent22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言