fdaudens HF Staff commited on
Commit
a7e14dd
·
verified ·
1 Parent(s): 5eb3ebd

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +141 -0
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()