Agent1 / agent_core.py
Mayank2027's picture
Update agent_core.py
74ff83c verified
Raw
History Blame Contribute Delete
14.6 kB
# agent_core.py
import asyncio
import base64
import json
import hashlib
import random
import time # <-- FIXED: missing import added
from typing import Dict, Any, List, Optional
from dataclasses import dataclass, field
from enum import Enum
import httpx
import groq
from openai import AsyncOpenAI # for OpenRouter compatibility
from playwright.async_api import async_playwright, Browser, Page, BrowserContext
from playwright_stealth import stealth_async
from fake_useragent import UserAgent
from stem import Signal
from stem.control import Controller
# ---------- HARDCODED API KEYS (as provided) ----------
GROQ_KEYS = [
"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",
"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"
]
OPENROUTER_KEYS = [
"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",
"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",
"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",
"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",
"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",
"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",
"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"
]
class LLMRouter:
def __init__(self):
self.groq_idx = 0
self.or_idx = 0
self.groq_clients = [groq.Groq(api_key=k) for k in GROQ_KEYS]
self.or_clients = [AsyncOpenAI(base_url="https://openrouter.ai/api/v1", api_key=k) for k in OPENROUTER_KEYS]
async def query_groq_vision(self, prompt: str, image_b64: str) -> str:
client = self.groq_clients[self.groq_idx % len(self.groq_clients)]
self.groq_idx += 1
response = client.chat.completions.create(
model="llama-3.2-90b-vision-preview",
messages=[{
"role": "user",
"content": [
{"type": "text", "text": prompt},
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{image_b64}"}}
]
}],
temperature=0.1,
max_tokens=4096
)
return response.choices[0].message.content
async def query_openrouter_text(self, prompt: str, system: str = "") -> str:
client = self.or_clients[self.or_idx % len(self.or_clients)]
self.or_idx += 1
response = await client.chat.completions.create(
model="anthropic/claude-3.5-sonnet",
messages=[
{"role": "system", "content": system},
{"role": "user", "content": prompt}
],
temperature=0.3,
max_tokens=8192
)
return response.choices[0].message.content
# ---------- ANONYMITY ENGINE ----------
class AnonymityEngine:
def __init__(self, tor_socks_port=9050, control_port=9051, control_pass=""):
self.tor_socks = f"socks5://127.0.0.1:{tor_socks_port}"
self.ua = UserAgent()
self.controller = Controller.from_port(port=control_port)
self.controller.authenticate(control_pass)
self.proxy_pool = [
"socks5://res1:pass@geo.ip:1080",
"socks5://res2:pass@geo.ip:1081",
"socks5://res3:pass@geo.ip:1082"
] # Rotating residential proxies – expand with your own
def renew_tor_ip(self):
self.controller.signal(Signal.NEWNYM)
time.sleep(1)
def get_browser_context_args(self) -> Dict:
proxy = random.choice(self.proxy_pool) if random.random() < 0.7 else self.tor_socks
return {
"proxy": {"server": proxy} if proxy.startswith("socks") else None,
"user_agent": self.ua.random,
"viewport": {"width": random.choice([1366, 1440, 1920]), "height": random.choice([768, 900, 1080])},
"locale": random.choice(["en-US", "en-GB", "de-DE", "fr-FR"]),
"timezone_id": random.choice(["America/New_York", "Europe/London", "Asia/Tokyo"]),
"permissions": [],
"device_scale_factor": random.choice([1, 2]),
"is_mobile": False,
"has_touch": False,
"color_scheme": random.choice(["light", "dark"]),
"extra_http_headers": {
"Accept-Language": random.choice(["en-US,en;q=0.9", "en-GB,en;q=0.8"]),
"DNT": "1"
}
}
async def apply_fingerprint_stealth(self, page: Page):
await stealth_async(page) # Patches navigator.webdriver, plugins, etc.
await page.evaluate("""
Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 8 });
Object.defineProperty(navigator, 'deviceMemory', { get: () => 8 });
Object.defineProperty(navigator, 'platform', { get: () => 'Win32' });
const originalGetContext = HTMLCanvasElement.prototype.getContext;
HTMLCanvasElement.prototype.getContext = function(type, ...args) {
if (type === 'webgl' || type === 'webgl2') {
const ctx = originalGetContext.call(this, type, ...args);
ctx.getParameter = new Proxy(ctx.getParameter, {
apply: (target, thisArg, args) => {
if (args[0] === 37445) return 'Intel Inc.';
if (args[0] === 37446) return 'Intel Iris Plus Graphics';
return Reflect.apply(target, thisArg, args);
}
});
return ctx;
}
return originalGetContext.call(this, type, ...args);
};
""")
# ---------- PLAYWRIGHT AGENT ----------
class AIONAgent:
def __init__(self):
self.llm = LLMRouter()
self.anonymity = AnonymityEngine()
self.playwright = None
self.browser: Browser = None
self.context: BrowserContext = None
self.page: Page = None
self.memory = [] # episodic memory (jsonl)
self.tool_schemas = self._load_tools()
async def __aenter__(self):
self.playwright = await async_playwright().start()
await self._launch_browser()
return self
async def __aexit__(self, *args):
if self.browser:
await self.browser.close()
if self.playwright:
await self.playwright.stop()
async def _launch_browser(self):
args = self.anonymity.get_browser_context_args()
# Launch a fresh Chromium with stealth flags
browser_args = [
"--disable-blink-features=AutomationControlled",
"--disable-dev-shm-usage",
"--no-sandbox",
"--disable-setuid-sandbox",
"--disable-gpu",
"--window-size=1920,1080",
"--disable-web-security", # for CORS bypass if needed
"--disable-features=IsolateOrigins,site-per-process",
"--disable-extensions",
"--disable-plugins",
"--disable-images" # optional – set to False for full rendering
]
if args.get("proxy"):
proxy_str = args["proxy"]["server"]
if proxy_str.startswith("socks"):
browser_args.append(f"--proxy-server={proxy_str}")
self.browser = await self.playwright.chromium.launch(
headless=False, # Set True for Hugging Face – but we need Xvfb
args=browser_args,
chromium_sandbox=False
)
self.context = await self.browser.new_context(
user_agent=args["user_agent"],
viewport=args["viewport"],
locale=args["locale"],
timezone_id=args["timezone_id"],
extra_http_headers=args["extra_http_headers"],
device_scale_factor=args["device_scale_factor"],
color_scheme=args["color_scheme"]
)
self.page = await self.context.new_page()
await self.anonymity.apply_fingerprint_stealth(self.page)
# Rotate IP on every new context
self.anonymity.renew_tor_ip()
# ---------- CORE TOOLS ----------
async def navigate(self, url: str, wait_until: str = "networkidle"):
await self.page.goto(url, wait_until=wait_until, timeout=60000)
return await self.page.title()
async def screenshot(self, full_page: bool = True, selector: str = None) -> str:
if selector:
element = await self.page.query_selector(selector)
screenshot_bytes = await element.screenshot()
else:
screenshot_bytes = await self.page.screenshot(full_page=full_page, type="png")
b64 = base64.b64encode(screenshot_bytes).decode("utf-8")
# Optionally upload to external host for persistence
return b64
async def click(self, selector: str, force: bool = False):
await self.page.click(selector, force=force)
await self.page.wait_for_load_state("networkidle", timeout=30000)
async def type_text(self, selector: str, text: str, delay: int = 50):
await self.page.fill(selector, text) # or type() with delay
async def extract_text(self, selector: str = "body") -> str:
return await self.page.inner_text(selector)
async def execute_js(self, script: str) -> Any:
return await self.page.evaluate(script)
async def wait_for_selector(self, selector: str, timeout: int = 30000):
await self.page.wait_for_selector(selector, timeout=timeout)
async def handle_popup(self) -> str:
async with self.page.expect_popup() as popup_info:
popup = await popup_info.value
await popup.wait_for_load_state()
return await popup.title()
# ---------- MULTIMODAL REASONING LOOP ----------
async def plan_and_act(self, objective: str, max_steps: int = 10):
"""ReAct loop: observe -> reason -> act -> screenshot -> reflect."""
for step in range(max_steps):
# 1. Capture current state
page_text = await self.extract_text()
screenshot_b64 = await self.screenshot(full_page=False)
# 2. Build prompt for GROK vision (decide next action)
prompt = f"""
Objective: {objective}
Current page text (truncated): {page_text[:2000]}
Available tools: {json.dumps(self.tool_schemas, indent=2)}
You are a web automation agent. Based on the screenshot and text, output a JSON with:
- "thought": brief analysis
- "action": one of [navigate, click, type, extract, scroll, wait, eval, screenshot, done]
- "params": parameters for that action (e.g., {{"selector":"#login", "text":"password"}})
- "confidence": 0-1
Return ONLY valid JSON.
"""
decision_json = await self.llm.query_groq_vision(prompt, screenshot_b64)
# Parse JSON (with fallback)
try:
decision = json.loads(decision_json)
except:
# Use OpenRouter as fallback parser
decision = json.loads(await self.llm.query_openrouter_text(
f"Fix this malformed JSON: {decision_json}",
system="Return only corrected JSON."
))
# 3. Execute action
action = decision.get("action")
params = decision.get("params", {})
if action == "done":
return await self.screenshot(full_page=True)
elif action == "navigate":
await self.navigate(params["url"])
elif action == "click":
await self.click(params["selector"])
elif action == "type":
await self.type_text(params["selector"], params["text"])
elif action == "extract":
result = await self.extract_text(params.get("selector", "body"))
self.memory.append({"step": step, "extracted": result})
elif action == "eval":
result = await self.execute_js(params["script"])
self.memory.append({"step": step, "js_result": result})
elif action == "screenshot":
# Capture and store externally
img_b64 = await self.screenshot(full_page=params.get("full", True))
# upload to ImgBB or similar (fake endpoint)
# await self._upload_image(img_b64)
self.memory.append({"step": step, "screenshot_b64": img_b64[:100] + "..."})
# 4. Reflect and store in memory
reflection = await self.llm.query_openrouter_text(
f"Given action '{action}' with params {params}, what did you observe? Briefly summarize.",
system="You are an AI reflecting on browser automation."
)
self.memory.append({"step": step, "reflection": reflection})
return {"status": "max_steps_reached", "memory": self.memory}
def _load_tools(self) -> List[Dict]:
return [
{"name": "navigate", "description": "Go to URL", "params": {"url": "string"}},
{"name": "click", "description": "Click element by CSS selector", "params": {"selector": "string"}},
{"name": "type", "description": "Input text into field", "params": {"selector": "string", "text": "string"}},
{"name": "extract", "description": "Get inner text from selector", "params": {"selector": "string (optional)"}},
{"name": "eval", "description": "Execute JS in page", "params": {"script": "string"}},
{"name": "screenshot", "description": "Capture full or partial page", "params": {"full": "boolean"}},
{"name": "done", "description": "Finish task", "params": {}}
]
# ---------- ENTRY POINT FOR HUGGING FACE SPACE ----------
async def run_agent(objective: str):
async with AIONAgent() as agent:
result = await agent.plan_and_act(objective, max_steps=8)
# Save memory to persistent volume
with open("/data/memory.jsonl", "a") as f:
for entry in agent.memory:
f.write(json.dumps(entry) + "\n")
final_screenshot = await agent.screenshot(full_page=True)
return {
"final_screenshot": final_screenshot,
"memory": agent.memory,
"status": result.get("status", "completed")
}