mohammedelfeky-ai's picture
Update app.py
6f919e7 verified
from smolagents import CodeAgent, load_tool, tool, LiteLLMModel
import datetime
import pytz
import yaml
import os
import tempfile
import re
from typing import Dict, List, Optional, Union, Any
from smolagents import GradioUI
# --- Tool Specific Imports ---
from newsapi import NewsApiClient
# --- Assuming your tools are structured like this ---
# If these are classes, they should be instantiated.
# If they are @tool decorated functions, they are used directly.
from tools.final_answer import FinalAnswerTool
from tools.visit_webpage import VisitWebpageTool
from tools.web_search import DuckDuckGoSearchTool
# --- Gradio UI ---
# from Gradio_UI import GradioUI # Assuming Gradio_UI.py is in the same directory
# --- @tool Decorated Functions (Custom Tools) ---
@tool
def create_document(text: str, format: str = "docx") -> str:
"""Creates a document with the provided text and allows download.
Args:
text: The text content to write to the document.
format: The output format, either 'docx', 'pdf', or 'txt'. Default is 'docx'.
"""
try:
temp_dir = tempfile.mkdtemp()
file_name = "generated_document"
if format.lower() == "txt":
path = os.path.join(temp_dir, f"{file_name}.txt")
with open(path, "w", encoding="utf-8") as f:
f.write(text)
print(f"Document created (txt): {path}")
return path
elif format.lower() in ["docx", "pdf"]:
try:
import docx
from docx.shared import Pt
except ImportError:
return (f"ERROR: To create DOCX or PDF files, the 'python-docx' package is required. "
f"Please install it (e.g., 'pip install python-docx'). "
f"You can try creating a 'txt' file instead by specifying format='txt'.")
doc = docx.Document()
doc.add_heading('Generated Document', 0)
style = doc.styles['Normal']
font = style.font
font.name = 'Calibri'
font.size = Pt(11)
for paragraph in text.split('\n'):
if paragraph.strip():
doc.add_paragraph(paragraph)
docx_path = os.path.join(temp_dir, f"{file_name}.docx")
doc.save(docx_path)
print(f"Document created (docx): {docx_path}")
if format.lower() == "pdf":
try:
from docx2pdf import convert
pdf_path = os.path.join(temp_dir, f"{file_name}.pdf")
convert(docx_path, pdf_path)
print(f"Document converted to PDF: {pdf_path}")
return pdf_path
except ImportError:
err_msg = (f"ERROR: PDF conversion requires the 'docx2pdf' package. "
f"Please install it (e.g., 'pip install docx2pdf'). "
f"Document saved as DOCX instead at: {docx_path}")
print(err_msg)
return err_msg
except Exception as e_pdf:
err_msg = f"Error converting DOCX to PDF: {str(e_pdf)}. Document saved as DOCX at: {docx_path}"
print(err_msg)
return err_msg
return docx_path
else:
return f"Error: Unsupported format '{format}'. Supported formats are 'docx', 'pdf', 'txt'."
except Exception as e:
print(f"General error in create_document: {str(e)}")
return f"Error creating document: {str(e)}"
@tool
def get_file_download_link(file_path: str) -> str:
"""Informs that a file is ready for download and its type. (Used by agent to tell user).
Args:
file_path: Path to the file that should be made available for download.
"""
if not os.path.exists(file_path):
print(f"get_file_download_link: File not found at {file_path}")
return f"Error: File not found at {file_path}"
_, file_extension = os.path.splitext(file_path)
mime_types = {
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'.pdf': 'application/pdf',
'.txt': 'text/plain',
}
mime_type = mime_types.get(file_extension.lower(), 'application/octet-stream')
msg = f"File '{os.path.basename(file_path)}' is ready for download (type: {mime_type})."
print(f"get_file_download_link: {msg}")
return msg
@tool
def get_latest_news(query: Optional[str] = None,
category: Optional[str] = None,
country: Optional[str] = "us",
language: Optional[str] = "en",
page_size: int = 5) -> str:
"""
Fetches the latest news headlines.
You can specify a query, category, country, and language.
Args:
query: Keywords or a phrase to search for in the news articles. (e.g., "Tesla stock")
category: The category: business, entertainment, general, health, science, sports, technology.
country: The 2-letter ISO 3166-1 code of the country (e.g., 'us', 'gb'). Default 'us'.
language: The 2-letter ISO 639-1 code of the language (e.g., 'en', 'es'). Default 'en'.
page_size: Number of results (max 100, default 5).
"""
api_key = os.getenv("NEWS_API_KEY")
if not api_key:
return "ERROR: News API key (NEWS_API_KEY) is not set in environment variables."
newsapi = NewsApiClient(api_key=api_key)
print(f"DEBUG NewsTool: query='{query}', category='{category}', country='{country}', lang='{language}', size={page_size}")
try:
if query:
print(f"Fetching news with query: '{query}'")
top_headlines = newsapi.get_everything(q=query,
language=language,
sort_by='publishedAt',
page_size=page_size)
elif category or country != "us": # If category or specific country (other than default for top-headlines)
print(f"Fetching top headlines for category: '{category}', country: '{country}'")
top_headlines = newsapi.get_top_headlines(q=None,
category=category,
language=language,
country=country,
page_size=page_size)
else: # Default top headlines for US/English if nothing specific is asked
print(f"Fetching default top headlines for country: '{country}', language: '{language}'")
top_headlines = newsapi.get_top_headlines(language=language,
country=country,
page_size=page_size)
if top_headlines['status'] == 'ok':
articles = top_headlines['articles']
if not articles:
return "No news articles found for the given criteria."
formatted_news = "## Latest News:\n\n"
for i, article in enumerate(articles[:page_size]): # Ensure we respect page_size
title = article.get('title', 'N/A')
source_name = article.get('source', {}).get('name', 'N/A')
description = article.get('description', 'No description available.')
url = article.get('url', '#')
published_at_str = article.get('publishedAt', 'N/A')
published_at_formatted = published_at_str
try:
if published_at_str and published_at_str != 'N/A':
# Handle 'Z' for UTC timezone correctly
dt_object = datetime.datetime.fromisoformat(published_at_str.replace('Z', '+00:00'))
# Format to a more readable string, perhaps without timezone if it's always UTC from API
published_at_formatted = dt_object.strftime('%Y-%m-%d %H:%M') + " UTC"
except ValueError:
pass # Keep original string if parsing fails
formatted_news += (
f"{i+1}. **{title}**\n"
f" - Source: {source_name}\n"
f" - Published: {published_at_formatted}\n"
f" - Description: {description[:200] + '...' if description and len(description) > 200 else (description or '')}\n"
f" - URL: {url}\n\n"
)
return formatted_news.strip()
else:
err_msg = f"ERROR: Could not fetch news. API Response: {top_headlines.get('code')} - {top_headlines.get('message')}"
print(err_msg)
return err_msg
except Exception as e:
print(f"ERROR: Exception in get_latest_news: {str(e)}")
return f"ERROR: An exception occurred while fetching news: {str(e)}"
# --- Initialize Tools ---
final_answer = FinalAnswerTool()
web_search = DuckDuckGoSearchTool() # Your custom tool from tools/web_search.py
visit_webpage = VisitWebpageTool()
try:
image_generation_tool = load_tool("agents-course/text-to-image", trust_remote_code=True)
print(f"Successfully loaded image generation tool. Name: {getattr(image_generation_tool, 'name', 'N/A')}")
except Exception as e:
print(f"Error loading image generation tool: {e}. Image generation will not be available.")
image_generation_tool = None
# --- Configure LLM Model ---
if not os.getenv("GEMINI_KEY"):
print("CRITICAL: GEMINI_KEY environment variable not set. LLM will not work.")
# exit(1) # Or handle gracefully
model = LiteLLMModel(
model_id="gemini/gemini-1.5-flash-latest",
api_key=os.getenv("GEMINI_KEY"),
max_tokens=4096,
temperature=0.6
)
# --- Load Prompts ---
prompts_file = "prompts.yaml"
if not os.path.exists(prompts_file):
print(f"Warning: '{prompts_file}' not found. Using default agent prompts.")
prompt_templates = None
else:
with open(prompts_file, 'r') as stream:
prompt_templates = yaml.safe_load(stream)
# --- Initialize Agent ---
agent_tools = [
final_answer,
web_search,
visit_webpage,
create_document,
get_file_download_link,
get_latest_news,
]
if image_generation_tool:
agent_tools.append(image_generation_tool)
else:
print("Note: Image generation tool was not loaded, so it's not added to the agent.")
agent = CodeAgent(
model=model,
tools=agent_tools,
max_steps=10,
verbosity_level=1,
prompt_templates=prompt_templates
)
# --- Main Application Entry Point ---
if __name__ == "__main__":
# Ensure NEWS_API_KEY is set if you want the news tool to work
if not os.getenv("NEWS_API_KEY"):
print("Warning: NEWS_API_KEY environment variable not set. News tool will return an error.")
print("Starting Gradio UI...")
#from Gradio_UI import GradioUI # Assuming Gradio_UI.py is in the same directory
ui = GradioUI(agent) # Removed file_upload_folder argument to disable UI uploads
ui.launch()