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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,,