File size: 10,074 Bytes
96e9df9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
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()