Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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 封装
|
| 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]
|
| 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 |
-
# 工具定义(
|
| 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
|
| 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.
|
| 343 |
-
|
| 344 |
-
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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(
|
| 370 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 = {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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:
|