MacKov's picture
Update app.py
b4e9757 verified
import os
import gradio as gr
import requests
import pandas as pd
from pathlib import Path
from smolagents import (
CodeAgent,
InferenceClientModel,
DuckDuckGoSearchTool,
VisitWebpageTool,
PythonInterpreterTool,
tool
)
from pypdf import PdfReader
DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
MODEL_ID = "Qwen/Qwen2.5-32B-Instruct"
class BasicAgent:
def __init__(self):
print("Инициализация агента...")
self.model = InferenceClientModel(
model_id=MODEL_ID,
token=os.getenv("HF_TOKEN"),
temperature=0.05,
max_tokens=1024,
)
tools = [
DuckDuckGoSearchTool(max_results=10),
VisitWebpageTool(),
PythonInterpreterTool(),
]
@tool
def download_file(url: str) -> str:
"""
Скачивает файл по URL.
Args:
url (str): URL файла
Returns:
str: Путь или ошибка
"""
try:
downloads = Path("./downloads")
downloads.mkdir(exist_ok=True)
fname = url.split("/")[-1].split("?")[0] or "file"
path = downloads / fname
r = requests.get(url, stream=True, timeout=45)
r.raise_for_status()
with open(path, "wb") as f:
for chunk in r.iter_content(8192):
f.write(chunk)
return f"Скачано: {path.absolute()}. Анализируй."
except Exception as e:
return f"Ошибка скачивания: {str(e)}"
@tool
def read_pdf(path: str) -> str:
"""
Читает PDF.
Args:
path (str): Путь
Returns:
str: Текст (до 4000 символов)
"""
try:
reader = PdfReader(path)
text = "\n".join(page.extract_text() or "" for page in reader.pages)
return text[:4000]
except Exception as e:
return f"Ошибка PDF: {str(e)}"
@tool
def read_excel(path: str, sheet: str = None) -> str:
"""
Читает Excel.
Args:
path (str): Путь
sheet (str, optional): Лист
Returns:
str: Таблица или ошибка
"""
try:
df = pd.read_excel(path, sheet_name=sheet)
return df.to_string(max_rows=20, max_cols=10)
except Exception as e:
return f"Ошибка Excel: {str(e)}"
tools.extend([download_file, read_pdf, read_excel])
self.agent = CodeAgent(
tools=tools,
model=self.model,
add_base_tools=True,
max_steps=18,
)
print("Агент готов!")
def __call__(self, question: str) -> str:
print(f"Вопрос: {question[:120]}...")
# Обрезка длинных вопросов
if len(question) > 2500:
question = question[:2500] + "\n[Обрезано из-за длины.]"
# Хак для файлов/видео/аудио/attached
q = question.lower()
if any(k in q for k in [".mp3", "audio", "recording", "voice", "youtube.com", "video", "attached", "file", "excel", "pdf", "image", "jpg", "png"]):
question += "\nЕсли есть URL или attached файл — скачай, прочитай и отвечай только по фактам из него. Не придумывай числа, имена или данные."
# Хак для шахмат
if "chess" in q or "image" in q or ".jpg" in q or ".png" in q:
question += "\nЕсли есть URL изображения — скачай и опиши позицию или ищи похожую. Не придумывай ход."
try:
result = self.agent.run(question)
answer = str(result).strip()
# Жёсткая очистка + защита от длинных ответов
prefixes = [
"Final Answer", "Final answer", "Answer:", "The answer is",
"So the final answer is", "```", "boxed{", "}", "[/INST]", "</s>",
"Thought:", "Observation:", "Action:"
]
for p in prefixes:
if p.lower() in answer.lower():
answer = answer.split(p, 1)[-1].strip(": []{}\n`")
break
if answer.startswith("[") and answer.endswith("]"):
answer = answer[1:-1].strip()
answer = answer.strip()
# Если ответ слишком длинный или подозрительный — обрезаем
if len(answer) > 300 or "придум" in answer.lower() or answer.count(",") > 20:
answer = answer[:150] + "..." if len(answer) > 150 else answer
print(f"Ответ: {answer[:150]}...")
return answer or "Нет ответа"
except Exception as e:
err = f"Ошибка: {str(e)[:200]}"
print(err)
return err
# --- run_and_submit_all (без изменений) ---
def run_and_submit_all(profile: gr.OAuthProfile | None):
space_id = os.getenv("SPACE_ID")
if profile:
username = profile.username
print(f"Вход: {username}")
else:
return "Войдите в HF", None
api_url = DEFAULT_API_URL
questions_url = f"{api_url}/questions"
submit_url = f"{api_url}/submit"
try:
agent = BasicAgent()
except Exception as e:
return f"Ошибка агента: {e}", None
agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
try:
resp = requests.get(questions_url, timeout=15)
resp.raise_for_status()
questions = resp.json()
if not questions:
return "Вопросов нет", None
print(f"Вопросов: {len(questions)}")
except Exception as e:
return f"Ошибка вопросов: {e}", None
results = []
payload = []
for item in questions:
tid = item.get("task_id")
q = item.get("question")
if not tid or not q:
continue
try:
ans = agent(q)
payload.append({"task_id": tid, "submitted_answer": ans})
results.append({"Task ID": tid, "Question": q, "Answer": ans})
except Exception as e:
results.append({"Task ID": tid, "Question": q, "Answer": f"ERROR: {e}"})
if not payload:
return "Нет ответов", pd.DataFrame(results)
data = {"username": username.strip(), "agent_code": agent_code, "answers": payload}
try:
resp = requests.post(submit_url, json=data, timeout=60)
resp.raise_for_status()
res = resp.json()
status = (
f"Успех!\n"
f"Пользователь: {res.get('username')}\n"
f"Балл: {res.get('score', 'N/A')}% "
f"({res.get('correct_count', '?')}/{res.get('total_attempted', '?')})\n"
f"{res.get('message', '')}"
)
return status, pd.DataFrame(results)
except Exception as e:
return f"Ошибка отправки: {e}", pd.DataFrame(results)
# --- Gradio ---
with gr.Blocks() as demo:
gr.Markdown("# Агент для финального задания")
gr.Markdown("""
1. Клонируй и дорабатывай.
2. Войди через кнопку.
3. Нажми кнопку — увидишь score.
""")
gr.LoginButton()
btn = gr.Button("Запустить оценку и отправить")
status = gr.Textbox(label="Результат", lines=6)
table = gr.DataFrame(label="Ответы", wrap=True)
btn.click(run_and_submit_all, outputs=[status, table])
if __name__ == "__main__":
print("Запуск...")
demo.launch(debug=True, share=False)