BiGuan commited on
Commit
e7fb476
·
verified ·
1 Parent(s): f8539a8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +59 -19
app.py CHANGED
@@ -27,7 +27,6 @@ from youtube_transcript_api import YouTubeTranscriptApi
27
  # 配置常量
28
  # =============================================================================
29
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
30
- # AGICTO_BASE_URL 请设置为 https://api.agicto.cn (不含 /v1)
31
  AGICTO_BASE_URL = os.getenv("AGICTO_BASE_URL", "https://api.agicto.cn")
32
  AGICTO_API_KEY = os.getenv("AGICTO_API_KEY", "")
33
  QWEN_MODEL = "qwen3.5-35b-a3b"
@@ -36,7 +35,6 @@ QWEN_MODEL = "qwen3.5-35b-a3b"
36
  # 进度监控器
37
  # =============================================================================
38
  class ProgressMonitor:
39
- # ... 保持不变 ...
40
  def __init__(self):
41
  self.current = 0
42
  self.total = 0
@@ -80,16 +78,15 @@ class ProgressMonitor:
80
  return html
81
 
82
  # =============================================================================
83
- # Qwen LLM 封装(修正 URL 拼接)
84
  # =============================================================================
85
  class QwenLLM:
86
  def __init__(self, model=QWEN_MODEL):
87
  self.model = model
88
  self.api_key = AGICTO_API_KEY
89
- # 规范化 base_url,确保末尾没有多余斜杠,并去掉可能存在的 /v1
90
  base = AGICTO_BASE_URL.rstrip('/')
91
  if base.endswith('/v1'):
92
- base = base[:-3] # 去掉 /v1
93
  self.base_url = base
94
  if not self.api_key:
95
  print("⚠️ 未设置 AGICTO_API_KEY,请检查环境变量")
@@ -108,8 +105,6 @@ class QwenLLM:
108
  if functions:
109
  body["tools"] = [{"type": "function", "function": f} for f in functions]
110
  body["tool_choice"] = "auto"
111
-
112
- # 统一使用 /v1/chat/completions 路径
113
  url = f"{self.base_url}/v1/chat/completions"
114
  try:
115
  resp = requests.post(url, headers=headers, json=body, timeout=60)
@@ -190,7 +185,7 @@ class QwenLLM:
190
  return formatted
191
 
192
  # =============================================================================
193
- # 工具定义(analyze_image / transcribe_audio 也需要使用修正后的 URL
194
  # =============================================================================
195
  api_url_tasks = DEFAULT_API_URL
196
 
@@ -322,16 +317,16 @@ def download_file_for_task(task_id: str) -> str:
322
  return f"文件下载失败: {e}"
323
 
324
  # =============================================================================
325
- # LangGraph Agent
326
  # =============================================================================
327
  class AgentState(TypedDict):
328
  messages: Annotated[Sequence[BaseMessage], operator.add]
329
  final_answer: str
330
  task_id: str
 
331
 
332
  tools = [web_search, web_scraper, calculator, analyze_image, transcribe_audio, get_youtube_transcript, download_file_for_task]
333
  tool_node = ToolNode(tools)
334
-
335
  llm = QwenLLM()
336
  functions = [convert_to_openai_function(t) for t in tools]
337
  llm_with_tools = llm.bind_functions(functions)
@@ -339,22 +334,50 @@ llm_with_tools = llm.bind_functions(functions)
339
  def agent_node(state: AgentState) -> dict:
340
  messages = state["messages"]
341
  task_id = state.get("task_id", "")
342
- sys_prompt = f"""You are a helpful assistant answering GAIA Level 1 questions. Use tools if needed.
343
- When you know the answer, output only the answer string, without any extra text or "FINAL ANSWER:".
344
- Current task ID: {task_id}. If you need the file for this task, use download_file_for_task with task_id="{task_id}"."""
 
345
  full = [SystemMessage(content=sys_prompt)] + list(messages)
346
  response = llm_with_tools.invoke(full)
347
  return {"messages": [response]}
348
 
349
  def should_continue(state: AgentState) -> str:
350
- last = state["messages"][-1]
 
 
 
 
 
 
 
 
 
351
  if hasattr(last, "additional_kwargs") and "function_call" in last.additional_kwargs:
352
  return "tools"
 
 
 
 
 
 
 
353
  return "finish"
354
 
 
 
 
 
 
 
 
 
 
 
355
  def finish_node(state: AgentState) -> dict:
356
  last = state["messages"][-1]
357
  content = last.content
 
358
  answer = content.strip().split("\n")[-1].strip()
359
  if "FINAL ANSWER:" in answer:
360
  answer = answer.split("FINAL ANSWER:")[-1].strip()
@@ -365,19 +388,37 @@ def build_graph():
365
  workflow.add_node("agent", agent_node)
366
  workflow.add_node("tools", tool_node)
367
  workflow.add_node("finish", finish_node)
 
 
 
368
  workflow.set_entry_point("agent")
369
- workflow.add_conditional_edges("agent", should_continue, {"tools": "tools", "finish": "finish"})
370
- workflow.add_edge("tools", "agent")
 
 
 
 
 
 
371
  workflow.add_edge("finish", END)
 
372
  return workflow.compile()
373
 
 
 
 
374
  class LangGraphAgent:
375
  def __init__(self):
376
  self.graph = build_graph()
377
  print("LangGraphAgent 初始化完成,使用模型:", QWEN_MODEL)
378
 
379
  def __call__(self, question: str, task_id: str = "") -> str:
380
- state = {"messages": [HumanMessage(content=question)], "final_answer": "", "task_id": task_id}
 
 
 
 
 
381
  try:
382
  final_state = self.graph.invoke(state)
383
  return final_state["final_answer"]
@@ -386,7 +427,7 @@ class LangGraphAgent:
386
  return f"Error: {e}"
387
 
388
  # =============================================================================
389
- # 主运行函数
390
  # =============================================================================
391
  import pandas as pd
392
 
@@ -479,7 +520,6 @@ with gr.Blocks(title="GAIA Agent") as demo:
479
  )
480
 
481
  if __name__ == "__main__":
482
- # 检查必要环境变量
483
  if not AGICTO_API_KEY:
484
  print("❌ 错误:AGICTO_API_KEY 未设置!请在 Space 的 Settings -> Repository Secrets 中添加。")
485
  if "v1" in AGICTO_BASE_URL:
 
27
  # 配置常量
28
  # =============================================================================
29
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
 
30
  AGICTO_BASE_URL = os.getenv("AGICTO_BASE_URL", "https://api.agicto.cn")
31
  AGICTO_API_KEY = os.getenv("AGICTO_API_KEY", "")
32
  QWEN_MODEL = "qwen3.5-35b-a3b"
 
35
  # 进度监控器
36
  # =============================================================================
37
  class ProgressMonitor:
 
38
  def __init__(self):
39
  self.current = 0
40
  self.total = 0
 
78
  return html
79
 
80
  # =============================================================================
81
+ # Qwen LLM 封装
82
  # =============================================================================
83
  class QwenLLM:
84
  def __init__(self, model=QWEN_MODEL):
85
  self.model = model
86
  self.api_key = AGICTO_API_KEY
 
87
  base = AGICTO_BASE_URL.rstrip('/')
88
  if base.endswith('/v1'):
89
+ base = base[:-3]
90
  self.base_url = base
91
  if not self.api_key:
92
  print("⚠️ 未设置 AGICTO_API_KEY,请检查环境变量")
 
105
  if functions:
106
  body["tools"] = [{"type": "function", "function": f} for f in functions]
107
  body["tool_choice"] = "auto"
 
 
108
  url = f"{self.base_url}/v1/chat/completions"
109
  try:
110
  resp = requests.post(url, headers=headers, json=body, timeout=60)
 
185
  return formatted
186
 
187
  # =============================================================================
188
+ # 工具定义(所有工具均附带 description
189
  # =============================================================================
190
  api_url_tasks = DEFAULT_API_URL
191
 
 
317
  return f"文件下载失败: {e}"
318
 
319
  # =============================================================================
320
+ # LangGraph 状态与节点
321
  # =============================================================================
322
  class AgentState(TypedDict):
323
  messages: Annotated[Sequence[BaseMessage], operator.add]
324
  final_answer: str
325
  task_id: str
326
+ tool_attempts: int # 已执行工具调用次数
327
 
328
  tools = [web_search, web_scraper, calculator, analyze_image, transcribe_audio, get_youtube_transcript, download_file_for_task]
329
  tool_node = ToolNode(tools)
 
330
  llm = QwenLLM()
331
  functions = [convert_to_openai_function(t) for t in tools]
332
  llm_with_tools = llm.bind_functions(functions)
 
334
  def agent_node(state: AgentState) -> dict:
335
  messages = state["messages"]
336
  task_id = state.get("task_id", "")
337
+ sys_prompt = f"""You are a helpful assistant answering GAIA Level 1 questions.
338
+ IMPORTANT: You MUST use at least one tool (e.g., web_search, web_scraper, download_file_for_task) to verify or retrieve information, even if you think you already know the answer.
339
+ When you have the final answer, output only the answer string, without any extra text or "FINAL ANSWER:".
340
+ Current task ID: {task_id}. If the question requires a file, use download_file_for_task with task_id="{task_id}"."""
341
  full = [SystemMessage(content=sys_prompt)] + list(messages)
342
  response = llm_with_tools.invoke(full)
343
  return {"messages": [response]}
344
 
345
  def should_continue(state: AgentState) -> str:
346
+ messages = state["messages"]
347
+ last = messages[-1]
348
+ tool_attempts = state.get("tool_attempts", 0)
349
+ MAX_TOOL_CALLS = 5
350
+
351
+ # 超过最大调用次数,强制结束
352
+ if tool_attempts >= MAX_TOOL_CALLS:
353
+ return "finish"
354
+
355
+ # 如果 LLM 请求了工具调用,允许执行
356
  if hasattr(last, "additional_kwargs") and "function_call" in last.additional_kwargs:
357
  return "tools"
358
+
359
+ # 尚未调用过任何工具?强制要求使用工具
360
+ tool_msg_count = sum(1 for m in messages if isinstance(m, ToolMessage))
361
+ if tool_msg_count == 0:
362
+ return "force_tool"
363
+
364
+ # 已经用过工具,可以结束
365
  return "finish"
366
 
367
+ def force_tool_node(state: AgentState) -> dict:
368
+ new_msg = HumanMessage(
369
+ content="You have not used any tools yet. Please use at least one tool to find or verify the answer. "
370
+ "Search the web, download a file, or analyze an image if provided."
371
+ )
372
+ return {"messages": [new_msg]}
373
+
374
+ def increment_tool_count(state: AgentState) -> dict:
375
+ return {"tool_attempts": state.get("tool_attempts", 0) + 1}
376
+
377
  def finish_node(state: AgentState) -> dict:
378
  last = state["messages"][-1]
379
  content = last.content
380
+ # 提取最终答案(纯文本)
381
  answer = content.strip().split("\n")[-1].strip()
382
  if "FINAL ANSWER:" in answer:
383
  answer = answer.split("FINAL ANSWER:")[-1].strip()
 
388
  workflow.add_node("agent", agent_node)
389
  workflow.add_node("tools", tool_node)
390
  workflow.add_node("finish", finish_node)
391
+ workflow.add_node("force_tool", force_tool_node)
392
+ workflow.add_node("count_tools", increment_tool_count)
393
+
394
  workflow.set_entry_point("agent")
395
+ workflow.add_conditional_edges(
396
+ "agent",
397
+ should_continue,
398
+ {"tools": "tools", "force_tool": "force_tool", "finish": "finish"}
399
+ )
400
+ workflow.add_edge("tools", "count_tools")
401
+ workflow.add_edge("count_tools", "agent")
402
+ workflow.add_edge("force_tool", "agent")
403
  workflow.add_edge("finish", END)
404
+
405
  return workflow.compile()
406
 
407
+ # =============================================================================
408
+ # Agent 类
409
+ # =============================================================================
410
  class LangGraphAgent:
411
  def __init__(self):
412
  self.graph = build_graph()
413
  print("LangGraphAgent 初始化完成,使用模型:", QWEN_MODEL)
414
 
415
  def __call__(self, question: str, task_id: str = "") -> str:
416
+ state = {
417
+ "messages": [HumanMessage(content=question)],
418
+ "final_answer": "",
419
+ "task_id": task_id,
420
+ "tool_attempts": 0
421
+ }
422
  try:
423
  final_state = self.graph.invoke(state)
424
  return final_state["final_answer"]
 
427
  return f"Error: {e}"
428
 
429
  # =============================================================================
430
+ # 主运行函数(生成器,实时进度)
431
  # =============================================================================
432
  import pandas as pd
433
 
 
520
  )
521
 
522
  if __name__ == "__main__":
 
523
  if not AGICTO_API_KEY:
524
  print("❌ 错误:AGICTO_API_KEY 未设置!请在 Space 的 Settings -> Repository Secrets 中添加。")
525
  if "v1" in AGICTO_BASE_URL: