csabakecskemeti commited on
Commit
93bede5
·
verified ·
1 Parent(s): edb7869

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +297 -55
app.py CHANGED
@@ -1,64 +1,306 @@
 
1
  import gradio as gr
2
- from huggingface_hub import InferenceClient
3
-
4
- """
5
- For more information on `huggingface_hub` Inference API support, please check the docs: https://huggingface.co/docs/huggingface_hub/v0.22.2/en/guides/inference
6
- """
7
- client = InferenceClient("HuggingFaceH4/zephyr-7b-beta")
8
-
9
-
10
- def respond(
11
- message,
12
- history: list[tuple[str, str]],
13
- system_message,
14
- max_tokens,
15
- temperature,
16
- top_p,
17
- ):
18
- messages = [{"role": "system", "content": system_message}]
19
-
20
- for val in history:
21
- if val[0]:
22
- messages.append({"role": "user", "content": val[0]})
23
- if val[1]:
24
- messages.append({"role": "assistant", "content": val[1]})
25
-
26
- messages.append({"role": "user", "content": message})
27
-
28
- response = ""
29
-
30
- for message in client.chat_completion(
31
- messages,
32
- max_tokens=max_tokens,
33
- stream=True,
34
- temperature=temperature,
35
- top_p=top_p,
36
- ):
37
- token = message.choices[0].delta.content
38
-
39
- response += token
40
- yield response
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
 
42
 
43
- """
44
- For information on how to customize the ChatInterface, peruse the gradio docs: https://www.gradio.app/docs/chatinterface
45
- """
46
- demo = gr.ChatInterface(
47
- respond,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  additional_inputs=[
49
- gr.Textbox(value="You are a friendly Chatbot.", label="System message"),
50
- gr.Slider(minimum=1, maximum=2048, value=512, step=1, label="Max new tokens"),
51
- gr.Slider(minimum=0.1, maximum=4.0, value=0.7, step=0.1, label="Temperature"),
52
- gr.Slider(
53
- minimum=0.1,
54
- maximum=1.0,
55
- value=0.95,
56
- step=0.05,
57
- label="Top-p (nucleus sampling)",
58
  ),
 
 
 
 
 
 
 
 
 
 
59
  ],
 
 
 
 
 
 
 
60
  )
61
 
62
-
63
  if __name__ == "__main__":
64
- demo.launch()
 
1
+ import os
2
  import gradio as gr
3
+ import requests
4
+ import json
5
+ import asyncio
6
+ from typing import List, Dict, Any, Generator
7
+ import logging
8
+ from duckduckgo_search import DDGS
9
+ from bs4 import BeautifulSoup
10
+ import re
11
+
12
+ logging.basicConfig(level=logging.INFO)
13
+ logger = logging.getLogger(__name__)
14
+
15
+ # Configuration from environment variables with defaults
16
+ DEFAULT_IP = {public_ip}
17
+ DEFAULT_PORT = {port}
18
+ DEFAULT_KEY = {api_key}
19
+ DEFAULT_MODEL = {model}
20
+
21
+ llm_ip = os.environ.get('LLM_IP', DEFAULT_IP)
22
+ llm_port = os.environ.get('LLM_PORT', DEFAULT_PORT)
23
+ llm_key = os.environ.get('LLM_KEY', DEFAULT_KEY)
24
+ llm_model = os.environ.get('LLM_MODEL', DEFAULT_MODEL)
25
+
26
+ class WebTools:
27
+ def __init__(self):
28
+ self.session = requests.Session()
29
+ self.session.headers.update({
30
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
31
+ })
32
+ self.ddgs = DDGS()
33
+
34
+ def search_web(self, query: str, max_results: int = 5) -> str:
35
+ """Search the web using DuckDuckGo"""
36
+ try:
37
+ results = self.ddgs.text(query, max_results=max_results)
38
+ if not results:
39
+ return f"No search results found for: {query}"
40
+
41
+ formatted_results = f"Search results for '{query}':\n\n"
42
+ for i, result in enumerate(results, 1):
43
+ title = result.get('title', 'No title')
44
+ body = result.get('body', 'No description')
45
+ href = result.get('href', 'No URL')
46
+ formatted_results += f"{i}. **{title}**\n{body}\nURL: {href}\n\n"
47
+
48
+ return formatted_results
49
+ except Exception as e:
50
+ logger.error(f"Search error: {e}")
51
+ return f"Search error: {str(e)}"
52
+
53
+ def visit_website(self, url: str) -> str:
54
+ """Visit a website and extract its text content"""
55
+ try:
56
+ if not url.startswith(('http://', 'https://')):
57
+ url = 'https://' + url
58
+
59
+ response = self.session.get(url, timeout=10)
60
+ response.raise_for_status()
61
+
62
+ soup = BeautifulSoup(response.content, 'html.parser')
63
+
64
+ # Remove script and style elements
65
+ for script in soup(["script", "style", "nav", "footer", "header"]):
66
+ script.decompose()
67
+
68
+ # Get text content
69
+ text = soup.get_text()
70
+
71
+ # Clean up text
72
+ lines = (line.strip() for line in text.splitlines())
73
+ chunks = (phrase.strip() for line in lines for phrase in line.split(" "))
74
+ text = ' '.join(chunk for chunk in chunks if chunk)
75
+
76
+ # Limit text length
77
+ if len(text) > 3000:
78
+ text = text[:3000] + "... (content truncated)"
79
+
80
+ return f"Content from {url}:\n\n{text}"
81
+ except Exception as e:
82
+ logger.error(f"Website visit error: {e}")
83
+ return f"Error visiting {url}: {str(e)}"
84
+
85
+ class LLMClient:
86
+ def __init__(self, ip: str, port: str, api_key: str, model: str):
87
+ self.ip = ip
88
+ self.port = port
89
+ self.api_key = api_key
90
+ self.model = model
91
+ self.base_url = f"http://{ip}:{port}/v1/chat/completions"
92
+
93
+ def call_llm(self, messages: List[Dict], max_tokens: int = 512, stream: bool = False):
94
+ """Call the LLM API"""
95
+ headers = {
96
+ "Content-Type": "application/json",
97
+ "Authorization": f"Bearer {self.api_key}"
98
+ }
99
+ data = {
100
+ "model": self.model,
101
+ "messages": messages,
102
+ "max_tokens": max_tokens,
103
+ "stream": stream
104
+ }
105
+
106
+ try:
107
+ response = requests.post(self.base_url, headers=headers, json=data,
108
+ stream=stream, timeout=30)
109
+ response.raise_for_status()
110
+
111
+ if stream:
112
+ return response
113
+ else:
114
+ result = response.json()
115
+ return result["choices"][0]["message"]["content"]
116
+ except Exception as e:
117
+ logger.error(f"LLM API call failed: {e}")
118
+ return f"Error: {str(e)}"
119
+
120
+ class ReactAgent:
121
+ def __init__(self, llm_client: LLMClient):
122
+ self.llm_client = llm_client
123
+ self.web_tools = WebTools()
124
+ self.system_prompt = """You are a helpful AI assistant with access to web browsing capabilities. You can:
125
+ 1. Search the web using DuckDuckGo
126
+ 2. Visit and analyze websites
127
+ 3. Answer questions based on current information
128
+
129
+ When a user asks something that requires current information or web searching, use the available tools.
130
+
131
+ Available tools:
132
+ - search_web(query): Search DuckDuckGo for information
133
+ - visit_website(url): Visit and extract content from a website
134
+
135
+ Format your tool calls as: TOOL[tool_name: parameters]
136
+ For example: TOOL[search_web: latest news about AI] or TOOL[visit_website: https://example.com]
137
 
138
+ Always explain what you're doing and provide helpful responses based on the information you gather."""
139
 
140
+ def parse_tool_calls(self, text: str) -> List[Dict]:
141
+ """Parse tool calls from agent response"""
142
+ tool_pattern = r'TOOL\[(\w+):\s*([^\]]+)\]'
143
+ matches = re.findall(tool_pattern, text)
144
+
145
+ tools = []
146
+ for tool_name, params in matches:
147
+ tools.append({
148
+ 'name': tool_name,
149
+ 'params': params.strip()
150
+ })
151
+ return tools
152
+
153
+ def execute_tool(self, tool_name: str, params: str) -> str:
154
+ """Execute a tool and return results"""
155
+ try:
156
+ if tool_name == 'search_web':
157
+ return self.web_tools.search_web(params)
158
+ elif tool_name == 'visit_website':
159
+ return self.web_tools.visit_website(params)
160
+ else:
161
+ return f"Unknown tool: {tool_name}"
162
+ except Exception as e:
163
+ return f"Tool execution error: {str(e)}"
164
+
165
+ def process_message(self, message: str, history: List[List[str]], max_tokens: int) -> Generator[str, None, None]:
166
+ """Process user message with ReAct pattern"""
167
+ try:
168
+ # Format chat history
169
+ messages = [{"role": "system", "content": self.system_prompt}]
170
+
171
+ for user_msg, assistant_msg in history:
172
+ messages.append({"role": "user", "content": user_msg})
173
+ if assistant_msg:
174
+ messages.append({"role": "assistant", "content": assistant_msg})
175
+
176
+ messages.append({"role": "user", "content": message})
177
+
178
+ # Initial LLM call
179
+ response = self.llm_client.call_llm(messages, max_tokens, stream=True)
180
+
181
+ current_response = ""
182
+ tool_calls_made = False
183
+
184
+ # Stream initial response
185
+ for line in response.iter_lines():
186
+ if line:
187
+ line = line.decode('utf-8')
188
+ if line.startswith('data: '):
189
+ data_str = line[6:]
190
+ if data_str.strip() == '[DONE]':
191
+ break
192
+ try:
193
+ data = json.loads(data_str)
194
+ if 'choices' in data and len(data['choices']) > 0:
195
+ delta = data['choices'][0].get('delta', {})
196
+ content = delta.get('content', '')
197
+ if content:
198
+ current_response += content
199
+ yield current_response
200
+ except json.JSONDecodeError:
201
+ continue
202
+
203
+ # Check for tool calls
204
+ tool_calls = self.parse_tool_calls(current_response)
205
+
206
+ if tool_calls:
207
+ tool_calls_made = True
208
+ for tool_call in tool_calls:
209
+ yield current_response + f"\n\n🔍 Executing {tool_call['name']}..."
210
+
211
+ tool_result = self.execute_tool(tool_call['name'], tool_call['params'])
212
+
213
+ # Add tool result to conversation
214
+ messages.append({"role": "assistant", "content": current_response})
215
+ messages.append({"role": "user", "content": f"Tool result:\n{tool_result}\n\nPlease provide a helpful response based on this information."})
216
+
217
+ # Get final response
218
+ final_response = self.llm_client.call_llm(messages, max_tokens, stream=True)
219
+
220
+ final_text = current_response + f"\n\n**Tool Results:**\n{tool_result}\n\n**Response:**\n"
221
+
222
+ for line in final_response.iter_lines():
223
+ if line:
224
+ line = line.decode('utf-8')
225
+ if line.startswith('data: '):
226
+ data_str = line[6:]
227
+ if data_str.strip() == '[DONE]':
228
+ break
229
+ try:
230
+ data = json.loads(data_str)
231
+ if 'choices' in data and len(data['choices']) > 0:
232
+ delta = data['choices'][0].get('delta', {})
233
+ content = delta.get('content', '')
234
+ if content:
235
+ final_text += content
236
+ yield final_text
237
+ except json.JSONDecodeError:
238
+ continue
239
+ break # Only handle first tool call for now
240
+
241
+ except Exception as e:
242
+ error_msg = f"Agent error: {str(e)}"
243
+ logger.error(error_msg)
244
+ yield error_msg
245
+
246
+ # Initialize components
247
+ llm_client = LLMClient(llm_ip, llm_port, llm_key, llm_model)
248
+ agent = ReactAgent(llm_client)
249
+
250
+ def generate_response(message: str, history: List[List[str]], system_prompt: str,
251
+ max_tokens: int, ip: str, port: str, api_key: str, model: str):
252
+ """Generate streaming response using the agent"""
253
+ global llm_client, agent
254
+
255
+ # Update LLM client if parameters changed
256
+ if (ip != llm_client.ip or port != llm_client.port or
257
+ api_key != llm_client.api_key or model != llm_client.model):
258
+ llm_client = LLMClient(ip, port, api_key, model)
259
+ agent = ReactAgent(llm_client)
260
+
261
+ # Update system prompt if provided
262
+ if system_prompt.strip():
263
+ agent.system_prompt = system_prompt
264
+
265
+ # Generate response
266
+ for response in agent.process_message(message, history, max_tokens):
267
+ yield response
268
+
269
+ # Create Gradio interface
270
+ chatbot = gr.ChatInterface(
271
+ generate_response,
272
+ chatbot=gr.Chatbot(
273
+ avatar_images=[
274
+ None,
275
+ "https://cdn-avatars.huggingface.co/v1/production/uploads/64e6d37e02dee9bcb9d9fa18/o_HhUnXb_PgyYlqJ6gfEO.png"
276
+ ],
277
+ height="64vh"
278
+ ),
279
  additional_inputs=[
280
+ gr.Textbox(
281
+ "You are a helpful AI assistant with web browsing capabilities. You can search the web and visit websites to provide current information. Use TOOL[search_web: query] to search or TOOL[visit_website: url] to browse websites.",
282
+ label="System Prompt",
283
+ lines=3
 
 
 
 
 
284
  ),
285
+ gr.Slider(50, 2048, label="Max Tokens", value=512,
286
+ info="Maximum number of tokens in the response"),
287
+ gr.Textbox(llm_ip, label="LLM IP Address",
288
+ info="IP address of the LLM server"),
289
+ gr.Textbox(llm_port, label="LLM Port",
290
+ info="Port of the LLM server"),
291
+ gr.Textbox(llm_key, label="API Key", type="password",
292
+ info="API key for the LLM server"),
293
+ gr.Textbox(llm_model, label="Model Name",
294
+ info="Name of the model to use"),
295
  ],
296
+ title="🤖 AI Agent with Web Browsing",
297
+ description="Chat with an AI agent that can search the web and browse websites using DuckDuckGo. Use natural language to ask for current information!",
298
+ theme="finlaymacklon/smooth_slate",
299
+ submit_btn="Send",
300
+ retry_btn="🔄 Regenerate Response",
301
+ undo_btn="↩ Delete Previous",
302
+ clear_btn="🗑️ Clear Chat"
303
  )
304
 
 
305
  if __name__ == "__main__":
306
+ chatbot.queue().launch()%