Exxe commited on
Commit
29195d6
Β·
verified Β·
1 Parent(s): bdfdf7c

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +323 -0
  2. requirements.txt +2 -0
app.py ADDED
@@ -0,0 +1,323 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ import json
4
+ import requests
5
+ import re
6
+ import traceback
7
+ from datetime import datetime
8
+
9
+ # ── Configuration ──
10
+ OPENROUTER_API_KEY = os.environ.get("OPENROUTER_API_KEY")
11
+ MODEL = "deepseek/deepseek-chat-v3-0324" # Free on OpenRouter
12
+ SITE_URL = "https://huggingface.co/spaces/YOUR_USERNAME/autonomous-agent"
13
+ SITE_NAME = "Autonomous Agent"
14
+ MAX_ITERATIONS = 10 # Safety limit for agent loop
15
+
16
+ # ── OpenRouter Client ──
17
+ def call_llm(messages, tools=None):
18
+ """Call OpenRouter API with optional tool definitions."""
19
+ headers = {
20
+ "Authorization": f"Bearer {OPENROUTER_API_KEY}",
21
+ "Content-Type": "application/json",
22
+ "HTTP-Referer": SITE_URL,
23
+ "X-Title": SITE_NAME,
24
+ }
25
+ payload = {
26
+ "model": MODEL,
27
+ "messages": messages,
28
+ "max_tokens": 2048,
29
+ "temperature": 0.6,
30
+ }
31
+ if tools:
32
+ payload["tools"] = tools
33
+ payload["tool_choice"] = "auto"
34
+
35
+ resp = requests.post(
36
+ "https://openrouter.ai/api/v1/chat/completions",
37
+ headers=headers,
38
+ json=payload,
39
+ timeout=60,
40
+ )
41
+ resp.raise_for_status()
42
+ return resp.json()
43
+
44
+
45
+ # ── Tool Definitions (OpenAI function-calling format) ──
46
+ TOOL_DEFINITIONS = [
47
+ {
48
+ "type": "function",
49
+ "function": {
50
+ "name": "web_search",
51
+ "description": "Search the web for current information. Returns top results with titles and snippets.",
52
+ "parameters": {
53
+ "type": "object",
54
+ "properties": {
55
+ "query": {"type": "string", "description": "Search query"}
56
+ },
57
+ "required": ["query"],
58
+ },
59
+ },
60
+ },
61
+ {
62
+ "type": "function",
63
+ "function": {
64
+ "name": "read_webpage",
65
+ "description": "Fetch and extract text content from a URL. Useful for reading articles, docs, or API references.",
66
+ "parameters": {
67
+ "type": "object",
68
+ "properties": {
69
+ "url": {"type": "string", "description": "URL to fetch"}
70
+ },
71
+ "required": ["url"],
72
+ },
73
+ },
74
+ },
75
+ {
76
+ "type": "function",
77
+ "function": {
78
+ "name": "execute_python",
79
+ "description": "Execute Python code and return stdout. Use for calculations, data processing, file manipulation. No network access in sandbox.",
80
+ "parameters": {
81
+ "type": "object",
82
+ "properties": {
83
+ "code": {"type": "string", "description": "Python code to execute"}
84
+ },
85
+ "required": ["code"],
86
+ },
87
+ },
88
+ },
89
+ {
90
+ "type": "function",
91
+ "function": {
92
+ "name": "write_file",
93
+ "description": "Write content to a file in the /data workspace. Returns confirmation.",
94
+ "parameters": {
95
+ "type": "object",
96
+ "properties": {
97
+ "path": {"type": "string", "description": "File path (relative to /data/)"},
98
+ "content": {"type": "string", "description": "File content to write"}
99
+ },
100
+ "required": ["path", "content"],
101
+ },
102
+ },
103
+ },
104
+ {
105
+ "type": "function",
106
+ "function": {
107
+ "name": "read_file",
108
+ "description": "Read content from a file in the /data workspace.",
109
+ "parameters": {
110
+ "type": "object",
111
+ "properties": {
112
+ "path": {"type": "string", "description": "File path (relative to /data/)"}
113
+ },
114
+ "required": ["path"],
115
+ },
116
+ },
117
+ },
118
+ ]
119
+
120
+
121
+ # ── Tool Implementations ──
122
+ def tool_web_search(query):
123
+ """Search using DuckDuckGo HTML (no API key needed)."""
124
+ try:
125
+ resp = requests.get(
126
+ "https://html.duckduckgo.com/html/",
127
+ params={"q": query},
128
+ headers={"User-Agent": "Mozilla/5.0"},
129
+ timeout=10,
130
+ )
131
+ results = []
132
+ # Extract result snippets from HTML
133
+ snippets = re.findall(
134
+ r'<a rel="nofollow" class="result__a"[^>]*>(.*?)</a>.*?'
135
+ r'<a class="result__snippet"[^>]*>(.*?)</a>',
136
+ resp.text, re.DOTALL,
137
+ )
138
+ for title, snippet in snippets[:5]:
139
+ clean_title = re.sub(r"<.*?>", "", title).strip()
140
+ clean_snippet = re.sub(r"<.*?>", "", snippet).strip()
141
+ results.append(f"**{clean_title}**\n{clean_snippet}")
142
+ return "\n\n".join(results) if results else "No results found."
143
+ except Exception as e:
144
+ return f"Search error: {e}"
145
+
146
+
147
+ def tool_read_webpage(url):
148
+ """Fetch and extract text from a URL."""
149
+ try:
150
+ resp = requests.get(
151
+ url,
152
+ headers={"User-Agent": "Mozilla/5.0"},
153
+ timeout=15,
154
+ )
155
+ resp.raise_for_status()
156
+ # Strip HTML tags for plain text
157
+ text = re.sub(r"<script[^>]*>.*?</script>", "", resp.text, flags=re.DOTALL)
158
+ text = re.sub(r"<style[^>]*>.*?</style>", "", text, flags=re.DOTALL)
159
+ text = re.sub(r"<.*?>", " ", text)
160
+ text = re.sub(r"\s+", " ", text).strip()
161
+ return text[:5000] # Limit to 5000 chars
162
+ except Exception as e:
163
+ return f"Error fetching URL: {e}"
164
+
165
+
166
+ def tool_execute_python(code):
167
+ """Execute Python code in a restricted namespace."""
168
+ import io
169
+ import contextlib
170
+
171
+ output = io.StringIO()
172
+ namespace = {"__builtins__": __builtins__}
173
+ try:
174
+ with contextlib.redirect_stdout(output):
175
+ exec(code, namespace)
176
+ result = output.getvalue()
177
+ return result if result else "(Code executed successfully, no output)"
178
+ except Exception as e:
179
+ return f"Error: {traceback.format_exc()}"
180
+
181
+
182
+ def tool_write_file(path, content):
183
+ """Write content to /data/ directory."""
184
+ try:
185
+ full_path = os.path.join("/data", path)
186
+ os.makedirs(os.path.dirname(full_path), exist_ok=True)
187
+ with open(full_path, "w") as f:
188
+ f.write(content)
189
+ return f"File written: /data/{path} ({len(content)} chars)"
190
+ except Exception as e:
191
+ return f"Error writing file: {e}"
192
+
193
+
194
+ def tool_read_file(path):
195
+ """Read content from /data/ directory."""
196
+ try:
197
+ full_path = os.path.join("/data", path)
198
+ with open(full_path, "r") as f:
199
+ return f.read()[:5000]
200
+ except Exception as e:
201
+ return f"Error reading file: {e}"
202
+
203
+
204
+ # Map tool names to implementations
205
+ TOOL_MAP = {
206
+ "web_search": tool_web_search,
207
+ "read_webpage": tool_read_webpage,
208
+ "execute_python": tool_execute_python,
209
+ "write_file": tool_write_file,
210
+ "read_file": tool_read_file,
211
+ }
212
+
213
+
214
+ # ── Agent Loop (ReAct) ──
215
+ SYSTEM_PROMPT = """You are an autonomous AI agent. You have access to tools that let you search the web,
216
+ read webpages, execute Python code, and read/write files.
217
+
218
+ When given a task:
219
+ 1. Think step-by-step about what you need to do
220
+ 2. Use tools to gather information or perform actions
221
+ 3. Synthesize the results into a clear answer
222
+ 4. If one approach fails, try another
223
+
224
+ Always be thorough. If a task requires multiple steps, complete all of them.
225
+ When writing code, always show the code and its output.
226
+ """
227
+
228
+ def run_agent(user_message, history):
229
+ """Run the autonomous agent loop with streaming."""
230
+ # Build conversation history
231
+ messages = [{"role": "system", "content": SYSTEM_PROMPT}]
232
+ for user_msg, asst_msg in history:
233
+ messages.append({"role": "user", "content": user_msg})
234
+ messages.append({"role": "assistant", "content": asst_msg})
235
+ messages.append({"role": "user", "content": user_message})
236
+
237
+ full_response = ""
238
+ iteration = 0
239
+
240
+ while iteration < MAX_ITERATIONS:
241
+ iteration += 1
242
+
243
+ # Call LLM
244
+ try:
245
+ result = call_llm(messages, tools=TOOL_DEFINITIONS)
246
+ except Exception as e:
247
+ error_msg = f"⚠️ API Error: {e}"
248
+ full_response += f"\n{error_msg}"
249
+ yield full_response.strip()
250
+ break
251
+
252
+ choice = result["choices"][0]
253
+ msg = choice["message"]
254
+
255
+ # If LLM wants to call tools
256
+ if msg.get("tool_calls"):
257
+ # Add assistant message with tool calls to history
258
+ messages.append(msg)
259
+
260
+ for tool_call in msg["tool_calls"]:
261
+ fn_name = tool_call["function"]["name"]
262
+ fn_args = json.loads(tool_call["function"]["arguments"])
263
+ tool_call_id = tool_call["id"]
264
+
265
+ # Show the user what the agent is doing
266
+ args_str = ", ".join(f"{k}={v!r}" for k, v in fn_args.items())
267
+ action_text = f"\nπŸ”§ **{fn_name}**({args_str})\n"
268
+ full_response += action_text
269
+ yield full_response.strip()
270
+
271
+ # Execute the tool
272
+ if fn_name in TOOL_MAP:
273
+ tool_result = TOOL_MAP[fn_name](**fn_args)
274
+ else:
275
+ tool_result = f"Unknown tool: {fn_name}"
276
+
277
+ # Show result summary
278
+ result_preview = tool_result[:300] + ("..." if len(tool_result) > 300 else "")
279
+ full_response += f"πŸ“‹ {result_preview}\n"
280
+ yield full_response.strip()
281
+
282
+ # Feed result back to LLM
283
+ messages.append({
284
+ "role": "tool",
285
+ "tool_call_id": tool_call_id,
286
+ "content": tool_result,
287
+ })
288
+ # Continue loop β€” LLM will reason about tool results
289
+
290
+ else:
291
+ # LLM gave a final answer (no tool calls)
292
+ final_text = msg.get("content", "")
293
+ if full_response:
294
+ full_response += f"\n\n---\n\n{final_text}"
295
+ else:
296
+ full_response = final_text
297
+ yield full_response.strip()
298
+ break
299
+
300
+ else:
301
+ # Hit iteration limit
302
+ full_response += "\n\n⚠️ Reached maximum iterations. Task may be incomplete."
303
+ yield full_response.strip()
304
+
305
+
306
+ # ── Gradio Interface ──
307
+ demo = gr.ChatInterface(
308
+ fn=run_agent,
309
+ title="πŸ€– Autonomous Agent",
310
+ description=(
311
+ "An autonomous AI agent powered by DeepSeek V3 (free via OpenRouter). "
312
+ "It can search the web, read pages, execute Python code, and manage files.\n\n"
313
+ "Try: *'Research the latest news about AI agents and write a summary to a file'*"
314
+ ),
315
+ type="messages",
316
+ chatbot=gr.Chatbot(height=600, show_copy_button=True),
317
+ textbox=gr.Textbox(placeholder="Give me a task...", scale=7),
318
+ theme=gr.themes.Soft(),
319
+ )
320
+
321
+ if __name__ == "__main__":
322
+ demo.queue(max_size=20)
323
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ gradio==4.44.0
2
+ requests==2.32.3