Spaces:
Paused
Paused
Update agents.py
Browse files
agents.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
# agents.py –
|
| 2 |
|
| 3 |
import json
|
| 4 |
import logging
|
|
@@ -15,7 +15,7 @@ class BaseAgent:
|
|
| 15 |
def __init__(self, name: str, role: str, system_prompt: str, tools: ToolRegistry, state: Any):
|
| 16 |
self.name = name
|
| 17 |
self.role = role
|
| 18 |
-
self.system_prompt = system_prompt[:
|
| 19 |
self.tools = tools
|
| 20 |
self.state = state
|
| 21 |
self.history = []
|
|
@@ -25,20 +25,19 @@ class BaseAgent:
|
|
| 25 |
self.plan_index = 0
|
| 26 |
|
| 27 |
async def think(self, context: str) -> Dict:
|
| 28 |
-
# Build context with
|
| 29 |
history_text = ""
|
| 30 |
if self.history:
|
| 31 |
-
last_entries = self.history[-
|
| 32 |
history_text = "\n".join([
|
| 33 |
-
f"
|
| 34 |
-
for
|
| 35 |
])
|
| 36 |
|
| 37 |
-
full_context = f"Task: {self.state.task}\nHistory:\n{history_text}\
|
| 38 |
-
if len(full_context) >
|
| 39 |
-
full_context = full_context[:
|
| 40 |
|
| 41 |
-
# Short system prompt
|
| 42 |
short_prompt = self.system_prompt + """
|
| 43 |
Output ONLY JSON. Format: {"thought":"...","action":{"tool":"...","params":{...}}}
|
| 44 |
"""
|
|
@@ -49,6 +48,14 @@ Output ONLY JSON. Format: {"thought":"...","action":{"tool":"...","params":{...}
|
|
| 49 |
raw, meta = await LLMClient.chat(messages, temperature=0.1, response_format="json")
|
| 50 |
logger.info(f"Raw LLM response: {raw[:150]}...")
|
| 51 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
json_data = self._extract_json(raw)
|
| 53 |
if json_data:
|
| 54 |
try:
|
|
@@ -64,15 +71,13 @@ Output ONLY JSON. Format: {"thought":"...","action":{"tool":"...","params":{...}
|
|
| 64 |
except Exception as e:
|
| 65 |
logger.error(f"JSON parse error: {e}")
|
| 66 |
|
| 67 |
-
#
|
| 68 |
-
if self.plan and self.plan_index
|
| 69 |
-
|
| 70 |
return {
|
| 71 |
-
"thought": "
|
| 72 |
-
"action": {"tool": "
|
| 73 |
}
|
| 74 |
-
|
| 75 |
-
logger.warning("Using fallback navigate")
|
| 76 |
return {
|
| 77 |
"thought": "Fallback",
|
| 78 |
"action": {"tool": "navigate", "params": {"url": "https://example.com"}}
|
|
@@ -80,8 +85,8 @@ Output ONLY JSON. Format: {"thought":"...","action":{"tool":"...","params":{...}
|
|
| 80 |
|
| 81 |
def _summarize_result(self, result):
|
| 82 |
if isinstance(result, dict):
|
| 83 |
-
return result.get("result", "") or result.get("data", "") or str(result)[:
|
| 84 |
-
return str(result)[:
|
| 85 |
|
| 86 |
def _extract_json(self, text: str) -> str:
|
| 87 |
start = text.find('{')
|
|
@@ -103,8 +108,8 @@ Output ONLY JSON. Format: {"thought":"...","action":{"tool":"...","params":{...}
|
|
| 103 |
if tool_name == "done":
|
| 104 |
self.done = True
|
| 105 |
self.result = params.get("result", "Task completed.")
|
|
|
|
| 106 |
try:
|
| 107 |
-
await asyncio.sleep(0.2)
|
| 108 |
screenshot = await self.tools("screenshot")
|
| 109 |
return {"result": self.result, "screenshot": screenshot.get("result", "")}
|
| 110 |
except:
|
|
@@ -112,16 +117,24 @@ Output ONLY JSON. Format: {"thought":"...","action":{"tool":"...","params":{...}
|
|
| 112 |
|
| 113 |
try:
|
| 114 |
result = await self.tools(tool_name, **params)
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
|
|
|
|
|
|
|
|
|
| 118 |
# Truncate large data
|
| 119 |
-
if "data" in result and isinstance(result["data"], str) and len(result["data"]) >
|
| 120 |
-
result["data"] = result["data"][:
|
| 121 |
return result
|
| 122 |
except Exception as e:
|
| 123 |
logger.error(f"Action failed: {e}")
|
| 124 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
|
| 126 |
async def run(self, context: str) -> Dict:
|
| 127 |
self.history = []
|
|
@@ -130,10 +143,9 @@ Output ONLY JSON. Format: {"thought":"...","action":{"tool":"...","params":{...}
|
|
| 130 |
self.plan = []
|
| 131 |
self.plan_index = 0
|
| 132 |
steps = 0
|
| 133 |
-
max_steps =
|
| 134 |
|
| 135 |
while not self.done and steps < max_steps:
|
| 136 |
-
# Build context with current step info
|
| 137 |
data = await self.think(context)
|
| 138 |
thought = data.get("thought", "")
|
| 139 |
action = data.get("action", {"tool": "done", "params": {"result": "No action"}})
|
|
@@ -149,9 +161,13 @@ Output ONLY JSON. Format: {"thought":"...","action":{"tool":"...","params":{...}
|
|
| 149 |
if self.done:
|
| 150 |
break
|
| 151 |
|
| 152 |
-
# If loop ended without done,
|
| 153 |
-
if not self.done:
|
| 154 |
-
self.result = self.history[-1].get("result", {}).get("result", "Task incomplete")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
return {
|
| 156 |
"agent": self.name,
|
| 157 |
"result": self.result,
|
|
@@ -170,42 +186,33 @@ Output ONLY JSON. Format: {"thought":"...","action":{"tool":"...","params":{...}
|
|
| 170 |
}
|
| 171 |
|
| 172 |
|
| 173 |
-
# ---------- Specialist Agents
|
| 174 |
class ResearchAgent(BaseAgent):
|
| 175 |
def __init__(self, tools: ToolRegistry, state: Any):
|
| 176 |
-
prompt = ""
|
| 177 |
-
You are a Research Agent. Task: Go to wikipedia and search for AI.
|
| 178 |
-
Follow these steps exactly:
|
| 179 |
-
1. Navigate to https://www.wikipedia.org
|
| 180 |
-
2. Fill #searchInput with "AI"
|
| 181 |
-
3. Click button[type='submit']
|
| 182 |
-
4. Extract text from #firstHeading
|
| 183 |
-
5. Done with result: "Article title is: " + title.
|
| 184 |
-
Only output one action per JSON.
|
| 185 |
-
"""
|
| 186 |
super().__init__("Research Agent", "Gather", prompt, tools, state)
|
| 187 |
|
| 188 |
class SalesAgent(BaseAgent):
|
| 189 |
def __init__(self, tools: ToolRegistry, state: Any):
|
| 190 |
-
prompt = "You are a Sales Agent. Generate proposals and emails.
|
| 191 |
super().__init__("Sales Agent", "Sales", prompt, tools, state)
|
| 192 |
|
| 193 |
class SupportAgent(BaseAgent):
|
| 194 |
def __init__(self, tools: ToolRegistry, state: Any):
|
| 195 |
-
prompt = "You are a Support Agent. Search knowledge base and generate FAQs.
|
| 196 |
super().__init__("Support Agent", "Support", prompt, tools, state)
|
| 197 |
|
| 198 |
class DataAgent(BaseAgent):
|
| 199 |
def __init__(self, tools: ToolRegistry, state: Any):
|
| 200 |
-
prompt = "You are a Data Agent. Analyse data and create plots.
|
| 201 |
super().__init__("Data Agent", "Data", prompt, tools, state)
|
| 202 |
|
| 203 |
class CodeAgent(BaseAgent):
|
| 204 |
def __init__(self, tools: ToolRegistry, state: Any):
|
| 205 |
-
prompt = "You are a Code Agent. Write and execute Python code.
|
| 206 |
super().__init__("Code Agent", "Code", prompt, tools, state)
|
| 207 |
|
| 208 |
class ContentAgent(BaseAgent):
|
| 209 |
def __init__(self, tools: ToolRegistry, state: Any):
|
| 210 |
-
prompt = "You are a Content Agent. Summarise and translate text.
|
| 211 |
super().__init__("Content Agent", "Content", prompt, tools, state)
|
|
|
|
| 1 |
+
# agents.py – FIXED: forced done, screenshot on error, truncated context
|
| 2 |
|
| 3 |
import json
|
| 4 |
import logging
|
|
|
|
| 15 |
def __init__(self, name: str, role: str, system_prompt: str, tools: ToolRegistry, state: Any):
|
| 16 |
self.name = name
|
| 17 |
self.role = role
|
| 18 |
+
self.system_prompt = system_prompt[:400] # Keep short
|
| 19 |
self.tools = tools
|
| 20 |
self.state = state
|
| 21 |
self.history = []
|
|
|
|
| 25 |
self.plan_index = 0
|
| 26 |
|
| 27 |
async def think(self, context: str) -> Dict:
|
| 28 |
+
# Build context with last 2 actions only, heavily truncated
|
| 29 |
history_text = ""
|
| 30 |
if self.history:
|
| 31 |
+
last_entries = self.history[-2:]
|
| 32 |
history_text = "\n".join([
|
| 33 |
+
f"Action: {entry['action'].get('tool')} -> Result: {self._summarize_result(entry['result'])}"
|
| 34 |
+
for entry in last_entries
|
| 35 |
])
|
| 36 |
|
| 37 |
+
full_context = f"Task: {self.state.task}\nHistory:\n{history_text}\nInstruction: {context}"
|
| 38 |
+
if len(full_context) > 800:
|
| 39 |
+
full_context = full_context[:800] + "\n... (truncated)"
|
| 40 |
|
|
|
|
| 41 |
short_prompt = self.system_prompt + """
|
| 42 |
Output ONLY JSON. Format: {"thought":"...","action":{"tool":"...","params":{...}}}
|
| 43 |
"""
|
|
|
|
| 48 |
raw, meta = await LLMClient.chat(messages, temperature=0.1, response_format="json")
|
| 49 |
logger.info(f"Raw LLM response: {raw[:150]}...")
|
| 50 |
|
| 51 |
+
# If we already have a plan and all steps executed, force done
|
| 52 |
+
if self.plan and self.plan_index >= len(self.plan):
|
| 53 |
+
logger.info("Plan completed, forcing done")
|
| 54 |
+
return {
|
| 55 |
+
"thought": "Plan completed",
|
| 56 |
+
"action": {"tool": "done", "params": {"result": "Task completed by plan"}}
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
json_data = self._extract_json(raw)
|
| 60 |
if json_data:
|
| 61 |
try:
|
|
|
|
| 71 |
except Exception as e:
|
| 72 |
logger.error(f"JSON parse error: {e}")
|
| 73 |
|
| 74 |
+
# Fallback: if we have a plan, execute next step; else navigate
|
| 75 |
+
if self.plan and self.plan_index < len(self.plan):
|
| 76 |
+
# We don't have a specific action for the next step, so fallback to navigate
|
| 77 |
return {
|
| 78 |
+
"thought": "Fallback to navigate",
|
| 79 |
+
"action": {"tool": "navigate", "params": {"url": "https://example.com"}}
|
| 80 |
}
|
|
|
|
|
|
|
| 81 |
return {
|
| 82 |
"thought": "Fallback",
|
| 83 |
"action": {"tool": "navigate", "params": {"url": "https://example.com"}}
|
|
|
|
| 85 |
|
| 86 |
def _summarize_result(self, result):
|
| 87 |
if isinstance(result, dict):
|
| 88 |
+
return result.get("result", "") or result.get("data", "") or str(result)[:80]
|
| 89 |
+
return str(result)[:80]
|
| 90 |
|
| 91 |
def _extract_json(self, text: str) -> str:
|
| 92 |
start = text.find('{')
|
|
|
|
| 108 |
if tool_name == "done":
|
| 109 |
self.done = True
|
| 110 |
self.result = params.get("result", "Task completed.")
|
| 111 |
+
# Always capture a final screenshot
|
| 112 |
try:
|
|
|
|
| 113 |
screenshot = await self.tools("screenshot")
|
| 114 |
return {"result": self.result, "screenshot": screenshot.get("result", "")}
|
| 115 |
except:
|
|
|
|
| 117 |
|
| 118 |
try:
|
| 119 |
result = await self.tools(tool_name, **params)
|
| 120 |
+
# Capture screenshot after action
|
| 121 |
+
try:
|
| 122 |
+
screenshot = await self.tools("screenshot")
|
| 123 |
+
result["screenshot"] = screenshot.get("result", "")
|
| 124 |
+
except:
|
| 125 |
+
result["screenshot"] = ""
|
| 126 |
# Truncate large data
|
| 127 |
+
if "data" in result and isinstance(result["data"], str) and len(result["data"]) > 200:
|
| 128 |
+
result["data"] = result["data"][:200] + "... (truncated)"
|
| 129 |
return result
|
| 130 |
except Exception as e:
|
| 131 |
logger.error(f"Action failed: {e}")
|
| 132 |
+
# Even on error, try to capture screenshot
|
| 133 |
+
try:
|
| 134 |
+
screenshot = await self.tools("screenshot")
|
| 135 |
+
return {"error": str(e), "screenshot": screenshot.get("result", "")}
|
| 136 |
+
except:
|
| 137 |
+
return {"error": str(e)}
|
| 138 |
|
| 139 |
async def run(self, context: str) -> Dict:
|
| 140 |
self.history = []
|
|
|
|
| 143 |
self.plan = []
|
| 144 |
self.plan_index = 0
|
| 145 |
steps = 0
|
| 146 |
+
max_steps = 8 # Reduce to speed up
|
| 147 |
|
| 148 |
while not self.done and steps < max_steps:
|
|
|
|
| 149 |
data = await self.think(context)
|
| 150 |
thought = data.get("thought", "")
|
| 151 |
action = data.get("action", {"tool": "done", "params": {"result": "No action"}})
|
|
|
|
| 161 |
if self.done:
|
| 162 |
break
|
| 163 |
|
| 164 |
+
# If loop ended without done, mark done with last result
|
| 165 |
+
if not self.done and self.history:
|
| 166 |
+
self.result = self.history[-1].get("result", {}).get("result", "Task incomplete")
|
| 167 |
+
elif not self.done:
|
| 168 |
+
self.result = "No actions executed"
|
| 169 |
+
self.done = True # Force completion to avoid infinite loop
|
| 170 |
+
|
| 171 |
return {
|
| 172 |
"agent": self.name,
|
| 173 |
"result": self.result,
|
|
|
|
| 186 |
}
|
| 187 |
|
| 188 |
|
| 189 |
+
# ---------- Specialist Agents ----------
|
| 190 |
class ResearchAgent(BaseAgent):
|
| 191 |
def __init__(self, tools: ToolRegistry, state: Any):
|
| 192 |
+
prompt = "You are a Research Agent. Task: Go to wikipedia and search for AI. Steps: navigate, fill search, click, extract title, done."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
super().__init__("Research Agent", "Gather", prompt, tools, state)
|
| 194 |
|
| 195 |
class SalesAgent(BaseAgent):
|
| 196 |
def __init__(self, tools: ToolRegistry, state: Any):
|
| 197 |
+
prompt = "You are a Sales Agent. Generate proposals and emails."
|
| 198 |
super().__init__("Sales Agent", "Sales", prompt, tools, state)
|
| 199 |
|
| 200 |
class SupportAgent(BaseAgent):
|
| 201 |
def __init__(self, tools: ToolRegistry, state: Any):
|
| 202 |
+
prompt = "You are a Support Agent. Search knowledge base and generate FAQs."
|
| 203 |
super().__init__("Support Agent", "Support", prompt, tools, state)
|
| 204 |
|
| 205 |
class DataAgent(BaseAgent):
|
| 206 |
def __init__(self, tools: ToolRegistry, state: Any):
|
| 207 |
+
prompt = "You are a Data Agent. Analyse data and create plots."
|
| 208 |
super().__init__("Data Agent", "Data", prompt, tools, state)
|
| 209 |
|
| 210 |
class CodeAgent(BaseAgent):
|
| 211 |
def __init__(self, tools: ToolRegistry, state: Any):
|
| 212 |
+
prompt = "You are a Code Agent. Write and execute Python code."
|
| 213 |
super().__init__("Code Agent", "Code", prompt, tools, state)
|
| 214 |
|
| 215 |
class ContentAgent(BaseAgent):
|
| 216 |
def __init__(self, tools: ToolRegistry, state: Any):
|
| 217 |
+
prompt = "You are a Content Agent. Summarise and translate text."
|
| 218 |
super().__init__("Content Agent", "Content", prompt, tools, state)
|