semantio 0.0.3__py3-none-any.whl → 0.0.4__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
semantio/agent.py CHANGED
@@ -56,8 +56,11 @@ class Agent(BaseModel):
56
56
  super().__init__(**kwargs)
57
57
  # Initialize the model and tools here if needed
58
58
  self._initialize_model()
59
- # Automatically discover and register tools if not provided
59
+ # Initialize tools as an empty list if not provided
60
60
  if self.tools is None:
61
+ self.tools = []
62
+ # Automatically discover and register tools if auto tool is enabled
63
+ if self.auto_tool and not self.tools:
61
64
  self.tools = self._discover_tools()
62
65
  # Pass the LLM instance to each tool
63
66
  for tool in self.tools:
@@ -213,7 +216,6 @@ class Agent(BaseModel):
213
216
  message: Optional[Union[str, Image, List, Dict]] = None,
214
217
  stream: bool = False,
215
218
  markdown: bool = False,
216
- tools: Optional[List[BaseTool]] = None,
217
219
  team: Optional[List['Agent']] = None,
218
220
  **kwargs,
219
221
  ) -> Union[str, Dict]: # Add return type hint
@@ -228,7 +230,7 @@ class Agent(BaseModel):
228
230
  return response
229
231
  else:
230
232
  # Generate and return the response
231
- response = self._generate_response(message, markdown=markdown, tools=tools, team=team, **kwargs)
233
+ response = self._generate_response(message, markdown=markdown, team=team, **kwargs)
232
234
  print(response) # Print the response to the console
233
235
  return response
234
236
 
@@ -245,43 +247,6 @@ class Agent(BaseModel):
245
247
  if self.tools is None:
246
248
  self.tools = []
247
249
  self.tools.append(tool)
248
-
249
- def _detect_tool_call(self, message: str) -> Optional[Dict[str, Any]]:
250
- """
251
- Use the LLM to detect which tool should be called based on the user's query.
252
- """
253
- if not self.tools:
254
- logger.warning("No tools available to detect.")
255
- return None
256
-
257
- # Create a prompt for the LLM
258
- prompt = f"""
259
- You are an AI agent that helps users by selecting the most appropriate tool to answer their query. Below is a list of available tools and their functionalities:
260
-
261
- {self._get_tool_descriptions()}
262
-
263
- Based on the user's query, select the most appropriate tool. Respond with the name of the tool (e.g., "CryptoPriceChecker"). If no tool is suitable, respond with "None".
264
-
265
- User Query: "{message}"
266
- """
267
-
268
- try:
269
- # Call the LLM to generate the response
270
- response = self.llm_instance.generate(prompt=prompt)
271
- tool_name = response.strip().replace('"', '').replace("'", "")
272
-
273
- # Find the tool in the list of available tools
274
- tool = next((t for t in self.tools if t.name.lower() == tool_name.lower()), None)
275
- if tool:
276
- logger.info(f"Detected tool call: {tool.name}")
277
- return {
278
- "tool": tool.name,
279
- "input": {"query": message}
280
- }
281
- except Exception as e:
282
- logger.error(f"Failed to detect tool call: {e}")
283
-
284
- return None
285
250
 
286
251
  def _analyze_query_and_select_tools(self, query: str) -> List[Dict[str, Any]]:
287
252
  """
@@ -324,17 +289,16 @@ class Agent(BaseModel):
324
289
  return []
325
290
 
326
291
 
327
- def _generate_response(self, message: str, markdown: bool = False, tools: Optional[List[BaseTool]] = None, team: Optional[List['Agent']] = None, **kwargs) -> str:
292
+ def _generate_response(self, message: str, markdown: bool = False, team: Optional[List['Agent']] = None, **kwargs) -> str:
328
293
  """Generate the agent's response, including tool execution and context retrieval."""
329
- # Use the specified tools or team if provided
330
- if tools is not None:
331
- self.tools = tools
294
+ # Use the specified team if provided
332
295
  if team is not None:
333
296
  return self._generate_team_response(message, team, markdown=markdown, **kwargs)
334
297
 
335
298
  # Initialize tool_outputs as an empty dictionary
336
299
  tool_outputs = {}
337
300
  responses = []
301
+ tool_calls = []
338
302
 
339
303
  # Use the LLM to analyze the query and dynamically select tools when auto_tool is enabled
340
304
  if self.auto_tool:
@@ -344,7 +308,7 @@ class Agent(BaseModel):
344
308
  if self.tools:
345
309
  tool_calls = [
346
310
  {
347
- "tool": tool.__class__.__name__,
311
+ "tool": tool.name,
348
312
  "input": {
349
313
  "query": message, # Use the message as the query
350
314
  "context": None, # No context provided by default
@@ -352,10 +316,8 @@ class Agent(BaseModel):
352
316
  }
353
317
  for tool in self.tools
354
318
  ]
355
- else:
356
- tool_calls = kwargs.get("tool_calls", [])
357
319
 
358
- # Execute tools if any are detected
320
+ # Execute tools if any are detected
359
321
  if tool_calls:
360
322
  for tool_call in tool_calls:
361
323
  tool_name = tool_call["tool"]
@@ -396,7 +358,7 @@ class Agent(BaseModel):
396
358
  except Exception as e:
397
359
  logger.error(f"Failed to generate LLM response: {e}")
398
360
  responses.append(f"An error occurred while generating the analysis: {e}")
399
- if not tool_calls:
361
+ if not self.tools and not tool_calls:
400
362
  # If no tools were executed, proceed with the original logic
401
363
  # Retrieve relevant context using RAG
402
364
  rag_context = self.rag.retrieve(message) if self.rag else None
@@ -0,0 +1,271 @@
1
+ # web_browser.py
2
+ from typing import Dict, Any, List, Optional
3
+ from pydantic import Field, BaseModel
4
+ from selenium import webdriver
5
+ from selenium.webdriver.common.by import By
6
+ from selenium.webdriver.support.ui import WebDriverWait
7
+ from selenium.webdriver.support import expected_conditions as EC
8
+ from selenium.webdriver.chrome.options import Options
9
+ from selenium.webdriver.chrome.service import Service
10
+ from webdriver_manager.chrome import ChromeDriverManager
11
+ from bs4 import BeautifulSoup
12
+ import json
13
+ import time
14
+ import re
15
+ import logging
16
+ from .base_tool import BaseTool
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+ class BrowserPlan(BaseModel):
21
+ tasks: List[Dict[str, Any]] = Field(
22
+ ...,
23
+ description="List of automation tasks to execute"
24
+ )
25
+
26
+ class WebBrowserTool(BaseTool):
27
+ name: str = Field("WebBrowser", description="Name of the tool")
28
+ description: str = Field(
29
+ "Universal web automation tool for dynamic website interactions",
30
+ description="Tool description"
31
+ )
32
+
33
+ def execute(self, input: Dict[str, Any]) -> Dict[str, Any]:
34
+ """Execute dynamic web automation workflow"""
35
+ driver = None
36
+ try:
37
+ driver = self._init_browser(input.get("headless", False))
38
+ results = []
39
+ current_url = ""
40
+
41
+ # Generate initial plan
42
+ plan = self._generate_plan(input['query'], current_url)
43
+
44
+ for task in plan.tasks:
45
+ result = self._execute_safe_task(driver, task)
46
+ results.append(result)
47
+
48
+ if not result['success']:
49
+ break
50
+
51
+ # Update context for next tasks
52
+ current_url = driver.current_url
53
+
54
+ return {"status": "success", "results": results}
55
+
56
+ except Exception as e:
57
+ return {"status": "error", "message": str(e)}
58
+ finally:
59
+ if driver:
60
+ driver.quit()
61
+
62
+ def _init_browser(self, headless: bool) -> webdriver.Chrome:
63
+ """Initialize browser with advanced options"""
64
+ options = Options()
65
+ options.add_argument("--start-maximized")
66
+ options.add_argument("--disable-blink-features=AutomationControlled")
67
+ options.add_experimental_option("excludeSwitches", ["enable-automation"])
68
+
69
+ if headless:
70
+ options.add_argument("--headless=new")
71
+
72
+ return webdriver.Chrome(
73
+ service=Service(ChromeDriverManager().install()),
74
+ options=options
75
+ )
76
+
77
+ def _generate_plan(self, query: str, current_url: str) -> BrowserPlan:
78
+ """Generate adaptive execution plan using LLM"""
79
+ prompt = f"""Generate browser automation plan for: {query}
80
+
81
+ Current URL: {current_url or 'No page loaded yet'}
82
+
83
+ Required JSON format:
84
+ {{
85
+ "tasks": [
86
+ {{
87
+ "action": "navigate|click|type|wait|scroll",
88
+ "selector": "CSS selector (optional)",
89
+ "value": "input text/URL/seconds",
90
+ "description": "action purpose"
91
+ }}
92
+ ]
93
+ }}
94
+
95
+ Guidelines:
96
+ 1. Prefer IDs in selectors (#element-id)
97
+ 2. Use semantic attributes (aria-label, name)
98
+ 3. Include wait steps after navigation
99
+ 4. Prioritize visible elements
100
+ 5. Add scroll steps for hidden elements
101
+ """
102
+
103
+ response = self.llm.generate(prompt=prompt)
104
+ return self._parse_plan(response)
105
+
106
+ def _parse_plan(self, response: str) -> BrowserPlan:
107
+ """Robust JSON parsing with multiple fallback strategies"""
108
+ try:
109
+ # Try extracting JSON from markdown code block
110
+ json_match = re.search(r'```json\n?(.+?)\n?```', response, re.DOTALL)
111
+ if json_match:
112
+ plan_data = json.loads(json_match.group(1).strip())
113
+ else:
114
+ # Fallback to extract first JSON object
115
+ json_str = re.search(r'\{.*\}', response, re.DOTALL).group()
116
+ plan_data = json.loads(json_str)
117
+
118
+ # Validate tasks structure
119
+ validated_tasks = []
120
+ for task in plan_data.get("tasks", []):
121
+ if not all(key in task for key in ["action", "description"]):
122
+ continue
123
+ validated_tasks.append({
124
+ "action": task["action"],
125
+ "selector": task.get("selector", ""),
126
+ "value": task.get("value", ""),
127
+ "description": task["description"]
128
+ })
129
+
130
+ return BrowserPlan(tasks=validated_tasks)
131
+
132
+ except (json.JSONDecodeError, AttributeError) as e:
133
+ logger.error(f"Plan parsing failed: {e}")
134
+ return BrowserPlan(tasks=[])
135
+
136
+ def _execute_safe_task(self, driver, task: Dict) -> Dict[str, Any]:
137
+ """Execute task with comprehensive error handling"""
138
+ try:
139
+ action = task["action"].lower()
140
+ selector = task.get("selector", "")
141
+ value = task.get("value", "")
142
+
143
+ if action == "navigate":
144
+ return self._handle_navigation(driver, value)
145
+
146
+ elif action == "click":
147
+ return self._handle_click(driver, selector)
148
+
149
+ elif action == "type":
150
+ return self._handle_typing(driver, selector, value)
151
+
152
+ elif action == "wait":
153
+ return self._handle_wait(value)
154
+
155
+ elif action == "scroll":
156
+ return self._handle_scroll(driver, selector)
157
+
158
+ return {
159
+ "action": action,
160
+ "success": False,
161
+ "message": f"Unsupported action: {action}"
162
+ }
163
+
164
+ except Exception as e:
165
+ return {
166
+ "action": action,
167
+ "success": False,
168
+ "message": f"Critical error: {str(e)}"
169
+ }
170
+
171
+ def _handle_navigation(self, driver, url: str) -> Dict[str, Any]:
172
+ """Smart navigation handler"""
173
+ if not url.startswith(("http://", "https://")):
174
+ url = f"https://{url}"
175
+
176
+ try:
177
+ driver.get(url)
178
+ WebDriverWait(driver, 15).until(
179
+ EC.presence_of_element_located((By.TAG_NAME, "body"))
180
+ )
181
+ return {
182
+ "action": "navigate",
183
+ "success": True,
184
+ "message": f"Navigated to {url}"
185
+ }
186
+ except Exception as e:
187
+ return {
188
+ "action": "navigate",
189
+ "success": False,
190
+ "message": f"Navigation failed: {str(e)}"
191
+ }
192
+
193
+ def _handle_click(self, driver, selector: str) -> Dict[str, Any]:
194
+ """Dynamic click handler"""
195
+ try:
196
+ element = WebDriverWait(driver, 15).until(
197
+ EC.element_to_be_clickable((By.CSS_SELECTOR, selector))
198
+ )
199
+ driver.execute_script("arguments[0].scrollIntoView({behavior: 'smooth'});", element)
200
+ element.click()
201
+ return {
202
+ "action": "click",
203
+ "success": True,
204
+ "message": f"Clicked element: {selector}"
205
+ }
206
+ except Exception as e:
207
+ return {
208
+ "action": "click",
209
+ "success": False,
210
+ "message": f"Click failed: {str(e)}"
211
+ }
212
+
213
+ def _handle_typing(self, driver, selector: str, text: str) -> Dict[str, Any]:
214
+ """Universal typing handler"""
215
+ try:
216
+ element = WebDriverWait(driver, 15).until(
217
+ EC.presence_of_element_located((By.CSS_SELECTOR, selector))
218
+ )
219
+ element.clear()
220
+ element.send_keys(text)
221
+ return {
222
+ "action": "type",
223
+ "success": True,
224
+ "message": f"Typed '{text}' into {selector}"
225
+ }
226
+ except Exception as e:
227
+ return {
228
+ "action": "type",
229
+ "success": False,
230
+ "message": f"Typing failed: {str(e)}"
231
+ }
232
+
233
+ def _handle_wait(self, seconds: str) -> Dict[str, Any]:
234
+ """Configurable wait handler"""
235
+ try:
236
+ wait_time = float(seconds)
237
+ time.sleep(wait_time)
238
+ return {
239
+ "action": "wait",
240
+ "success": True,
241
+ "message": f"Waited {wait_time} seconds"
242
+ }
243
+ except ValueError:
244
+ return {
245
+ "action": "wait",
246
+ "success": False,
247
+ "message": "Invalid wait time"
248
+ }
249
+
250
+ def _handle_scroll(self, driver, selector: str) -> Dict[str, Any]:
251
+ """Smart scroll handler"""
252
+ try:
253
+ if selector:
254
+ element = WebDriverWait(driver, 15).until(
255
+ EC.presence_of_element_located((By.CSS_SELECTOR, selector))
256
+ )
257
+ driver.execute_script("arguments[0].scrollIntoView({behavior: 'smooth'});", element)
258
+ else:
259
+ driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
260
+
261
+ return {
262
+ "action": "scroll",
263
+ "success": True,
264
+ "message": f"Scrolled to {selector or 'page bottom'}"
265
+ }
266
+ except Exception as e:
267
+ return {
268
+ "action": "scroll",
269
+ "success": False,
270
+ "message": f"Scroll failed: {str(e)}"
271
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: semantio
3
- Version: 0.0.3
3
+ Version: 0.0.4
4
4
  Summary: A powerful SDK for building AI agents
5
5
  Home-page: https://github.com/Syenah/semantio
6
6
  Author: Rakesh
@@ -33,6 +33,10 @@ Requires-Dist: sentence-transformers
33
33
  Requires-Dist: fuzzywuzzy
34
34
  Requires-Dist: duckduckgo-search
35
35
  Requires-Dist: yfinance
36
+ Requires-Dist: selenium
37
+ Requires-Dist: beautifulsoup4
38
+ Requires-Dist: webdriver-manager
39
+ Requires-Dist: validators
36
40
 
37
41
  # Semantio: The Mother of Your AI Agents
38
42
 
@@ -1,5 +1,5 @@
1
1
  semantio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- semantio/agent.py,sha256=plQ4D76cnJ1FaGlEuKDeA53aW_hMDvt5sbmUuTHqvFQ,30143
2
+ semantio/agent.py,sha256=hKytSI5LqNnxqVvwI2hOINqPgrdhUXY9MS_90_crZPs,28584
3
3
  semantio/memory.py,sha256=eNAwyAokppHzMcIyFgOw2hT2wnLQBd9GL4T5eallNV4,281
4
4
  semantio/rag.py,sha256=ROy3Pa1NURcDs6qQZ8IMoa5Xlzt6I-msEq0C1p8UgB0,472
5
5
  semantio/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -27,15 +27,16 @@ semantio/tools/base_tool.py,sha256=xBNSa_8a8WmA4BGRLG2dE7wj9GnBcZo7-P2SyD86GvY,5
27
27
  semantio/tools/crypto.py,sha256=mut1ztvpPcUUP3b563dh_FmKtP68KmNis3Qm8WENj8w,5559
28
28
  semantio/tools/duckduckgo.py,sha256=6mGn0js0cIsVxQlAgB8AYNLP05H8WmJKnSVosiO9iH0,5034
29
29
  semantio/tools/stocks.py,sha256=BVuK61O9OmWQjj0YdiCJY6TzpiFJ_An1UJB2RkDfX2k,5393
30
+ semantio/tools/web_browser.py,sha256=wqr5pj2GybkK9IHDb8C1BipS8ujV2l36WlwA8ZbKd88,9711
30
31
  semantio/utils/__init__.py,sha256=Lx4X4iJpRhZzRmpQb80XXh5Ve8ZMOkadWAxXSmHpO_8,244
31
32
  semantio/utils/config.py,sha256=ZTwUTqxjW3-w94zoU7GzivWyJe0JJGvBfuB4RUOuEs8,1198
32
33
  semantio/utils/date_utils.py,sha256=x3oqRGv6ee_KCJ0LvCqqZh_FSgS6YGOHBwZQS4TJetY,1471
33
34
  semantio/utils/file_utils.py,sha256=b_cMuJINEGk9ikNuNHSn9lsmICWwvtnCDZ03ndH_S2I,1779
34
35
  semantio/utils/logger.py,sha256=TmGbP8BRjLMWjXi2GWzZ0RIXt70x9qX3FuIqghCNlwM,510
35
36
  semantio/utils/validation_utils.py,sha256=iwoxEb4Q5ILqV6tbesMjPWPCCoL3AmPLejGUy6q8YvQ,1284
36
- semantio-0.0.3.dist-info/LICENSE,sha256=teQbWD2Zlcl1_Fo29o2tNbs6G26hbCQiUzds5fQGYlY,1063
37
- semantio-0.0.3.dist-info/METADATA,sha256=M5Q-waTknpyWrD_HV9G76jMKgPHPrBBwM5Hl8we4ulo,6800
38
- semantio-0.0.3.dist-info/WHEEL,sha256=ewwEueio1C2XeHTvT17n8dZUJgOvyCWCt0WVNLClP9o,92
39
- semantio-0.0.3.dist-info/entry_points.txt,sha256=zbPgevSLwcLpdRHqI_atE8EOt8lK2vRF1AoDflDTo18,53
40
- semantio-0.0.3.dist-info/top_level.txt,sha256=Yte_6mb-bh-I_lQwMjk1GijZkxPoX4Zmp3kBftC1ZlA,9
41
- semantio-0.0.3.dist-info/RECORD,,
37
+ semantio-0.0.4.dist-info/LICENSE,sha256=teQbWD2Zlcl1_Fo29o2tNbs6G26hbCQiUzds5fQGYlY,1063
38
+ semantio-0.0.4.dist-info/METADATA,sha256=youxODbkR3gNERG-mD7zbUbe5ix-0lUiWCHUI1_Y5IY,6913
39
+ semantio-0.0.4.dist-info/WHEEL,sha256=ewwEueio1C2XeHTvT17n8dZUJgOvyCWCt0WVNLClP9o,92
40
+ semantio-0.0.4.dist-info/entry_points.txt,sha256=zbPgevSLwcLpdRHqI_atE8EOt8lK2vRF1AoDflDTo18,53
41
+ semantio-0.0.4.dist-info/top_level.txt,sha256=Yte_6mb-bh-I_lQwMjk1GijZkxPoX4Zmp3kBftC1ZlA,9
42
+ semantio-0.0.4.dist-info/RECORD,,