File size: 5,382 Bytes
a7e14dd
 
 
 
 
 
 
 
 
 
 
 
 
5dbccd2
cb79291
 
a7e14dd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# 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
import subprocess
subprocess.run(["playwright", "install"])

# 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()