fdaudens HF Staff commited on
Commit
b3fa23a
·
verified ·
1 Parent(s): d59f446

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +97 -138
app.py CHANGED
@@ -1,25 +1,41 @@
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
  import subprocess
15
  subprocess.run(["playwright", "install"])
16
 
17
  # allow nested loops in Spaces
18
  nest_asyncio.apply()
19
 
 
 
 
 
 
 
 
 
20
  # --- Secrets via env vars ---
21
  HF_TOKEN = os.getenv("HF_TOKEN")
22
- OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
23
  OPENWEATHERMAP_KEY = os.getenv("OPENWEATHERMAP_API_KEY")
24
  SERPER_API_KEY = os.getenv("SERPER_API_KEY")
25
 
@@ -29,16 +45,18 @@ llm = HuggingFaceInferenceAPI(
29
  token=HF_TOKEN,
30
  task="conversational"
31
  )
32
- # OpenAI for pure function-calling
33
- openai_llm = OpenAI(
34
- model="gpt-4o",
35
- api_key=OPENAI_API_KEY,
36
- temperature=0.0,
37
- streaming=False,
38
- )
39
 
40
- # --- Memory ---
41
- memory = ChatMemoryBuffer.from_defaults(token_limit=4096)
 
 
 
 
 
 
 
 
 
42
 
43
  # --- Tools Setup ---
44
  # DuckDuckGo
@@ -65,6 +83,7 @@ async def _start_browser():
65
  return await PlaywrightToolSpec.create_async_playwright_browser(headless=True)
66
  browser = asyncio.get_event_loop().run_until_complete(_start_browser())
67
  playwright_tool_spec = PlaywrightToolSpec.from_async_browser(browser)
 
68
  navigate_tool = FunctionTool.from_defaults(
69
  playwright_tool_spec.navigate_to,
70
  name="web_navigate",
@@ -88,141 +107,81 @@ def fetch_google_news_rss():
88
  google_rss_tool = FunctionTool.from_defaults(
89
  fn=fetch_google_news_rss,
90
  name="fetch_google_news_rss",
91
- description="Get headlines & URLs from Google News RSS."
92
  )
93
 
94
  # Serper
95
- async def fetch_serper(ctx, query):
96
- if not SERPER_API_KEY:
97
- raise ValueError("SERPER_API_KEY missing")
98
  url = f"https://google.serper.dev/news?q={query}&tbs=qdr%3Ad"
99
- hdr = {"X-API-KEY": SERPER_API_KEY, "Content-Type":"application/json"}
100
- async with aiohttp.ClientSession() as s:
101
- r = await s.get(url, headers=hdr)
102
- r.raise_for_status()
103
- return await r.json()
104
- serper_news_tool = FunctionTool.from_defaults(
105
- fetch_serper, name="fetch_news_from_serper",
106
- description="Search today’s news via Serper."
107
- )
108
 
109
- # --- Agents ---
110
- # 1. Google News RSS Agent (replaces old google_news_agent)
111
- google_rss_agent = FunctionAgent(
112
- name="google_rss_agent",
113
- description="Fetches latest headlines and URLs from Google News RSS feed.",
114
- system_prompt="You are an agent that fetches the latest headlines and URLs from the Google News RSS feed.",
115
- tools=[google_rss_tool],
116
- llm=openai_llm,
117
- memory=memory,
118
- )
119
-
120
- # 2. Web Browsing Agent
121
- web_browsing_agent = ReActAgent(
122
- name="web_browsing_agent",
123
- description="Fetches Serper URLs, navigates to each link, extracts the text and builds a summary",
124
- system_prompt=(
125
- "You are a news-content agent. When asked for details on a headline:\n"
126
- "1. Call `fetch_news_from_serper(query)` to get JSON with article URLs.\n"
127
- "2. For each top URL, call `web_navigate(url)` then `web_extract_text()`.\n"
128
- "3. Synthesize those texts into a concise summary."
129
- ),
130
- tools=[serper_news_tool, navigate_tool, extract_text_tool, extract_links_tool],
131
- llm=llm,
132
- memory=memory,
133
- )
134
-
135
- # 3. Weather Agent
136
- weather_agent = ReActAgent(
137
- name="weather_agent",
138
- description="Answers weather-related questions.",
139
- system_prompt="You are a weather agent that provides current weather and forecasts.",
140
- tools=[weather_tool, forecast_tool],
141
- llm=openai_llm,
142
- )
143
-
144
- # 4. DuckDuckGo Search Agent
145
- search_agent = ReActAgent(
146
- name="search_agent",
147
- description="Searches general info using DuckDuckGo.",
148
- system_prompt="You are a search agent that uses DuckDuckGo to answer questions.",
149
- tools=[search_tool],
150
- llm=openai_llm,
151
  )
152
 
153
- router_agent = ReActAgent(
154
- name="router_agent",
155
- description="Routes queries to the correct specialist agent.",
156
- system_prompt=(
157
- "You are RouterAgent. "
158
- "Given the user query, reply with exactly one name from: "
159
- "['google_rss_agent','weather_agent','search_agent','web_browsing_agent']."
160
- ),
161
- llm=llm,
162
- tools=[
163
- FunctionTool.from_defaults(
164
- fn=lambda ctx, choice: choice,
165
- name="choose_agent",
166
- description="Return the chosen agent name."
167
- )
168
- ],
169
- can_handoff_to=[
170
- "google_rss_agent",
171
- "weather_agent",
172
- "search_agent",
173
- "web_browsing_agent",
174
- ],
175
- )
176
 
177
- workflow = AgentWorkflow(
178
- agents=[router_agent, google_rss_agent, web_browsing_agent, weather_agent, search_agent],
179
- root_agent="router_agent"
180
- )
181
- ctx = Context(workflow)
182
-
183
- # # Sync wrapper
184
- # async def respond(query: str) -> str:
185
- # out = await workflow.run(user_msg=query, ctx=ctx, memory=memory)
186
- # return out.response.blocks[0].text
187
-
188
- # # Async response handler for Gradio
189
- # async def respond_gradio(query, chat_history):
190
- # answer = await respond(query)
191
- # return chat_history + [[query, answer]]
192
-
193
- # New unified respond() function
194
- async def respond(message, history):
195
- out = await workflow.run(user_msg=message, ctx=ctx, memory=memory)
196
- answer = out.response.blocks[0].text
197
- # Return the full updated history
198
- return history + [[message, answer]]
199
-
200
- # --- Gradio UI ---
201
- # with gr.Blocks() as demo:
202
- # gr.Markdown("## 🤖 Perspicacity")
203
- # gr.Markdown(
204
- # "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"
205
- # "🧪 Built for fun during the [AI Agents course](https://huggingface.co/learn/agents-course/unit0/introduction) at Hugging Face — it's just a demo to show what agents can do. \n"
206
- # "🙌 Got ideas or improvements? PRs welcome!"
207
- # )
208
- # chatbot = gr.Chatbot()
209
- # txt = gr.Textbox(placeholder="Ask me about news, weather, etc…")
210
-
211
- # txt.submit(respond_gradio, inputs=[txt, chatbot], outputs=chatbot)
212
-
213
- # demo.launch()
214
- with gr.Blocks() as demo:
215
- gr.Markdown("## 🗞️ Multi‐Agent News & Weather Chatbot")
216
  gr.Markdown(
217
  "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"
218
  "🧪 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"
219
  "🙌 Got ideas or improvements? PRs welcome! \n\n"
220
  "👉 _Try asking “What’s the weather in Montreal?” or “What’s in the news today?”_"
221
  )
 
 
 
 
222
 
223
- chatbot = gr.Chatbot()
224
- txt = gr.Textbox(placeholder="Ask me about news, weather or anything…")
225
-
226
- txt.submit(respond, inputs=[txt, chatbot], outputs=chatbot)
227
-
228
- demo.launch()
 
1
  # app.py
2
+ import os
3
+ import logging
4
+ import asyncio
5
+ import nest_asyncio
6
+ from datetime import datetime
7
+ import uuid
8
+ import aiohttp
9
+ import gradio as gr
10
+
11
+ from langfuse.llama_index import LlamaIndexInstrumentor
12
  from llama_index.tools.duckduckgo import DuckDuckGoSearchToolSpec
13
  from llama_index.tools.weather import OpenWeatherMapToolSpec
14
  from llama_index.tools.playwright import PlaywrightToolSpec
15
  from llama_index.core.tools import FunctionTool
16
+ from llama_index.core.agent.workflow import AgentWorkflow
17
+ from llama_index.core.workflow import Context
18
  from llama_index.llms.huggingface_api import HuggingFaceInferenceAPI
 
19
  from llama_index.core.memory import ChatMemoryBuffer
20
  from llama_index.readers.web import RssReader
21
+
 
22
  import subprocess
23
  subprocess.run(["playwright", "install"])
24
 
25
  # allow nested loops in Spaces
26
  nest_asyncio.apply()
27
 
28
+ # --- Llangfuse ---
29
+ instrumentor = LlamaIndexInstrumentor(
30
+ public_key=os.environ.get("LANGFUSE_PUBLIC_KEY"),
31
+ secret_key=os.environ.get("LANGFUSE_SECRET_KEY"),
32
+ host=os.environ.get("LANGFUSE_HOST"),
33
+ )
34
+ instrumentor.start()
35
+
36
  # --- Secrets via env vars ---
37
  HF_TOKEN = os.getenv("HF_TOKEN")
38
+ # OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
39
  OPENWEATHERMAP_KEY = os.getenv("OPENWEATHERMAP_API_KEY")
40
  SERPER_API_KEY = os.getenv("SERPER_API_KEY")
41
 
 
45
  token=HF_TOKEN,
46
  task="conversational"
47
  )
 
 
 
 
 
 
 
48
 
49
+ memory = ChatMemoryBuffer.from_defaults(token_limit=8192)
50
+ today_str = datetime.now().strftime("%B %d, %Y")
51
+ ANON_USER_ID = os.environ.get("ANON_USER_ID", uuid.uuid4().hex)
52
+
53
+ # # OpenAI for pure function-calling
54
+ # openai_llm = OpenAI(
55
+ # model="gpt-4o",
56
+ # api_key=OPENAI_API_KEY,
57
+ # temperature=0.0,
58
+ # streaming=False,
59
+ # )
60
 
61
  # --- Tools Setup ---
62
  # DuckDuckGo
 
83
  return await PlaywrightToolSpec.create_async_playwright_browser(headless=True)
84
  browser = asyncio.get_event_loop().run_until_complete(_start_browser())
85
  playwright_tool_spec = PlaywrightToolSpec.from_async_browser(browser)
86
+
87
  navigate_tool = FunctionTool.from_defaults(
88
  playwright_tool_spec.navigate_to,
89
  name="web_navigate",
 
107
  google_rss_tool = FunctionTool.from_defaults(
108
  fn=fetch_google_news_rss,
109
  name="fetch_google_news_rss",
110
+ description="Fetch latest headlines and URLs from Google News RSS."
111
  )
112
 
113
  # Serper
114
+ async def fetch_serper_news(query: str):
115
+ if not serper_api_key:
116
+ raise ValueError("Missing SERPER_API_KEY environment variable")
117
  url = f"https://google.serper.dev/news?q={query}&tbs=qdr%3Ad"
118
+ headers = {"X-API-KEY": serper_api_key, "Content-Type": "application/json"}
119
+ async with aiohttp.ClientSession() as session:
120
+ async with session.get(url, headers=headers) as resp:
121
+ resp.raise_for_status()
122
+ return await resp.json()
 
 
 
 
123
 
124
+ serper_news_tool = FunctionTool.from_defaults(
125
+ fetch_serper_news,
126
+ name="fetch_news_from_serper",
127
+ description="Fetch news articles on a given topic via the Serper API."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  )
129
 
130
+ # Create the agent workflow
131
+ tools = [
132
+ duckduckgo_tool,
133
+ navigate_tool,
134
+ extract_text_tool,
135
+ extract_links_tool,
136
+ weather_tool,
137
+ forecast_tool,
138
+ google_rss_tool,
139
+ serper_news_tool,
140
+ ]
141
+ web_agent = AgentWorkflow.from_tools_or_functions(tools, llm=llm)
142
+ ctx = Context(web_agent)
143
+
144
+ # Async helper to run agent queries
145
+ def run_query_sync(query: str):
146
+ """Helper to run async agent.run in sync context."""
147
+ return asyncio.get_event_loop().run_until_complete(
148
+ web_agent.run(query, ctx=ctx)
149
+ )
 
 
 
150
 
151
+ async def run_query(query: str):
152
+ trace_id = f"agent-run-{uuid.uuid4().hex}"
153
+ try:
154
+ with instrumentor.observe(
155
+ trace_id=trace_id,
156
+ session_id="web-agent-session",
157
+ user_id=ANON_USER_ID,
158
+ ):
159
+ return await web_agent.run(query, ctx=ctx)
160
+ finally:
161
+ instrumentor.flush()
162
+
163
+ # Gradio interface function
164
+ async def gradio_query(user_input, chat_history=None):
165
+ chat_history = chat_history or []
166
+ result = await run_query(user_input)
167
+ response = result.response
168
+ chat_history.append((user_input, response))
169
+ return chat_history, chat_history
170
+
171
+ # Build and launch Gradio app
172
+ grb = gr.Blocks()
173
+ with grb:
174
+ gr.Markdown("## Perspicacity")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  gr.Markdown(
176
  "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"
177
  "🧪 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"
178
  "🙌 Got ideas or improvements? PRs welcome! \n\n"
179
  "👉 _Try asking “What’s the weather in Montreal?” or “What’s in the news today?”_"
180
  )
181
+ chatbot = gr.Chatbot() # conversation UI
182
+ txt = gr.Textbox(placeholder="Ask me anything...", show_label=False)
183
+ txt.submit(gradio_query, [txt, chatbot], [chatbot, chatbot])
184
+ gr.Button("Send").click(gradio_query, [txt, chatbot], [chatbot, chatbot])
185
 
186
+ if __name__ == "__main__":
187
+ grb.launch()