Shami96 commited on
Commit
0d2526a
Β·
verified Β·
1 Parent(s): c4251e6

Upload server.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. server.py +109 -190
server.py CHANGED
@@ -1,241 +1,160 @@
1
  """
2
- Shami's Deep Agent β€” FastAPI server for HuggingFace Spaces.
3
 
4
- Exposes a simple chat API that wraps Deep Agents with Groq backend.
5
- Accessible from anywhere via the HF Spaces URL.
 
 
 
 
 
 
6
  """
7
 
8
  import os
9
  import json
10
- import asyncio
11
  from datetime import datetime
12
- from pathlib import Path
13
  from contextlib import asynccontextmanager
 
14
 
15
  from fastapi import FastAPI, HTTPException
16
- from fastapi.responses import HTMLResponse, StreamingResponse
17
- from pydantic import BaseModel
18
-
19
- # ── Agent setup ──────────────────────────────────────────────────────────────
20
-
21
- AGENT = None
22
- CONVERSATIONS: dict[str, list[dict]] = {}
23
 
 
 
24
 
25
- def create_agent():
26
- """Create the Deep Agent with Groq backend."""
27
- try:
28
- from deepagents import create_deep_agent
29
-
30
- agents_md = Path("AGENTS.md").read_text()
31
-
32
- agent = create_deep_agent(
33
- model="groq:llama-3.3-70b-versatile",
34
- system_prompt=agents_md,
35
- )
36
- return agent
37
- except Exception as e:
38
- print(f"[WARN] Could not create Deep Agent: {e}")
39
- print("[INFO] Falling back to direct Groq API mode")
40
- return None
41
-
42
-
43
- def get_groq_client():
44
- """Direct Groq client as fallback when Deep Agents SDK isn't compatible."""
45
- try:
46
- from groq import Groq
47
- return Groq(api_key=os.environ.get("GROQ_API_KEY"))
48
- except ImportError:
49
- return None
50
-
51
-
52
- async def chat_with_groq(message: str, conversation_id: str) -> str:
53
- """Simple Groq chat as fallback."""
54
- client = get_groq_client()
55
- if not client:
56
- return "Error: Groq client not available. Check GROQ_API_KEY."
57
-
58
- # Load AGENTS.md as system prompt
59
- system_prompt = Path("AGENTS.md").read_text() if Path("AGENTS.md").exists() else "You are a helpful assistant."
60
- # Strip YAML frontmatter
61
- if system_prompt.startswith("---"):
62
- end = system_prompt.find("---", 3)
63
- if end != -1:
64
- system_prompt = system_prompt[end + 3:].strip()
65
-
66
- if conversation_id not in CONVERSATIONS:
67
- CONVERSATIONS[conversation_id] = []
68
-
69
- history = CONVERSATIONS[conversation_id]
70
- messages = [{"role": "system", "content": system_prompt}] + history + [{"role": "user", "content": message}]
71
-
72
- try:
73
- response = client.chat.completions.create(
74
- model="llama-3.3-70b-versatile",
75
- messages=messages,
76
- max_tokens=4096,
77
- temperature=0.7,
78
- )
79
- reply = response.choices[0].message.content or ""
80
- history.append({"role": "user", "content": message})
81
- history.append({"role": "assistant", "content": reply})
82
- # Keep history manageable
83
- if len(history) > 40:
84
- CONVERSATIONS[conversation_id] = history[-20:]
85
- return reply
86
- except Exception as e:
87
- return f"Error: {e}"
88
 
89
-
90
- # ── FastAPI app ──────────────────────────────────────────────────────────────
91
 
92
  @asynccontextmanager
93
  async def lifespan(app: FastAPI):
94
- global AGENT
95
- print(f"[{datetime.now().isoformat()}] Starting Shami's Deep Agent...")
96
  print(f" GROQ_API_KEY: {'SET' if os.environ.get('GROQ_API_KEY') else 'MISSING'}")
97
- AGENT = create_agent()
98
- if AGENT:
99
- print(" Mode: Deep Agents SDK")
100
- else:
101
- print(" Mode: Direct Groq API (fallback)")
102
  yield
103
- print("Shutting down...")
104
 
105
 
106
  app = FastAPI(
107
- title="Shami's Deep Agent",
108
- description="Personal AI agent powered by Groq + Deep Agents",
109
  lifespan=lifespan,
110
  )
111
 
112
 
113
- class ChatRequest(BaseModel):
114
- message: str
115
- conversation_id: str = "default"
116
 
 
 
 
117
 
118
- class ChatResponse(BaseModel):
119
- reply: str
 
 
 
 
120
  conversation_id: str
121
- model: str = "groq/llama-3.3-70b-versatile"
122
  timestamp: str
123
 
 
 
 
 
124
 
125
- @app.get("/", response_class=HTMLResponse)
126
- async def home():
127
- return """
128
- <!DOCTYPE html>
129
- <html>
130
- <head>
131
- <title>Shami's Deep Agent</title>
132
- <meta name="viewport" content="width=device-width, initial-scale=1">
133
- <style>
134
- * { margin: 0; padding: 0; box-sizing: border-box; }
135
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #0a0a0a; color: #e0e0e0; height: 100vh; display: flex; flex-direction: column; }
136
- .header { padding: 16px 24px; border-bottom: 1px solid #222; background: #111; }
137
- .header h1 { font-size: 18px; color: #fff; }
138
- .header p { font-size: 12px; color: #666; margin-top: 4px; }
139
- .chat { flex: 1; overflow-y: auto; padding: 24px; display: flex; flex-direction: column; gap: 16px; }
140
- .msg { max-width: 80%; padding: 12px 16px; border-radius: 12px; line-height: 1.5; font-size: 14px; white-space: pre-wrap; }
141
- .msg.user { align-self: flex-end; background: #1a3a5c; color: #e0e0e0; }
142
- .msg.assistant { align-self: flex-start; background: #1a1a1a; border: 1px solid #333; }
143
- .msg.assistant pre { background: #0d0d0d; padding: 8px; border-radius: 6px; overflow-x: auto; margin: 8px 0; }
144
- .msg.assistant code { font-family: 'SF Mono', monospace; font-size: 13px; }
145
- .input-area { padding: 16px 24px; border-top: 1px solid #222; background: #111; display: flex; gap: 12px; }
146
- .input-area input { flex: 1; padding: 12px 16px; background: #1a1a1a; border: 1px solid #333; border-radius: 8px; color: #e0e0e0; font-size: 14px; outline: none; }
147
- .input-area input:focus { border-color: #4a9eff; }
148
- .input-area button { padding: 12px 24px; background: #4a9eff; color: #fff; border: none; border-radius: 8px; font-size: 14px; cursor: pointer; }
149
- .input-area button:hover { background: #3a8eef; }
150
- .input-area button:disabled { background: #333; cursor: not-allowed; }
151
- .typing { color: #666; font-style: italic; }
152
- </style>
153
- </head>
154
- <body>
155
- <div class="header">
156
- <h1>Shami's Deep Agent</h1>
157
- <p>Groq / llama-3.3-70b-versatile &middot; Research &middot; Coding &middot; Planning</p>
158
- </div>
159
- <div class="chat" id="chat">
160
- <div class="msg assistant">Hey! I'm Shami's personal AI agent. Ask me anything β€” coding, research, planning, writing. Powered by Groq for fast responses.</div>
161
- </div>
162
- <div class="input-area">
163
- <input type="text" id="input" placeholder="Ask anything..." autofocus />
164
- <button id="send" onclick="sendMessage()">Send</button>
165
- </div>
166
- <script>
167
- const chat = document.getElementById('chat');
168
- const input = document.getElementById('input');
169
- const sendBtn = document.getElementById('send');
170
- const convId = 'web-' + Date.now();
171
-
172
- input.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !sendBtn.disabled) sendMessage(); });
173
-
174
- async function sendMessage() {
175
- const msg = input.value.trim();
176
- if (!msg) return;
177
- input.value = '';
178
- sendBtn.disabled = true;
179
-
180
- chat.innerHTML += `<div class="msg user">${escapeHtml(msg)}</div>`;
181
- chat.innerHTML += `<div class="msg assistant typing" id="typing">Thinking...</div>`;
182
- chat.scrollTop = chat.scrollHeight;
183
-
184
- try {
185
- const res = await fetch('/chat', {
186
- method: 'POST',
187
- headers: { 'Content-Type': 'application/json' },
188
- body: JSON.stringify({ message: msg, conversation_id: convId })
189
- });
190
- const data = await res.json();
191
- document.getElementById('typing')?.remove();
192
- chat.innerHTML += `<div class="msg assistant">${formatMarkdown(data.reply)}</div>`;
193
- } catch (e) {
194
- document.getElementById('typing')?.remove();
195
- chat.innerHTML += `<div class="msg assistant">Error: ${e.message}</div>`;
196
- }
197
- chat.scrollTop = chat.scrollHeight;
198
- sendBtn.disabled = false;
199
- input.focus();
200
- }
201
 
202
- function escapeHtml(s) { return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
203
- function formatMarkdown(s) {
204
- return escapeHtml(s)
205
- .replace(/```(\\w+)?\\n([\\s\\S]*?)```/g, '<pre><code>$2</code></pre>')
206
- .replace(/`([^`]+)`/g, '<code>$1</code>')
207
- .replace(/\\*\\*([^*]+)\\*\\*/g, '<strong>$1</strong>')
208
- .replace(/\\n/g, '<br>');
209
- }
210
- </script>
211
- </body>
212
- </html>
213
- """
214
 
 
 
 
 
 
 
 
 
215
 
216
- @app.post("/chat", response_model=ChatResponse)
217
- async def chat_endpoint(req: ChatRequest):
218
- reply = await chat_with_groq(req.message, req.conversation_id)
219
- return ChatResponse(
220
- reply=reply,
221
  conversation_id=req.conversation_id,
 
 
 
 
 
 
 
 
 
222
  timestamp=datetime.now().isoformat(),
223
  )
224
 
225
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
  @app.get("/health")
227
  async def health():
228
  return {
229
  "status": "ok",
230
- "mode": "deep_agents" if AGENT else "groq_direct",
231
  "model": "groq/llama-3.3-70b-versatile",
232
  "timestamp": datetime.now().isoformat(),
233
  }
234
 
235
 
236
- @app.get("/api/models")
237
- async def list_models():
238
- return {"models": ["groq/llama-3.3-70b-versatile"]}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
 
240
 
241
  if __name__ == "__main__":
 
1
  """
2
+ Shami's Multi-Agent System β€” FastAPI server for HuggingFace Spaces.
3
 
4
+ 5 purpose-built agents, each with custom tools:
5
+ - Job Search: find, evaluate, draft cover letters
6
+ - Research: multi-source research with structured reports
7
+ - Code Review: GitHub PR analysis
8
+ - Upwork Proposals: tailored proposal drafting
9
+ - n8n Handler: freelance message analysis + reply drafting
10
+
11
+ All powered by Groq (free, fast) with Tavily web search.
12
  """
13
 
14
  import os
15
  import json
 
16
  from datetime import datetime
 
17
  from contextlib import asynccontextmanager
18
+ from pathlib import Path
19
 
20
  from fastapi import FastAPI, HTTPException
21
+ from fastapi.responses import HTMLResponse, FileResponse
22
+ from fastapi.staticfiles import StaticFiles
23
+ from pydantic import BaseModel, Field
 
 
 
 
24
 
25
+ from agents.engine import run_agent
26
+ from agents.definitions import AGENTS
27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
+ # ── App setup ────────────────────────────────────────────────────────────────
 
30
 
31
  @asynccontextmanager
32
  async def lifespan(app: FastAPI):
33
+ print(f"[{datetime.now().isoformat()}] Starting Shami's Agent System")
 
34
  print(f" GROQ_API_KEY: {'SET' if os.environ.get('GROQ_API_KEY') else 'MISSING'}")
35
+ print(f" TAVILY_API_KEY: {'SET' if os.environ.get('TAVILY_API_KEY') else 'MISSING'}")
36
+ print(f" Agents: {', '.join(AGENTS.keys())}")
 
 
 
37
  yield
38
+ print("Shutting down.")
39
 
40
 
41
  app = FastAPI(
42
+ title="Shami's Agent System",
43
+ description="Multi-agent AI system β€” job search, research, code review, proposals, freelance messaging",
44
  lifespan=lifespan,
45
  )
46
 
47
 
48
+ # ── Request/Response models ──────────────────────────────────────────────────
 
 
49
 
50
+ class AgentRequest(BaseModel):
51
+ message: str = Field(..., description="The goal or task for the agent")
52
+ conversation_id: str = Field(default="default", description="Conversation ID for context")
53
 
54
+ class AgentResponse(BaseModel):
55
+ agent: str
56
+ result: str
57
+ tool_calls_made: int
58
+ tools_used: list[str]
59
+ iterations: int
60
  conversation_id: str
 
61
  timestamp: str
62
 
63
+ class N8nWebhookRequest(BaseModel):
64
+ message: str = Field(..., description="The incoming freelance message")
65
+ platform: str = Field(default="unknown", description="Source platform (upwork/fiverr/email)")
66
+ sender: str = Field(default="", description="Sender name or username")
67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
 
69
+ # ── Agent endpoints ──────────────────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
70
 
71
+ @app.post("/agent/{agent_type}", response_model=AgentResponse)
72
+ async def run_agent_endpoint(agent_type: str, req: AgentRequest):
73
+ """Run a specific agent with a goal."""
74
+ if agent_type not in AGENTS:
75
+ raise HTTPException(
76
+ status_code=404,
77
+ detail=f"Unknown agent: {agent_type}. Available: {list(AGENTS.keys())}",
78
+ )
79
 
80
+ agent_def = AGENTS[agent_type]
81
+ result = await run_agent(
82
+ goal=req.message,
83
+ system_prompt=agent_def["prompt"],
84
+ tools=agent_def["tools"],
85
  conversation_id=req.conversation_id,
86
+ )
87
+
88
+ return AgentResponse(
89
+ agent=agent_def["name"],
90
+ result=result["result"],
91
+ tool_calls_made=result["tool_calls_made"],
92
+ tools_used=result["tools_used"],
93
+ iterations=result["iterations"],
94
+ conversation_id=result["conversation_id"],
95
  timestamp=datetime.now().isoformat(),
96
  )
97
 
98
 
99
+ @app.post("/agent/n8n", response_model=AgentResponse)
100
+ async def n8n_webhook(req: N8nWebhookRequest):
101
+ """n8n webhook endpoint β€” receives freelance messages, returns analysis + draft reply."""
102
+ agent_def = AGENTS["n8n"]
103
+ goal = f"Platform: {req.platform}\nSender: {req.sender}\n\nMessage:\n{req.message}"
104
+
105
+ result = await run_agent(
106
+ goal=goal,
107
+ system_prompt=agent_def["prompt"],
108
+ tools=agent_def["tools"],
109
+ conversation_id=f"n8n-{datetime.now().strftime('%Y%m%d-%H%M')}",
110
+ )
111
+
112
+ return AgentResponse(
113
+ agent=agent_def["name"],
114
+ result=result["result"],
115
+ tool_calls_made=result["tool_calls_made"],
116
+ tools_used=result["tools_used"],
117
+ iterations=result["iterations"],
118
+ conversation_id=result["conversation_id"],
119
+ timestamp=datetime.now().isoformat(),
120
+ )
121
+
122
+
123
+ # ── Utility endpoints ────────────────────────────────────────────────────────
124
+
125
  @app.get("/health")
126
  async def health():
127
  return {
128
  "status": "ok",
129
+ "agents": {k: {"name": v["name"], "tools": [t.__name__ for t in v["tools"]]} for k, v in AGENTS.items()},
130
  "model": "groq/llama-3.3-70b-versatile",
131
  "timestamp": datetime.now().isoformat(),
132
  }
133
 
134
 
135
+ @app.get("/agents")
136
+ async def list_agents():
137
+ """List all available agents with their descriptions."""
138
+ return {
139
+ "agents": [
140
+ {
141
+ "id": k,
142
+ "name": v["name"],
143
+ "description": v["description"],
144
+ "icon": v["icon"],
145
+ "tools": [t.__name__ for t in v["tools"]],
146
+ "endpoint": f"/agent/{k}",
147
+ }
148
+ for k, v in AGENTS.items()
149
+ ]
150
+ }
151
+
152
+
153
+ # ── Web UI ───────────────────────────────────────────────────────────────────
154
+
155
+ @app.get("/", response_class=HTMLResponse)
156
+ async def home():
157
+ return (Path(__file__).parent / "static" / "index.html").read_text()
158
 
159
 
160
  if __name__ == "__main__":