perspicacity / app.py
fdaudens's picture
fdaudens HF Staff
Create app.py
a7e14dd verified
raw
history blame
5.32 kB
# app.py
import os, asyncio, aiohttp, nest_asyncio
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 ReActAgent, FunctionAgent, AgentWorkflow
from llama_index.llms.huggingface_api import HuggingFaceInferenceAPI
from llama_index.llms.openai import OpenAI
from llama_index.core.memory import ChatMemoryBuffer
from llama_index.readers.web import RssReader
from llama_index.core.workflow import Context
import gradio as gr
# allow nested loops in Spaces
nest_asyncio.apply()
# --- 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 ---
hf_llm = HuggingFaceInferenceAPI(
model_name="Qwen/Qwen2.5-Coder-32B-Instruct",
token=HF_TOKEN, task="conversational"
)
oa_llm = OpenAI(model="gpt-4o", api_key=OPENAI_API_KEY, temperature=0.0)
# --- Memory ---
memory = ChatMemoryBuffer.from_defaults(token_limit=4096)
# --- Tools Setup ---
# DuckDuckGo
duck_spec = DuckDuckGoSearchToolSpec()
search_tool = FunctionTool.from_defaults(duck_spec.duckduckgo_full_search)
# Weather
weather_spec = OpenWeatherMapToolSpec(key=OPENWEATHERMAP_KEY)
weather_cur = FunctionTool.from_defaults(
weather_spec.weather_at_location,
name="current_weather",
description="Get the current weather for a location."
)
weather_fc = FunctionTool.from_defaults(
weather_spec.forecast_tommorrow_at_location,
name="weather_forecast",
description="Get tomorrow’s forecast for a location."
)
# 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())
pw_spec = PlaywrightToolSpec.from_async_browser(browser)
navigate_tool = FunctionTool.from_defaults(pw_spec.navigate_to, name="web_navigate", description="Go to URL")
extract_text = FunctionTool.from_defaults(pw_spec.extract_text, name="web_extract_text", description="Extract page text")
# …add extract_links if desired…
# 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="Get headlines & URLs from Google News RSS."
)
# Serper
async def fetch_serper(ctx, query):
if not SERPER_API_KEY:
raise ValueError("SERPER_API_KEY missing")
url = f"https://google.serper.dev/news?q={query}&tbs=qdr%3Ad"
hdr = {"X-API-KEY": SERPER_API_KEY, "Content-Type":"application/json"}
async with aiohttp.ClientSession() as s:
r = await s.get(url, headers=hdr)
r.raise_for_status()
return await r.json()
serper_tool = FunctionTool.from_defaults(
fetch_serper, name="fetch_news_from_serper",
description="Search today’s news via Serper."
)
# --- Agents ---
google_rss_agent = FunctionAgent(
name="google_rss_agent", llm=oa_llm, memory=memory,
tools=[google_rss_tool],
system_prompt="Fetch the latest headlines from Google News RSS."
)
web_agent = ReActAgent(
name="web_browsing_agent", llm=hf_llm, memory=memory,
tools=[serper_tool, navigate_tool, extract_text],
system_prompt=(
"When asked for details on a headline, "
"1) call fetch_news_from_serper(query); "
"2) navigate to each URL; 3) extract_text(); "
"4) summarize."
)
)
weather_agent = ReActAgent(
name="weather_agent", llm=oa_llm,
tools=[weather_cur, weather_fc],
system_prompt="You are a weather agent."
)
search_agent = ReActAgent(
name="search_agent", llm=oa_llm,
tools=[search_tool],
system_prompt="You are a search agent using DuckDuckGo."
)
router = ReActAgent(
name="router_agent", llm=hf_llm,
tools=[FunctionTool.from_defaults(lambda ctx, choice: choice,
name="choose_agent",
description="Return agent name.")],
system_prompt=(
"Route the user query to exactly one of: "
"['google_rss_agent','weather_agent','search_agent','web_browsing_agent']."
),
can_handoff_to=["google_rss_agent","weather_agent","search_agent","web_browsing_agent"]
)
workflow = AgentWorkflow(
agents=[router, google_rss_agent, web_agent, weather_agent, search_agent],
root_agent="router_agent"
)
ctx = Context(workflow)
# Sync wrapper
def respond(query: str) -> str:
out = asyncio.run(workflow.run(user_msg=query, ctx=ctx, memory=memory))
return out.response.blocks[0].text
# --- Gradio UI ---
with gr.Blocks() as demo:
gr.Markdown("## 🗞️ Multi‐Agent News & Weather Chatbot")
chatbot = gr.Chatbot()
txt = gr.Textbox(placeholder="Ask me about news, weather or anything…")
txt.submit(lambda q, chat: (chat + [[q, respond(q)]]), [txt, chatbot], chatbot)
demo.launch()