File size: 23,301 Bytes
6910be8
 
487f6cd
 
 
80065b1
f7aa8b5
80065b1
487f6cd
 
e2ed866
 
 
487f6cd
 
 
 
6910be8
b8d97fc
6910be8
fdfea8b
 
 
 
 
 
 
 
80065b1
30e7055
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
431ee48
 
d2ee51e
431ee48
 
 
 
 
d2ee51e
 
 
 
 
 
 
 
6910be8
d2ee51e
30e7055
 
 
 
 
 
 
 
 
 
 
 
 
431ee48
 
 
 
 
d2ee51e
6910be8
431ee48
 
 
 
 
 
 
 
 
 
 
 
 
d2ee51e
431ee48
6910be8
431ee48
d2ee51e
6910be8
431ee48
 
 
 
 
d2ee51e
431ee48
d2ee51e
431ee48
d2ee51e
431ee48
d2ee51e
30e7055
 
431ee48
e2ed866
6910be8
e2ed866
b8d97fc
80065b1
6910be8
80065b1
b8d97fc
80065b1
 
e2ed866
 
 
 
 
 
6910be8
 
e2ed866
487f6cd
30e7055
 
b8d97fc
 
 
 
30e7055
 
 
 
 
b8d97fc
30e7055
487f6cd
30e7055
 
 
e2ed866
d2ee51e
e2ed866
 
 
80065b1
 
e2ed866
30e7055
 
 
e2ed866
d2ee51e
30e7055
 
 
 
 
 
 
d2ee51e
 
 
6910be8
d2ee51e
487f6cd
30e7055
e2ed866
 
30e7055
 
 
 
 
fdfea8b
6910be8
fdfea8b
30e7055
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e2ed866
d2ee51e
e2ed866
80065b1
e2ed866
80065b1
 
 
 
 
 
 
6910be8
142caaa
d2ee51e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80065b1
fdfea8b
80065b1
fdfea8b
80065b1
30e7055
 
 
 
 
 
 
 
 
 
487f6cd
431ee48
 
 
80065b1
7891f22
80065b1
30e7055
 
 
 
80065b1
487f6cd
80065b1
 
 
 
 
 
 
 
d2ee51e
 
 
 
 
 
 
 
 
 
 
80065b1
fdfea8b
80065b1
fdfea8b
80065b1
487f6cd
fdfea8b
 
431ee48
 
 
 
 
80065b1
7891f22
80065b1
 
 
e2ed866
80065b1
e2ed866
80065b1
 
f7aa8b5
80065b1
 
 
f7aa8b5
80065b1
 
 
 
 
 
 
d2ee51e
 
 
 
 
f7aa8b5
d2ee51e
 
 
 
 
 
 
80065b1
d2ee51e
 
 
 
 
80065b1
d2ee51e
 
 
 
 
 
 
 
 
 
 
 
6910be8
d2ee51e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1991487
80065b1
 
487f6cd
80065b1
487f6cd
80065b1
 
 
 
487f6cd
80065b1
 
 
 
 
 
f7aa8b5
80065b1
f7aa8b5
6910be8
80065b1
 
f7aa8b5
80065b1
487f6cd
30e7055
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d2ee51e
fdfea8b
 
d2ee51e
f7aa8b5
 
80065b1
487f6cd
80065b1
1991487
80065b1
 
 
f7aa8b5
487f6cd
f7aa8b5
80065b1
1991487
30e7055
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80065b1
d2ee51e
e2ed866
30e7055
 
 
80065b1
f7aa8b5
80065b1
487f6cd
f7aa8b5
80065b1
487f6cd
30e7055
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
487f6cd
80065b1
f7aa8b5
80065b1
30e7055
 
487f6cd
f7aa8b5
80065b1
487f6cd
d2ee51e
487f6cd
f7aa8b5
80065b1
 
 
30e7055
 
 
80065b1
487f6cd
e2ed866
 
 
487f6cd
6910be8
f7aa8b5
487f6cd
 
 
f7aa8b5
487f6cd
80065b1
f7aa8b5
80065b1
487f6cd
 
 
 
f7aa8b5
80065b1
487f6cd
80065b1
 
 
 
 
487f6cd
e2ed866
 
30e7055
 
 
 
 
 
 
 
 
 
 
e2ed866
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
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
# app.py - Hugging Face 多模态 AI 智能体 (终极精简修复版)
# 你只需要在 Settings -> Secrets 中设置 HF_TOKEN
import os
import time
import json
import re
import base64
import tempfile
import gradio as gr

# =======================
# 配置:Hugging Face Token(从 Secrets 获取)
# =======================
HF_TOKEN = os.getenv("HF_TOKEN")
if not HF_TOKEN:
    raise ValueError("请设置 HF_TOKEN 环境变量(Settings → Secrets)")

# ⚠️ 关键修改:直接使用 Hugging Face 官方 Inference API
# 无需部署 Endpoint,支持  后缀
INFERENCE_API_BASE_URL = "https://api-inference.huggingface.co/v1"

# 添加调试模式
DEBUG_MODE = os.getenv("DEBUG_MODE", "false").lower() == "true"

def debug_print(message):
    """调试打印函数"""
    if DEBUG_MODE:
        print(f"[DEBUG] {message}")

# 添加网络连接测试函数
def test_hf_connection():
    """测试Hugging Face API连接"""
    try:
        import requests
        test_url = "https://api-inference.huggingface.co/v1/models"
        headers = {"Authorization": f"Bearer {HF_TOKEN}"}
        
        debug_print("测试Hugging Face API连接...")
        response = requests.get(test_url, headers=headers, timeout=10)
        debug_print(f"连接测试响应状态: {response.status_code}")
        
        if response.status_code == 200:
            debug_print("Hugging Face API连接成功")
            return True
        elif response.status_code == 401:
            print("错误: Hugging Face API密钥无效")
            return False
        else:
            print(f"警告: Hugging Face API连接测试返回状态码 {response.status_code}")
            return True  # 其他状态码可能仍然可以工作
    except Exception as e:
        print(f"警告: 无法连接到Hugging Face API: {e}")
        return False

def extract_json_from_response(response_text):
    """
    从模型响应中提取并解析JSON(修复UTF-8编码问题)
    :param response_text: 模型的响应文本
    :return: 解析后的JSON对象
    """
    if not response_text:
        raise ValueError("响应文本为空")

    # 步骤1: 强制转换为UTF-8,避免编码混淆
    try:
        response_text = response_text.encode('latin1').decode('utf-8')
    except Exception:
        try:
            response_text = response_text.encode('utf-8').decode('utf-8')
        except Exception:
            pass

    # 清理响应文本,移除可能的前缀或后缀
    response_text = response_text.strip()
    
    # 移除可能的 Markdown 代码块标记
    if response_text.startswith("```json"):
        response_text = response_text[7:]
    if response_text.startswith("```"):
        response_text = response_text[3:]
    if response_text.endswith("```"):
        response_text = response_text[:-3]
        
    response_text = response_text.strip()

    # 方法1: 直接解析
    try:
        return json.loads(response_text)
    except json.JSONDecodeError as e:
        debug_print(f"直接解析失败: {e}")

    # 方法2: 查找第一个完整的JSON对象
    stack = []
    start = -1
    for i, char in enumerate(response_text):
        if char == '{':
            if not stack:
                start = i
            stack.append(char)
        elif char == '}':
            if stack:
                stack.pop()
                if not stack and start != -1:
                    json_str = response_text[start:i+1]
                    try:
                        json_str = json_str.encode('utf-8').decode('utf-8')
                        return json.loads(json_str)
                    except (json.JSONDecodeError, UnicodeDecodeError):
                        continue

    # 方法3: 使用正则表达式提取
    json_matches = re.findall(r'\{(?:[^{}]|(?R))*\}', response_text)
    if json_matches:
        clean_json = max(json_matches, key=len)
        clean_json = clean_json.strip()
        clean_json = re.sub(r'(?<!\\)"', '"', clean_json)

        try:
            clean_json = clean_json.encode('utf-8').decode('utf-8')
            return json.loads(clean_json)
        except (json.JSONDecodeError, UnicodeDecodeError) as e:
            debug_print(f"清理后的JSON解析失败: {e}")

    # 如果所有方法都失败,抛出更详细的错误信息
    raise ValueError(f"无法从响应中提取有效的JSON。响应长度: {len(response_text)}, 响应前缀: {response_text[:100] if len(response_text) > 100 else response_text}")

# =======================
# 工具函数:调用模型
# =======================
def query_model(prompt, model_name="Qwen/Qwen3-4B-Thinking-2507"):
    """
    通用模型调用函数。
    :param prompt: 输入提示词
    :param model_name: 要调用的完整模型ID,例如 "Qwen/Qwen3-4B-Thinking-2507"
    :return: 模型生成的文本
    """
    try:
        from openai import OpenAI
    except ImportError:
        raise ImportError("请安装 openai: pip install openai")

    client = OpenAI(
        base_url=INFERENCE_API_BASE_URL,  # 使用官方 API
        api_key=HF_TOKEN  # 使用你的 Token
    )

    # 添加模型名称验证和备用模型
    available_models = [
        "Qwen/Qwen3-4B-Thinking-2507",
        "Qwen/Qwen2.5-7B-Instruct",
        "meta-llama/Llama-3.1-8B-Instruct",
        "mistralai/Mistral-Nemo-Instruct-2407"
    ]
    
    # 如果指定的模型不在可用列表中,使用默认模型
    if model_name not in available_models:
        print(f"警告: 模型 {model_name} 不在推荐列表中,将使用默认模型")
        model_name = "Qwen/Qwen3-4B-Thinking-2507"

    try:
        debug_print(f"正在调用模型: {model_name}")
        debug_print(f"提示词长度: {len(prompt)} 字符")
        
        response = client.chat.completions.create(
            model=model_name,
            messages=[
                {"role": "user", "content": prompt}
            ],
            max_tokens=1000,
            temperature=0.7
        )
        
        debug_print(f"API响应状态: 成功")
        
        if response.choices:
            content = response.choices[0].message.content.strip()
            debug_print(f"响应内容长度: {len(content) if content else 0} 字符")
            
            # 检查空响应
            if not content:
                print("警告: 模型返回空内容")
                return None
                
            try:
                content = content.encode('utf-8').decode('utf-8')
            except Exception:
                pass
            return content
        else:
            print("警告: API响应中没有choices字段")
            return None
    except Exception as e:
        error_msg = f"Error calling model: {e}"
        print(error_msg)
        debug_print(f"错误详情: {type(e).__name__}: {str(e)}")
        
        # 更详细的错误处理
        if "401" in str(e):
            print("错误:API密钥无效。请检查HF_TOKEN环境变量。")
        elif "404" in str(e):
            print(f"错误:模型 {model_name} 未找到。尝试使用备用模型...")
            # 尝试备用模型
            for backup_model in available_models:
                if backup_model != model_name:
                    print(f"尝试备用模型: {backup_model}")
                    try:
                        response = client.chat.completions.create(
                            model=backup_model,
                            messages=[
                                {"role": "user", "content": prompt}
                            ],
                            max_tokens=1000,
                            temperature=0.7
                        )
                        if response.choices:
                            content = response.choices[0].message.content.strip()
                            # 检查空响应
                            if not content:
                                print(f"备用模型 {backup_model} 返回空内容")
                                continue
                                
                            try:
                                content = content.encode('utf-8').decode('utf-8')
                            except Exception:
                                pass
                            print(f"备用模型 {backup_model} 调用成功")
                            return content
                    except Exception as backup_e:
                        print(f"备用模型 {backup_model} 调用失败: {backup_e}")
                        continue
            print("所有模型都调用失败")
        elif "timeout" in str(e).lower() or "time out" in str(e).lower():
            print("错误:API调用超时,请检查网络连接。")
        elif "connection" in str(e).lower():
            print("错误:网络连接问题,请检查网络设置。")
        return None

# =======================
# 核心组件:任务规划器 (Planner)
# =======================
def plan_tasks(user_request, filename=None):
    """
    调用LLM将用户需求分解为具体的、可执行的步骤。
    返回一个包含步骤列表的字典。
    """
    file_info = f"用户上传了文件: {filename}" if filename else "用户没有上传文件。"

    # 重构提示词,避免编码偏移
    planning_prompt = (
        "你是一个AI任务规划专家。请根据以下信息,将用户的复杂需求分解为一系列具体的、可执行的原子步骤。\n\n"
        f"{file_info}\n"
        f"用户需求: {user_request}\n\n"
        "请严格按照以下JSON格式输出你的计划,不要包含任何其他文字,只输出JSON:\n\n"
        "{\n"
        '    "thought": "你的整体思考过程",\n'
        '    "steps": [\n'
        '        {\n'
        '            "step_number": 1,\n'
        '            "description": "第一步要做什么的详细描述",\n'
        '            "action": "需要执行的动作类型,例如: \'call_model\'",\n'
        '            "model_id": "Hugging Face上要调用的模型ID,根据文件类型和需求选择最合适的模型。",\n'
        '            "input": "该步骤需要的输入参数(可以是字符串或对象)"\n'
        "        }\n"
        "    ]\n"
        "}"
    )

    debug_print("开始调用模型进行任务规划...")
    planning_response = query_model(planning_prompt)
    debug_print(f"模型响应: {planning_response[:200] if planning_response else '空响应'}")

    # 增强空响应处理
    if not planning_response:
        print("任务规划失败: 模型返回空响应")
        print("可能的原因:")
        print("1. Hugging Face API密钥无效")
        print("2. 网络连接问题")
        print("3. 模型当前不可用")
        print("4. 请求超时")
        raise Exception("任务规划失败: 模型返回空响应")

    try:
        plan = extract_json_from_response(planning_response)
        debug_print("JSON解析成功")
        return plan
    except Exception as e:
        error_msg = f"规划解析失败: {e}\n模型响应内容: {planning_response[:500] if planning_response else '空响应'}"
        print(error_msg)
        # 添加更多调试信息
        if planning_response:
            print(f"响应长度: {len(planning_response)} 字符")
            print(f"响应前100字符: {planning_response[:100] if len(planning_response) > 100 else planning_response}")
        raise Exception("任务规划失败,请重新描述您的需求。")

# =======================
# 核心组件:任务验证器 (Validator)
# =======================
def validate_step(step, step_result, original_request):
    """
    调用LLM验证当前步骤的执行结果是否满足要求。
    :return: (是否通过, 反馈信息)
    """
    validation_prompt = (
        "你是一个严格的AI任务质量检查员。请根据原始用户需求,判断当前步骤的执行结果是否合格。\n\n"
        f"原始用户需求: {original_request}\n"
        f"当前步骤描述: {step.get('description', '无描述')}\n"
        f"当前步骤执行结果: {step_result}\n\n"
        "请严格按照以下JSON格式输出你的验证结果,不要包含任何其他文字,只输出JSON:\n\n"
        "{\n"
        '    "is_valid": true 或 false,\n'
        '    "feedback": "你的详细反馈,如果失败请说明原因"\n'
        "}"
    )

    debug_print("开始调用模型进行步骤验证...")
    validation_response = query_model(validation_prompt)
    debug_print(f"验证响应: {validation_response[:200] if validation_response else '空响应'}")

    try:
        if not validation_response:
            raise ValueError("模型返回空响应")
        result = extract_json_from_response(validation_response)
        is_valid = result.get("is_valid", False)
        feedback = result.get("feedback", "验证无反馈")
        debug_print(f"验证结果: 通过={is_valid}, 反馈={feedback}")
        return is_valid, feedback
    except Exception as e:
        error_msg = f"验证解析失败: {e}\n模型响应内容: {validation_response[:500] if validation_response else '空响应'}"
        print(error_msg)
        return False, "验证过程出现异常,请检查。"

# =======================
# 核心组件:任务执行器 (Executor)
# =======================
def execute_step(step, uploaded_file):
    """
    根据步骤描述,调用具体的 Hugging Face 模型执行操作。
    :return: (执行结果的文本描述, 生成的文件路径(如果有))
    """
    action_type = step.get("action")
    model_id = step.get("model_id")
    tool_input = step.get("input", {})
    description = step.get("description", "")

    result_text = "执行成功。"
    generated_file = None

    try:
        from openai import OpenAI
        client = OpenAI(
            base_url=INFERENCE_API_BASE_URL,
            api_key=HF_TOKEN
        )

        messages = []

        if uploaded_file:
            with open(uploaded_file.name, "rb") as file:
                file_bytes = file.read()
                file_base64 = base64.b64encode(file_bytes).decode('utf-8')
                file_ext = uploaded_file.name.split('.')[-1].lower()

            file_context = f"[用户上传的文件 (类型: {file_ext}) 已转换为Base64编码,内容如下]:\n{file_base64}\n\n"
            messages.append({"role": "user", "content": file_context})

        if isinstance(tool_input, str):
            user_instruction = tool_input
        else:
            user_instruction = json.dumps(tool_input, ensure_ascii=False)

        messages.append({"role": "user", "content": user_instruction})

        response = client.chat.completions.create(
            model=model_id,
            messages=messages,
            max_tokens=1500,
            temperature=0.7
        )
        result_text = response.choices[0].message.content.strip()

        if "data:image/" in result_text or "application/" in result_text:
            try:
                header, encoded = result_text.split(",", 1)
                file_data = base64.b64decode(encoded)

                if "image/png" in header:
                    ext = "png"
                elif "image/jpeg" in header:
                    ext = "jpg"
                elif "application/pdf" in header:
                    ext = "pdf"
                else:
                    ext = "bin"

                temp_dir = tempfile.mkdtemp()
                file_path = os.path.join(temp_dir, f"generated_output.{ext}")
                with open(file_path, "wb") as f:
                    f.write(file_data)
                generated_file = file_path
                result_text = f"已生成文件: generated_output.{ext}"

            except Exception as decode_error:
                print(f"Base64解码失败: {decode_error}")

    except Exception as e:
        raise Exception(f"执行动作 '{action_type}' 时出错: {str(e)}")

    return result_text, generated_file

# =======================
# 主控函数:AI 智能体 (Agent)
# =======================
def ai_agent_master(uploaded_file, user_request):
    """
    AI智能体主控函数
    :param uploaded_file: Gradio上传的文件对象
    :param user_request: 用户的文本需求
    :yield: (执行日志, 下载文件路径)
    """
    chat_history = []
    chat_history.append({"role": "user", "content": f"需求: {user_request}"})
    if uploaded_file:
        chat_history.append({"role": "user", "content": f"已上传文件: {uploaded_file.name}"})
    yield chat_history, None

    try:
        chat_history.append({"role": "assistant", "content": "正在分析需求并分解任务..."})
        yield chat_history, None

        # 添加重试机制
        max_retries = 3
        retry_count = 0
        task_plan = None
        
        while retry_count < max_retries and task_plan is None:
            try:
                task_plan = plan_tasks(user_request, uploaded_file.name if uploaded_file else None)
            except Exception as e:
                retry_count += 1
                if retry_count < max_retries:
                    print(f"任务规划失败,{retry_count}秒后重试... (第{retry_count}次)")
                    time.sleep(retry_count)  # 递增延迟
                else:
                    raise e  # 达到最大重试次数,抛出异常

        if not task_plan or "steps" not in task_plan:
            raise Exception("任务规划返回无效格式,请重试。")

        plan_summary = "\n".join([f"{s['step_number']}. [{s['model_id']}] {s['description']}" for s in task_plan.get("steps", [])])
        chat_history.append({"role": "assistant", "content": f"任务分解完成:\n{plan_summary}"})
        yield chat_history, None

        final_generated_file = None

        for step in task_plan.get("steps", []):
            step_num = step.get("step_number", "未知")
            step_desc = step.get("description", "无描述")
            step_model = step.get("model_id", "未知")

            chat_history.append({"role": "assistant", "content": f"正在调用模型 {step_model} 执行步骤 {step_num}: {step_desc}..."})
            yield chat_history, None

            # 添加步骤执行重试机制
            step_retry_count = 0
            step_result = None
            step_file = None
            
            while step_retry_count < max_retries and step_result is None:
                try:
                    step_result, step_file = execute_step(step, uploaded_file)
                except Exception as e:
                    step_retry_count += 1
                    if step_retry_count < max_retries:
                        print(f"步骤执行失败,{step_retry_count}秒后重试... (第{step_retry_count}次)")
                        time.sleep(step_retry_count)
                    else:
                        raise e  # 达到最大重试次数,抛出异常
            
            if step_file:
                final_generated_file = step_file

            if not step_result:
                raise Exception(f"步骤 {step_num} 执行返回空结果")

            display_result = step_result if len(step_result) < 500 else step_result[:500] + "... (结果过长已截断)"
            chat_history.append({"role": "assistant", "content": f"步骤 {step_num} 结果: {display_result}"})
            yield chat_history, None

            chat_history.append({"role": "assistant", "content": f"正在验证步骤 {step_num}..."})
            yield chat_history, None

            # 添加验证重试机制
            validation_retry_count = 0
            is_valid = False
            validation_feedback = ""
            
            while validation_retry_count < max_retries and not is_valid:
                try:
                    is_valid, validation_feedback = validate_step(step, step_result, user_request)
                except Exception as e:
                    validation_retry_count += 1
                    if validation_retry_count < max_retries:
                        print(f"步骤验证失败,{validation_retry_count}秒后重试... (第{validation_retry_count}次)")
                        time.sleep(validation_retry_count)
                    else:
                        # 验证失败不终止整个流程,但记录警告
                        print(f"步骤 {step_num} 验证失败: {e}")
                        is_valid = True  # 继续执行而不是终止
                        validation_feedback = "验证过程出现异常,但继续执行下一步"

            if not is_valid:
                chat_history.append({"role": "assistant", "content": f"步骤 {step_num} 未通过验证: {validation_feedback}"})
                yield chat_history, None
                # 不再抛出异常终止整个流程,而是记录警告继续
                print(f"警告: 步骤 {step_num} 验证失败,但继续执行下一步")

            chat_history.append({"role": "assistant", "content": f"步骤 {step_num} 验证通过!反馈: {validation_feedback}"})
            yield chat_history, None

            time.sleep(1)

        chat_history.append({"role": "assistant", "content": "所有任务步骤已成功完成!"})
        yield chat_history, final_generated_file

    except Exception as e:
        error_msg = f"执行过程中出现错误: {str(e)}"
        print(f"详细错误信息: {type(e).__name__}: {str(e)}")
        chat_history.append({"role": "assistant", "content": error_msg})
        yield chat_history, None

# =======================
# Gradio 界面
# =======================
with gr.Blocks(theme=gr.themes.Soft()) as demo:
    gr.Markdown("# 🤖 多模态 AI 智能体 (终极精简版)")
    gr.Markdown("上传任意文件,AI将自动判断类型并调用合适的模型完成您的任务。")

    with gr.Row():
        with gr.Column(scale=2):
            file_upload = gr.File(label="📂 上传文件 (支持: .docx, .xlsx, .pptx, .pdf, .jpg, .png, .txt 等)")
            user_input = gr.Textbox(
                label="你的复杂需求",
                placeholder="例如:\n1. 总结我上传的PDF。\n2. 分析这个Excel表格,告诉我销售额最高的产品。\n3. 把这张图片里的文字提取出来。\n4. 根据我的Word大纲,写一篇完整的文章。",
                lines=4
            )
            submit_btn = gr.Button("🚀 开始执行", variant="primary")

        with gr.Column(scale=3):
            output = gr.Chatbot(label="执行日志与结果", height=600, type='messages')
            download_file = gr.File(label="📥 下载结果文件")

    submit_btn.click(
        fn=ai_agent_master,
        inputs=[file_upload, user_input],
        outputs=[output, download_file]
    )

# 启动
if __name__ == "__main__":
    print("正在初始化AI智能体...")
    if DEBUG_MODE:
        print("调试模式已启用")
    
    # 测试Hugging Face连接
    if test_hf_connection():
        print("Hugging Face API连接测试通过")
    else:
        print("警告: Hugging Face API连接测试失败,但仍将继续启动...")
    
    print("启动Gradio界面...")
    demo.launch()