fdaudens commited on
Commit
2d3b132
Β·
0 Parent(s):

first commit

Browse files
Files changed (3) hide show
  1. README.md +195 -0
  2. agent_gradio_chat.py +466 -0
  3. requirements.txt +5 -0
README.md ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # πŸ“° AI News Research Assistant
2
+
3
+ An intelligent AI-powered news research assistant built with Gradio that provides real-time news search, article fetching, and comprehensive summaries using GPT-OSS models.
4
+
5
+ ## ✨ Features
6
+
7
+ - **Real-time News Search**: Access current headlines from Google News RSS feeds
8
+ - **Topic-specific Research**: Search for news on specific subjects, companies, or events
9
+ - **Site-restricted Search**: Limit searches to specific news domains
10
+ - **Article Content Extraction**: Download and analyze full article content when needed
11
+ - **AI-powered Summaries**: Get intelligent news summaries with proper citations
12
+ - **Multiple Model Support**: Choose between GPT-OSS 120B and 20B models
13
+ - **Modern Web Interface**: Clean, responsive Gradio-based chat interface
14
+ - **Langfuse Integration**: Built-in observability and tracing for interactions
15
+
16
+ ## πŸš€ Quick Start
17
+
18
+ ### Prerequisites
19
+
20
+ - Python 3.8+
21
+ - API keys for:
22
+ - Hugging Face (HF_TOKEN)
23
+ - Serper API (SERPER_API_KEY)
24
+ - Langfuse (optional, for observability)
25
+
26
+ ### Installation
27
+
28
+ 1. **Clone the repository**
29
+ ```bash
30
+ git clone <your-repo-url>
31
+ cd agent_gradio_app
32
+ ```
33
+
34
+ 2. **Install dependencies**
35
+ ```bash
36
+ pip install -r requirements.txt
37
+ ```
38
+
39
+ 3. **Set up environment variables**
40
+ ```bash
41
+ # Create .env file
42
+ echo "HF_TOKEN=your_huggingface_token" > .env
43
+ echo "SERPER_API_KEY=your_serper_api_key" >> .env
44
+ echo "LANGFUSE_PUBLIC_KEY=your_langfuse_public_key" >> .env
45
+ echo "LANGFUSE_SECRET_KEY=your_langfuse_secret_key" >> .env
46
+ ```
47
+
48
+ 4. **Run the application**
49
+ ```bash
50
+ python agent_gradio_chat.py
51
+ ```
52
+
53
+ 5. **Open your browser**
54
+ Navigate to `http://localhost:7860`
55
+
56
+ ## πŸ”§ Configuration
57
+
58
+ ### Available Models
59
+
60
+ - **GPT-OSS 120B**: Larger, more capable model for complex reasoning tasks
61
+ - **GPT-OSS 20B**: Faster, more efficient model for quick responses
62
+
63
+ ### Environment Variables
64
+
65
+ | Variable | Description | Required |
66
+ |----------|-------------|----------|
67
+ | `HF_TOKEN` | Hugging Face API token | Yes |
68
+ | `SERPER_API_KEY` | Serper API key for web search | Yes |
69
+ | `LANGFUSE_PUBLIC_KEY` | Langfuse public key for observability | No |
70
+ | `LANGFUSE_SECRET_KEY` | Langfuse secret key for observability | No |
71
+
72
+ ## πŸ’‘ Usage Examples
73
+
74
+ ### General News Requests
75
+ - "What are the top news stories today?"
76
+ - "What's happening in the world right now?"
77
+ - "Show me the latest headlines"
78
+
79
+ ### Topic-specific Research
80
+ - "What's the latest on artificial intelligence?"
81
+ - "Tell me about recent developments in climate change"
82
+ - "What's happening with Tesla stock?"
83
+
84
+ ### Site-specific Searches
85
+ - "What's the latest climate change news on the BBC?"
86
+ - "Show me recent AI articles from MIT Technology Review"
87
+ - "What's new on Ars Technica about cybersecurity?"
88
+
89
+ ## πŸ› οΈ How It Works
90
+
91
+ The application uses a sophisticated agent loop that:
92
+
93
+ 1. **Analyzes User Queries**: Understands the type of news request
94
+ 2. **Selects Appropriate Tools**: Chooses from available search and fetch tools
95
+ 3. **Executes Searches**: Performs targeted news searches using various APIs
96
+ 4. **Extracts Content**: Downloads and processes article content when needed
97
+ 5. **Synthesizes Information**: Provides comprehensive summaries with citations
98
+ 6. **Tracks Interactions**: Logs all interactions for observability
99
+
100
+ ### Available Tools
101
+
102
+ - **`fetch_google_news_rss`**: Get top headlines from Google News
103
+ - **`serper_news_search`**: Search for specific topics in Google News
104
+ - **`serper_site_search`**: Restrict searches to specific domains
105
+ - **`fetch_article`**: Download and extract article content
106
+
107
+ ## πŸ“ Project Structure
108
+
109
+ ```
110
+ agent_gradio_app/
111
+ β”œβ”€β”€ agent_gradio_chat.py # Main application file
112
+ β”œβ”€β”€ requirements.txt # Python dependencies
113
+ β”œβ”€β”€ config.json # Configuration file
114
+ β”œβ”€β”€ .env # Environment variables (create this)
115
+ β”œβ”€β”€ README.md # This file
116
+ └── run_app.sh # Convenience script to run the app
117
+ ```
118
+
119
+ ## πŸ” API Dependencies
120
+
121
+ - **Hugging Face**: Model inference and hosting
122
+ - **Serper API**: Web search capabilities
123
+ - **Trafilatura**: Article content extraction
124
+ - **Langfuse**: Observability and tracing
125
+
126
+ ## πŸš€ Deployment
127
+
128
+ ### Local Development
129
+ ```bash
130
+ python agent_gradio_chat.py
131
+ ```
132
+
133
+ ### Production Deployment
134
+ ```bash
135
+ # Using the convenience script
136
+ chmod +x run_app.sh
137
+ ./run_app.sh
138
+
139
+ # Or directly with custom settings
140
+ python agent_gradio_chat.py --server-name 0.0.0.0 --server-port 7860
141
+ ```
142
+
143
+ ### Docker (Optional)
144
+ ```dockerfile
145
+ FROM python:3.9-slim
146
+ WORKDIR /app
147
+ COPY requirements.txt .
148
+ RUN pip install -r requirements.txt
149
+ COPY . .
150
+ EXPOSE 7860
151
+ CMD ["python", "agent_gradio_chat.py"]
152
+ ```
153
+
154
+ ## πŸ§ͺ Testing
155
+
156
+ Test the application with various news queries:
157
+
158
+ ```bash
159
+ # Test basic functionality
160
+ python -c "
161
+ from agent_gradio_chat import run_agent
162
+ response = run_agent('What are the top news stories today?')
163
+ print(response)
164
+ "
165
+ ```
166
+
167
+ ## 🀝 Contributing
168
+
169
+ 1. Fork the repository
170
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
171
+ 3. Commit your changes (`git commit -m 'Add amazing feature'`)
172
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
173
+ 5. Open a Pull Request
174
+
175
+ ## πŸ“ License
176
+
177
+ This project is licensed under the MIT License - see the LICENSE file for details.
178
+
179
+ ## πŸ™ Acknowledgments
180
+
181
+ - [Gradio](https://gradio.app/) for the web interface framework
182
+ - [Hugging Face](https://huggingface.co/) for model hosting and inference
183
+ - [OpenAI](https://openai.com/) for the GPT-OSS models
184
+ - [Serper](https://serper.dev/) for web search capabilities
185
+
186
+ ## πŸ“ž Support
187
+
188
+ For issues, questions, or contributions:
189
+ - Open an issue on GitHub
190
+ - Check the documentation
191
+ - Review the code comments for implementation details
192
+
193
+ ---
194
+
195
+ **Happy news researching! πŸš€πŸ“°**
agent_gradio_chat.py ADDED
@@ -0,0 +1,466 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ import json
4
+ import time
5
+ import requests
6
+ import trafilatura
7
+ import xml.etree.ElementTree as ET
8
+ from typing import Any, Dict, List, Optional
9
+ from openai import OpenAI
10
+ from dotenv import load_dotenv
11
+ from langfuse import Langfuse
12
+
13
+ load_dotenv()
14
+
15
+ # ---------- Config ----------
16
+ HF_TOKEN = os.getenv("HF_TOKEN")
17
+ SERPER_API_KEY = os.getenv("SERPER_API_KEY")
18
+ assert HF_TOKEN, "Missing HF_TOKEN"
19
+ assert SERPER_API_KEY, "Missing SERPER_API_KEY"
20
+
21
+ # Available models for selection
22
+ AVAILABLE_MODELS = [
23
+ "openai/gpt-oss-120b:fireworks-ai",
24
+ "openai/gpt-oss-20b:fireworks-ai"
25
+ ]
26
+
27
+ # Default model
28
+ DEFAULT_MODEL = "openai/gpt-oss-120b:fireworks-ai"
29
+ BASE_URL = "https://router.huggingface.co/v1"
30
+
31
+ client = OpenAI(base_url=BASE_URL, api_key=HF_TOKEN)
32
+
33
+ langfuse = Langfuse(
34
+ public_key=os.getenv("LANGFUSE_PUBLIC_KEY"),
35
+ secret_key=os.getenv("LANGFUSE_SECRET_KEY")
36
+ )
37
+
38
+ # ---------- Tools ----------
39
+ def fetch_google_news_rss(num: int = 10) -> List[Dict[str, Any]]:
40
+ """Fetch general news from Google News RSS feed."""
41
+ try:
42
+ url = "https://news.google.com/rss"
43
+ r = requests.get(url, timeout=30)
44
+ r.raise_for_status()
45
+
46
+ # Parse RSS XML
47
+ root = ET.fromstring(r.content)
48
+ items = root.findall('.//item')
49
+
50
+ results = []
51
+ for item in items[:num]:
52
+ title = item.find('title')
53
+ link = item.find('link')
54
+ pub_date = item.find('pubDate')
55
+ source = item.find('source')
56
+
57
+ results.append({
58
+ "title": title.text if title is not None else "No title",
59
+ "link": link.text if link is not None else "",
60
+ "pub_date": pub_date.text if pub_date is not None else "No date",
61
+ "source": source.text if source is not None else "Google News"
62
+ })
63
+
64
+ return results
65
+ except Exception as e:
66
+ return {"ok": False, "error": repr(e)}
67
+
68
+ def serper_news_search(query: str, num: int = 5) -> List[Dict[str, Any]]:
69
+ """Fetch news for a specific topic or query."""
70
+ url = "https://google.serper.dev/news"
71
+ headers = {"X-API-KEY": SERPER_API_KEY, "Content-Type": "application/json"}
72
+ payload = {"q": query, "gl": "us", "hl": "en", "tbs": "qdr:d"}
73
+ r = requests.post(url, headers=headers, json=payload, timeout=30)
74
+ r.raise_for_status()
75
+ data = r.json()
76
+ results = []
77
+ for item in data.get("news", [])[:num]:
78
+ results.append({
79
+ "title": item.get("title"),
80
+ "link": item.get("link"),
81
+ "snippet": item.get("snippet"),
82
+ "date": item.get("date"), # ISO8601 when available
83
+ "source": item.get("source")
84
+ })
85
+ return results
86
+
87
+ def serper_site_search(query: str, site: str, num: int = 5) -> List[Dict[str, Any]]:
88
+ """Site restricted web search."""
89
+ url = "https://google.serper.dev/search"
90
+ headers = {"X-API-KEY": SERPER_API_KEY, "Content-Type": "application/json"}
91
+ payload = {"q": f"site:{site} {query}", "gl": "us", "hl": "en"}
92
+ r = requests.post(url, headers=headers, json=payload, timeout=30)
93
+ r.raise_for_status()
94
+ data = r.json()
95
+ results = []
96
+ for item in data.get("organic", [])[:num]:
97
+ results.append({
98
+ "title": item.get("title"),
99
+ "link": item.get("link"),
100
+ "snippet": item.get("snippet"),
101
+ "favicons": item.get("favicons", {})
102
+ })
103
+ return results
104
+
105
+ def fetch_article(url: str, max_chars: int = 12000) -> Dict[str, Any]:
106
+ """Fetch and extract clean article text with trafilatura."""
107
+ try:
108
+ downloaded = trafilatura.fetch_url(url, timeout=30)
109
+ text = trafilatura.extract(downloaded, include_comments=False) if downloaded else None
110
+ if not text:
111
+ return {"ok": False, "error": "could_not_extract"}
112
+ text = text.strip()
113
+ if len(text) > max_chars:
114
+ text = text[:max_chars] + " ..."
115
+ return {"ok": True, "text": text}
116
+ except Exception as e:
117
+ return {"ok": False, "error": repr(e)}
118
+
119
+ # OpenAI-style tool specs for function calling
120
+ TOOLS = [
121
+ {
122
+ "type": "function",
123
+ "function": {
124
+ "name": "fetch_google_news_rss",
125
+ "description": "Fetch general top headlines from Google News RSS feed. Use this when you want to see what's happening in the world today without a specific topic focus.",
126
+ "parameters": {
127
+ "type": "object",
128
+ "properties": {
129
+ "num": {"type": "integer", "minimum": 1, "maximum": 20, "description": "Number of news items to fetch"}
130
+ },
131
+ "required": []
132
+ }
133
+ }
134
+ },
135
+ {
136
+ "type": "function",
137
+ "function": {
138
+ "name": "serper_news_search",
139
+ "description": "Search Google News for articles about a specific topic or query. Use this when you need news about particular subjects, companies, or events.",
140
+ "parameters": {
141
+ "type": "object",
142
+ "properties": {
143
+ "query": {"type": "string"},
144
+ "num": {"type": "integer", "minimum": 1, "maximum": 20}
145
+ },
146
+ "required": ["query"]
147
+ }
148
+ }
149
+ },
150
+ {
151
+ "type": "function",
152
+ "function": {
153
+ "name": "serper_site_search",
154
+ "description": "Search a specific news domain for relevant articles.",
155
+ "parameters": {
156
+ "type": "object",
157
+ "properties": {
158
+ "query": {"type": "string"},
159
+ "site": {"type": "string", "description": "Domain like ft.com or nytimes.com"},
160
+ "num": {"type": "integer", "minimum": 1, "maximum": 10}
161
+ },
162
+ "required": ["query", "site"]
163
+ }
164
+ }
165
+ },
166
+ {
167
+ "type": "function",
168
+ "function": {
169
+ "name": "fetch_article",
170
+ "description": "Download and extract the main text of an article from a URL. ONLY use this when the user asks specific questions about article content, details, or wants to analyze/quote from particular articles. Do NOT use this for general news summaries or overviews.",
171
+ "parameters": {
172
+ "type": "object",
173
+ "properties": {
174
+ "url": {"type": "string"},
175
+ "max_chars": {"type": "integer", "minimum": 1000, "maximum": 60000}
176
+ },
177
+ "required": ["url"]
178
+ }
179
+ }
180
+ }
181
+ ]
182
+
183
+ FUNCTION_MAP = {
184
+ "fetch_google_news_rss": fetch_google_news_rss,
185
+ "serper_news_search": serper_news_search,
186
+ "serper_site_search": serper_site_search,
187
+ "fetch_article": fetch_article,
188
+ }
189
+
190
+ # ---------- Agent loop ----------
191
+ def call_model(messages: List[Dict[str, str]], tools=TOOLS, temperature: float = 0.3, model: str = DEFAULT_MODEL):
192
+ """One step with tool calling support."""
193
+ try:
194
+ return client.chat.completions.create(
195
+ model=model,
196
+ temperature=temperature,
197
+ messages=messages,
198
+ tools=tools,
199
+ tool_choice="auto"
200
+ )
201
+ except Exception as e:
202
+ print(f"Error calling model: {e}")
203
+ raise
204
+
205
+ def run_agent(user_prompt: str, site_limit: Optional[str] = None, model: str = DEFAULT_MODEL) -> str:
206
+ """
207
+ High level prompt for a news agent.
208
+ It may search, read links, then synthesize and cite URLs.
209
+ """
210
+ system = {
211
+ "role": "system",
212
+ "content": (
213
+ "You are a careful news agent. Follow these steps:\n"
214
+ "1. For general news requests: Use fetch_google_news_rss to get top headlines\n"
215
+ "2. For specific topic requests: Use serper_news_search with the topic\n"
216
+ "3. ONLY use fetch_article when the user asks specific questions about article content, details, or wants to analyze/quote from particular articles\n"
217
+ "4. For general news summaries, provide information based on headlines and snippets without fetching full articles\n"
218
+ "5. STOP calling tools and provide your final answer\n"
219
+ "6. Always include a bullet list of sources with URLs\n"
220
+ "IMPORTANT: After reading articles (if any), you must provide your final answer without calling more tools.\n\n"
221
+ "TOOL SELECTION GUIDE:\n"
222
+ "- fetch_google_news_rss: Use for 'what's happening today' or 'top news' requests\n"
223
+ "- serper_news_search: Use for specific topics like 'AI chips', 'Nvidia', 'climate change'\n"
224
+ "- serper_site_search: Use when restricted to specific news sources\n"
225
+ "- fetch_article: ONLY use when user asks about specific article content, details, or wants to analyze particular articles\n"
226
+ "PRIORITY: For general news requests, provide summaries based on headlines and snippets. Only fetch full articles when specifically needed for detailed analysis.\n"
227
+ ),
228
+ }
229
+
230
+ messages: List[Dict[str, str]] = [system, {"role": "user", "content": user_prompt}]
231
+ if site_limit:
232
+ messages.append({"role": "user", "content": f"Restrict searches to {site_limit} when appropriate."})
233
+
234
+ for step in range(6): # small safety cap
235
+ try:
236
+ resp = call_model(messages, model=model)
237
+ msg = resp.choices[0].message
238
+
239
+ # If the model wants to call tools
240
+ if getattr(msg, "tool_calls", None) and msg.tool_calls:
241
+ # Add the assistant message with tool calls to the conversation
242
+ assistant_message = {
243
+ "role": "assistant",
244
+ "content": msg.content or "",
245
+ "tool_calls": [
246
+ {
247
+ "id": tool_call.id,
248
+ "type": "function",
249
+ "function": {
250
+ "name": tool_call.function.name,
251
+ "arguments": tool_call.function.arguments
252
+ }
253
+ }
254
+ for tool_call in msg.tool_calls
255
+ ]
256
+ }
257
+ messages.append(assistant_message)
258
+
259
+ # Process each tool call
260
+ for tool_call in msg.tool_calls:
261
+ name = tool_call.function.name
262
+ args = {}
263
+ try:
264
+ args = json.loads(tool_call.function.arguments or "{}")
265
+ except json.JSONDecodeError:
266
+ args = {}
267
+
268
+ fn = FUNCTION_MAP.get(name)
269
+ if not fn:
270
+ messages.append({
271
+ "role": "tool",
272
+ "tool_call_id": tool_call.id,
273
+ "name": name,
274
+ "content": json.dumps({"ok": False, "error": "unknown_tool"})
275
+ })
276
+ continue
277
+
278
+ try:
279
+ result = fn(**args)
280
+ except TypeError as e:
281
+ result = {"ok": False, "error": f"bad_args: {e}"}
282
+ except Exception as e:
283
+ result = {"ok": False, "error": repr(e)}
284
+
285
+ tool_response = {
286
+ "role": "tool",
287
+ "tool_call_id": tool_call.id,
288
+ "name": name,
289
+ "content": json.dumps(result),
290
+ }
291
+ messages.append(tool_response)
292
+
293
+ # After processing tools, add a reminder to synthesize
294
+ if step >= 2: # After 2+ tool calls, encourage synthesis
295
+ messages.append({
296
+ "role": "user",
297
+ "content": "You now have sufficient information. Please provide your final answer with sources."
298
+ })
299
+
300
+ # Continue loop so the model can see tool outputs
301
+ continue
302
+
303
+ # If we have a final assistant message without tool calls
304
+ if msg.content:
305
+ return msg.content
306
+
307
+ # Fallback tiny sleep then continue
308
+ time.sleep(0.2)
309
+
310
+ except Exception as e:
311
+ # If there's an error, try to continue or return error message
312
+ if step == 5: # Last step
313
+ return f"Error occurred during processing: {e}"
314
+ time.sleep(0.5)
315
+ continue
316
+
317
+ return "I could not complete the task within the step limit. Try refining your query."
318
+
319
+ # ---------- Gradio Interface ----------
320
+ def chat_with_agent(message, history, model):
321
+ """Handle chat messages and return agent responses."""
322
+ if not message.strip():
323
+ return history
324
+
325
+ # Create a trace for this interaction
326
+ trace = langfuse.trace(
327
+ name="chat_interaction",
328
+ input={"user_message": message, "model": model, "history_length": len(history)}
329
+ )
330
+
331
+ try:
332
+ response = run_agent(message, None, model)
333
+
334
+ # Update trace with success info and output
335
+ trace.update(
336
+ output={"agent_response": response},
337
+ metadata={
338
+ "model": model,
339
+ "message_length": len(message),
340
+ "response_length": len(response),
341
+ "success": True
342
+ }
343
+ )
344
+
345
+ # Flush the trace to send it to Langfuse
346
+ langfuse.flush()
347
+
348
+ history.append({"role": "user", "content": message})
349
+ history.append({"role": "assistant", "content": response})
350
+ return history
351
+
352
+ except Exception as e:
353
+ # Update trace with error info
354
+ trace.update(
355
+ output={"error": str(e)},
356
+ level="ERROR",
357
+ metadata={
358
+ "error": str(e),
359
+ "success": False
360
+ }
361
+ )
362
+
363
+ # Flush the trace to send it to Langfuse
364
+ langfuse.flush()
365
+
366
+ error_msg = f"Sorry, I encountered an error: {str(e)}"
367
+ history.append({"role": "user", "content": message})
368
+ history.append({"role": "assistant", "content": error_msg})
369
+ return history
370
+
371
+ def clear_chat():
372
+ """Clear the chat history."""
373
+ return [], ""
374
+
375
+ # Create the Gradio interface
376
+ with gr.Blocks(
377
+ title="Chat with the News",
378
+ theme=gr.themes.Monochrome()
379
+ ) as demo:
380
+
381
+ # Header using Gradio markdown
382
+ gr.Markdown("""
383
+ # πŸ“° Chat with the News
384
+
385
+ Your AI-powered news research assistant with real-time search capabilities, based on [GPT-OSS models](https://huggingface.co/collections/openai/gpt-oss-68911959590a1634ba11c7a4) and running on inference providers.
386
+ """)
387
+
388
+ # Examples section using Gradio markdown
389
+ gr.Markdown("""
390
+ ### πŸ’‘ Try these examples:
391
+
392
+ - **General:** "What are the top news stories today?"
393
+ - **Specific topic:** "What's the latest on artificial intelligence?"
394
+ - **Site-specific:** "What's the latest climate change news on the BBC?"
395
+ """)
396
+
397
+ # Model selector
398
+ model_selector = gr.Dropdown(
399
+ choices=AVAILABLE_MODELS,
400
+ value=DEFAULT_MODEL,
401
+ label="πŸ€– Select Model",
402
+ info="Choose between GPT-OSS 120B and 20B models"
403
+ )
404
+
405
+ # Message input
406
+ msg = gr.Textbox(
407
+ label="Ask me about the news",
408
+ placeholder="What would you like to know about today?",
409
+ lines=2
410
+ )
411
+
412
+ # Buttons in a row
413
+ with gr.Row():
414
+ submit_btn = gr.Button("πŸš€ Send", variant="primary", size="lg")
415
+ clear_btn = gr.Button("πŸ—‘οΈ Clear Chat", variant="secondary", size="lg")
416
+
417
+ # Chat interface
418
+ chatbot = gr.Chatbot(
419
+ label="News Agent",
420
+ height=500,
421
+ show_label=False,
422
+ container=True,
423
+ type="messages"
424
+ )
425
+
426
+ # Event handlers
427
+ submit_btn.click(
428
+ chat_with_agent,
429
+ inputs=[msg, chatbot, model_selector],
430
+ outputs=[chatbot],
431
+ show_progress=True
432
+ )
433
+
434
+ msg.submit(
435
+ chat_with_agent,
436
+ inputs=[msg, chatbot, model_selector],
437
+ outputs=[chatbot],
438
+ show_progress=True
439
+ )
440
+
441
+ clear_btn.click(
442
+ clear_chat,
443
+ outputs=[chatbot, msg]
444
+ )
445
+
446
+ # Instructions using Gradio markdown
447
+ gr.Markdown("""
448
+ ---
449
+
450
+ ### ℹ️ How it works
451
+
452
+ This AI agent can search Google News, fetch articles from specific sources, and provide comprehensive news summaries with proper citations. It uses real-time data and can restrict searches to specific news domains when requested.
453
+
454
+ **Model Selection:**
455
+ - **GPT-OSS 120B**: Larger, more capable model for complex reasoning tasks
456
+ - **GPT-OSS 20B**: Faster, more efficient model for quick responses
457
+ """)
458
+
459
+ # Launch the app
460
+ if __name__ == "__main__":
461
+ demo.launch(
462
+ server_name="0.0.0.0",
463
+ server_port=7860,
464
+ share=False,
465
+ show_error=True
466
+ )
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ gradio
2
+ openai
3
+ python-dotenv
4
+ trafilatura
5
+ langfuse