BiGuan commited on
Commit
8173b9c
·
verified ·
1 Parent(s): ff7290f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +47 -50
app.py CHANGED
@@ -16,7 +16,7 @@ import operator
16
  from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, ToolMessage, SystemMessage
17
  from langchain_core.tools import tool
18
  from langgraph.graph import StateGraph, END
19
- from langgraph.prebuilt import ToolNode # <-- 改用 ToolNode
20
  from langchain_core.utils.function_calling import convert_to_openai_function
21
 
22
  # 其他工具依赖
@@ -27,14 +27,16 @@ from youtube_transcript_api import YouTubeTranscriptApi
27
  # 配置常量
28
  # =============================================================================
29
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
30
- AGICTO_BASE_URL = os.getenv("AGICTO_BASE_URL", "https://agicto.com/model")
 
31
  AGICTO_API_KEY = os.getenv("AGICTO_API_KEY", "")
32
  QWEN_MODEL = "qwen3.5-35b-a3b"
33
 
34
  # =============================================================================
35
- # 进度监控器(仅用于 UI,不参与评分)
36
  # =============================================================================
37
  class ProgressMonitor:
 
38
  def __init__(self):
39
  self.current = 0
40
  self.total = 0
@@ -78,13 +80,17 @@ class ProgressMonitor:
78
  return html
79
 
80
  # =============================================================================
81
- # Qwen LLM 封装(通过 agicto.com API
82
  # =============================================================================
83
  class QwenLLM:
84
  def __init__(self, model=QWEN_MODEL):
85
  self.model = model
86
  self.api_key = AGICTO_API_KEY
87
- self.base_url = AGICTO_BASE_URL
 
 
 
 
88
  if not self.api_key:
89
  print("⚠️ 未设置 AGICTO_API_KEY,请检查环境变量")
90
 
@@ -102,8 +108,11 @@ class QwenLLM:
102
  if functions:
103
  body["tools"] = [{"type": "function", "function": f} for f in functions]
104
  body["tool_choice"] = "auto"
 
 
 
105
  try:
106
- resp = requests.post(f"{self.base_url}/v1/chat/completions", headers=headers, json=body, timeout=60)
107
  resp.raise_for_status()
108
  return resp.json()
109
  except Exception as e:
@@ -181,13 +190,19 @@ class QwenLLM:
181
  return formatted
182
 
183
  # =============================================================================
184
- # 工具定义
185
  # =============================================================================
186
  api_url_tasks = DEFAULT_API_URL
187
 
 
 
 
 
 
 
188
  @tool
189
  def web_search(query: str) -> str:
190
- """搜索互联网信息"""
191
  try:
192
  url = "https://api.duckduckgo.com/"
193
  params = {"q": query, "format": "json", "no_html": 1}
@@ -205,7 +220,7 @@ def web_search(query: str) -> str:
205
 
206
  @tool
207
  def web_scraper(url: str) -> str:
208
- """抓取网页文本内容"""
209
  try:
210
  headers = {"User-Agent": "Mozilla/5.0"}
211
  resp = requests.get(url, headers=headers, timeout=15)
@@ -220,7 +235,7 @@ def web_scraper(url: str) -> str:
220
 
221
  @tool
222
  def calculator(expression: str) -> str:
223
- """计算数学表达式"""
224
  try:
225
  import math
226
  allowed = {k: v for k, v in math.__dict__.items() if not k.startswith("__")}
@@ -244,7 +259,10 @@ def analyze_image(image_data: str) -> str:
244
  ]}],
245
  "max_tokens": 800
246
  }
247
- resp = requests.post(f"{AGICTO_BASE_URL}/v1/chat/completions", headers=headers, json=body, timeout=30)
 
 
 
248
  if resp.status_code == 200:
249
  return resp.json()["choices"][0]["message"]["content"]
250
  return f"图片分析失败: {resp.status_code}"
@@ -263,7 +281,9 @@ def transcribe_audio(audio_path: str) -> str:
263
  else:
264
  audio_data = open(audio_path, "rb")
265
  files = {"file": audio_data, "model": (None, "whisper-1")}
266
- resp = requests.post(f"{AGICTO_BASE_URL}/v1/audio/transcriptions", headers=headers, files=files, timeout=60)
 
 
267
  if resp.status_code == 200:
268
  return resp.json()["text"]
269
  return f"转录失败: {resp.status_code}"
@@ -272,7 +292,7 @@ def transcribe_audio(audio_path: str) -> str:
272
 
273
  @tool
274
  def get_youtube_transcript(video_url: str) -> str:
275
- """获取 YouTube 视频字幕"""
276
  try:
277
  if "watch?v=" in video_url:
278
  vid = video_url.split("v=")[1].split("&")[0]
@@ -287,7 +307,7 @@ def get_youtube_transcript(video_url: str) -> str:
287
 
288
  @tool
289
  def download_file_for_task(task_id: str) -> str:
290
- """下载 GAIA 任务关联的文件(图片、音频等)并返回内容或描述"""
291
  try:
292
  url = f"{api_url_tasks}/files/{task_id}"
293
  resp = requests.get(url, timeout=20)
@@ -310,25 +330,15 @@ def download_file_for_task(task_id: str) -> str:
310
  return f"文件下载失败: {e}"
311
 
312
  # =============================================================================
313
- # LangGraph Agent 状态与图构建
314
  # =============================================================================
315
  class AgentState(TypedDict):
316
  messages: Annotated[Sequence[BaseMessage], operator.add]
317
  final_answer: str
318
  task_id: str
319
 
320
- tools = [
321
- web_search,
322
- web_scraper,
323
- calculator,
324
- analyze_image,
325
- transcribe_audio,
326
- get_youtube_transcript,
327
- download_file_for_task
328
- ]
329
-
330
- # 创建 ToolNode(直接基于工具列表)
331
- tool_node = ToolNode(tools) # 新版 LangGraph 使用 ToolNode
332
 
333
  llm = QwenLLM()
334
  functions = [convert_to_openai_function(t) for t in tools]
@@ -363,28 +373,19 @@ def build_graph():
363
  workflow.add_node("agent", agent_node)
364
  workflow.add_node("tools", tool_node)
365
  workflow.add_node("finish", finish_node)
366
-
367
  workflow.set_entry_point("agent")
368
  workflow.add_conditional_edges("agent", should_continue, {"tools": "tools", "finish": "finish"})
369
  workflow.add_edge("tools", "agent")
370
  workflow.add_edge("finish", END)
371
-
372
  return workflow.compile()
373
 
374
- # =============================================================================
375
- # Agent 类
376
- # =============================================================================
377
  class LangGraphAgent:
378
  def __init__(self):
379
  self.graph = build_graph()
380
  print("LangGraphAgent 初始化完成,使用模型:", QWEN_MODEL)
381
 
382
  def __call__(self, question: str, task_id: str = "") -> str:
383
- state = {
384
- "messages": [HumanMessage(content=question)],
385
- "final_answer": "",
386
- "task_id": task_id
387
- }
388
  try:
389
  final_state = self.graph.invoke(state)
390
  return final_state["final_answer"]
@@ -393,8 +394,10 @@ class LangGraphAgent:
393
  return f"Error: {e}"
394
 
395
  # =============================================================================
396
- # 主运行函数(生成器,实时进度)
397
  # =============================================================================
 
 
398
  def run_and_submit_all(profile: gr.OAuthProfile | None) -> Generator:
399
  space_id = os.getenv("SPACE_ID")
400
  if not profile:
@@ -412,7 +415,6 @@ def run_and_submit_all(profile: gr.OAuthProfile | None) -> Generator:
412
  yield f"<div>Agent 初始化失败: {e}</div>", f"Agent 初始化失败: {e}", pd.DataFrame()
413
  return
414
 
415
- # 获取题目
416
  try:
417
  resp = requests.get(f"{api_url}/questions", timeout=15)
418
  resp.raise_for_status()
@@ -448,11 +450,7 @@ def run_and_submit_all(profile: gr.OAuthProfile | None) -> Generator:
448
  yield monitor.get_html(), "没有答案可提交", pd.DataFrame(results_log)
449
  return
450
 
451
- submission = {
452
- "username": username.strip(),
453
- "agent_code": agent_code,
454
- "answers": answers_payload
455
- }
456
  try:
457
  resp = requests.post(f"{api_url}/submit", json=submission, timeout=60)
458
  resp.raise_for_status()
@@ -472,28 +470,27 @@ def run_and_submit_all(profile: gr.OAuthProfile | None) -> Generator:
472
  # =============================================================================
473
  # Gradio 界面
474
  # =============================================================================
475
- import pandas as pd
476
-
477
  with gr.Blocks(title="GAIA Agent") as demo:
478
  gr.Markdown("""
479
  # 🤖 GAIA Level 1 Agent (LangGraph + Qwen)
480
  **模型:** Qwen3.5-35B-A3B | **API:** agicto.com
481
  点击按钮获取题目,Agent 自动调用工具并回答,最后提交评分。
482
  """)
483
-
484
  gr.LoginButton()
485
-
486
  run_btn = gr.Button("🚀 运行评测并提交", variant="primary")
487
-
488
  progress_html = gr.HTML(label="实时进度")
489
  status_output = gr.Textbox(label="提交结果 / 总分", lines=5, interactive=False)
490
  results_table = gr.DataFrame(label="题目与 Agent 答案", wrap=True)
491
-
492
  run_btn.click(
493
  fn=run_and_submit_all,
494
  outputs=[progress_html, status_output, results_table]
495
  )
496
 
497
  if __name__ == "__main__":
 
 
 
 
 
498
  print("启动 Gradio App...")
499
  demo.queue().launch(server_name="0.0.0.0", server_port=7860)
 
16
  from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, ToolMessage, SystemMessage
17
  from langchain_core.tools import tool
18
  from langgraph.graph import StateGraph, END
19
+ from langgraph.prebuilt import ToolNode
20
  from langchain_core.utils.function_calling import convert_to_openai_function
21
 
22
  # 其他工具依赖
 
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"
34
 
35
  # =============================================================================
36
+ # 进度监控器
37
  # =============================================================================
38
  class ProgressMonitor:
39
+ # ... 保持不变 ...
40
  def __init__(self):
41
  self.current = 0
42
  self.total = 0
 
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,请检查环境变量")
96
 
 
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)
116
  resp.raise_for_status()
117
  return resp.json()
118
  except Exception as e:
 
190
  return formatted
191
 
192
  # =============================================================================
193
+ # 工具定义(analyze_image / transcribe_audio 也需要使用修正后的 URL)
194
  # =============================================================================
195
  api_url_tasks = DEFAULT_API_URL
196
 
197
+ def _get_api_base():
198
+ base = AGICTO_BASE_URL.rstrip('/')
199
+ if base.endswith('/v1'):
200
+ base = base[:-3]
201
+ return base
202
+
203
  @tool
204
  def web_search(query: str) -> str:
205
+ # ... 不变 ...
206
  try:
207
  url = "https://api.duckduckgo.com/"
208
  params = {"q": query, "format": "json", "no_html": 1}
 
220
 
221
  @tool
222
  def web_scraper(url: str) -> str:
223
+ # ... 不变 ...
224
  try:
225
  headers = {"User-Agent": "Mozilla/5.0"}
226
  resp = requests.get(url, headers=headers, timeout=15)
 
235
 
236
  @tool
237
  def calculator(expression: str) -> str:
238
+ # ... 不变 ...
239
  try:
240
  import math
241
  allowed = {k: v for k, v in math.__dict__.items() if not k.startswith("__")}
 
259
  ]}],
260
  "max_tokens": 800
261
  }
262
+ # 使用修正后的基地址
263
+ base = _get_api_base()
264
+ url = f"{base}/v1/chat/completions"
265
+ resp = requests.post(url, headers=headers, json=body, timeout=30)
266
  if resp.status_code == 200:
267
  return resp.json()["choices"][0]["message"]["content"]
268
  return f"图片分析失败: {resp.status_code}"
 
281
  else:
282
  audio_data = open(audio_path, "rb")
283
  files = {"file": audio_data, "model": (None, "whisper-1")}
284
+ base = _get_api_base()
285
+ url = f"{base}/v1/audio/transcriptions"
286
+ resp = requests.post(url, headers=headers, files=files, timeout=60)
287
  if resp.status_code == 200:
288
  return resp.json()["text"]
289
  return f"转录失败: {resp.status_code}"
 
292
 
293
  @tool
294
  def get_youtube_transcript(video_url: str) -> str:
295
+ # ... 不变 ...
296
  try:
297
  if "watch?v=" in video_url:
298
  vid = video_url.split("v=")[1].split("&")[0]
 
307
 
308
  @tool
309
  def download_file_for_task(task_id: str) -> str:
310
+ # ... 不变 ...
311
  try:
312
  url = f"{api_url_tasks}/files/{task_id}"
313
  resp = requests.get(url, timeout=20)
 
330
  return f"文件下载失败: {e}"
331
 
332
  # =============================================================================
333
+ # LangGraph Agent
334
  # =============================================================================
335
  class AgentState(TypedDict):
336
  messages: Annotated[Sequence[BaseMessage], operator.add]
337
  final_answer: str
338
  task_id: str
339
 
340
+ tools = [web_search, web_scraper, calculator, analyze_image, transcribe_audio, get_youtube_transcript, download_file_for_task]
341
+ tool_node = ToolNode(tools)
 
 
 
 
 
 
 
 
 
 
342
 
343
  llm = QwenLLM()
344
  functions = [convert_to_openai_function(t) for t in tools]
 
373
  workflow.add_node("agent", agent_node)
374
  workflow.add_node("tools", tool_node)
375
  workflow.add_node("finish", finish_node)
 
376
  workflow.set_entry_point("agent")
377
  workflow.add_conditional_edges("agent", should_continue, {"tools": "tools", "finish": "finish"})
378
  workflow.add_edge("tools", "agent")
379
  workflow.add_edge("finish", END)
 
380
  return workflow.compile()
381
 
 
 
 
382
  class LangGraphAgent:
383
  def __init__(self):
384
  self.graph = build_graph()
385
  print("LangGraphAgent 初始化完成,使用模型:", QWEN_MODEL)
386
 
387
  def __call__(self, question: str, task_id: str = "") -> str:
388
+ state = {"messages": [HumanMessage(content=question)], "final_answer": "", "task_id": task_id}
 
 
 
 
389
  try:
390
  final_state = self.graph.invoke(state)
391
  return final_state["final_answer"]
 
394
  return f"Error: {e}"
395
 
396
  # =============================================================================
397
+ # 主运行函数
398
  # =============================================================================
399
+ import pandas as pd
400
+
401
  def run_and_submit_all(profile: gr.OAuthProfile | None) -> Generator:
402
  space_id = os.getenv("SPACE_ID")
403
  if not profile:
 
415
  yield f"<div>Agent 初始化失败: {e}</div>", f"Agent 初始化失败: {e}", pd.DataFrame()
416
  return
417
 
 
418
  try:
419
  resp = requests.get(f"{api_url}/questions", timeout=15)
420
  resp.raise_for_status()
 
450
  yield monitor.get_html(), "没有答案可提交", pd.DataFrame(results_log)
451
  return
452
 
453
+ submission = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
 
 
 
 
454
  try:
455
  resp = requests.post(f"{api_url}/submit", json=submission, timeout=60)
456
  resp.raise_for_status()
 
470
  # =============================================================================
471
  # Gradio 界面
472
  # =============================================================================
 
 
473
  with gr.Blocks(title="GAIA Agent") as demo:
474
  gr.Markdown("""
475
  # 🤖 GAIA Level 1 Agent (LangGraph + Qwen)
476
  **模型:** Qwen3.5-35B-A3B | **API:** agicto.com
477
  点击按钮获取题目,Agent 自动调用工具并回答,最后提交评分。
478
  """)
 
479
  gr.LoginButton()
 
480
  run_btn = gr.Button("🚀 运行评测并提交", variant="primary")
 
481
  progress_html = gr.HTML(label="实时进度")
482
  status_output = gr.Textbox(label="提交结果 / 总分", lines=5, interactive=False)
483
  results_table = gr.DataFrame(label="题目与 Agent 答案", wrap=True)
 
484
  run_btn.click(
485
  fn=run_and_submit_all,
486
  outputs=[progress_html, status_output, results_table]
487
  )
488
 
489
  if __name__ == "__main__":
490
+ # 检查必要环境变量
491
+ if not AGICTO_API_KEY:
492
+ print("❌ 错误:AGICTO_API_KEY 未设置!请在 Space 的 Settings -> Repository Secrets 中添加。")
493
+ if "v1" in AGICTO_BASE_URL:
494
+ print("⚠️ 提示:AGICTO_BASE_URL 不应包含 /v1,已自动去除。请考虑设置为 https://api.agicto.cn")
495
  print("启动 Gradio App...")
496
  demo.queue().launch(server_name="0.0.0.0", server_port=7860)