Upload moremodels2.py
Browse files- moremodels2.py +261 -0
moremodels2.py
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import requests
|
| 2 |
+
import json # 雖然 requests 會處理 json,但保留導入並無害
|
| 3 |
+
import time
|
| 4 |
+
from datetime import datetime
|
| 5 |
+
import difflib
|
| 6 |
+
|
| 7 |
+
# 設定 Ollama API 的基礎 URL
|
| 8 |
+
OLLAMA_BASE_URL = "http://localhost:11434/api"
|
| 9 |
+
OLLAMA_GENERATE_URL = f"{OLLAMA_BASE_URL}/generate"
|
| 10 |
+
OLLAMA_TAGS_URL = f"{OLLAMA_BASE_URL}/tags" # 用於獲取模型列表的 API 端點
|
| 11 |
+
|
| 12 |
+
# 不再需要硬性指定模型列表
|
| 13 |
+
# MODELS = ["TaiwanPro-r", "gemmapro", "gemmapro-r", "gemmapro-20kctx"]
|
| 14 |
+
|
| 15 |
+
def get_available_models():
|
| 16 |
+
"""向 Ollama API 查詢本機可用的模型列表"""
|
| 17 |
+
print("[查詢] 正在獲取本機可用的 Ollama 模型...")
|
| 18 |
+
try:
|
| 19 |
+
response = requests.get(OLLAMA_TAGS_URL)
|
| 20 |
+
response.raise_for_status() # 檢查 HTTP 請求是否成功
|
| 21 |
+
data = response.json()
|
| 22 |
+
# 從回應中提取模型名稱列表
|
| 23 |
+
# API 回應格式通常是 {"models": [{"name": "model1:tag", ...}, ...]}
|
| 24 |
+
if "models" in data and isinstance(data["models"], list):
|
| 25 |
+
model_names = [model.get("name") for model in data["models"] if model.get("name")]
|
| 26 |
+
if not model_names:
|
| 27 |
+
print("[警告] 未在本機找到任何 Ollama 模型。請確認已安裝並載入模型。")
|
| 28 |
+
return []
|
| 29 |
+
print(f"[成功] 找到可用的模型: {', '.join(model_names)}")
|
| 30 |
+
return model_names
|
| 31 |
+
else:
|
| 32 |
+
print("[錯誤] 無法從 Ollama API 回應中解析模型列表。回應格式可能已變更。")
|
| 33 |
+
print(f"原始回應: {data}")
|
| 34 |
+
return []
|
| 35 |
+
except requests.exceptions.ConnectionError:
|
| 36 |
+
print(f"[錯誤] 無法連接到 Ollama 服務於 {OLLAMA_BASE_URL}。請確認 Ollama 正在運行。")
|
| 37 |
+
return None # 返回 None 表示連接失敗
|
| 38 |
+
except requests.exceptions.RequestException as e:
|
| 39 |
+
print(f"[錯誤] 查詢可用模型時發生錯誤: {e}")
|
| 40 |
+
return []
|
| 41 |
+
except json.JSONDecodeError:
|
| 42 |
+
print(f"[錯誤] 無法解析來自 {OLLAMA_TAGS_URL} 的回應 (非 JSON 格式): {response.text}")
|
| 43 |
+
return []
|
| 44 |
+
|
| 45 |
+
def send_request_to_ollama(prompt, model):
|
| 46 |
+
"""向指定模型發送請求並獲取回應"""
|
| 47 |
+
data = {
|
| 48 |
+
"model": model,
|
| 49 |
+
"prompt": prompt,
|
| 50 |
+
"stream": False
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
try:
|
| 54 |
+
# 使用定義好的 generate URL
|
| 55 |
+
response = requests.post(OLLAMA_GENERATE_URL, json=data)
|
| 56 |
+
response.raise_for_status() # 檢查 HTTP 請求是否成功 (狀態碼 2xx)
|
| 57 |
+
return response.json()["response"]
|
| 58 |
+
except requests.exceptions.RequestException as e:
|
| 59 |
+
print(f"[錯誤] 模型 {model} 請求失敗: {e}")
|
| 60 |
+
# 如果是因為模型不存在 (404 Not Found),提供更具體的提示
|
| 61 |
+
if response is not None and response.status_code == 404:
|
| 62 |
+
print(f" 提示:模型 '{model}' 可能未完全下載或不存在於 Ollama 中。")
|
| 63 |
+
return f"[錯誤] 模型 '{model}' 未找到或不可用。"
|
| 64 |
+
return f"[錯誤] 向 {model} 發送請求時發生錯誤: {str(e)}" # 提供更明確的錯誤訊息
|
| 65 |
+
except KeyError:
|
| 66 |
+
print(f"[錯誤] 模型 {model} 回應格式不符預期,找不到 'response' 鍵。")
|
| 67 |
+
return f"[錯誤] 模型 {model} 回應格式錯誤。"
|
| 68 |
+
except json.JSONDecodeError:
|
| 69 |
+
print(f"[錯誤] 模型 {model} 回應非有效的 JSON 格式: {response.text}")
|
| 70 |
+
return f"[錯誤] 無法解析來自 {model} 的回應。"
|
| 71 |
+
|
| 72 |
+
def initialize_markdown_file(models_to_run):
|
| 73 |
+
"""初始化 Markdown 報告檔案,包含 YAML metadata"""
|
| 74 |
+
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 75 |
+
metadata = {
|
| 76 |
+
"title": "多模型推理彙整報告 (自動偵測可用模型)",
|
| 77 |
+
"date": timestamp,
|
| 78 |
+
"models_queried": models_to_run, # 使用實際查詢的模型列表
|
| 79 |
+
"author": "自動化程式",
|
| 80 |
+
"description": "本報告整合多個本機可用的模型對多個問題的回應,進行去蕪存菁後的彙整。"
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
try: # 增加檔案操作的錯誤處理
|
| 84 |
+
with open("output_moremodels_auto.md", "w", encoding="utf-8") as file: # 更改檔案名以區分
|
| 85 |
+
file.write("---\n")
|
| 86 |
+
for key, value in metadata.items():
|
| 87 |
+
if isinstance(value, list):
|
| 88 |
+
file.write(f"{key}:\n")
|
| 89 |
+
for item in value:
|
| 90 |
+
file.write(f" - {item}\n") # 標準 YAML 列表縮排
|
| 91 |
+
else:
|
| 92 |
+
file.write(f"{key}: {value}\n")
|
| 93 |
+
file.write("---\n\n")
|
| 94 |
+
file.write(f"# {metadata['title']}\n\n")
|
| 95 |
+
file.write(f"產出時間: {timestamp}\n\n")
|
| 96 |
+
if models_to_run: # 只有在找到模型時才寫入
|
| 97 |
+
file.write(f"使用模型: {', '.join(models_to_run)}\n\n---\n\n")
|
| 98 |
+
else:
|
| 99 |
+
file.write("未找到可用的模型進行查詢。\n\n---\n\n")
|
| 100 |
+
print("[初始化] 已建立 output_moremodels_auto.md")
|
| 101 |
+
return True
|
| 102 |
+
except IOError as e:
|
| 103 |
+
print(f"[錯���] 無法寫入檔案 output_moremodels_auto.md: {e}")
|
| 104 |
+
return False # 返回 False 表示初始化失敗
|
| 105 |
+
|
| 106 |
+
def append_to_markdown(index, prompt, responses):
|
| 107 |
+
"""將問題與各模型回應結果寫入 Markdown 檔案"""
|
| 108 |
+
try: # 增加檔案操作的錯誤處理
|
| 109 |
+
with open("output_moremodels_auto.md", "a", encoding="utf-8") as file:
|
| 110 |
+
file.write(f"## 問題 {index}\n\n")
|
| 111 |
+
file.write(f"### 提問\n\n```\n{prompt}\n```\n\n")
|
| 112 |
+
|
| 113 |
+
if not responses:
|
| 114 |
+
file.write("沒有任何模型提供回應。\n\n")
|
| 115 |
+
else:
|
| 116 |
+
for model, response in responses.items():
|
| 117 |
+
# 確保 response 是字串,避免後續處理錯誤
|
| 118 |
+
response_text = str(response) if response is not None else "[無回應]"
|
| 119 |
+
file.write(f"### 模型:{model}\n\n{response_text.strip()}\n\n")
|
| 120 |
+
|
| 121 |
+
# 自動生成摘要彙整
|
| 122 |
+
# 確保將有效的回應傳遞給摘要函數
|
| 123 |
+
valid_responses = {m: r for m, r in responses.items() if isinstance(r, str) and not r.startswith("[錯誤]")}
|
| 124 |
+
summary = summarize_responses(prompt, valid_responses) # 傳遞有效的回應字典
|
| 125 |
+
file.write(f"### 彙整摘要\n\n{summary}\n\n")
|
| 126 |
+
|
| 127 |
+
file.write("---\n\n")
|
| 128 |
+
except IOError as e:
|
| 129 |
+
print(f"[錯誤] 無法附加內容至檔案 output_moremodels_auto.md: {e}")
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
def summarize_responses(prompt, responses):
|
| 133 |
+
"""
|
| 134 |
+
將多個模型的回應進行比較,提取相似的句子,並整合成通順的摘要。
|
| 135 |
+
(此函數邏輯保持不變)
|
| 136 |
+
"""
|
| 137 |
+
# 如果沒有有效的回應,直接返回提示訊息
|
| 138 |
+
if not responses:
|
| 139 |
+
return "沒有從任何模型收到有效回應可供摘要。"
|
| 140 |
+
|
| 141 |
+
# 將每個回應分句
|
| 142 |
+
sentence_lists = []
|
| 143 |
+
model_names = list(responses.keys()) # 記錄模型名稱順序
|
| 144 |
+
for response in responses.values():
|
| 145 |
+
# 更穩健的分句方式,處理不同結尾符號和換行
|
| 146 |
+
processed_response = response.replace('\n', ' ')
|
| 147 |
+
sentences = []
|
| 148 |
+
current_sentence = ""
|
| 149 |
+
for char in processed_response:
|
| 150 |
+
current_sentence += char
|
| 151 |
+
if char in ['。', '!', '?', '.', '!', '?']:
|
| 152 |
+
trimmed_sentence = current_sentence.strip()
|
| 153 |
+
if trimmed_sentence:
|
| 154 |
+
sentences.append(trimmed_sentence)
|
| 155 |
+
current_sentence = ""
|
| 156 |
+
trimmed_sentence = current_sentence.strip()
|
| 157 |
+
if trimmed_sentence:
|
| 158 |
+
sentences.append(trimmed_sentence)
|
| 159 |
+
sentence_lists.append(sentences)
|
| 160 |
+
|
| 161 |
+
processed_indices = set()
|
| 162 |
+
summary_sentences = []
|
| 163 |
+
# 將模型索引與句子索引和句子本身綁定
|
| 164 |
+
all_sentences = [(i, j, sent) for i, lst in enumerate(sentence_lists) for j, sent in enumerate(lst)]
|
| 165 |
+
|
| 166 |
+
for idx1 in range(len(all_sentences)):
|
| 167 |
+
model_idx1, sent_idx1, sent1 = all_sentences[idx1]
|
| 168 |
+
if (model_idx1, sent_idx1) in processed_indices:
|
| 169 |
+
continue
|
| 170 |
+
|
| 171 |
+
best_match = None
|
| 172 |
+
max_similarity = 0.7
|
| 173 |
+
|
| 174 |
+
for idx2 in range(idx1 + 1, len(all_sentences)):
|
| 175 |
+
model_idx2, sent_idx2, sent2 = all_sentences[idx2]
|
| 176 |
+
if model_idx1 == model_idx2: # 確保是不同模型的回應
|
| 177 |
+
continue
|
| 178 |
+
if (model_idx2, sent_idx2) in processed_indices:
|
| 179 |
+
continue
|
| 180 |
+
|
| 181 |
+
similarity = difflib.SequenceMatcher(None, sent1, sent2).ratio()
|
| 182 |
+
if similarity > max_similarity:
|
| 183 |
+
max_similarity = similarity
|
| 184 |
+
chosen_sentence = sent1 if len(sent1) <= len(sent2) else sent2
|
| 185 |
+
best_match = ((model_idx1, sent_idx1), (model_idx2, sent_idx2), chosen_sentence)
|
| 186 |
+
|
| 187 |
+
if best_match:
|
| 188 |
+
idx_pair1, idx_pair2, chosen = best_match
|
| 189 |
+
processed_indices.add(idx_pair1)
|
| 190 |
+
processed_indices.add(idx_pair2)
|
| 191 |
+
summary_sentences.append(chosen)
|
| 192 |
+
|
| 193 |
+
if not summary_sentences:
|
| 194 |
+
# 如果沒有找到足夠相似的句子,可以選擇提供各模型的簡短預覽
|
| 195 |
+
summary = "各模型提供了不同的觀點,未偵測到足夠相似的核心內容可供直接彙整。重點預覽如下:\n\n"
|
| 196 |
+
for model_index, model_name in enumerate(model_names):
|
| 197 |
+
response_text = responses[model_name]
|
| 198 |
+
preview = response_text.strip().replace('\n', ' ')[:100] # 截取前 100 字元預覽
|
| 199 |
+
summary += f"- **{model_name}**: {preview}...\n"
|
| 200 |
+
else:
|
| 201 |
+
summary = "綜合各模型的相似觀點,摘要如下:\n\n"
|
| 202 |
+
unique_summary_sentences = []
|
| 203 |
+
for sentence in summary_sentences:
|
| 204 |
+
if sentence not in unique_summary_sentences:
|
| 205 |
+
unique_summary_sentences.append(sentence)
|
| 206 |
+
for sentence in unique_summary_sentences:
|
| 207 |
+
summary += f"- {sentence}\n"
|
| 208 |
+
|
| 209 |
+
return summary
|
| 210 |
+
|
| 211 |
+
def main():
|
| 212 |
+
# 1. 獲取可用的模型
|
| 213 |
+
available_models = get_available_models()
|
| 214 |
+
|
| 215 |
+
# 如果無法連���到 Ollama 或找不到模型,則終止程式
|
| 216 |
+
if available_models is None: # 連接失敗
|
| 217 |
+
print("[終止] 無法執行,因無法連接到 Ollama 服務。")
|
| 218 |
+
return
|
| 219 |
+
if not available_models: # 找到服務但沒有模型
|
| 220 |
+
print("[終止] 無法執行,因未找到任何可用的模型。")
|
| 221 |
+
# 仍嘗試初始化文件以記錄情況
|
| 222 |
+
initialize_markdown_file([])
|
| 223 |
+
return
|
| 224 |
+
|
| 225 |
+
# 可自訂多個問題
|
| 226 |
+
questions = [
|
| 227 |
+
"介紹台灣的夜市文化",
|
| 228 |
+
"台灣人工智慧發展的現況與挑戰為何?",
|
| 229 |
+
"請用繁體中文解釋什麼是大型語言模型 (LLM)。"
|
| 230 |
+
]
|
| 231 |
+
|
| 232 |
+
# 2. 初始化 Markdown 文件 (使用找到的模型列表)
|
| 233 |
+
if not initialize_markdown_file(available_models):
|
| 234 |
+
print("[終止] Markdown 檔案初始化失敗,程式結束。")
|
| 235 |
+
return # 如果檔案無法建立,後續寫入會失敗
|
| 236 |
+
|
| 237 |
+
print("[開始] 向偵測到的模型發送請求...")
|
| 238 |
+
|
| 239 |
+
# 3. 迭代問題和可用的模型
|
| 240 |
+
for i, prompt in enumerate(questions, 1):
|
| 241 |
+
print(f"[處理中] 問題 {i}/{len(questions)}: {prompt[:30]}...")
|
| 242 |
+
model_responses = {}
|
| 243 |
+
|
| 244 |
+
for model in available_models: # 使用動態獲取的模型列表
|
| 245 |
+
print(f" └▶ 模型 {model} 推理中...")
|
| 246 |
+
start_time = time.time() # 記錄開始時間
|
| 247 |
+
response = send_request_to_ollama(prompt, model)
|
| 248 |
+
end_time = time.time() # 記錄結束時間
|
| 249 |
+
elapsed_time = end_time - start_time
|
| 250 |
+
print(f" 回應耗時: {elapsed_time:.2f} 秒") # 顯示每個模型的回應時間
|
| 251 |
+
model_responses[model] = response
|
| 252 |
+
# 如果需要,可以在這裡加回 time.sleep(1)
|
| 253 |
+
|
| 254 |
+
# 4. 將結果寫入 Markdown
|
| 255 |
+
append_to_markdown(i, prompt, model_responses)
|
| 256 |
+
print(f"[完成] 問題 {i} 已處理並寫入檔案。")
|
| 257 |
+
|
| 258 |
+
print(f"[完成] 所有問題已處理完畢,結果保存在 output_moremodels_auto.md")
|
| 259 |
+
|
| 260 |
+
if __name__ == "__main__":
|
| 261 |
+
main()
|