Spaces:
Runtime error
Runtime error
Create app.py
Browse files
app.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# app.py
|
| 2 |
+
import os, asyncio, aiohttp, nest_asyncio
|
| 3 |
+
from llama_index.tools.duckduckgo import DuckDuckGoSearchToolSpec
|
| 4 |
+
from llama_index.tools.weather import OpenWeatherMapToolSpec
|
| 5 |
+
from llama_index.tools.playwright import PlaywrightToolSpec
|
| 6 |
+
from llama_index.core.tools import FunctionTool
|
| 7 |
+
from llama_index.core.agent.workflow import ReActAgent, FunctionAgent, AgentWorkflow
|
| 8 |
+
from llama_index.llms.huggingface_api import HuggingFaceInferenceAPI
|
| 9 |
+
from llama_index.llms.openai import OpenAI
|
| 10 |
+
from llama_index.core.memory import ChatMemoryBuffer
|
| 11 |
+
from llama_index.readers.web import RssReader
|
| 12 |
+
from llama_index.core.workflow import Context
|
| 13 |
+
import gradio as gr
|
| 14 |
+
|
| 15 |
+
# allow nested loops in Spaces
|
| 16 |
+
nest_asyncio.apply()
|
| 17 |
+
|
| 18 |
+
# --- Secrets via env vars ---
|
| 19 |
+
HF_TOKEN = os.getenv("HF_TOKEN")
|
| 20 |
+
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
| 21 |
+
OPENWEATHERMAP_KEY = os.getenv("OPENWEATHERMAP_API_KEY")
|
| 22 |
+
SERPER_API_KEY = os.getenv("SERPER_API_KEY")
|
| 23 |
+
|
| 24 |
+
# --- LLMs ---
|
| 25 |
+
hf_llm = HuggingFaceInferenceAPI(
|
| 26 |
+
model_name="Qwen/Qwen2.5-Coder-32B-Instruct",
|
| 27 |
+
token=HF_TOKEN, task="conversational"
|
| 28 |
+
)
|
| 29 |
+
oa_llm = OpenAI(model="gpt-4o", api_key=OPENAI_API_KEY, temperature=0.0)
|
| 30 |
+
|
| 31 |
+
# --- Memory ---
|
| 32 |
+
memory = ChatMemoryBuffer.from_defaults(token_limit=4096)
|
| 33 |
+
|
| 34 |
+
# --- Tools Setup ---
|
| 35 |
+
# DuckDuckGo
|
| 36 |
+
duck_spec = DuckDuckGoSearchToolSpec()
|
| 37 |
+
search_tool = FunctionTool.from_defaults(duck_spec.duckduckgo_full_search)
|
| 38 |
+
|
| 39 |
+
# Weather
|
| 40 |
+
weather_spec = OpenWeatherMapToolSpec(key=OPENWEATHERMAP_KEY)
|
| 41 |
+
weather_cur = FunctionTool.from_defaults(
|
| 42 |
+
weather_spec.weather_at_location,
|
| 43 |
+
name="current_weather",
|
| 44 |
+
description="Get the current weather for a location."
|
| 45 |
+
)
|
| 46 |
+
weather_fc = FunctionTool.from_defaults(
|
| 47 |
+
weather_spec.forecast_tommorrow_at_location,
|
| 48 |
+
name="weather_forecast",
|
| 49 |
+
description="Get tomorrow’s forecast for a location."
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
+
# Playwright (synchronous start)
|
| 53 |
+
async def _start_browser():
|
| 54 |
+
return await PlaywrightToolSpec.create_async_playwright_browser(headless=True)
|
| 55 |
+
browser = asyncio.get_event_loop().run_until_complete(_start_browser())
|
| 56 |
+
pw_spec = PlaywrightToolSpec.from_async_browser(browser)
|
| 57 |
+
navigate_tool = FunctionTool.from_defaults(pw_spec.navigate_to, name="web_navigate", description="Go to URL")
|
| 58 |
+
extract_text = FunctionTool.from_defaults(pw_spec.extract_text, name="web_extract_text", description="Extract page text")
|
| 59 |
+
# …add extract_links if desired…
|
| 60 |
+
|
| 61 |
+
# Google News RSS
|
| 62 |
+
def fetch_google_news_rss():
|
| 63 |
+
docs = RssReader(html_to_text=True).load_data(["https://news.google.com/rss"])
|
| 64 |
+
return [{"title":d.metadata.get("title",""), "url":d.metadata.get("link","")} for d in docs]
|
| 65 |
+
google_rss_tool = FunctionTool.from_defaults(
|
| 66 |
+
fn=fetch_google_news_rss,
|
| 67 |
+
name="fetch_google_news_rss",
|
| 68 |
+
description="Get headlines & URLs from Google News RSS."
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
# Serper
|
| 72 |
+
async def fetch_serper(ctx, query):
|
| 73 |
+
if not SERPER_API_KEY:
|
| 74 |
+
raise ValueError("SERPER_API_KEY missing")
|
| 75 |
+
url = f"https://google.serper.dev/news?q={query}&tbs=qdr%3Ad"
|
| 76 |
+
hdr = {"X-API-KEY": SERPER_API_KEY, "Content-Type":"application/json"}
|
| 77 |
+
async with aiohttp.ClientSession() as s:
|
| 78 |
+
r = await s.get(url, headers=hdr)
|
| 79 |
+
r.raise_for_status()
|
| 80 |
+
return await r.json()
|
| 81 |
+
serper_tool = FunctionTool.from_defaults(
|
| 82 |
+
fetch_serper, name="fetch_news_from_serper",
|
| 83 |
+
description="Search today’s news via Serper."
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
# --- Agents ---
|
| 87 |
+
google_rss_agent = FunctionAgent(
|
| 88 |
+
name="google_rss_agent", llm=oa_llm, memory=memory,
|
| 89 |
+
tools=[google_rss_tool],
|
| 90 |
+
system_prompt="Fetch the latest headlines from Google News RSS."
|
| 91 |
+
)
|
| 92 |
+
web_agent = ReActAgent(
|
| 93 |
+
name="web_browsing_agent", llm=hf_llm, memory=memory,
|
| 94 |
+
tools=[serper_tool, navigate_tool, extract_text],
|
| 95 |
+
system_prompt=(
|
| 96 |
+
"When asked for details on a headline, "
|
| 97 |
+
"1) call fetch_news_from_serper(query); "
|
| 98 |
+
"2) navigate to each URL; 3) extract_text(); "
|
| 99 |
+
"4) summarize."
|
| 100 |
+
)
|
| 101 |
+
)
|
| 102 |
+
weather_agent = ReActAgent(
|
| 103 |
+
name="weather_agent", llm=oa_llm,
|
| 104 |
+
tools=[weather_cur, weather_fc],
|
| 105 |
+
system_prompt="You are a weather agent."
|
| 106 |
+
)
|
| 107 |
+
search_agent = ReActAgent(
|
| 108 |
+
name="search_agent", llm=oa_llm,
|
| 109 |
+
tools=[search_tool],
|
| 110 |
+
system_prompt="You are a search agent using DuckDuckGo."
|
| 111 |
+
)
|
| 112 |
+
router = ReActAgent(
|
| 113 |
+
name="router_agent", llm=hf_llm,
|
| 114 |
+
tools=[FunctionTool.from_defaults(lambda ctx, choice: choice,
|
| 115 |
+
name="choose_agent",
|
| 116 |
+
description="Return agent name.")],
|
| 117 |
+
system_prompt=(
|
| 118 |
+
"Route the user query to exactly one of: "
|
| 119 |
+
"['google_rss_agent','weather_agent','search_agent','web_browsing_agent']."
|
| 120 |
+
),
|
| 121 |
+
can_handoff_to=["google_rss_agent","weather_agent","search_agent","web_browsing_agent"]
|
| 122 |
+
)
|
| 123 |
+
|
| 124 |
+
workflow = AgentWorkflow(
|
| 125 |
+
agents=[router, google_rss_agent, web_agent, weather_agent, search_agent],
|
| 126 |
+
root_agent="router_agent"
|
| 127 |
+
)
|
| 128 |
+
ctx = Context(workflow)
|
| 129 |
+
|
| 130 |
+
# Sync wrapper
|
| 131 |
+
def respond(query: str) -> str:
|
| 132 |
+
out = asyncio.run(workflow.run(user_msg=query, ctx=ctx, memory=memory))
|
| 133 |
+
return out.response.blocks[0].text
|
| 134 |
+
|
| 135 |
+
# --- Gradio UI ---
|
| 136 |
+
with gr.Blocks() as demo:
|
| 137 |
+
gr.Markdown("## 🗞️ Multi‐Agent News & Weather Chatbot")
|
| 138 |
+
chatbot = gr.Chatbot()
|
| 139 |
+
txt = gr.Textbox(placeholder="Ask me about news, weather or anything…")
|
| 140 |
+
txt.submit(lambda q, chat: (chat + [[q, respond(q)]]), [txt, chatbot], chatbot)
|
| 141 |
+
demo.launch()
|