Spaces:
Sleeping
Sleeping
# app.py | |
import os | |
import logging | |
import asyncio | |
import nest_asyncio | |
from datetime import datetime | |
import uuid | |
import aiohttp | |
import gradio as gr | |
from langfuse.llama_index import LlamaIndexInstrumentor | |
from llama_index.tools.duckduckgo import DuckDuckGoSearchToolSpec | |
from llama_index.tools.weather import OpenWeatherMapToolSpec | |
from llama_index.tools.playwright import PlaywrightToolSpec | |
from llama_index.core.tools import FunctionTool | |
from llama_index.core.agent.workflow import AgentWorkflow | |
from llama_index.core.workflow import Context | |
from llama_index.llms.huggingface_api import HuggingFaceInferenceAPI | |
from llama_index.core.memory import ChatMemoryBuffer | |
from llama_index.readers.web import RssReader | |
import subprocess | |
subprocess.run(["playwright", "install"]) | |
# allow nested loops in Spaces | |
nest_asyncio.apply() | |
# --- Llangfuse --- | |
instrumentor = LlamaIndexInstrumentor( | |
public_key=os.environ.get("LANGFUSE_PUBLIC_KEY"), | |
secret_key=os.environ.get("LANGFUSE_SECRET_KEY"), | |
host=os.environ.get("LANGFUSE_HOST"), | |
) | |
instrumentor.start() | |
# --- Secrets via env vars --- | |
HF_TOKEN = os.getenv("HF_TOKEN") | |
# OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") | |
OPENWEATHERMAP_KEY = os.getenv("OPENWEATHERMAP_API_KEY") | |
SERPER_API_KEY = os.getenv("SERPER_API_KEY") | |
# --- LLMs --- | |
llm = HuggingFaceInferenceAPI( | |
model_name="Qwen/Qwen2.5-Coder-32B-Instruct", | |
token=HF_TOKEN, | |
task="conversational", | |
parameters={ | |
"max_new_tokens": 2048, | |
} | |
) | |
memory = ChatMemoryBuffer.from_defaults(token_limit=8192) | |
today_str = datetime.now().strftime("%B %d, %Y") | |
ANON_USER_ID = os.environ.get("ANON_USER_ID", uuid.uuid4().hex) | |
# # OpenAI for pure function-calling | |
# openai_llm = OpenAI( | |
# model="gpt-4o", | |
# api_key=OPENAI_API_KEY, | |
# temperature=0.0, | |
# streaming=False, | |
# ) | |
# --- Tools Setup --- | |
# DuckDuckGo | |
duck_spec = DuckDuckGoSearchToolSpec() | |
search_tool = FunctionTool.from_defaults(duck_spec.duckduckgo_full_search) | |
# Weather | |
openweather_api_key=OPENWEATHERMAP_KEY | |
weather_tool_spec = OpenWeatherMapToolSpec(key=openweather_api_key) | |
weather_tool = FunctionTool.from_defaults( | |
weather_tool_spec.weather_at_location, | |
name="current_weather", | |
description="Get the current weather at a specific location (city, country)." | |
) | |
forecast_tool = FunctionTool.from_defaults( | |
weather_tool_spec.forecast_tommorrow_at_location, | |
name="weather_forecast", | |
description="Get tomorrow's weather forecast for a specific location (city, country)." | |
) | |
# Playwright (synchronous start) | |
async def _start_browser(): | |
return await PlaywrightToolSpec.create_async_playwright_browser(headless=True) | |
browser = asyncio.get_event_loop().run_until_complete(_start_browser()) | |
playwright_tool_spec = PlaywrightToolSpec.from_async_browser(browser) | |
navigate_tool = FunctionTool.from_defaults( | |
playwright_tool_spec.navigate_to, | |
name="web_navigate", | |
description="Navigate to a specific URL." | |
) | |
extract_text_tool = FunctionTool.from_defaults( | |
playwright_tool_spec.extract_text, | |
name="web_extract_text", | |
description="Extract all text from the current page." | |
) | |
extract_links_tool = FunctionTool.from_defaults( | |
playwright_tool_spec.extract_hyperlinks, | |
name="web_extract_links", | |
description="Extract all hyperlinks from the current page." | |
) | |
# Google News RSS | |
def fetch_google_news_rss(): | |
docs = RssReader(html_to_text=True).load_data(["https://news.google.com/rss"]) | |
return [{"title":d.metadata.get("title",""), "url":d.metadata.get("link","")} for d in docs] | |
google_rss_tool = FunctionTool.from_defaults( | |
fn=fetch_google_news_rss, | |
name="fetch_google_news_rss", | |
description="Fetch latest headlines and URLs from Google News RSS." | |
) | |
# Serper | |
async def fetch_serper_news(query: str): | |
if not serper_api_key: | |
raise ValueError("Missing SERPER_API_KEY environment variable") | |
url = f"https://google.serper.dev/news?q={query}&tbs=qdr%3Ad" | |
headers = {"X-API-KEY": serper_api_key, "Content-Type": "application/json"} | |
async with aiohttp.ClientSession() as session: | |
async with session.get(url, headers=headers) as resp: | |
resp.raise_for_status() | |
return await resp.json() | |
serper_news_tool = FunctionTool.from_defaults( | |
fetch_serper_news, | |
name="fetch_news_from_serper", | |
description="Fetch news articles on a given topic via the Serper API." | |
) | |
# Create the agent workflow | |
tools = [ | |
search_tool, | |
navigate_tool, | |
extract_text_tool, | |
extract_links_tool, | |
weather_tool, | |
forecast_tool, | |
google_rss_tool, | |
serper_news_tool, | |
] | |
web_agent = AgentWorkflow.from_tools_or_functions(tools, llm=llm) | |
ctx = Context(web_agent) | |
# Async helper to run agent queries | |
def run_query_sync(query: str): | |
"""Helper to run async agent.run in sync context.""" | |
return asyncio.get_event_loop().run_until_complete( | |
web_agent.run(query, ctx=ctx) | |
) | |
async def run_query(query: str): | |
trace_id = f"agent-run-{uuid.uuid4().hex}" | |
try: | |
with instrumentor.observe( | |
trace_id=trace_id, | |
session_id="web-agent-session", | |
user_id=ANON_USER_ID, | |
): | |
return await web_agent.run(query, ctx=ctx) | |
finally: | |
instrumentor.flush() | |
# Gradio interface function | |
async def gradio_query(user_input, chat_history=None): | |
history = chat_history or [] | |
history.append({"role": "user", "content": user_input}) | |
result = await run_query(user_input) | |
text = result.response if isinstance(result.response, str) else str(result.response) | |
history.append({"role": "assistant", "content": text}) | |
return history, history | |
# Build and launch Gradio app | |
grb = gr.Blocks() | |
with grb: | |
gr.Markdown("## Perspicacity") | |
gr.Markdown( | |
"This bot can check the news, tell you the weather, and even browse websites to answer follow-up questions — all powered by a team of tiny AI agents working behind the scenes.\n\n" | |
"🧪 Built for fun during the [AI Agents course](https://huggingface.co/learn/agents-course/unit0/introduction) — it's just a demo to show what agents can do. \n" | |
"🙌 Got ideas or improvements? PRs welcome! \n\n" | |
"👉 _Try asking “What’s the weather in Montreal?” or “What’s in the news today?”_" | |
) | |
chatbot = gr.Chatbot(type="messages") | |
txt = gr.Textbox(placeholder="Ask me anything...", show_label=False) | |
txt.submit( | |
gradio_query, | |
inputs=[txt, chatbot], | |
outputs=[chatbot, chatbot] # first for display, second for state | |
) | |
gr.Button("Send").click(gradio_query, [txt, chatbot], [chatbot, chatbot]) | |
if __name__ == "__main__": | |
grb.launch() |