semantio 0.0.3__py3-none-any.whl → 0.0.5__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
@@ -16,6 +16,7 @@ from .tools.base_tool import BaseTool
16
16
  from pathlib import Path
17
17
  import importlib
18
18
  import os
19
+ from .memory import Memory
19
20
 
20
21
  # Configure logging
21
22
  logging.basicConfig(level=logging.INFO)
@@ -48,6 +49,13 @@ class Agent(BaseModel):
48
49
  semantic_model: Optional[Any] = Field(None, description="SentenceTransformer model for semantic matching.")
49
50
  team: Optional[List['Agent']] = Field(None, description="List of assistants in the team.")
50
51
  auto_tool: bool = Field(False, description="Whether to automatically detect and call tools.")
52
+ memory: Memory = Field(default_factory=Memory)
53
+ memory_config: Dict = Field(
54
+ default_factory=lambda: {
55
+ "max_context_length": 4000,
56
+ "summarization_threshold": 3000
57
+ }
58
+ )
51
59
 
52
60
  # Allow arbitrary types
53
61
  model_config = ConfigDict(arbitrary_types_allowed=True)
@@ -56,8 +64,16 @@ class Agent(BaseModel):
56
64
  super().__init__(**kwargs)
57
65
  # Initialize the model and tools here if needed
58
66
  self._initialize_model()
59
- # Automatically discover and register tools if not provided
67
+ # Initialize memory with config
68
+ self.memory = Memory(
69
+ max_context_length=self.memory_config.get("max_context_length", 4000),
70
+ summarization_threshold=self.memory_config.get("summarization_threshold", 3000)
71
+ )
72
+ # Initialize tools as an empty list if not provided
60
73
  if self.tools is None:
74
+ self.tools = []
75
+ # Automatically discover and register tools if auto tool is enabled
76
+ if self.auto_tool and not self.tools:
61
77
  self.tools = self._discover_tools()
62
78
  # Pass the LLM instance to each tool
63
79
  for tool in self.tools:
@@ -213,23 +229,33 @@ class Agent(BaseModel):
213
229
  message: Optional[Union[str, Image, List, Dict]] = None,
214
230
  stream: bool = False,
215
231
  markdown: bool = False,
216
- tools: Optional[List[BaseTool]] = None,
217
232
  team: Optional[List['Agent']] = None,
218
233
  **kwargs,
219
- ) -> Union[str, Dict]: # Add return type hint
234
+ ) -> Union[str, Dict]:
220
235
  """Print the agent's response to the console and return it."""
236
+
237
+ # Store user message if provided
238
+ if message and isinstance(message, str):
239
+ self.memory.add_message(role="user", content=message)
221
240
 
222
241
  if stream:
223
242
  # Handle streaming response
224
243
  response = ""
225
244
  for chunk in self._stream_response(message, markdown=markdown, **kwargs):
226
- print(chunk)
245
+ print(chunk, end="", flush=True)
227
246
  response += chunk
247
+ # Store agent response
248
+ if response:
249
+ self.memory.add_message(role="assistant", content=response)
250
+ print() # New line after streaming
228
251
  return response
229
252
  else:
230
253
  # Generate and return the response
231
- response = self._generate_response(message, markdown=markdown, tools=tools, team=team, **kwargs)
254
+ response = self._generate_response(message, markdown=markdown, team=team, **kwargs)
232
255
  print(response) # Print the response to the console
256
+ # Store agent response
257
+ if response:
258
+ self.memory.add_message(role="assistant", content=response)
233
259
  return response
234
260
 
235
261
 
@@ -245,43 +271,6 @@ class Agent(BaseModel):
245
271
  if self.tools is None:
246
272
  self.tools = []
247
273
  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
274
 
286
275
  def _analyze_query_and_select_tools(self, query: str) -> List[Dict[str, Any]]:
287
276
  """
@@ -324,18 +313,15 @@ class Agent(BaseModel):
324
313
  return []
325
314
 
326
315
 
327
- def _generate_response(self, message: str, markdown: bool = False, tools: Optional[List[BaseTool]] = None, team: Optional[List['Agent']] = None, **kwargs) -> str:
316
+ def _generate_response(self, message: str, markdown: bool = False, team: Optional[List['Agent']] = None, **kwargs) -> str:
328
317
  """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
318
+ # Use the specified team if provided
332
319
  if team is not None:
333
320
  return self._generate_team_response(message, team, markdown=markdown, **kwargs)
334
-
335
321
  # Initialize tool_outputs as an empty dictionary
336
322
  tool_outputs = {}
337
323
  responses = []
338
-
324
+ tool_calls = []
339
325
  # Use the LLM to analyze the query and dynamically select tools when auto_tool is enabled
340
326
  if self.auto_tool:
341
327
  tool_calls = self._analyze_query_and_select_tools(message)
@@ -344,7 +330,7 @@ class Agent(BaseModel):
344
330
  if self.tools:
345
331
  tool_calls = [
346
332
  {
347
- "tool": tool.__class__.__name__,
333
+ "tool": tool.name,
348
334
  "input": {
349
335
  "query": message, # Use the message as the query
350
336
  "context": None, # No context provided by default
@@ -352,10 +338,8 @@ class Agent(BaseModel):
352
338
  }
353
339
  for tool in self.tools
354
340
  ]
355
- else:
356
- tool_calls = kwargs.get("tool_calls", [])
357
341
 
358
- # Execute tools if any are detected
342
+ # Execute tools if any are detected
359
343
  if tool_calls:
360
344
  for tool_call in tool_calls:
361
345
  tool_name = tool_call["tool"]
@@ -385,41 +369,50 @@ class Agent(BaseModel):
385
369
  try:
386
370
  # Prepare the context for the LLM
387
371
  context = {
372
+ "conversation_history": self.memory.get_context(self.llm_instance),
388
373
  "tool_outputs": tool_outputs,
389
374
  "rag_context": self.rag.retrieve(message) if self.rag else None,
390
- "knowledge_base_context": self._find_all_relevant_keys(message, self._flatten_data(self.knowledge_base)) if self.knowledge_base else None,
375
+ "knowledge_base": self._get_knowledge_context(message) if self.knowledge_base else None,
391
376
  }
392
-
377
+ # 3. Build a memory-aware prompt.
378
+ prompt = self._build_memory_prompt(message, context)
379
+ # To (convert MemoryEntry objects to dicts and remove metadata):
380
+ memory_entries = [{"role": e.role, "content": e.content} for e in self.memory.storage.retrieve()]
393
381
  # Generate a response using the LLM
394
- llm_response = self.llm_instance.generate(prompt=message, context=context, **kwargs)
382
+ llm_response = self.llm_instance.generate(prompt=prompt, context=context, memory=memory_entries, **kwargs)
395
383
  responses.append(f"**Analysis:**\n\n{llm_response}")
396
384
  except Exception as e:
397
385
  logger.error(f"Failed to generate LLM response: {e}")
398
386
  responses.append(f"An error occurred while generating the analysis: {e}")
399
- if not tool_calls:
387
+ if not self.tools and not tool_calls:
400
388
  # If no tools were executed, proceed with the original logic
401
389
  # Retrieve relevant context using RAG
402
390
  rag_context = self.rag.retrieve(message) if self.rag else None
403
391
  # Retrieve relevant context from the knowledge base (API result)
404
- knowledge_base_context = None
405
- if self.knowledge_base:
406
- # Flatten the knowledge base
407
- flattened_data = self._flatten_data(self.knowledge_base)
408
- # Find all relevant key-value pairs in the knowledge base
409
- relevant_values = self._find_all_relevant_keys(message, flattened_data)
410
- if relevant_values:
411
- knowledge_base_context = ", ".join(relevant_values)
392
+ # knowledge_base_context = None
393
+ # if self.knowledge_base:
394
+ # # Flatten the knowledge base
395
+ # flattened_data = self._flatten_data(self.knowledge_base)
396
+ # # Find all relevant key-value pairs in the knowledge base
397
+ # relevant_values = self._find_all_relevant_keys(message, flattened_data)
398
+ # if relevant_values:
399
+ # knowledge_base_context = ", ".join(relevant_values)
412
400
 
413
401
  # Combine both contexts (RAG and knowledge base)
414
402
  context = {
403
+ "conversation_history": self.memory.get_context(self.llm_instance),
415
404
  "rag_context": rag_context,
416
- "knowledge_base_context": knowledge_base_context,
405
+ "knowledge_base": self._get_knowledge_context(message),
417
406
  }
418
407
  # Prepare the prompt with instructions, description, and context
419
- prompt = self._build_prompt(message, context)
408
+ # 3. Build a memory-aware prompt.
409
+ prompt = self._build_memory_prompt(message, context)
410
+ # To (convert MemoryEntry objects to dicts and remove metadata):
411
+ memory_entries = [{"role": e.role, "content": e.content} for e in self.memory.storage.retrieve()]
420
412
 
421
413
  # Generate the response using the LLM
422
- response = self.llm_instance.generate(prompt=prompt, context=context, **kwargs)
414
+ response = self.llm_instance.generate(prompt=prompt, context=context, memory=memory_entries, **kwargs)
415
+
423
416
 
424
417
  # Format the response based on the json_output flag
425
418
  if self.json_output:
@@ -432,9 +425,37 @@ class Agent(BaseModel):
432
425
  if markdown:
433
426
  return f"**Response:**\n\n{response}"
434
427
  return response
435
- # Combine all responses into a single string
436
428
  return "\n\n".join(responses)
437
429
 
430
+ # Modified prompt construction with memory integration
431
+ def _build_memory_prompt(self, user_input: str, context: dict) -> str:
432
+ """Enhanced prompt builder with memory context."""
433
+ prompt_parts = []
434
+
435
+ if self.description:
436
+ prompt_parts.append(f"# ROLE\n{self.description}")
437
+
438
+ if self.instructions:
439
+ prompt_parts.append(f"# INSTRUCTIONS\n" + "\n".join(f"- {i}" for i in self.instructions))
440
+
441
+ if context['conversation_history']:
442
+ prompt_parts.append(f"# CONVERSATION HISTORY\n{context['conversation_history']}")
443
+
444
+ if context['knowledge_base']:
445
+ prompt_parts.append(f"# KNOWLEDGE BASE\n{context['knowledge_base']}")
446
+
447
+ prompt_parts.append(f"# USER INPUT\n{user_input}")
448
+
449
+ return "\n\n".join(prompt_parts)
450
+
451
+ def _get_knowledge_context(self, message: str) -> str:
452
+ """Retrieve and format knowledge base context."""
453
+ if not self.knowledge_base:
454
+ return ""
455
+
456
+ flattened = self._flatten_data(self.knowledge_base)
457
+ relevant = self._find_all_relevant_keys(message, flattened)
458
+ return "\n".join(f"- {item}" for item in relevant) if relevant else ""
438
459
  def _generate_team_response(self, message: str, team: List['Agent'], markdown: bool = False, **kwargs) -> str:
439
460
  """Generate a response using a team of assistants."""
440
461
  responses = []
@@ -581,17 +602,21 @@ class Agent(BaseModel):
581
602
  """Run the agent in a CLI app."""
582
603
  from rich.prompt import Prompt
583
604
 
605
+ # Print initial message if provided
584
606
  if message:
585
607
  self.print_response(message=message, **kwargs)
586
608
 
587
609
  _exit_on = exit_on or ["exit", "quit", "bye"]
588
610
  while True:
589
- message = Prompt.ask(f"[bold] {self.emoji} {self.user_name} [/bold]")
590
- if message in _exit_on:
611
+ try:
612
+ message = Prompt.ask(f"[bold] {self.emoji} {self.user_name} [/bold]")
613
+ if message in _exit_on:
614
+ break
615
+ self.print_response(message=message, **kwargs)
616
+ except KeyboardInterrupt:
617
+ print("\n\nSession ended. Goodbye!")
591
618
  break
592
619
 
593
- self.print_response(message=message, **kwargs)
594
-
595
620
  def _generate_api(self):
596
621
  """Generate an API for the agent if api=True."""
597
622
  from .api.api_generator import APIGenerator
semantio/memory.py CHANGED
@@ -1,11 +1,54 @@
1
- from typing import List, Dict
2
-
1
+ from .models import MemoryEntry
2
+ from .storage import BaseMemoryStorage, InMemoryStorage, FileStorage
3
+ from typing import List, Dict, Optional
4
+ from .llm.base_llm import BaseLLM
3
5
  class Memory:
4
- def __init__(self):
5
- self.history = []
6
+ def __init__(
7
+ self,
8
+ storage: BaseMemoryStorage = InMemoryStorage(),
9
+ max_context_length: int = 4000,
10
+ summarization_threshold: int = 3000
11
+ ):
12
+ self.storage = storage
13
+ self.max_context_length = max_context_length
14
+ self.summarization_threshold = summarization_threshold
15
+ self._current_context = ""
16
+
17
+ def add_message(self, role: str, content: str, metadata: Optional[Dict] = None):
18
+ entry = MemoryEntry(
19
+ role=role,
20
+ content=content,
21
+ metadata=metadata or {}
22
+ )
23
+ self.storage.store(entry)
24
+ self._manage_context()
25
+
26
+ def get_context(self, llm: Optional[BaseLLM] = None) -> str:
27
+ if len(self._current_context) < self.summarization_threshold:
28
+ return self._current_context
29
+
30
+ # Automatic summarization when context grows too large
31
+ if llm:
32
+ return self.summarize(llm)
33
+ return self._current_context[:self.max_context_length]
34
+ def _manage_context(self):
35
+ # Include roles in the conversation history
36
+ full_history = "\n".join([f"{e.role}: {e.content}" for e in self.storage.retrieve()])
37
+ if len(full_history) > self.max_context_length:
38
+ self._current_context = full_history[-self.max_context_length:]
39
+ else:
40
+ self._current_context = full_history
6
41
 
7
- def add_message(self, role: str, content: str):
8
- self.history.append({"role": role, "content": content})
42
+ def summarize(self, llm: BaseLLM) -> str:
43
+ # Include roles in the history for summarization
44
+ history = "\n".join([f"{e.role}: {e.content}" for e in self.storage.retrieve()])
45
+ prompt = f"""
46
+ Summarize this conversation history maintaining key details and references:
47
+ {history[-self.summarization_threshold:]}
48
+ """
49
+ self._current_context = llm.generate(prompt)
50
+ return self._current_context
9
51
 
10
- def get_history(self) -> List[Dict]:
11
- return self.history
52
+ def clear(self):
53
+ self.storage = InMemoryStorage()
54
+ self._current_context = ""
semantio/models.py ADDED
@@ -0,0 +1,9 @@
1
+ from pydantic import BaseModel, Field
2
+ from datetime import datetime
3
+ from typing import Dict
4
+
5
+ class MemoryEntry(BaseModel):
6
+ role: str # "user" or "assistant"
7
+ content: str
8
+ timestamp: datetime = Field(default_factory=datetime.now)
9
+ metadata: Dict = Field(default_factory=dict)
@@ -0,0 +1,5 @@
1
+ from .base_storage import BaseMemoryStorage
2
+ from .in_memory_storage import InMemoryStorage
3
+ from .local_storage import FileStorage
4
+
5
+ __all__ = ['BaseMemoryStorage', 'InMemoryStorage', 'FileStorage']
@@ -0,0 +1,12 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import List, Optional
3
+ from ..models import MemoryEntry
4
+
5
+ class BaseMemoryStorage(ABC):
6
+ @abstractmethod
7
+ def store(self, entry: MemoryEntry):
8
+ pass
9
+
10
+ @abstractmethod
11
+ def retrieve(self, query: Optional[str] = None, limit: int = 20) -> List[MemoryEntry]:
12
+ pass
@@ -0,0 +1,14 @@
1
+ # hashai/storage/in_memory_storage.py
2
+ from typing import List, Optional
3
+ from ..models import MemoryEntry
4
+ from .base_storage import BaseMemoryStorage
5
+
6
+ class InMemoryStorage(BaseMemoryStorage):
7
+ def __init__(self):
8
+ self.history: List[MemoryEntry] = []
9
+
10
+ def store(self, entry: MemoryEntry):
11
+ self.history.append(entry)
12
+
13
+ def retrieve(self, query: Optional[str] = None, limit: int = 10) -> List[MemoryEntry]:
14
+ return self.history[-limit:]
@@ -0,0 +1,29 @@
1
+ import json
2
+ from typing import List, Optional
3
+ from ..models import MemoryEntry
4
+ from .base_storage import BaseMemoryStorage
5
+
6
+ class FileStorage(BaseMemoryStorage):
7
+ def __init__(self, file_path: str = "memory.json"):
8
+ self.file_path = file_path
9
+ self.history = self._load_from_file()
10
+
11
+ def _load_from_file(self) -> List[MemoryEntry]:
12
+ try:
13
+ with open(self.file_path, "r") as f:
14
+ data = json.load(f)
15
+ return [MemoryEntry(**entry) for entry in data]
16
+ except (FileNotFoundError, json.JSONDecodeError):
17
+ return []
18
+
19
+ def _save_to_file(self):
20
+ with open(self.file_path, "w") as f:
21
+ data = [entry.dict() for entry in self.history]
22
+ json.dump(data, f, default=str)
23
+
24
+ def store(self, entry: MemoryEntry):
25
+ self.history.append(entry)
26
+ self._save_to_file()
27
+
28
+ def retrieve(self, query: Optional[str] = None, limit: int = 20) -> List[MemoryEntry]:
29
+ return self.history[-limit:]
@@ -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
  MIT License
2
2
 
3
- Copyright (c) 2025 Syenah
3
+ Copyright (c) 2025 Syenah (Semantio)
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: semantio
3
- Version: 0.0.3
3
+ Version: 0.0.5
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,6 +1,7 @@
1
1
  semantio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- semantio/agent.py,sha256=plQ4D76cnJ1FaGlEuKDeA53aW_hMDvt5sbmUuTHqvFQ,30143
3
- semantio/memory.py,sha256=eNAwyAokppHzMcIyFgOw2hT2wnLQBd9GL4T5eallNV4,281
2
+ semantio/agent.py,sha256=uPFz1WP2eb-z-tryQOX8necS8_tv4Il6qxNmZux9hNk,31709
3
+ semantio/memory.py,sha256=en9n3UySnj4rA0x3uR1sEdEzA7EkboQNbEHQ5KuEehw,2115
4
+ semantio/models.py,sha256=7hmP-F_aSU8WvsG3NGeC_hep-rUbiSbjUFMDVbpKxQE,289
4
5
  semantio/rag.py,sha256=ROy3Pa1NURcDs6qQZ8IMoa5Xlzt6I-msEq0C1p8UgB0,472
5
6
  semantio/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
7
  semantio/api/api_generator.py,sha256=Q-USITEpluRESEaQuOmF7m1vhLKYU9P8eGlQppKT9J4,829
@@ -19,23 +20,26 @@ semantio/llm/gemini.py,sha256=er3zv1jOvWQBGbPuv4fS4pR_c_abHyhroe-rkXupOO4,1959
19
20
  semantio/llm/groq.py,sha256=1AH30paKzDIQjBjWPQPN44QwFHsIOVwI-a587-cDIVc,4285
20
21
  semantio/llm/mistral.py,sha256=NpvaB1cE6-jMEBdT0mTf6Ca4Qq2LS8QivDKI6AgdRjE,1061
21
22
  semantio/llm/openai.py,sha256=I3ab-d_zFxm-TDhYk6t1PzDtElPJEEQ2eSiARBNIGi4,5174
22
- semantio/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
+ semantio/storage/__init__.py,sha256=bGSJjA1qk6DUDrBijmWcQk3Y1a2K00MPoKI5KH43Ang,196
24
+ semantio/storage/base_storage.py,sha256=R9tQfidVZlCN6CyvnhB-Tc2lIZ7yQsyX4cbMoud64XM,336
23
25
  semantio/storage/cloud_storage.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
- semantio/storage/local_storage.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
+ semantio/storage/in_memory_storage.py,sha256=aZT8rRHF6Kz_udaqf0rux7XRFKf9Hr3d4c3Ylry7J14,474
27
+ semantio/storage/local_storage.py,sha256=Z8jCPo2MwZ8tuhQywWkHyxTrdSyYtzAPSNd46DTCth8,1007
25
28
  semantio/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
29
  semantio/tools/base_tool.py,sha256=xBNSa_8a8WmA4BGRLG2dE7wj9GnBcZo7-P2SyD86GvY,571
27
30
  semantio/tools/crypto.py,sha256=mut1ztvpPcUUP3b563dh_FmKtP68KmNis3Qm8WENj8w,5559
28
31
  semantio/tools/duckduckgo.py,sha256=6mGn0js0cIsVxQlAgB8AYNLP05H8WmJKnSVosiO9iH0,5034
29
32
  semantio/tools/stocks.py,sha256=BVuK61O9OmWQjj0YdiCJY6TzpiFJ_An1UJB2RkDfX2k,5393
33
+ semantio/tools/web_browser.py,sha256=wqr5pj2GybkK9IHDb8C1BipS8ujV2l36WlwA8ZbKd88,9711
30
34
  semantio/utils/__init__.py,sha256=Lx4X4iJpRhZzRmpQb80XXh5Ve8ZMOkadWAxXSmHpO_8,244
31
35
  semantio/utils/config.py,sha256=ZTwUTqxjW3-w94zoU7GzivWyJe0JJGvBfuB4RUOuEs8,1198
32
36
  semantio/utils/date_utils.py,sha256=x3oqRGv6ee_KCJ0LvCqqZh_FSgS6YGOHBwZQS4TJetY,1471
33
37
  semantio/utils/file_utils.py,sha256=b_cMuJINEGk9ikNuNHSn9lsmICWwvtnCDZ03ndH_S2I,1779
34
38
  semantio/utils/logger.py,sha256=TmGbP8BRjLMWjXi2GWzZ0RIXt70x9qX3FuIqghCNlwM,510
35
39
  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,,
40
+ semantio-0.0.5.dist-info/LICENSE,sha256=mziLlfb9hZ8HKxm9V6BiHpmgJvmcDvswu1QBlDB-6vU,1074
41
+ semantio-0.0.5.dist-info/METADATA,sha256=PtDbsZ-tWXbte0RR40K5O_OklMKZiUsb-3dxGlmjklQ,6913
42
+ semantio-0.0.5.dist-info/WHEEL,sha256=ewwEueio1C2XeHTvT17n8dZUJgOvyCWCt0WVNLClP9o,92
43
+ semantio-0.0.5.dist-info/entry_points.txt,sha256=zbPgevSLwcLpdRHqI_atE8EOt8lK2vRF1AoDflDTo18,53
44
+ semantio-0.0.5.dist-info/top_level.txt,sha256=Yte_6mb-bh-I_lQwMjk1GijZkxPoX4Zmp3kBftC1ZlA,9
45
+ semantio-0.0.5.dist-info/RECORD,,