praisonaiagents 0.0.96__py3-none-any.whl → 0.0.97__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.
@@ -11,6 +11,7 @@ from .agents.autoagents import AutoAgents
11
11
  from .knowledge.knowledge import Knowledge
12
12
  from .knowledge.chunking import Chunking
13
13
  from .mcp.mcp import MCP
14
+ from .session import Session
14
15
  from .main import (
15
16
  TaskOutput,
16
17
  ReflectionOutput,
@@ -40,6 +41,7 @@ __all__ = [
40
41
  'TaskOutput',
41
42
  'ReflectionOutput',
42
43
  'AutoAgents',
44
+ 'Session',
43
45
  'display_interaction',
44
46
  'display_self_reflection',
45
47
  'display_instruction',
@@ -16,7 +16,8 @@ from ..main import (
16
16
  display_self_reflection,
17
17
  ReflectionOutput,
18
18
  client,
19
- adisplay_instruction
19
+ adisplay_instruction,
20
+ approval_callback
20
21
  )
21
22
  import inspect
22
23
  import uuid
@@ -570,6 +571,35 @@ Your Goal: {self.goal}
570
571
  """
571
572
  logging.debug(f"{self.name} executing tool {function_name} with arguments: {arguments}")
572
573
 
574
+ # Check if approval is required for this tool
575
+ from ..approval import is_approval_required, console_approval_callback, get_risk_level, mark_approved, ApprovalDecision
576
+ if is_approval_required(function_name):
577
+ risk_level = get_risk_level(function_name)
578
+ logging.info(f"Tool {function_name} requires approval (risk level: {risk_level})")
579
+
580
+ # Use global approval callback or default console callback
581
+ callback = approval_callback or console_approval_callback
582
+
583
+ try:
584
+ decision = callback(function_name, arguments, risk_level)
585
+ if not decision.approved:
586
+ error_msg = f"Tool execution denied: {decision.reason}"
587
+ logging.warning(error_msg)
588
+ return {"error": error_msg, "approval_denied": True}
589
+
590
+ # Mark as approved in context to prevent double approval in decorator
591
+ mark_approved(function_name)
592
+
593
+ # Use modified arguments if provided
594
+ if decision.modified_args:
595
+ arguments = decision.modified_args
596
+ logging.info(f"Using modified arguments: {arguments}")
597
+
598
+ except Exception as e:
599
+ error_msg = f"Error during approval process: {str(e)}"
600
+ logging.error(error_msg)
601
+ return {"error": error_msg, "approval_error": True}
602
+
573
603
  # Special handling for MCP tools
574
604
  # Check if tools is an MCP instance with the requested function name
575
605
  from ..mcp.mcp import MCP
@@ -982,43 +1012,7 @@ Your Goal: {self.goal}
982
1012
  if not response:
983
1013
  return None
984
1014
 
985
- tool_calls = getattr(response.choices[0].message, 'tool_calls', None)
986
1015
  response_text = response.choices[0].message.content.strip()
987
- if tool_calls: ## TODO: Most likely this tool call is already called in _chat_completion, so maybe we can remove this.
988
- messages.append({
989
- "role": "assistant",
990
- "content": response_text,
991
- "tool_calls": tool_calls
992
- })
993
-
994
- for tool_call in tool_calls:
995
- function_name = tool_call.function.name
996
- arguments = json.loads(tool_call.function.arguments)
997
-
998
- if self.verbose:
999
- display_tool_call(f"Agent {self.name} is calling function '{function_name}' with arguments: {arguments}", console=self.console)
1000
-
1001
- tool_result = self.execute_tool(function_name, arguments)
1002
-
1003
- if tool_result:
1004
- if self.verbose:
1005
- display_tool_call(f"Function '{function_name}' returned: {tool_result}", console=self.console)
1006
- messages.append({
1007
- "role": "tool",
1008
- "tool_call_id": tool_call.id,
1009
- "content": json.dumps(tool_result)
1010
- })
1011
- else:
1012
- messages.append({
1013
- "role": "tool",
1014
- "tool_call_id": tool_call.id,
1015
- "content": "Function returned an empty output"
1016
- })
1017
-
1018
- response = self._chat_completion(messages, temperature=temperature, stream=stream)
1019
- if not response:
1020
- return None
1021
- response_text = response.choices[0].message.content.strip()
1022
1016
 
1023
1017
  # Handle output_json or output_pydantic if specified
1024
1018
  if output_json or output_pydantic:
@@ -1418,6 +1412,21 @@ Your Goal: {self.goal}
1418
1412
  """Async version of execute_tool"""
1419
1413
  try:
1420
1414
  logging.info(f"Executing async tool: {function_name} with arguments: {arguments}")
1415
+
1416
+ # Check if approval is required for this tool
1417
+ from ..approval import is_approval_required, request_approval
1418
+ if is_approval_required(function_name):
1419
+ decision = await request_approval(function_name, arguments)
1420
+ if not decision.approved:
1421
+ error_msg = f"Tool execution denied: {decision.reason}"
1422
+ logging.warning(error_msg)
1423
+ return {"error": error_msg, "approval_denied": True}
1424
+
1425
+ # Use modified arguments if provided
1426
+ if decision.modified_args:
1427
+ arguments = decision.modified_args
1428
+ logging.info(f"Using modified arguments: {arguments}")
1429
+
1421
1430
  # Try to find the function in the agent's tools list first
1422
1431
  func = None
1423
1432
  for tool in self.tools:
@@ -63,7 +63,6 @@ def process_task_context(context_item, verbose=0, user_id=None):
63
63
  """
64
64
  Process a single context item for task execution.
65
65
  This helper function avoids code duplication between async and sync execution methods.
66
-
67
66
  Args:
68
67
  context_item: The context item to process (can be string, list, task object, or dict)
69
68
  verbose: Verbosity level for logging
@@ -203,7 +202,6 @@ class PraisonAIAgents:
203
202
  mem_cfg = memory_config
204
203
  if not mem_cfg:
205
204
  mem_cfg = next((t.config.get('memory_config') for t in tasks if hasattr(t, 'config') and t.config), None)
206
-
207
205
  # Set default memory config if none provided
208
206
  if not mem_cfg:
209
207
  mem_cfg = {
@@ -215,7 +213,6 @@ class PraisonAIAgents:
215
213
  },
216
214
  "rag_db_path": "./.praison/chroma_db"
217
215
  }
218
-
219
216
  # Add embedder config if provided
220
217
  if embedder:
221
218
  if isinstance(embedder, dict):
@@ -231,17 +228,14 @@ class PraisonAIAgents:
231
228
  self.shared_memory = Memory(config=mem_cfg, verbose=verbose)
232
229
  if verbose >= 5:
233
230
  logger.info("Initialized shared memory for PraisonAIAgents")
234
-
235
231
  # Distribute memory to tasks
236
232
  for task in tasks:
237
233
  if not task.memory:
238
234
  task.memory = self.shared_memory
239
235
  if verbose >= 5:
240
236
  logger.info(f"Assigned shared memory to task {task.id}")
241
-
242
237
  except Exception as e:
243
238
  logger.error(f"Failed to initialize shared memory: {e}")
244
-
245
239
  # Update tasks with shared memory
246
240
  if self.shared_memory:
247
241
  for task in tasks:
@@ -898,6 +892,105 @@ Context:
898
892
  def clear_state(self) -> None:
899
893
  """Clear all state values"""
900
894
  self._state.clear()
895
+
896
+ # Convenience methods for enhanced state management
897
+ def has_state(self, key: str) -> bool:
898
+ """Check if a state key exists"""
899
+ return key in self._state
900
+
901
+ def get_all_state(self) -> Dict[str, Any]:
902
+ """Get a copy of the entire state dictionary"""
903
+ return self._state.copy()
904
+
905
+ def delete_state(self, key: str) -> bool:
906
+ """Delete a state key if it exists. Returns True if deleted, False if key didn't exist."""
907
+ if key in self._state:
908
+ del self._state[key]
909
+ return True
910
+ return False
911
+
912
+ def increment_state(self, key: str, amount: float = 1, default: float = 0) -> float:
913
+ """Increment a numeric state value. Creates the key with default if it doesn't exist."""
914
+ current = self._state.get(key, default)
915
+ if not isinstance(current, (int, float)):
916
+ raise TypeError(f"Cannot increment non-numeric value at key '{key}': {type(current).__name__}")
917
+ new_value = current + amount
918
+ self._state[key] = new_value
919
+ return new_value
920
+
921
+ def append_to_state(self, key: str, value: Any, max_length: Optional[int] = None) -> List[Any]:
922
+ """Append a value to a list state. Creates the list if it doesn't exist.
923
+
924
+ Args:
925
+ key: State key
926
+ value: Value to append
927
+ max_length: Optional maximum length for the list
928
+
929
+ Returns:
930
+ The updated list
931
+
932
+ Raises:
933
+ TypeError: If the existing value is not a list and convert_to_list=False
934
+ """
935
+ if key not in self._state:
936
+ self._state[key] = []
937
+ elif not isinstance(self._state[key], list):
938
+ # Be explicit about type conversion for better user experience
939
+ current_value = self._state[key]
940
+ self._state[key] = [current_value]
941
+
942
+ self._state[key].append(value)
943
+
944
+ # Trim list if max_length is specified
945
+ if max_length and len(self._state[key]) > max_length:
946
+ self._state[key] = self._state[key][-max_length:]
947
+
948
+ return self._state[key]
949
+
950
+ def save_session_state(self, session_id: str, include_memory: bool = True) -> None:
951
+ """Save current state to memory for session persistence"""
952
+ if self.shared_memory and include_memory:
953
+ state_data = {
954
+ "session_id": session_id,
955
+ "user_id": self.user_id,
956
+ "run_id": self.run_id,
957
+ "state": self._state,
958
+ "agents": [agent.name for agent in self.agents],
959
+ "process": self.process
960
+ }
961
+ self.shared_memory.store_short_term(
962
+ text=f"Session state for {session_id}",
963
+ metadata={
964
+ "type": "session_state",
965
+ "session_id": session_id,
966
+ "user_id": self.user_id,
967
+ "state_data": state_data
968
+ }
969
+ )
970
+
971
+ def restore_session_state(self, session_id: str) -> bool:
972
+ """Restore state from memory for session persistence. Returns True if restored."""
973
+ if not self.shared_memory:
974
+ return False
975
+
976
+ # Use metadata-based search for better SQLite compatibility
977
+ results = self.shared_memory.search_short_term(
978
+ query=f"type:session_state",
979
+ limit=10 # Get more results to filter by session_id
980
+ )
981
+
982
+ # Filter results by session_id in metadata
983
+ for result in results:
984
+ metadata = result.get("metadata", {})
985
+ if (metadata.get("type") == "session_state" and
986
+ metadata.get("session_id") == session_id):
987
+ state_data = metadata.get("state_data", {})
988
+ if "state" in state_data:
989
+ # Merge with existing state instead of replacing
990
+ self._state.update(state_data["state"])
991
+ return True
992
+
993
+ return False
901
994
 
902
995
  def launch(self, path: str = '/agents', port: int = 8000, host: str = '0.0.0.0', debug: bool = False, protocol: str = "http"):
903
996
  """
@@ -0,0 +1,263 @@
1
+ """
2
+ Human Approval Framework for PraisonAI Agents
3
+
4
+ This module provides a minimal human-in-the-loop approval system for dangerous tool operations.
5
+ It extends the existing callback system to require human approval before executing high-risk tools.
6
+ """
7
+
8
+ import logging
9
+ import asyncio
10
+ from typing import Dict, Set, Optional, Callable, Any, Literal
11
+ from functools import wraps
12
+ from contextvars import ContextVar
13
+ from rich.console import Console
14
+ from rich.panel import Panel
15
+ from rich.text import Text
16
+ from rich.prompt import Confirm
17
+
18
+ # Global registries for approval requirements
19
+ APPROVAL_REQUIRED_TOOLS: Set[str] = set()
20
+ TOOL_RISK_LEVELS: Dict[str, str] = {}
21
+
22
+ # Risk levels
23
+ RiskLevel = Literal["critical", "high", "medium", "low"]
24
+
25
+ # Global approval callback
26
+ approval_callback: Optional[Callable] = None
27
+
28
+ # Context variable to track if we're in an approved execution context
29
+ _approved_context: ContextVar[Set[str]] = ContextVar('approved_context', default=set())
30
+
31
+ class ApprovalDecision:
32
+ """Result of an approval request"""
33
+ def __init__(self, approved: bool, modified_args: Optional[Dict[str, Any]] = None, reason: str = ""):
34
+ self.approved = approved
35
+ self.modified_args = modified_args or {}
36
+ self.reason = reason
37
+
38
+ def set_approval_callback(callback_fn: Callable):
39
+ """Set a custom approval callback function.
40
+
41
+ The callback should accept (function_name, arguments, risk_level) and return ApprovalDecision.
42
+ """
43
+ global approval_callback
44
+ approval_callback = callback_fn
45
+
46
+ def mark_approved(tool_name: str):
47
+ """Mark a tool as approved in the current context."""
48
+ approved = _approved_context.get(set())
49
+ approved.add(tool_name)
50
+ _approved_context.set(approved)
51
+
52
+ def is_already_approved(tool_name: str) -> bool:
53
+ """Check if a tool is already approved in the current context."""
54
+ approved = _approved_context.get(set())
55
+ return tool_name in approved
56
+
57
+ def clear_approval_context():
58
+ """Clear the approval context."""
59
+ _approved_context.set(set())
60
+
61
+ def require_approval(risk_level: RiskLevel = "high"):
62
+ """Decorator to mark a tool as requiring human approval.
63
+
64
+ Args:
65
+ risk_level: The risk level of the tool ("critical", "high", "medium", "low")
66
+ """
67
+ def decorator(func):
68
+ tool_name = getattr(func, '__name__', str(func))
69
+ APPROVAL_REQUIRED_TOOLS.add(tool_name)
70
+ TOOL_RISK_LEVELS[tool_name] = risk_level
71
+
72
+ @wraps(func)
73
+ def wrapper(*args, **kwargs):
74
+ # Skip approval if already approved in current context
75
+ if is_already_approved(tool_name):
76
+ return func(*args, **kwargs)
77
+
78
+ # Request approval before executing the function
79
+ try:
80
+ # Try to check if we're in an async context
81
+ try:
82
+ asyncio.get_running_loop()
83
+ # We're in an async context, but this is a sync function
84
+ # Fall back to sync approval to avoid loop conflicts
85
+ raise RuntimeError("Use sync fallback in async context")
86
+ except RuntimeError:
87
+ # Either no running loop or we want sync fallback
88
+ # Use asyncio.run for clean async execution
89
+ decision = asyncio.run(request_approval(tool_name, kwargs))
90
+ except Exception as e:
91
+ # Fallback to sync approval if async fails
92
+ logging.warning(f"Async approval failed, using sync fallback: {e}")
93
+ callback = approval_callback or console_approval_callback
94
+ decision = callback(tool_name, kwargs, risk_level)
95
+
96
+ if not decision.approved:
97
+ raise PermissionError(f"Execution of {tool_name} denied: {decision.reason}")
98
+
99
+ # Mark as approved and merge modified args
100
+ mark_approved(tool_name)
101
+ kwargs.update(decision.modified_args)
102
+ return func(*args, **kwargs)
103
+
104
+ @wraps(func)
105
+ async def async_wrapper(*args, **kwargs):
106
+ # Skip approval if already approved in current context
107
+ if is_already_approved(tool_name):
108
+ return await func(*args, **kwargs)
109
+
110
+ # Request approval before executing the function
111
+ decision = await request_approval(tool_name, kwargs)
112
+ if not decision.approved:
113
+ raise PermissionError(f"Execution of {tool_name} denied: {decision.reason}")
114
+
115
+ # Mark as approved and merge modified args
116
+ mark_approved(tool_name)
117
+ kwargs.update(decision.modified_args)
118
+ return await func(*args, **kwargs)
119
+
120
+ # Return the appropriate wrapper based on function type
121
+ if asyncio.iscoroutinefunction(func):
122
+ return async_wrapper
123
+ else:
124
+ return wrapper
125
+
126
+ return decorator
127
+
128
+ def console_approval_callback(function_name: str, arguments: Dict[str, Any], risk_level: str) -> ApprovalDecision:
129
+ """Default console-based approval callback.
130
+
131
+ Displays tool information and prompts user for approval via console.
132
+ """
133
+ console = Console()
134
+
135
+ # Create risk level styling
136
+ risk_colors = {
137
+ "critical": "bold red",
138
+ "high": "red",
139
+ "medium": "yellow",
140
+ "low": "blue"
141
+ }
142
+ risk_color = risk_colors.get(risk_level, "white")
143
+
144
+ # Display tool information
145
+ tool_info = f"[bold]Function:[/] {function_name}\n"
146
+ tool_info += f"[bold]Risk Level:[/] [{risk_color}]{risk_level.upper()}[/{risk_color}]\n"
147
+ tool_info += f"[bold]Arguments:[/]\n"
148
+
149
+ for key, value in arguments.items():
150
+ # Truncate long values for display
151
+ str_value = str(value)
152
+ if len(str_value) > 100:
153
+ str_value = str_value[:97] + "..."
154
+ tool_info += f" {key}: {str_value}\n"
155
+
156
+ console.print(Panel(
157
+ tool_info.strip(),
158
+ title="🔒 Tool Approval Required",
159
+ border_style=risk_color,
160
+ title_align="left"
161
+ ))
162
+
163
+ # Get user approval
164
+ try:
165
+ approved = Confirm.ask(
166
+ f"[{risk_color}]Do you want to execute this {risk_level} risk tool?[/{risk_color}]",
167
+ default=False
168
+ )
169
+
170
+ if approved:
171
+ console.print("[green]✅ Tool execution approved[/green]")
172
+ return ApprovalDecision(approved=True, reason="User approved")
173
+ else:
174
+ console.print("[red]❌ Tool execution denied[/red]")
175
+ return ApprovalDecision(approved=False, reason="User denied")
176
+
177
+ except KeyboardInterrupt:
178
+ console.print("\n[red]❌ Tool execution cancelled by user[/red]")
179
+ return ApprovalDecision(approved=False, reason="User cancelled")
180
+ except Exception as e:
181
+ console.print(f"[red]Error during approval: {e}[/red]")
182
+ return ApprovalDecision(approved=False, reason=f"Approval error: {e}")
183
+
184
+ async def request_approval(function_name: str, arguments: Dict[str, Any]) -> ApprovalDecision:
185
+ """Request approval for a tool execution.
186
+
187
+ Args:
188
+ function_name: Name of the function to execute
189
+ arguments: Arguments to pass to the function
190
+
191
+ Returns:
192
+ ApprovalDecision with approval status and any modifications
193
+ """
194
+ # Check if approval is required
195
+ if function_name not in APPROVAL_REQUIRED_TOOLS:
196
+ return ApprovalDecision(approved=True, reason="No approval required")
197
+
198
+ risk_level = TOOL_RISK_LEVELS.get(function_name, "medium")
199
+
200
+ # Use custom callback if set, otherwise use console callback
201
+ callback = approval_callback or console_approval_callback
202
+
203
+ try:
204
+ # Handle async callbacks
205
+ if asyncio.iscoroutinefunction(callback):
206
+ decision = await callback(function_name, arguments, risk_level)
207
+ else:
208
+ # Run sync callback in executor to avoid blocking
209
+ loop = asyncio.get_event_loop()
210
+ decision = await loop.run_in_executor(None, callback, function_name, arguments, risk_level)
211
+
212
+ return decision
213
+
214
+ except Exception as e:
215
+ logging.error(f"Error in approval callback: {e}")
216
+ return ApprovalDecision(approved=False, reason=f"Approval callback error: {e}")
217
+
218
+ # Default dangerous tools - can be configured at runtime
219
+ DEFAULT_DANGEROUS_TOOLS = {
220
+ # Critical risk tools
221
+ "execute_command": "critical",
222
+ "kill_process": "critical",
223
+ "execute_code": "critical",
224
+
225
+ # High risk tools
226
+ "write_file": "high",
227
+ "delete_file": "high",
228
+ "move_file": "high",
229
+ "copy_file": "high",
230
+ "execute_query": "high",
231
+
232
+ # Medium risk tools
233
+ "evaluate": "medium",
234
+ "crawl": "medium",
235
+ "scrape_page": "medium",
236
+ }
237
+
238
+ def configure_default_approvals():
239
+ """Configure default dangerous tools to require approval."""
240
+ for tool_name, risk_level in DEFAULT_DANGEROUS_TOOLS.items():
241
+ APPROVAL_REQUIRED_TOOLS.add(tool_name)
242
+ TOOL_RISK_LEVELS[tool_name] = risk_level
243
+
244
+ def add_approval_requirement(tool_name: str, risk_level: RiskLevel = "high"):
245
+ """Dynamically add approval requirement for a tool."""
246
+ APPROVAL_REQUIRED_TOOLS.add(tool_name)
247
+ TOOL_RISK_LEVELS[tool_name] = risk_level
248
+
249
+ def remove_approval_requirement(tool_name: str):
250
+ """Remove approval requirement for a tool."""
251
+ APPROVAL_REQUIRED_TOOLS.discard(tool_name)
252
+ TOOL_RISK_LEVELS.pop(tool_name, None)
253
+
254
+ def is_approval_required(tool_name: str) -> bool:
255
+ """Check if a tool requires approval."""
256
+ return tool_name in APPROVAL_REQUIRED_TOOLS
257
+
258
+ def get_risk_level(tool_name: str) -> Optional[str]:
259
+ """Get the risk level of a tool."""
260
+ return TOOL_RISK_LEVELS.get(tool_name)
261
+
262
+ # Initialize with defaults
263
+ configure_default_approvals()
@@ -17,9 +17,13 @@ class CustomMemory:
17
17
  }).from_config(config)
18
18
 
19
19
  @staticmethod
20
- def _add_to_vector_store(self, messages, metadata, filters, infer):
20
+ def _add_to_vector_store(self, messages, metadata=None, filters=None, infer=None):
21
21
  # Custom implementation that doesn't use LLM
22
- parsed_messages = "\n".join([msg["content"] for msg in messages])
22
+ # Handle different message formats for backward compatibility
23
+ if isinstance(messages, list):
24
+ parsed_messages = "\n".join([msg.get("content", str(msg)) if isinstance(msg, dict) else str(msg) for msg in messages])
25
+ else:
26
+ parsed_messages = str(messages)
23
27
 
24
28
  # Create a simple fact without using LLM
25
29
  new_retrieved_facts = [parsed_messages]
@@ -34,7 +38,7 @@ class CustomMemory:
34
38
  memory_id = self._create_memory(
35
39
  data=parsed_messages,
36
40
  existing_embeddings=new_message_embeddings,
37
- metadata=metadata
41
+ metadata=metadata or {}
38
42
  )
39
43
 
40
44
  return [{
@@ -137,6 +141,10 @@ class Knowledge:
137
141
  # Merge reranker config if provided
138
142
  if "reranker" in self._config:
139
143
  base_config["reranker"].update(self._config["reranker"])
144
+
145
+ # Merge graph_store config if provided (for graph memory support)
146
+ if "graph_store" in self._config:
147
+ base_config["graph_store"] = self._config["graph_store"]
140
148
  return base_config
141
149
 
142
150
  @cached_property
@@ -184,7 +192,22 @@ class Knowledge:
184
192
  if not content:
185
193
  return []
186
194
 
187
- result = self.memory.add(content, user_id=user_id, agent_id=agent_id, run_id=run_id, metadata=metadata)
195
+ # Try new API format first, fall back to old format for backward compatibility
196
+ try:
197
+ # Convert content to messages format for mem0 API compatibility
198
+ if isinstance(content, str):
199
+ messages = [{"role": "user", "content": content}]
200
+ else:
201
+ messages = content if isinstance(content, list) else [{"role": "user", "content": str(content)}]
202
+
203
+ result = self.memory.add(messages=messages, user_id=user_id, agent_id=agent_id, run_id=run_id, metadata=metadata)
204
+ except TypeError as e:
205
+ # Fallback to old API format if messages parameter is not supported
206
+ if "unexpected keyword argument" in str(e) or "positional argument" in str(e):
207
+ self._log(f"Falling back to legacy API format due to: {e}")
208
+ result = self.memory.add(content, user_id=user_id, agent_id=agent_id, run_id=run_id, metadata=metadata)
209
+ else:
210
+ raise
188
211
  self._log(f"Store operation result: {result}")
189
212
  return result
190
213
  except Exception as e:
@@ -1512,6 +1512,10 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
1512
1512
  if self.stop_phrases:
1513
1513
  params["stop"] = self.stop_phrases
1514
1514
 
1515
+ # Add extra settings for provider-specific parameters (e.g., num_ctx for Ollama)
1516
+ if self.extra_settings:
1517
+ params.update(self.extra_settings)
1518
+
1515
1519
  # Override with any provided parameters
1516
1520
  params.update(override_params)
1517
1521
 
praisonaiagents/main.py CHANGED
@@ -43,12 +43,18 @@ error_logs = []
43
43
  sync_display_callbacks = {}
44
44
  async_display_callbacks = {}
45
45
 
46
+ # Global approval callback registry
47
+ approval_callback = None
48
+
46
49
  # At the top of the file, add display_callbacks to __all__
47
50
  __all__ = [
48
51
  'error_logs',
49
52
  'register_display_callback',
53
+ 'register_approval_callback',
50
54
  'sync_display_callbacks',
51
55
  'async_display_callbacks',
56
+ 'execute_callback',
57
+ 'approval_callback',
52
58
  # ... other exports
53
59
  ]
54
60
 
@@ -65,6 +71,15 @@ def register_display_callback(display_type: str, callback_fn, is_async: bool = F
65
71
  else:
66
72
  sync_display_callbacks[display_type] = callback_fn
67
73
 
74
+ def register_approval_callback(callback_fn):
75
+ """Register a global approval callback function for dangerous tool operations.
76
+
77
+ Args:
78
+ callback_fn: Function that takes (function_name, arguments, risk_level) and returns ApprovalDecision
79
+ """
80
+ global approval_callback
81
+ approval_callback = callback_fn
82
+
68
83
  async def execute_callback(display_type: str, **kwargs):
69
84
  """Execute both sync and async callbacks for a given display type.
70
85
 
@@ -41,6 +41,7 @@ class Memory:
41
41
  - User memory (preferences/history for each user)
42
42
  - Quality score logic for deciding which data to store in LTM
43
43
  - Context building from multiple memory sources
44
+ - Graph memory support for complex relationship storage (via Mem0)
44
45
 
45
46
  Config example:
46
47
  {
@@ -53,9 +54,35 @@ class Memory:
53
54
  "api_key": "...", # if mem0 usage
54
55
  "org_id": "...",
55
56
  "project_id": "...",
56
- ...
57
+
58
+ # Graph memory configuration (optional)
59
+ "graph_store": {
60
+ "provider": "neo4j" or "memgraph",
61
+ "config": {
62
+ "url": "neo4j+s://xxx" or "bolt://localhost:7687",
63
+ "username": "neo4j" or "memgraph",
64
+ "password": "xxx"
65
+ }
66
+ },
67
+
68
+ # Optional additional configurations for graph memory
69
+ "vector_store": {
70
+ "provider": "qdrant",
71
+ "config": {"host": "localhost", "port": 6333}
72
+ },
73
+ "llm": {
74
+ "provider": "openai",
75
+ "config": {"model": "gpt-4o", "api_key": "..."}
76
+ },
77
+ "embedder": {
78
+ "provider": "openai",
79
+ "config": {"model": "text-embedding-3-small", "api_key": "..."}
80
+ }
57
81
  }
58
82
  }
83
+
84
+ Note: Graph memory requires "mem0ai[graph]" installation and works alongside
85
+ vector-based memory for enhanced relationship-aware retrieval.
59
86
  """
60
87
 
61
88
  def __init__(self, config: Dict[str, Any], verbose: int = 0):
@@ -78,6 +105,7 @@ class Memory:
78
105
  self.provider = self.cfg.get("provider", "rag")
79
106
  self.use_mem0 = (self.provider.lower() == "mem0") and MEM0_AVAILABLE
80
107
  self.use_rag = (self.provider.lower() == "rag") and CHROMADB_AVAILABLE and self.cfg.get("use_embedding", False)
108
+ self.graph_enabled = False # Initialize graph support flag
81
109
 
82
110
  # Create .praison directory if it doesn't exist
83
111
  os.makedirs(".praison", exist_ok=True)
@@ -137,16 +165,49 @@ class Memory:
137
165
  conn.close()
138
166
 
139
167
  def _init_mem0(self):
140
- """Initialize Mem0 client for agent or user memory."""
141
- from mem0 import MemoryClient
168
+ """Initialize Mem0 client for agent or user memory with optional graph support."""
142
169
  mem_cfg = self.cfg.get("config", {})
143
170
  api_key = mem_cfg.get("api_key", os.getenv("MEM0_API_KEY"))
144
171
  org_id = mem_cfg.get("org_id")
145
172
  proj_id = mem_cfg.get("project_id")
146
- if org_id and proj_id:
147
- self.mem0_client = MemoryClient(api_key=api_key, org_id=org_id, project_id=proj_id)
173
+
174
+ # Check if graph memory is enabled
175
+ graph_config = mem_cfg.get("graph_store")
176
+ use_graph = graph_config is not None
177
+
178
+ if use_graph:
179
+ # Initialize with graph memory support
180
+ from mem0 import Memory
181
+ self._log_verbose("Initializing Mem0 with graph memory support")
182
+
183
+ # Build Mem0 config with graph store
184
+ mem0_config = {}
185
+
186
+ # Add graph store configuration
187
+ mem0_config["graph_store"] = graph_config
188
+
189
+ # Add other configurations if provided
190
+ if "vector_store" in mem_cfg:
191
+ mem0_config["vector_store"] = mem_cfg["vector_store"]
192
+ if "llm" in mem_cfg:
193
+ mem0_config["llm"] = mem_cfg["llm"]
194
+ if "embedder" in mem_cfg:
195
+ mem0_config["embedder"] = mem_cfg["embedder"]
196
+
197
+ # Initialize Memory with graph support
198
+ self.mem0_client = Memory.from_config(config_dict=mem0_config)
199
+ self.graph_enabled = True
200
+ self._log_verbose("Graph memory initialized successfully")
148
201
  else:
149
- self.mem0_client = MemoryClient(api_key=api_key)
202
+ # Use traditional MemoryClient
203
+ from mem0 import MemoryClient
204
+ self._log_verbose("Initializing Mem0 with traditional memory client")
205
+
206
+ if org_id and proj_id:
207
+ self.mem0_client = MemoryClient(api_key=api_key, org_id=org_id, project_id=proj_id)
208
+ else:
209
+ self.mem0_client = MemoryClient(api_key=api_key)
210
+ self.graph_enabled = False
150
211
 
151
212
  def _init_chroma(self):
152
213
  """Initialize a local Chroma client for embedding-based search."""
@@ -0,0 +1,291 @@
1
+ """
2
+ Session Management for PraisonAI Agents
3
+
4
+ A simple wrapper around existing stateful capabilities to provide a unified
5
+ session API for developers building stateful agent applications.
6
+ """
7
+
8
+ import os
9
+ import uuid
10
+ from typing import Any, Dict, List, Optional
11
+ from .agent import Agent
12
+ from .memory import Memory
13
+ from .knowledge import Knowledge
14
+
15
+
16
+ class Session:
17
+ """
18
+ A simple wrapper around PraisonAI's existing stateful capabilities.
19
+
20
+ Provides a unified API for:
21
+ - Session management with persistent state
22
+ - Memory operations (short-term, long-term, user-specific)
23
+ - Knowledge base operations
24
+ - Agent state management
25
+
26
+ Example:
27
+ session = Session(session_id="chat_123", user_id="user_456")
28
+
29
+ # Create stateful agent
30
+ agent = session.Agent(
31
+ name="Assistant",
32
+ role="Helpful AI",
33
+ memory=True
34
+ )
35
+
36
+ # Save session state
37
+ session.save_state({"conversation_topic": "AI research"})
38
+
39
+ # Restore state later
40
+ session.restore_state()
41
+ """
42
+
43
+ def __init__(
44
+ self,
45
+ session_id: Optional[str] = None,
46
+ user_id: Optional[str] = None,
47
+ memory_config: Optional[Dict[str, Any]] = None,
48
+ knowledge_config: Optional[Dict[str, Any]] = None
49
+ ):
50
+ """
51
+ Initialize a new session with optional persistence.
52
+
53
+ Args:
54
+ session_id: Unique session identifier. Auto-generated if None.
55
+ user_id: User identifier for user-specific memory operations.
56
+ memory_config: Configuration for memory system (defaults to RAG)
57
+ knowledge_config: Configuration for knowledge base system
58
+ """
59
+ self.session_id = session_id or str(uuid.uuid4())[:8]
60
+ self.user_id = user_id or "default_user"
61
+
62
+ # Initialize memory with sensible defaults
63
+ default_memory_config = {
64
+ "provider": "rag",
65
+ "use_embedding": True,
66
+ "rag_db_path": f".praison/sessions/{self.session_id}/chroma_db"
67
+ }
68
+ if memory_config:
69
+ default_memory_config.update(memory_config)
70
+ self.memory_config = default_memory_config
71
+
72
+ # Initialize knowledge with session-specific config
73
+ default_knowledge_config = knowledge_config or {}
74
+ self.knowledge_config = default_knowledge_config
75
+
76
+ # Create session directory
77
+ os.makedirs(f".praison/sessions/{self.session_id}", exist_ok=True)
78
+
79
+ # Initialize components lazily
80
+ self._memory = None
81
+ self._knowledge = None
82
+ self._agents_instance = None
83
+
84
+ @property
85
+ def memory(self) -> Memory:
86
+ """Lazy-loaded memory instance"""
87
+ if self._memory is None:
88
+ self._memory = Memory(config=self.memory_config)
89
+ return self._memory
90
+
91
+ @property
92
+ def knowledge(self) -> Knowledge:
93
+ """Lazy-loaded knowledge instance"""
94
+ if self._knowledge is None:
95
+ self._knowledge = Knowledge(config=self.knowledge_config)
96
+ return self._knowledge
97
+
98
+ def Agent(
99
+ self,
100
+ name: str,
101
+ role: str = "Assistant",
102
+ instructions: Optional[str] = None,
103
+ tools: Optional[List[Any]] = None,
104
+ memory: bool = True,
105
+ knowledge: Optional[List[str]] = None,
106
+ **kwargs
107
+ ) -> Agent:
108
+ """
109
+ Create an agent with session context.
110
+
111
+ Args:
112
+ name: Agent name
113
+ role: Agent role
114
+ instructions: Agent instructions
115
+ tools: List of tools for the agent
116
+ memory: Enable memory for the agent
117
+ knowledge: Knowledge sources for the agent
118
+ **kwargs: Additional agent parameters
119
+
120
+ Returns:
121
+ Configured Agent instance
122
+ """
123
+ agent_kwargs = {
124
+ "name": name,
125
+ "role": role,
126
+ "user_id": self.user_id,
127
+ **kwargs
128
+ }
129
+
130
+ if instructions:
131
+ agent_kwargs["instructions"] = instructions
132
+ if tools:
133
+ agent_kwargs["tools"] = tools
134
+ if memory:
135
+ agent_kwargs["memory"] = self.memory
136
+ if knowledge:
137
+ agent_kwargs["knowledge"] = knowledge
138
+ agent_kwargs["knowledge_config"] = self.knowledge_config
139
+
140
+ return Agent(**agent_kwargs)
141
+
142
+ # Keep create_agent for backward compatibility
143
+ def create_agent(self, *args, **kwargs) -> Agent:
144
+ """Backward compatibility wrapper for Agent method"""
145
+ return self.Agent(*args, **kwargs)
146
+
147
+ def save_state(self, state_data: Dict[str, Any]) -> None:
148
+ """
149
+ Save session state data to memory.
150
+
151
+ Args:
152
+ state_data: Dictionary of state data to save
153
+ """
154
+ state_text = f"Session state: {state_data}"
155
+ self.memory.store_short_term(
156
+ text=state_text,
157
+ metadata={
158
+ "type": "session_state",
159
+ "session_id": self.session_id,
160
+ "user_id": self.user_id,
161
+ **state_data
162
+ }
163
+ )
164
+
165
+ def restore_state(self) -> Dict[str, Any]:
166
+ """
167
+ Restore session state from memory.
168
+
169
+ Returns:
170
+ Dictionary of restored state data
171
+ """
172
+ # Use metadata-based search for better SQLite compatibility
173
+ results = self.memory.search_short_term(
174
+ query=f"type:session_state",
175
+ limit=10 # Get more results to filter by session_id
176
+ )
177
+
178
+ # Filter results by session_id in metadata
179
+ for result in results:
180
+ metadata = result.get("metadata", {})
181
+ if (metadata.get("type") == "session_state" and
182
+ metadata.get("session_id") == self.session_id):
183
+ # Extract state data from metadata (excluding system fields)
184
+ state_data = {k: v for k, v in metadata.items()
185
+ if k not in ["type", "session_id", "user_id"]}
186
+ return state_data
187
+
188
+ return {}
189
+
190
+ def get_state(self, key: str, default: Any = None) -> Any:
191
+ """Get a specific state value"""
192
+ state = self.restore_state()
193
+ return state.get(key, default)
194
+
195
+ def set_state(self, key: str, value: Any) -> None:
196
+ """Set a specific state value"""
197
+ current_state = self.restore_state()
198
+ current_state[key] = value
199
+ self.save_state(current_state)
200
+
201
+ def add_memory(self, text: str, memory_type: str = "long", **metadata) -> None:
202
+ """
203
+ Add information to session memory.
204
+
205
+ Args:
206
+ text: Text to store
207
+ memory_type: "short" or "long" term memory
208
+ **metadata: Additional metadata
209
+ """
210
+ metadata.update({
211
+ "session_id": self.session_id,
212
+ "user_id": self.user_id
213
+ })
214
+
215
+ if memory_type == "short":
216
+ self.memory.store_short_term(text, metadata=metadata)
217
+ else:
218
+ self.memory.store_long_term(text, metadata=metadata)
219
+
220
+ def search_memory(self, query: str, memory_type: str = "long", limit: int = 5) -> List[Dict[str, Any]]:
221
+ """
222
+ Search session memory.
223
+
224
+ Args:
225
+ query: Search query
226
+ memory_type: "short" or "long" term memory
227
+ limit: Maximum results to return
228
+
229
+ Returns:
230
+ List of memory results
231
+ """
232
+ if memory_type == "short":
233
+ return self.memory.search_short_term(query, limit=limit)
234
+ return self.memory.search_long_term(query, limit=limit)
235
+
236
+ def add_knowledge(self, source: str) -> None:
237
+ """
238
+ Add knowledge source to session.
239
+
240
+ Args:
241
+ source: File path, URL, or text content
242
+ """
243
+ self.knowledge.add(source, user_id=self.user_id, agent_id=self.session_id)
244
+
245
+ def search_knowledge(self, query: str, limit: int = 5) -> List[Dict[str, Any]]:
246
+ """
247
+ Search session knowledge base.
248
+
249
+ Args:
250
+ query: Search query
251
+ limit: Maximum results to return
252
+
253
+ Returns:
254
+ List of knowledge results
255
+ """
256
+ return self.knowledge.search(query, agent_id=self.session_id, limit=limit)
257
+
258
+ def clear_memory(self, memory_type: str = "all") -> None:
259
+ """
260
+ Clear session memory.
261
+
262
+ Args:
263
+ memory_type: "short", "long", or "all"
264
+ """
265
+ if memory_type in ["short", "all"]:
266
+ self.memory.reset_short_term()
267
+ if memory_type in ["long", "all"]:
268
+ self.memory.reset_long_term()
269
+
270
+ def get_context(self, query: str, max_items: int = 3) -> str:
271
+ """
272
+ Build context from session memory and knowledge.
273
+
274
+ Args:
275
+ query: Query to build context for
276
+ max_items: Maximum items per section
277
+
278
+ Returns:
279
+ Formatted context string
280
+ """
281
+ return self.memory.build_context_for_task(
282
+ task_descr=query,
283
+ user_id=self.user_id,
284
+ max_items=max_items
285
+ )
286
+
287
+ def __str__(self) -> str:
288
+ return f"Session(id='{self.session_id}', user='{self.user_id}')"
289
+
290
+ def __repr__(self) -> str:
291
+ return self.__str__()
@@ -16,6 +16,7 @@ from typing import List, Dict, Union, Optional
16
16
  from pathlib import Path
17
17
  import shutil
18
18
  import logging
19
+ from ..approval import require_approval
19
20
 
20
21
  class FileTools:
21
22
  """Tools for file operations including read, write, list, and information."""
@@ -41,6 +42,7 @@ class FileTools:
41
42
  return error_msg
42
43
 
43
44
  @staticmethod
45
+ @require_approval(risk_level="high")
44
46
  def write_file(filepath: str, content: str, encoding: str = 'utf-8') -> bool:
45
47
  """
46
48
  Write content to a file.
@@ -134,6 +136,7 @@ class FileTools:
134
136
  return {'error': error_msg}
135
137
 
136
138
  @staticmethod
139
+ @require_approval(risk_level="high")
137
140
  def copy_file(src: str, dst: str) -> bool:
138
141
  """
139
142
  Copy a file from source to destination.
@@ -156,6 +159,7 @@ class FileTools:
156
159
  return False
157
160
 
158
161
  @staticmethod
162
+ @require_approval(risk_level="high")
159
163
  def move_file(src: str, dst: str) -> bool:
160
164
  """
161
165
  Move a file from source to destination.
@@ -178,6 +182,7 @@ class FileTools:
178
182
  return False
179
183
 
180
184
  @staticmethod
185
+ @require_approval(risk_level="high")
181
186
  def delete_file(filepath: str) -> bool:
182
187
  """
183
188
  Delete a file.
@@ -15,6 +15,7 @@ from importlib import util
15
15
  import io
16
16
  from contextlib import redirect_stdout, redirect_stderr
17
17
  import traceback
18
+ from ..approval import require_approval
18
19
 
19
20
  class PythonTools:
20
21
  """Tools for Python code execution and analysis."""
@@ -36,6 +37,7 @@ class PythonTools:
36
37
  f"Run: pip install {' '.join(missing)}"
37
38
  )
38
39
 
40
+ @require_approval(risk_level="critical")
39
41
  def execute_code(
40
42
  self,
41
43
  code: str,
@@ -13,6 +13,7 @@ import logging
13
13
  import os
14
14
  import time
15
15
  from typing import Dict, List, Optional, Union
16
+ from ..approval import require_approval
16
17
 
17
18
  class ShellTools:
18
19
  """Tools for executing shell commands safely."""
@@ -31,6 +32,7 @@ class ShellTools:
31
32
  "Run: pip install psutil"
32
33
  )
33
34
 
35
+ @require_approval(risk_level="critical")
34
36
  def execute_command(
35
37
  self,
36
38
  command: str,
@@ -146,6 +148,7 @@ class ShellTools:
146
148
  logging.error(error_msg)
147
149
  return []
148
150
 
151
+ @require_approval(risk_level="critical")
149
152
  def kill_process(
150
153
  self,
151
154
  pid: int,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: praisonaiagents
3
- Version: 0.0.96
3
+ Version: 0.0.97
4
4
  Summary: Praison AI agents for completing complex tasks with Self Reflection Agents
5
5
  Author: Mervin Praison
6
6
  Requires-Python: >=3.10
@@ -19,6 +19,9 @@ Requires-Dist: mem0ai>=0.1.0; extra == "knowledge"
19
19
  Requires-Dist: chromadb>=1.0.0; extra == "knowledge"
20
20
  Requires-Dist: markitdown[all]>=0.1.0; extra == "knowledge"
21
21
  Requires-Dist: chonkie>=1.0.2; extra == "knowledge"
22
+ Provides-Extra: graph
23
+ Requires-Dist: mem0ai[graph]>=0.1.0; extra == "graph"
24
+ Requires-Dist: chromadb>=1.0.0; extra == "graph"
22
25
  Provides-Extra: llm
23
26
  Requires-Dist: litellm>=1.50.0; extra == "llm"
24
27
  Requires-Dist: pydantic>=2.4.2; extra == "llm"
@@ -28,6 +31,7 @@ Requires-Dist: uvicorn>=0.34.0; extra == "api"
28
31
  Provides-Extra: all
29
32
  Requires-Dist: praisonaiagents[memory]; extra == "all"
30
33
  Requires-Dist: praisonaiagents[knowledge]; extra == "all"
34
+ Requires-Dist: praisonaiagents[graph]; extra == "all"
31
35
  Requires-Dist: praisonaiagents[llm]; extra == "all"
32
36
  Requires-Dist: praisonaiagents[mcp]; extra == "all"
33
37
  Requires-Dist: praisonaiagents[api]; extra == "all"
@@ -1,20 +1,22 @@
1
- praisonaiagents/__init__.py,sha256=Z2_rSA6mYozz0r3ioUgKzl3QV8uWRDS_QaqPg2oGjqg,1324
2
- praisonaiagents/main.py,sha256=D6XzpqdfglCQiWaH5LjRSv-bB3QkJso-i0h1uTFkPQI,15844
1
+ praisonaiagents/__init__.py,sha256=jd2-rTrVQRH7fY8gO70UZBvV0Sq6FyijUMokQHJa-q0,1368
2
+ praisonaiagents/approval.py,sha256=UJ4OhfihpFGR5CAaMphqpSvqdZCHi5w2MGw1MByZ1FQ,9813
3
+ praisonaiagents/main.py,sha256=_-XE7_Y7ChvtLQMivfNFrrnAhv4wSSDhH9WJMWlkS0w,16315
4
+ praisonaiagents/session.py,sha256=CI-ffCiOfmgB-1zFFik9daKCB5Sm41Q9ZOaq1-oSLW8,9250
3
5
  praisonaiagents/agent/__init__.py,sha256=j0T19TVNbfZcClvpbZDDinQxZ0oORgsMrMqx16jZ-bA,128
4
- praisonaiagents/agent/agent.py,sha256=DPpTgobDVZw2qlPzvNS-Xi-OF3RlM2cil6y15cbXIy8,86553
6
+ praisonaiagents/agent/agent.py,sha256=irU4M5n23LD57hHtB5K6FHXrWgE_p0HeCX6UuIvhMlQ,86753
5
7
  praisonaiagents/agent/image_agent.py,sha256=-5MXG594HVwSpFMcidt16YBp7udtik-Cp7eXlzLE1fY,8696
6
8
  praisonaiagents/agents/__init__.py,sha256=_1d6Pqyk9EoBSo7E68sKyd1jDRlN1vxvVIRpoMc0Jcw,168
7
- praisonaiagents/agents/agents.py,sha256=-cWRgok0X_4Mk-L7dW6bFdX7JVpxfe7R6aLmukktwKc,59381
9
+ praisonaiagents/agents/agents.py,sha256=C_yDdJB4XUuwKA9DrysAtAj3zSYT0IKtfCT4Pxo0oyI,63309
8
10
  praisonaiagents/agents/autoagents.py,sha256=Lc_b9mO2MeefBrsHkHoqFxEr5iRGrYuzDhslyybXwdw,13649
9
11
  praisonaiagents/knowledge/__init__.py,sha256=xL1Eh-a3xsHyIcU4foOWF-JdWYIYBALJH9bge0Ujuto,246
10
12
  praisonaiagents/knowledge/chunking.py,sha256=G6wyHa7_8V0_7VpnrrUXbEmUmptlT16ISJYaxmkSgmU,7678
11
- praisonaiagents/knowledge/knowledge.py,sha256=7Qk6qvUanapzdM1B_bik645PcorErKlAvADf5iKXrTE,14289
13
+ praisonaiagents/knowledge/knowledge.py,sha256=OKPar-XGyAp1ndmbOOdCgqFnTCqpOThYVSIZRxZyP58,15683
12
14
  praisonaiagents/llm/__init__.py,sha256=ttPQQJQq6Tah-0updoEXDZFKWtJAM93rBWRoIgxRWO8,689
13
- praisonaiagents/llm/llm.py,sha256=9wHmf0aGKf4a7YZ4JONmD7Ela8JBYVrkMFF2ei8Ivpk,93400
15
+ praisonaiagents/llm/llm.py,sha256=SzD_qoUqQnC9FpY-d1HHqKQGkIGPR5wEmE1OcqVEPFY,93577
14
16
  praisonaiagents/mcp/__init__.py,sha256=ibbqe3_7XB7VrIcUcetkZiUZS1fTVvyMy_AqCSFG8qc,240
15
17
  praisonaiagents/mcp/mcp.py,sha256=_gfp8hrSVT9aPqEDDfU8MiCdg0-3dVQpEQUE6AbrJlo,17243
16
18
  praisonaiagents/mcp/mcp_sse.py,sha256=DLh3F_aoVRM1X-7hgIOWOw4FQ1nGmn9YNbQTesykzn4,6792
17
- praisonaiagents/memory/memory.py,sha256=XvX2fohxyyP8RihbbtOZYt0g-bIDEyLwzfNCWV2Ayok,36175
19
+ praisonaiagents/memory/memory.py,sha256=6tvsLWkpvyNU-t-8d6XpW7vOFUm1pNReW5B7rA8c04M,38612
18
20
  praisonaiagents/process/__init__.py,sha256=lkYbL7Hn5a0ldvJtkdH23vfIIZLIcanK-65C0MwaorY,52
19
21
  praisonaiagents/process/process.py,sha256=gxhMXG3s4CzaREyuwE5zxCMx2Wp_b_Wd53tDfkj8Qk8,66567
20
22
  praisonaiagents/task/__init__.py,sha256=VL5hXVmyGjINb34AalxpBMl-YW9m5EDcRkMTKkSSl7c,80
@@ -26,12 +28,12 @@ praisonaiagents/tools/csv_tools.py,sha256=4Yr0QYwBXt-1BDXGLalB2eSsFR2mB5rH3KdHmR
26
28
  praisonaiagents/tools/duckdb_tools.py,sha256=KB3b-1HcX7ocoxskDpk_7RRpTGHnH8hizIY0ZdLRbIE,8816
27
29
  praisonaiagents/tools/duckduckgo_tools.py,sha256=ynlB5ZyWfHYjUq0JZXH12TganqTihgD-2IyRgs32y84,1657
28
30
  praisonaiagents/tools/excel_tools.py,sha256=e2HqcwnyBueOyss0xEKxff3zB4w4sNWCOMXvZfbDYlE,11309
29
- praisonaiagents/tools/file_tools.py,sha256=KRskI9q8up3sHaLQSaIGtvpl1brq419Up6YvB9QzSoI,8807
31
+ praisonaiagents/tools/file_tools.py,sha256=-RE1LfJA3vr7JYoHQaElGTLMAEvc0NvN8pCsO8YGOHg,9011
30
32
  praisonaiagents/tools/json_tools.py,sha256=ApUYNuQ1qnbmYNCxSlx6Tth_H1yo8mhWtZ7Rr2WS6C4,16507
31
33
  praisonaiagents/tools/newspaper_tools.py,sha256=NyhojNPeyULBGcAWGOT1X70qVkh3FgZrpH-S7PEmrwI,12667
32
34
  praisonaiagents/tools/pandas_tools.py,sha256=yzCeY4jetKrFIRA15Tr5OQ5d94T8DaSpzglx2UiWfPs,11092
33
- praisonaiagents/tools/python_tools.py,sha256=ByBpESi5Vk2ivpihav9AQeqf95K_D4qqINYN1q-q0bA,13428
34
- praisonaiagents/tools/shell_tools.py,sha256=P3fSrfOw71CGcrPwdPOA9Fr6Bgt_CAC71bUjCyvZCN8,9301
35
+ praisonaiagents/tools/python_tools.py,sha256=puqLANl5YaG1YG8ixkl_MgWayF7uj5iXUEE15UYwIZE,13513
36
+ praisonaiagents/tools/shell_tools.py,sha256=6IlnFkNg04tVxQVM_fYgscIWLtcgIikpEi3olB1THuA,9431
35
37
  praisonaiagents/tools/spider_tools.py,sha256=lrZnT1V1BC46We-AzBrDB1Ryifr3KKGmYNntMsScU7w,15094
36
38
  praisonaiagents/tools/test.py,sha256=UHOTNrnMo0_H6I2g48re1WNZkrR7f6z25UnlWxiOSbM,1600
37
39
  praisonaiagents/tools/tools.py,sha256=TK5njOmDSpMlyBnbeBzNSlnzXWlnNaTpVqkFPhkMArg,265
@@ -40,7 +42,7 @@ praisonaiagents/tools/xml_tools.py,sha256=iYTMBEk5l3L3ryQ1fkUnNVYK-Nnua2Kx2S0dxN
40
42
  praisonaiagents/tools/yaml_tools.py,sha256=uogAZrhXV9O7xvspAtcTfpKSQYL2nlOTvCQXN94-G9A,14215
41
43
  praisonaiagents/tools/yfinance_tools.py,sha256=s2PBj_1v7oQnOobo2fDbQBACEHl61ftG4beG6Z979ZE,8529
42
44
  praisonaiagents/tools/train/data/generatecot.py,sha256=H6bNh-E2hqL5MW6kX3hqZ05g9ETKN2-kudSjiuU_SD8,19403
43
- praisonaiagents-0.0.96.dist-info/METADATA,sha256=QxdS4_rx41RYGsDT5GO5G3g-D-l1HGzTPIxSZxaK_aQ,1273
44
- praisonaiagents-0.0.96.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
45
- praisonaiagents-0.0.96.dist-info/top_level.txt,sha256=_HsRddrJ23iDx5TTqVUVvXG2HeHBL5voshncAMDGjtA,16
46
- praisonaiagents-0.0.96.dist-info/RECORD,,
45
+ praisonaiagents-0.0.97.dist-info/METADATA,sha256=8vBs0ezuT_KyidSMGofmM_YJTr4p9z5dUT-5o4LpohA,1452
46
+ praisonaiagents-0.0.97.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
47
+ praisonaiagents-0.0.97.dist-info/top_level.txt,sha256=_HsRddrJ23iDx5TTqVUVvXG2HeHBL5voshncAMDGjtA,16
48
+ praisonaiagents-0.0.97.dist-info/RECORD,,