import requests import json # 雖然 requests 會處理 json,但保留導入並無害 import time from datetime import datetime import difflib # 設定使用的模型名稱 MODELS = ["gemmapro", "gemmapro-r", "gemmapro-20kctx"] OLLAMA_URL = "http://localhost:11434/api/generate" def send_request_to_ollama(prompt, model): """向指定模型發送請求並獲取回應""" data = { "model": model, "prompt": prompt, "stream": False } try: response = requests.post(OLLAMA_URL, json=data) response.raise_for_status() # 檢查 HTTP 請求是否成功 (狀態碼 2xx) return response.json()["response"] except requests.exceptions.RequestException as e: print(f"[錯誤] 模型 {model} 請求失敗: {e}") return f"[錯誤] 向 {model} 發送請求時發生錯誤: {str(e)}" # 提供更明確的錯誤訊息 except KeyError: print(f"[錯誤] 模型 {model} 回應格式不符預期,找不到 'response' 鍵。") return f"[錯誤] 模型 {model} 回應格式錯誤。" except json.JSONDecodeError: print(f"[錯誤] 模型 {model} 回應非有效的 JSON 格式: {response.text}") return f"[錯誤] 無法解析來自 {model} 的回應。" def initialize_markdown_file(): """初始化 Markdown 報告檔案,包含 YAML metadata""" timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") metadata = { "title": "多模型推理彙整報告", "date": timestamp, "models": MODELS, "author": "自動化程式", "description": "本報告整合多個模型對多個問題的回應,進行去蕪存菁後的彙整。" } try: # 增加檔案操作的錯誤處理 with open("output_moremodels.md", "w", encoding="utf-8") as file: file.write("---\n") for key, value in metadata.items(): if isinstance(value, list): file.write(f"{key}:\n") for item in value: file.write(f" - {item}\n") # 標準 YAML 列表縮排 else: file.write(f"{key}: {value}\n") file.write("---\n\n") file.write(f"# {metadata['title']}\n\n") file.write(f"產出時間: {timestamp}\n\n") file.write(f"使用模型: {', '.join(MODELS)}\n\n---\n\n") print("[初始化] 已建立 output_moremodels.md") except IOError as e: print(f"[錯誤] 無法寫入檔案 output_moremodels.md: {e}") # 如果無法建立檔案,後續的 append 會失敗,可以考慮在這裡中止程式 exit(1) # 中止程式執行 def append_to_markdown(index, prompt, responses): """將問題與各模型回應結果寫入 Markdown 檔案""" try: # 增加檔案操作的錯誤處理 with open("output_moremodels.md", "a", encoding="utf-8") as file: file.write(f"## 問題 {index}\n\n") file.write(f"### 提問\n\n```\n{prompt}\n```\n\n") for model, response in responses.items(): # 確保 response 是字串,避免後續處理錯誤 response_text = str(response) if response is not None else "[無回應]" file.write(f"### 模型:{model}\n\n{response_text.strip()}\n\n") # 自動生成摘要彙整 # 確保將有效的回應傳遞給摘要函數 valid_responses = {m: r for m, r in responses.items() if isinstance(r, str)} summary = summarize_responses(prompt, valid_responses) # 傳遞有效的回應字典 file.write(f"### 彙整摘要\n\n{summary}\n\n---\n\n") except IOError as e: print(f"[錯誤] 無法附加內容至檔案 output_moremodels.md: {e}") def summarize_responses(prompt, responses): """ 將多個模型的回應進行比較,提取相似的句子,並整合成通順的摘要。 注意:目前的實作僅基於句法相似度,可能無法完全捕捉語意。 """ # 如果沒有有效的回應,直接返回提示訊息 if not responses: return "沒有從任何模型收到有效回應可供摘要。" # 將每個回應分句 sentence_lists = [] for response in responses.values(): # 更穩健的分句方式,處理不同結尾符號和換行 # 替換換行符為空格,然後用常見的句尾符號分割 processed_response = response.replace('\n', ' ') sentences = [] current_sentence = "" for char in processed_response: current_sentence += char if char in ['。', '!', '?', '.', '!', '?']: # 包含中英文句尾符號 trimmed_sentence = current_sentence.strip() if trimmed_sentence: # 確保不是空字串 sentences.append(trimmed_sentence) current_sentence = "" # 加入最後一句(如果有的話且沒有結尾符號) trimmed_sentence = current_sentence.strip() if trimmed_sentence: sentences.append(trimmed_sentence) sentence_lists.append(sentences) # 建立一個集合來儲存已處理的句子索引,避免重複處理同一個句子 processed_indices = set() summary_sentences = [] all_sentences = [(i, j, sent) for i, lst in enumerate(sentence_lists) for j, sent in enumerate(lst)] # 比較所有句子對的相似度 for idx1 in range(len(all_sentences)): model_idx1, sent_idx1, sent1 = all_sentences[idx1] # 如果這個句子已經被處理過(作為相似對的一部分),則跳過 if (model_idx1, sent_idx1) in processed_indices: continue best_match = None max_similarity = 0.7 # 設定一個基礎閾值 (可調整) for idx2 in range(idx1 + 1, len(all_sentences)): model_idx2, sent_idx2, sent2 = all_sentences[idx2] # 確保比較的是不同模型的回應中的句子 if model_idx1 == model_idx2: continue # 如果第二個句子也處理過了,跳過 if (model_idx2, sent_idx2) in processed_indices: continue similarity = difflib.SequenceMatcher(None, sent1, sent2).ratio() # 找尋最高相似度且高於閾值的句子 if similarity > max_similarity: max_similarity = similarity # 選擇較短的句子作為代表 chosen_sentence = sent1 if len(sent1) <= len(sent2) else sent2 best_match = ((model_idx1, sent_idx1), (model_idx2, sent_idx2), chosen_sentence) # 如果找到了相似度高的句子對 if best_match: idx_pair1, idx_pair2, chosen = best_match # 將這對句子都標記為已處理 processed_indices.add(idx_pair1) processed_indices.add(idx_pair2) # 加入摘要列表 summary_sentences.append(chosen) # 處理剩下的、沒有找到高相似度配對的句子(可以選擇性加入) # 這裡可以加入邏輯來包含那些獨特但可能重要的句子 # unique_sentences = [] # for i, lst in enumerate(sentence_lists): # for j, sent in enumerate(lst): # if (i, j) not in processed_indices: # unique_sentences.append(sent) # # 可以選擇將 unique_sentences 加入摘要,或另外呈現 # 格式化輸出 if not summary_sentences: summary = "各模型提供了不同的觀點,未偵測到足夠相似的核心內容可供直接彙整。重點預覽如下:\n\n" for model, response in responses.items(): preview = response.strip().replace('\n', ' ')[:100] # 截取前 100 字元預覽 summary += f"- **{model}**: {preview}...\n" else: summary = "綜合各模型的相似觀點,摘要如下:\n\n" # 可以稍微整理一下摘要句子的順序或進行潤飾 # 目前直接列出 unique_summary_sentences = [] for sentence in summary_sentences: if sentence not in unique_summary_sentences: # 再次去重,以防萬一 unique_summary_sentences.append(sentence) for sentence in unique_summary_sentences: summary += f"- {sentence}\n" # 自動加上結尾句號(如果需要)或保留原樣 # 注意:這裡 prompt 參數沒有被使用,如果未來摘要邏輯需要參考原始問題,可以加入 return summary def main(): # 可自訂多個問題 questions = [ "介紹台灣的夜市文化", "台灣人工智慧發展的現況與挑戰為何?" ] initialize_markdown_file() # 如果這裡失敗,程式會中止 print("[開始] 向模型發送請求...") for i, prompt in enumerate(questions, 1): print(f"[處理中] 問題 {i}/{len(questions)}: {prompt[:30]}...") model_responses = {} for model in MODELS: print(f" └▶ 模型 {model} 推理中...") start_time = time.time() # 記錄開始時間 response = send_request_to_ollama(prompt, model) end_time = time.time() # 記錄結束時間 elapsed_time = end_time - start_time print(f" 回應耗時: {elapsed_time:.2f} 秒") # 顯示每個模型的回應時間 model_responses[model] = response # time.sleep(1) # 根據需要調整延遲,如果 Ollama 伺服器負載不高可以縮短或移除 append_to_markdown(i, prompt, model_responses) print(f"[完成] 問題 {i} 已處理並寫入檔案。") print("[完成] 所有問題已處理完畢,結果保存在 output_moremodels.md") if __name__ == "__main__": main()