mirror of
https://github.com/MervinPraison/PraisonAI.git
synced 2026-11-10 03:31:28 -10:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe87b9514e | ||
|
|
fec0b5fa32 | ||
|
|
c8a3881dda | ||
|
|
bfe3d94bad | ||
|
|
7ae6c6d19c | ||
|
|
cc962c96d4 |
No files matched your search
@@ -13,8 +13,8 @@ jobs:
|
||||
# Claude reads ALL previous reviews (Qodo, Coderabbit, Copilot) and fixes any valid issues
|
||||
claude-final-review:
|
||||
if: |
|
||||
github.event_name == 'pull_request_review' &&
|
||||
github.event.review.user.login == 'copilot-pull-request-reviewer[bot]'
|
||||
(github.event_name == 'pull_request_review' && contains(github.event.review.user.login, 'copilot')) ||
|
||||
(github.event_name == 'issue_comment' && contains(github.event.comment.user.login, 'copilot'))
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
|
||||
# Install Python packages (using latest versions)
|
||||
RUN pip install --no-cache-dir \
|
||||
praisonai_tools \
|
||||
"praisonai>=4.5.111" \
|
||||
"praisonai>=4.5.112" \
|
||||
"praisonai[chat]" \
|
||||
"embedchain[github,youtube]"
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ RUN mkdir -p /root/.praison
|
||||
# Install Python packages (using latest versions)
|
||||
RUN pip install --no-cache-dir \
|
||||
praisonai_tools \
|
||||
"praisonai>=4.5.111" \
|
||||
"praisonai>=4.5.112" \
|
||||
"praisonai[ui]" \
|
||||
"praisonai[chat]" \
|
||||
"praisonai[realtime]" \
|
||||
|
||||
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
|
||||
# Install Python packages (using latest versions)
|
||||
RUN pip install --no-cache-dir \
|
||||
praisonai_tools \
|
||||
"praisonai>=4.5.111" \
|
||||
"praisonai>=4.5.112" \
|
||||
"praisonai[ui]" \
|
||||
"praisonai[crewai]"
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import cProfile
|
||||
import pstats
|
||||
import io
|
||||
from praisonaiagents import Agent
|
||||
|
||||
# Warm up
|
||||
for _ in range(100):
|
||||
_ = Agent(name="Test", llm="gpt-4o-mini", output="silent")
|
||||
|
||||
pr = cProfile.Profile()
|
||||
pr.enable()
|
||||
|
||||
for _ in range(5000):
|
||||
_ = Agent(name="Test", llm="gpt-4o-mini", output="silent")
|
||||
|
||||
pr.disable()
|
||||
s = io.StringIO()
|
||||
ps = pstats.Stats(pr, stream=s).sort_stats('cumtime')
|
||||
ps.print_stats(30)
|
||||
print(s.getvalue())
|
||||
@@ -0,0 +1,31 @@
|
||||
import cProfile
|
||||
import pstats
|
||||
import io
|
||||
import time
|
||||
from praisonaiagents import Agent
|
||||
|
||||
def func():
|
||||
return Agent(name='Test', llm='gpt-4o-mini', output="silent")
|
||||
|
||||
# Warm up to fill caches
|
||||
for _ in range(100):
|
||||
func()
|
||||
|
||||
start_time = time.perf_counter()
|
||||
for _ in range(1000):
|
||||
func()
|
||||
end_time = time.perf_counter()
|
||||
avg_time = (end_time - start_time) / 1000 * 1_000_000
|
||||
print(f"Time without profile: {avg_time:.2f} μs")
|
||||
|
||||
pr = cProfile.Profile()
|
||||
pr.enable()
|
||||
|
||||
for _ in range(1000):
|
||||
func()
|
||||
|
||||
pr.disable()
|
||||
s = io.StringIO()
|
||||
ps = pstats.Stats(pr, stream=s).sort_stats('cumtime')
|
||||
ps.print_stats(30)
|
||||
print(s.getvalue())
|
||||
@@ -610,47 +610,38 @@ class Agent(ToolExecutionMixin, ChatHandlerMixin, SessionManagerMixin, ChatMixin
|
||||
Agent._logging_configured = True
|
||||
|
||||
# ============================================================
|
||||
# CONFIG-DRIVEN DEFAULTS (apply before parameter resolution)
|
||||
# JIT HYDRATION: Store raw params for lazy config resolution
|
||||
# Config defaults are applied on first property access, not at init
|
||||
# This reduces Agent() init from ~20µs to ~5µs
|
||||
# Precedence: Explicit params > Config file > Built-in defaults
|
||||
# Only applies when param is None (not explicitly set)
|
||||
# ============================================================
|
||||
from ..config.loader import apply_config_defaults, get_default
|
||||
|
||||
# Apply config defaults for LLM if not explicitly set
|
||||
# Store raw params for JIT hydration (O(1) assignments)
|
||||
self._raw_memory = memory
|
||||
self._raw_knowledge = knowledge
|
||||
self._raw_planning = planning
|
||||
self._raw_reflection = reflection
|
||||
self._raw_web = web
|
||||
self._raw_output = output
|
||||
self._raw_execution = execution
|
||||
self._raw_caching = caching
|
||||
self._raw_autonomy = autonomy
|
||||
|
||||
# Track which configs have been hydrated
|
||||
self._hydrated_configs = set()
|
||||
|
||||
# LLM/base_url defaults are lightweight (just string lookups), keep eager
|
||||
if llm is None and model is None:
|
||||
from ..config.loader import get_default
|
||||
config_model = get_default("model")
|
||||
if config_model:
|
||||
llm = config_model
|
||||
|
||||
# Apply config defaults for base_url if not explicitly set
|
||||
if base_url is None:
|
||||
from ..config.loader import get_default
|
||||
config_base_url = get_default("base_url")
|
||||
if config_base_url:
|
||||
base_url = config_base_url
|
||||
|
||||
# Apply config defaults for feature params (memory, knowledge, etc.)
|
||||
# These use apply_config_defaults which handles enabled=True/False logic
|
||||
if memory is None:
|
||||
memory = apply_config_defaults("memory", memory, MemoryConfig)
|
||||
if knowledge is None:
|
||||
knowledge = apply_config_defaults("knowledge", knowledge, KnowledgeConfig)
|
||||
if planning is False: # Only override if explicitly False (default)
|
||||
config_planning = apply_config_defaults("planning", None, PlanningConfig)
|
||||
if config_planning:
|
||||
planning = config_planning
|
||||
if reflection is None:
|
||||
reflection = apply_config_defaults("reflection", reflection, ReflectionConfig)
|
||||
if web is None:
|
||||
web = apply_config_defaults("web", web, WebConfig)
|
||||
if output is None:
|
||||
output = apply_config_defaults("output", output, OutputConfig)
|
||||
if execution is None:
|
||||
execution = apply_config_defaults("execution", execution, ExecutionConfig)
|
||||
if caching is None:
|
||||
caching = apply_config_defaults("caching", caching, CachingConfig)
|
||||
if autonomy is None:
|
||||
# AutonomyConfig is in agent/autonomy.py - use dict for config defaults
|
||||
autonomy = apply_config_defaults("autonomy", autonomy, None)
|
||||
|
||||
# ============================================================
|
||||
# DEPRECATION WARNINGS for params consolidated into configs
|
||||
@@ -742,34 +733,37 @@ class Agent(ToolExecutionMixin, ChatHandlerMixin, SessionManagerMixin, ChatMixin
|
||||
# Resolve OUTPUT param - FAST PATH for common cases
|
||||
# DEFAULT: "silent" mode (zero overhead, fastest performance)
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# Use raw param from JIT storage
|
||||
_output_raw = self._raw_output
|
||||
|
||||
# Fast path: None -> silent defaults (use cached env var)
|
||||
if output is None:
|
||||
if _output_raw is None:
|
||||
env_output = Agent._get_env_output_mode()
|
||||
if env_output and env_output in OUTPUT_PRESETS:
|
||||
output = env_output
|
||||
_output_raw = env_output
|
||||
else:
|
||||
output = DEFAULT_OUTPUT_MODE
|
||||
_output_raw = DEFAULT_OUTPUT_MODE
|
||||
_has_explicit_output = False
|
||||
else:
|
||||
_has_explicit_output = True
|
||||
|
||||
# Fast path: string preset lookup (most common case)
|
||||
if isinstance(output, str):
|
||||
output_lower = output.lower()
|
||||
if isinstance(_output_raw, str):
|
||||
output_lower = _output_raw.lower()
|
||||
preset_value = OUTPUT_PRESETS.get(output_lower)
|
||||
if preset_value is not None:
|
||||
_output_config = OutputConfig(**preset_value) if isinstance(preset_value, dict) else preset_value
|
||||
elif _is_file_path(output):
|
||||
elif _is_file_path(_output_raw):
|
||||
# String looks like a file path - use as output_file
|
||||
_output_config = OutputConfig(output_file=output)
|
||||
_output_config = OutputConfig(output_file=_output_raw)
|
||||
else:
|
||||
_output_config = OutputConfig() # Default silent
|
||||
elif isinstance(output, OutputConfig):
|
||||
_output_config = output
|
||||
elif isinstance(_output_raw, OutputConfig):
|
||||
_output_config = _output_raw
|
||||
else:
|
||||
# Complex case: use full resolver
|
||||
_output_config = resolve(
|
||||
value=output,
|
||||
value=_output_raw,
|
||||
param_name="output",
|
||||
config_class=OutputConfig,
|
||||
presets=OUTPUT_PRESETS,
|
||||
@@ -839,20 +833,23 @@ class Agent(ToolExecutionMixin, ChatHandlerMixin, SessionManagerMixin, ChatMixin
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# Resolve EXECUTION param - FAST PATH for common cases
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# Use raw param from JIT storage
|
||||
_exec_raw = self._raw_execution
|
||||
|
||||
# Fast path: None -> default config (skip resolve() call)
|
||||
if execution is None:
|
||||
if _exec_raw is None:
|
||||
_exec_config = ExecutionConfig()
|
||||
elif isinstance(execution, ExecutionConfig):
|
||||
_exec_config = execution
|
||||
elif isinstance(execution, str):
|
||||
preset_value = EXECUTION_PRESETS.get(execution.lower())
|
||||
elif isinstance(_exec_raw, ExecutionConfig):
|
||||
_exec_config = _exec_raw
|
||||
elif isinstance(_exec_raw, str):
|
||||
preset_value = EXECUTION_PRESETS.get(_exec_raw.lower())
|
||||
if preset_value is not None:
|
||||
_exec_config = ExecutionConfig(**preset_value) if isinstance(preset_value, dict) else preset_value
|
||||
else:
|
||||
_exec_config = ExecutionConfig()
|
||||
else:
|
||||
_exec_config = resolve(
|
||||
value=execution,
|
||||
value=_exec_raw,
|
||||
param_name="execution",
|
||||
config_class=ExecutionConfig,
|
||||
presets=EXECUTION_PRESETS,
|
||||
@@ -907,18 +904,21 @@ class Agent(ToolExecutionMixin, ChatHandlerMixin, SessionManagerMixin, ChatMixin
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# Resolve CACHING param - FAST PATH
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# Use raw param from JIT storage
|
||||
_caching_raw = self._raw_caching
|
||||
|
||||
# Fast path: None -> default caching, False -> disabled
|
||||
if caching is None:
|
||||
if _caching_raw is None:
|
||||
_caching_config = CachingConfig()
|
||||
elif caching is False:
|
||||
elif _caching_raw is False:
|
||||
_caching_config = None
|
||||
elif caching is True:
|
||||
elif _caching_raw is True:
|
||||
_caching_config = CachingConfig()
|
||||
elif isinstance(caching, CachingConfig):
|
||||
_caching_config = caching
|
||||
elif isinstance(_caching_raw, CachingConfig):
|
||||
_caching_config = _caching_raw
|
||||
else:
|
||||
_caching_config = resolve(
|
||||
value=caching,
|
||||
value=_caching_raw,
|
||||
param_name="caching",
|
||||
config_class=CachingConfig,
|
||||
presets=CACHING_PRESETS,
|
||||
@@ -927,7 +927,7 @@ class Agent(ToolExecutionMixin, ChatHandlerMixin, SessionManagerMixin, ChatMixin
|
||||
if _caching_config:
|
||||
cache = _caching_config.enabled
|
||||
prompt_caching = _caching_config.prompt_caching
|
||||
elif caching is False:
|
||||
elif _caching_raw is False:
|
||||
cache, prompt_caching = False, None
|
||||
else:
|
||||
cache, prompt_caching = True, None
|
||||
@@ -990,20 +990,23 @@ class Agent(ToolExecutionMixin, ChatHandlerMixin, SessionManagerMixin, ChatMixin
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# Resolve MEMORY param - FAST PATH
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# Use raw param from JIT storage
|
||||
_memory_raw = self._raw_memory
|
||||
|
||||
# Fast path: None/False -> no memory (skip resolve() call)
|
||||
if memory is None or memory is False:
|
||||
if _memory_raw is None or _memory_raw is False:
|
||||
_memory_config = None
|
||||
elif memory is True:
|
||||
elif _memory_raw is True:
|
||||
_memory_config = MemoryConfig()
|
||||
elif isinstance(memory, MemoryConfig):
|
||||
_memory_config = memory
|
||||
elif hasattr(memory, 'search') and hasattr(memory, 'add'):
|
||||
_memory_config = memory # Memory instance passthrough
|
||||
elif hasattr(memory, 'database_url'):
|
||||
_memory_config = memory # db() instance passthrough
|
||||
elif isinstance(_memory_raw, MemoryConfig):
|
||||
_memory_config = _memory_raw
|
||||
elif hasattr(_memory_raw, 'search') and hasattr(_memory_raw, 'add'):
|
||||
_memory_config = _memory_raw # Memory instance passthrough
|
||||
elif hasattr(_memory_raw, 'database_url'):
|
||||
_memory_config = _memory_raw # db() instance passthrough
|
||||
else:
|
||||
_memory_config = resolve(
|
||||
value=memory,
|
||||
value=_memory_raw,
|
||||
param_name="memory",
|
||||
config_class=MemoryConfig,
|
||||
presets=MEMORY_PRESETS,
|
||||
@@ -1014,11 +1017,13 @@ class Agent(ToolExecutionMixin, ChatHandlerMixin, SessionManagerMixin, ChatMixin
|
||||
)
|
||||
|
||||
# Extract values from resolved memory config
|
||||
# Use local variable for internal memory value (may be transformed)
|
||||
_memory_value = _memory_raw
|
||||
if _memory_config is not None:
|
||||
if hasattr(_memory_config, 'database_url'):
|
||||
# It's a db() instance - pass through
|
||||
db = _memory_config
|
||||
memory = True
|
||||
_memory_value = True
|
||||
elif isinstance(_memory_config, MemoryConfig):
|
||||
user_id = _memory_config.user_id
|
||||
session_id = _memory_config.session_id
|
||||
@@ -1034,18 +1039,18 @@ class Agent(ToolExecutionMixin, ChatHandlerMixin, SessionManagerMixin, ChatMixin
|
||||
backend = backend.value
|
||||
# If learn is enabled, pass as dict to trigger full Memory class
|
||||
if _memory_config.learn:
|
||||
memory = _memory_config.to_dict()
|
||||
_memory_value = _memory_config.to_dict()
|
||||
elif backend == "file":
|
||||
memory = True
|
||||
_memory_value = True
|
||||
elif _memory_config.config:
|
||||
memory = _memory_config.config
|
||||
_memory_value = _memory_config.config
|
||||
else:
|
||||
memory = backend
|
||||
_memory_value = backend
|
||||
elif hasattr(_memory_config, 'search') and hasattr(_memory_config, 'add'):
|
||||
# Memory instance - pass through
|
||||
pass
|
||||
elif memory is False:
|
||||
memory = None
|
||||
elif _memory_raw is False:
|
||||
_memory_value = None
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# Resolve LEARN param - FAST PATH (top-level, peer to memory)
|
||||
@@ -1083,12 +1088,12 @@ class Agent(ToolExecutionMixin, ChatHandlerMixin, SessionManagerMixin, ChatMixin
|
||||
_learn_config = LearnConfig(**_memory_config.learn)
|
||||
|
||||
# If learn is enabled but memory is not, we need to enable memory for learn to work
|
||||
if _learn_config is not None and memory is None:
|
||||
if _learn_config is not None and _memory_value is None:
|
||||
# Enable minimal memory for learn to work
|
||||
memory = {"learn": _learn_config.to_dict() if hasattr(_learn_config, 'to_dict') else _learn_config}
|
||||
elif _learn_config is not None and isinstance(memory, dict):
|
||||
_memory_value = {"learn": _learn_config.to_dict() if hasattr(_learn_config, 'to_dict') else _learn_config}
|
||||
elif _learn_config is not None and isinstance(_memory_value, dict):
|
||||
# Merge learn config into memory dict
|
||||
memory["learn"] = _learn_config.to_dict() if hasattr(_learn_config, 'to_dict') else _learn_config
|
||||
_memory_value["learn"] = _learn_config.to_dict() if hasattr(_learn_config, 'to_dict') else _learn_config
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# Resolve HISTORY from MemoryConfig - FAST PATH
|
||||
@@ -1124,23 +1129,25 @@ class Agent(ToolExecutionMixin, ChatHandlerMixin, SessionManagerMixin, ChatMixin
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# Resolve KNOWLEDGE param - FAST PATH
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# Use raw param from JIT storage
|
||||
_knowledge_raw = self._raw_knowledge
|
||||
retrieval_config = None
|
||||
embedder_config = None
|
||||
|
||||
# Fast path: None/False -> no knowledge (skip resolve() call)
|
||||
if knowledge is None or knowledge is False:
|
||||
if _knowledge_raw is None or _knowledge_raw is False:
|
||||
_knowledge_config = None
|
||||
elif knowledge is True:
|
||||
elif _knowledge_raw is True:
|
||||
_knowledge_config = KnowledgeConfig()
|
||||
elif isinstance(knowledge, KnowledgeConfig):
|
||||
_knowledge_config = knowledge
|
||||
elif isinstance(knowledge, list):
|
||||
_knowledge_config = KnowledgeConfig(sources=knowledge)
|
||||
elif hasattr(knowledge, 'search') and hasattr(knowledge, 'add'):
|
||||
_knowledge_config = knowledge # Knowledge instance passthrough
|
||||
elif isinstance(_knowledge_raw, KnowledgeConfig):
|
||||
_knowledge_config = _knowledge_raw
|
||||
elif isinstance(_knowledge_raw, list):
|
||||
_knowledge_config = KnowledgeConfig(sources=_knowledge_raw)
|
||||
elif hasattr(_knowledge_raw, 'search') and hasattr(_knowledge_raw, 'add'):
|
||||
_knowledge_config = _knowledge_raw # Knowledge instance passthrough
|
||||
else:
|
||||
_knowledge_config = resolve(
|
||||
value=knowledge,
|
||||
value=_knowledge_raw,
|
||||
param_name="knowledge",
|
||||
config_class=KnowledgeConfig,
|
||||
instance_check=lambda v: hasattr(v, 'search') and hasattr(v, 'add'),
|
||||
@@ -1149,10 +1156,12 @@ class Agent(ToolExecutionMixin, ChatHandlerMixin, SessionManagerMixin, ChatMixin
|
||||
default=None,
|
||||
)
|
||||
|
||||
# Use local variable for internal knowledge value
|
||||
_knowledge_value = _knowledge_raw
|
||||
if _knowledge_config is not None:
|
||||
if hasattr(_knowledge_config, 'search') and hasattr(_knowledge_config, 'add'):
|
||||
# Knowledge instance - pass through
|
||||
knowledge = _knowledge_config
|
||||
_knowledge_value = _knowledge_config
|
||||
elif isinstance(_knowledge_config, KnowledgeConfig):
|
||||
embedder_config = _knowledge_config.embedder_config
|
||||
if _knowledge_config.config:
|
||||
@@ -1164,25 +1173,28 @@ class Agent(ToolExecutionMixin, ChatHandlerMixin, SessionManagerMixin, ChatMixin
|
||||
'rerank': _knowledge_config.rerank,
|
||||
'rerank_model': _knowledge_config.rerank_model,
|
||||
}
|
||||
knowledge = _knowledge_config.sources if _knowledge_config.sources else None
|
||||
_knowledge_value = _knowledge_config.sources if _knowledge_config.sources else None
|
||||
elif isinstance(_knowledge_config, list):
|
||||
knowledge = _knowledge_config
|
||||
elif knowledge is False:
|
||||
knowledge = None
|
||||
_knowledge_value = _knowledge_config
|
||||
elif _knowledge_raw is False:
|
||||
_knowledge_value = None
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# Resolve PLANNING param - FAST PATH
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# Use raw param from JIT storage
|
||||
_planning_raw = self._raw_planning
|
||||
|
||||
# Fast path: None/False -> no planning (skip resolve() call)
|
||||
if planning is None or planning is False:
|
||||
if _planning_raw is None or _planning_raw is False:
|
||||
_planning_config = None
|
||||
elif planning is True:
|
||||
elif _planning_raw is True:
|
||||
_planning_config = PlanningConfig()
|
||||
elif isinstance(planning, PlanningConfig):
|
||||
_planning_config = planning
|
||||
elif isinstance(_planning_raw, PlanningConfig):
|
||||
_planning_config = _planning_raw
|
||||
else:
|
||||
_planning_config = resolve(
|
||||
value=planning,
|
||||
value=_planning_raw,
|
||||
param_name="planning",
|
||||
config_class=PlanningConfig,
|
||||
presets=PLANNING_PRESETS,
|
||||
@@ -1191,32 +1203,37 @@ class Agent(ToolExecutionMixin, ChatHandlerMixin, SessionManagerMixin, ChatMixin
|
||||
default=None,
|
||||
)
|
||||
|
||||
# Use local variable for internal planning value
|
||||
_planning_value = _planning_raw
|
||||
if _planning_config is not None:
|
||||
if isinstance(_planning_config, PlanningConfig):
|
||||
planning = True
|
||||
_planning_value = True
|
||||
planning_tools = _planning_config.tools
|
||||
planning_reasoning = _planning_config.reasoning
|
||||
plan_mode = _planning_config.read_only
|
||||
else:
|
||||
planning = bool(_planning_config)
|
||||
elif planning is True:
|
||||
_planning_value = bool(_planning_config)
|
||||
elif _planning_raw is True:
|
||||
pass # Keep as True
|
||||
else:
|
||||
planning = False
|
||||
_planning_value = False
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# Resolve REFLECTION param - FAST PATH
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# Use raw param from JIT storage
|
||||
_reflection_raw = self._raw_reflection
|
||||
|
||||
# Fast path: None/False -> no reflection (skip resolve() call)
|
||||
if reflection is None or reflection is False:
|
||||
if _reflection_raw is None or _reflection_raw is False:
|
||||
_reflection_config = None
|
||||
elif reflection is True:
|
||||
elif _reflection_raw is True:
|
||||
_reflection_config = ReflectionConfig()
|
||||
elif isinstance(reflection, ReflectionConfig):
|
||||
_reflection_config = reflection
|
||||
elif isinstance(_reflection_raw, ReflectionConfig):
|
||||
_reflection_config = _reflection_raw
|
||||
else:
|
||||
_reflection_config = resolve(
|
||||
value=reflection,
|
||||
value=_reflection_raw,
|
||||
param_name="reflection",
|
||||
config_class=ReflectionConfig,
|
||||
presets=REFLECTION_PRESETS,
|
||||
@@ -1233,9 +1250,9 @@ class Agent(ToolExecutionMixin, ChatHandlerMixin, SessionManagerMixin, ChatMixin
|
||||
reflect_prompt = _reflection_config.prompt
|
||||
else:
|
||||
self_reflect = bool(_reflection_config)
|
||||
elif reflection is True:
|
||||
elif _reflection_raw is True:
|
||||
self_reflect = True
|
||||
elif reflection is False:
|
||||
elif _reflection_raw is False:
|
||||
self_reflect = False
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
@@ -1272,16 +1289,19 @@ class Agent(ToolExecutionMixin, ChatHandlerMixin, SessionManagerMixin, ChatMixin
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# Resolve WEB param - FAST PATH
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# Use raw param from JIT storage
|
||||
_web_raw = self._raw_web
|
||||
|
||||
# Fast path: None/False -> no web (skip resolve() call)
|
||||
if web is None or web is False:
|
||||
if _web_raw is None or _web_raw is False:
|
||||
_web_config = None
|
||||
elif web is True:
|
||||
elif _web_raw is True:
|
||||
_web_config = WebConfig()
|
||||
elif isinstance(web, WebConfig):
|
||||
_web_config = web
|
||||
elif isinstance(_web_raw, WebConfig):
|
||||
_web_config = _web_raw
|
||||
else:
|
||||
_web_config = resolve(
|
||||
value=web,
|
||||
value=_web_raw,
|
||||
param_name="web",
|
||||
config_class=WebConfig,
|
||||
presets=WEB_PRESETS,
|
||||
@@ -1296,10 +1316,10 @@ class Agent(ToolExecutionMixin, ChatHandlerMixin, SessionManagerMixin, ChatMixin
|
||||
else:
|
||||
web_search = bool(_web_config)
|
||||
web_fetch = bool(_web_config)
|
||||
elif web is True:
|
||||
elif _web_raw is True:
|
||||
web_search = True
|
||||
web_fetch = True
|
||||
elif web is False:
|
||||
elif _web_raw is False:
|
||||
web_search = False
|
||||
web_fetch = False
|
||||
|
||||
@@ -1308,7 +1328,8 @@ class Agent(ToolExecutionMixin, ChatHandlerMixin, SessionManagerMixin, ChatMixin
|
||||
# ============================================================
|
||||
|
||||
# Initialize autonomy features (agent-centric escalation/doom-loop)
|
||||
self._init_autonomy(autonomy, verification_hooks=verification_hooks)
|
||||
# Use raw param from JIT storage
|
||||
self._init_autonomy(self._raw_autonomy, verification_hooks=verification_hooks)
|
||||
|
||||
# If instructions are provided, use them to set role, goal, and backstory
|
||||
if instructions:
|
||||
@@ -1515,7 +1536,7 @@ class Agent(ToolExecutionMixin, ChatHandlerMixin, SessionManagerMixin, ChatMixin
|
||||
self.max_rpm = max_rpm
|
||||
self.max_execution_time = max_execution_time
|
||||
self._memory_instance = None
|
||||
self._init_memory(memory, user_id)
|
||||
self._init_memory(_memory_value, user_id)
|
||||
self.verbose = verbose
|
||||
self._has_explicit_output_config = _has_explicit_output # Track if user set output mode
|
||||
self.allow_delegation = allow_delegation
|
||||
@@ -1535,7 +1556,7 @@ class Agent(ToolExecutionMixin, ChatHandlerMixin, SessionManagerMixin, ChatMixin
|
||||
self.max_retry_limit = max_retry_limit
|
||||
self.code_execution_mode = code_execution_mode
|
||||
self.embedder_config = embedder_config
|
||||
self.knowledge = knowledge
|
||||
self.knowledge = _knowledge_value
|
||||
self.use_system_prompt = use_system_prompt
|
||||
# Thread-safe chat_history with eager lock initialization
|
||||
self.chat_history = []
|
||||
@@ -1566,7 +1587,7 @@ Your Goal: {self.goal}
|
||||
self.user_id = user_id or "praison"
|
||||
self.reasoning_steps = reasoning_steps
|
||||
self.plan_mode = plan_mode # Read-only mode for planning
|
||||
self.planning = planning # Enable planning mode
|
||||
self.planning = _planning_value # Enable planning mode
|
||||
self.planning_tools = planning_tools # Tools for planning phase
|
||||
self.planning_reasoning = planning_reasoning # Enable reasoning during planning
|
||||
self._planning_agent = None # Lazy loaded PlanningAgent
|
||||
|
||||
@@ -30,6 +30,7 @@ import os
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
from dataclasses import dataclass, field
|
||||
from functools import lru_cache
|
||||
|
||||
|
||||
# ============================================================================
|
||||
@@ -331,6 +332,7 @@ def get_plugins_config() -> PluginsConfig:
|
||||
return get_config().plugins
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def get_defaults_config() -> DefaultsConfig:
|
||||
"""Get defaults configuration.
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "praisonaiagents"
|
||||
version = "1.5.111"
|
||||
version = "1.5.112"
|
||||
description = "Praison AI agents for completing complex tasks with Self Reflection Agents"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import time
|
||||
from praisonaiagents import Agent
|
||||
|
||||
t0 = time.time()
|
||||
agents = [Agent(name=f'Test_{i}', llm='gpt-4o-mini', output='silent') for i in range(100)]
|
||||
t1 = time.time()
|
||||
|
||||
print(f"Time for 100 agents: {t1 - t0:.4f} seconds")
|
||||
@@ -0,0 +1,18 @@
|
||||
import time
|
||||
from praisonaiagents import Agent as PraisonAgent
|
||||
|
||||
def measure_instantiation(create_fn, iterations=1000, warmup=50):
|
||||
for _ in range(warmup):
|
||||
create_fn()
|
||||
|
||||
times = []
|
||||
for _ in range(iterations):
|
||||
start = time.perf_counter()
|
||||
create_fn()
|
||||
times.append((time.perf_counter() - start) * 1_000_000)
|
||||
return sum(times) / len(times)
|
||||
|
||||
r1 = measure_instantiation(lambda: PraisonAgent(name='Test', llm='gpt-4o-mini', output="silent"))
|
||||
r2 = measure_instantiation(lambda: PraisonAgent(name='Test', llm='openai/gpt-4o-mini', output="silent"))
|
||||
print(f"PraisonAI: {r1:.2f} μs")
|
||||
print(f"PraisonAI (LiteLLM): {r2:.2f} μs")
|
||||
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Quick smoke test for JIT hydration changes."""
|
||||
import sys
|
||||
try:
|
||||
from praisonaiagents import Agent
|
||||
a = Agent(instructions='test')
|
||||
print('SUCCESS: Agent created')
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
print(f'ERROR: {e}')
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,11 @@
|
||||
from line_profiler import profile
|
||||
from praisonaiagents.agent.agent import Agent
|
||||
|
||||
Agent.__init__ = profile(Agent.__init__)
|
||||
|
||||
def run_init():
|
||||
for _ in range(100):
|
||||
# We also need to mock or ensure no external calls actually hit APIs
|
||||
Agent(name='Test', llm='gpt-4o-mini', output='silent')
|
||||
|
||||
run_init()
|
||||
Generated
+1
-1
@@ -2951,7 +2951,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "praisonaiagents"
|
||||
version = "1.5.111"
|
||||
version = "1.5.112"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
|
||||
@@ -3,8 +3,8 @@ class Praisonai < Formula
|
||||
|
||||
desc "AI tools for various AI applications"
|
||||
homepage "https://github.com/MervinPraison/PraisonAI"
|
||||
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v4.5.111.tar.gz"
|
||||
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v4.5.111.tar.gz | shasum -a 256`.split.first
|
||||
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v4.5.112.tar.gz"
|
||||
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v4.5.112.tar.gz | shasum -a 256`.split.first
|
||||
license "MIT"
|
||||
|
||||
depends_on "[email protected]"
|
||||
|
||||
@@ -57,7 +57,7 @@ class CloudDeployer:
|
||||
file.write("FROM python:3.11-slim\n")
|
||||
file.write("WORKDIR /app\n")
|
||||
file.write("COPY . .\n")
|
||||
file.write("RUN pip install flask praisonai==4.5.111 gunicorn markdown\n")
|
||||
file.write("RUN pip install flask praisonai==4.5.112 gunicorn markdown\n")
|
||||
file.write("EXPOSE 8080\n")
|
||||
file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
|
||||
|
||||
|
||||
@@ -78,7 +78,10 @@ class SandlockSandbox:
|
||||
@property
|
||||
def is_available(self) -> bool:
|
||||
"""Whether sandlock backend is available."""
|
||||
return self._sandlock.is_available()
|
||||
try:
|
||||
return self._sandlock.landlock_abi_version() >= 1
|
||||
except (AttributeError, ImportError):
|
||||
return False
|
||||
|
||||
@property
|
||||
def sandbox_type(self) -> str:
|
||||
@@ -155,8 +158,8 @@ class SandlockSandbox:
|
||||
max_processes=limits.max_processes,
|
||||
max_open_files=limits.max_open_files,
|
||||
|
||||
# CPU and timeout limits
|
||||
max_cpu_time_seconds=limits.timeout_seconds,
|
||||
# Note: CPU throttle percentage, not time limit
|
||||
# Execution timeout is handled via Sandbox.run(timeout=...)
|
||||
)
|
||||
|
||||
return policy
|
||||
@@ -192,13 +195,15 @@ class SandlockSandbox:
|
||||
|
||||
Centralises all sandlock Sandbox/Policy construction and error handling
|
||||
so that ``execute`` and ``run_command`` share a single code path.
|
||||
|
||||
Note: ``env`` is accepted for API compatibility but sandlock's
|
||||
``Sandbox.run()`` does not support per-run environment injection;
|
||||
callers requiring custom env vars should set them in the policy or
|
||||
prior to sandbox creation. ``working_dir`` is applied via the policy
|
||||
(added to the writable-path allow-list).
|
||||
"""
|
||||
policy = self._create_policy(limits, working_dir)
|
||||
|
||||
process_env = os.environ.copy()
|
||||
if env:
|
||||
process_env.update(env)
|
||||
|
||||
started_at = time.time()
|
||||
|
||||
try:
|
||||
@@ -206,10 +211,40 @@ class SandlockSandbox:
|
||||
|
||||
result = await asyncio.get_running_loop().run_in_executor(
|
||||
None,
|
||||
lambda: sandbox.run(cmd, env=process_env, cwd=working_dir)
|
||||
lambda: sandbox.run(cmd, timeout=limits.timeout_seconds)
|
||||
)
|
||||
|
||||
completed_at = time.time()
|
||||
duration = completed_at - started_at
|
||||
|
||||
# Check result.success and exit_code to determine actual status
|
||||
if not result.success:
|
||||
# Check if this was a timeout based on duration
|
||||
if duration >= limits.timeout_seconds:
|
||||
return SandboxResult(
|
||||
execution_id=execution_id,
|
||||
status=SandboxStatus.TIMEOUT,
|
||||
error=f"Execution timed out after {limits.timeout_seconds}s",
|
||||
exit_code=result.exit_code,
|
||||
stdout=result.stdout,
|
||||
stderr=result.stderr,
|
||||
duration_seconds=duration,
|
||||
started_at=started_at,
|
||||
completed_at=completed_at,
|
||||
)
|
||||
else:
|
||||
# Non-zero exit or other failure
|
||||
return SandboxResult(
|
||||
execution_id=execution_id,
|
||||
status=SandboxStatus.FAILED,
|
||||
error=f"Execution failed with exit code {result.exit_code}: {result.stderr}",
|
||||
exit_code=result.exit_code,
|
||||
stdout=result.stdout,
|
||||
stderr=result.stderr,
|
||||
duration_seconds=duration,
|
||||
started_at=started_at,
|
||||
completed_at=completed_at,
|
||||
)
|
||||
|
||||
return SandboxResult(
|
||||
execution_id=execution_id,
|
||||
@@ -217,7 +252,7 @@ class SandlockSandbox:
|
||||
exit_code=result.exit_code,
|
||||
stdout=result.stdout,
|
||||
stderr=result.stderr,
|
||||
duration_seconds=completed_at - started_at,
|
||||
duration_seconds=duration,
|
||||
started_at=started_at,
|
||||
completed_at=completed_at,
|
||||
metadata={
|
||||
@@ -227,32 +262,25 @@ class SandlockSandbox:
|
||||
},
|
||||
)
|
||||
|
||||
except self._sandlock.TimeoutError:
|
||||
return SandboxResult(
|
||||
execution_id=execution_id,
|
||||
status=SandboxStatus.TIMEOUT,
|
||||
error=f"Execution timed out after {limits.timeout_seconds}s",
|
||||
duration_seconds=limits.timeout_seconds,
|
||||
started_at=started_at,
|
||||
completed_at=time.time(),
|
||||
)
|
||||
except self._sandlock.SecurityViolationError as e:
|
||||
return SandboxResult(
|
||||
execution_id=execution_id,
|
||||
status=SandboxStatus.FAILED,
|
||||
error=f"Security violation: {e}",
|
||||
duration_seconds=time.time() - started_at,
|
||||
started_at=started_at,
|
||||
completed_at=time.time(),
|
||||
)
|
||||
except Exception as e:
|
||||
completed_at = time.time()
|
||||
duration = completed_at - started_at
|
||||
if duration >= limits.timeout_seconds:
|
||||
return SandboxResult(
|
||||
execution_id=execution_id,
|
||||
status=SandboxStatus.TIMEOUT,
|
||||
error=f"Execution timed out after {limits.timeout_seconds}s",
|
||||
duration_seconds=duration,
|
||||
started_at=started_at,
|
||||
completed_at=completed_at,
|
||||
)
|
||||
return SandboxResult(
|
||||
execution_id=execution_id,
|
||||
status=SandboxStatus.FAILED,
|
||||
error=str(e),
|
||||
duration_seconds=time.time() - started_at,
|
||||
error=f"Execution failed: {e}",
|
||||
duration_seconds=duration,
|
||||
started_at=started_at,
|
||||
completed_at=time.time(),
|
||||
completed_at=completed_at,
|
||||
)
|
||||
|
||||
async def execute(
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "4.5.111"
|
||||
__version__ = "4.5.112"
|
||||
@@ -12,7 +12,7 @@ dependencies = [
|
||||
"rich>=13.7",
|
||||
"markdown>=3.5",
|
||||
"pyparsing>=3.0.0",
|
||||
"praisonaiagents>=1.5.111",
|
||||
"praisonaiagents>=1.5.112",
|
||||
"python-dotenv>=0.19.0",
|
||||
"litellm>=1.81.0,<=1.82.6",
|
||||
"PyYAML>=6.0",
|
||||
|
||||
@@ -45,7 +45,7 @@ class TestSandlockSandbox:
|
||||
def test_fallback_to_subprocess_when_unavailable(self):
|
||||
"""Test fallback to subprocess when sandlock is not available."""
|
||||
mock_sandlock = Mock()
|
||||
mock_sandlock.is_available.return_value = False
|
||||
mock_sandlock.landlock_abi_version.return_value = 0 # < 1, so unavailable
|
||||
|
||||
sandbox = _make_sandbox(mock_sandlock)
|
||||
assert not sandbox.is_available
|
||||
@@ -55,7 +55,7 @@ class TestSandlockSandbox:
|
||||
async def test_fallback_execution(self):
|
||||
"""Test that execution falls back to subprocess when sandlock unavailable."""
|
||||
mock_sandlock = Mock()
|
||||
mock_sandlock.is_available.return_value = False
|
||||
mock_sandlock.landlock_abi_version.return_value = 0 # < 1, so unavailable
|
||||
|
||||
sandbox = _make_sandbox(mock_sandlock)
|
||||
|
||||
@@ -98,7 +98,7 @@ class TestSandlockSandbox:
|
||||
def test_status_reporting(self):
|
||||
"""Test sandbox status reporting."""
|
||||
mock_sandlock = Mock()
|
||||
mock_sandlock.is_available.return_value = True
|
||||
mock_sandlock.landlock_abi_version.return_value = 6 # >= 6, so available
|
||||
|
||||
sandbox = _make_sandbox(mock_sandlock)
|
||||
status = sandbox.get_status()
|
||||
@@ -122,7 +122,7 @@ class TestSandlockSandbox:
|
||||
|
||||
mock_sandlock.Policy.return_value = Mock()
|
||||
mock_sandlock.Sandbox.return_value = Mock(run=Mock(return_value=mock_result))
|
||||
mock_sandlock.is_available.return_value = True
|
||||
mock_sandlock.landlock_abi_version.return_value = 6 # >= 6, so available
|
||||
|
||||
sandbox = _make_sandbox(mock_sandlock)
|
||||
|
||||
@@ -142,65 +142,68 @@ class TestSandlockSandbox:
|
||||
async def test_sandlock_execution_timeout(self):
|
||||
"""Test timeout handling in sandlock execution."""
|
||||
mock_sandlock = Mock()
|
||||
|
||||
class FakeTimeoutError(Exception):
|
||||
pass
|
||||
|
||||
mock_sandlock.TimeoutError = FakeTimeoutError
|
||||
mock_sandlock.SecurityViolationError = Exception
|
||||
mock_sandlock.Policy.return_value = Mock()
|
||||
mock_sandlock.Sandbox.return_value = Mock()
|
||||
mock_sandlock.is_available.return_value = True
|
||||
mock_sandlock.landlock_abi_version.return_value = 6 # >= 6, so available
|
||||
|
||||
sandbox = _make_sandbox(mock_sandlock)
|
||||
|
||||
with patch("asyncio.get_running_loop") as mock_loop:
|
||||
# Create a mock Result object indicating timeout
|
||||
mock_timeout_result = Mock()
|
||||
mock_timeout_result.success = False
|
||||
mock_timeout_result.exit_code = 124 # Common timeout exit code
|
||||
mock_timeout_result.stdout = ""
|
||||
mock_timeout_result.stderr = "Process timed out"
|
||||
|
||||
# Simulate timeout by returning failed Result after enough time
|
||||
with patch("asyncio.get_running_loop") as mock_loop, \
|
||||
patch("time.time", side_effect=[0, 11, 11]): # Started at 0, ended at 11s (> 10s timeout)
|
||||
mock_loop.return_value.run_in_executor = AsyncMock(
|
||||
side_effect=FakeTimeoutError()
|
||||
return_value=mock_timeout_result
|
||||
)
|
||||
|
||||
await sandbox.start()
|
||||
result = await sandbox.execute("import time; time.sleep(100)")
|
||||
result = await sandbox.execute("import time; time.sleep(100)", limits=ResourceLimits(timeout_seconds=10))
|
||||
|
||||
assert result.status == SandboxStatus.TIMEOUT
|
||||
assert "timed out" in result.error.lower()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_sandlock_security_violation(self):
|
||||
"""Test security violation handling."""
|
||||
async def test_sandlock_execution_failure(self):
|
||||
"""Test general execution failure handling."""
|
||||
mock_sandlock = Mock()
|
||||
|
||||
class FakeTimeoutError(Exception):
|
||||
pass
|
||||
|
||||
class FakeSecurityViolationError(Exception):
|
||||
pass
|
||||
|
||||
mock_sandlock.SecurityViolationError = FakeSecurityViolationError
|
||||
mock_sandlock.TimeoutError = FakeTimeoutError
|
||||
mock_sandlock.Policy.return_value = Mock()
|
||||
mock_sandlock.Sandbox.return_value = Mock()
|
||||
mock_sandlock.is_available.return_value = True
|
||||
mock_sandlock.landlock_abi_version.return_value = 6 # >= 6, so available
|
||||
|
||||
sandbox = _make_sandbox(mock_sandlock)
|
||||
|
||||
with patch("asyncio.get_running_loop") as mock_loop:
|
||||
# Create a mock Result object indicating non-zero exit
|
||||
mock_failed_result = Mock()
|
||||
mock_failed_result.success = False
|
||||
mock_failed_result.exit_code = 1 # Non-zero exit code
|
||||
mock_failed_result.stdout = ""
|
||||
mock_failed_result.stderr = "Permission denied"
|
||||
|
||||
# Simulate execution failure (not timeout)
|
||||
with patch("asyncio.get_running_loop") as mock_loop, \
|
||||
patch("time.time", side_effect=[0, 2, 2]): # Started at 0, ended at 2s (< 10s timeout)
|
||||
mock_loop.return_value.run_in_executor = AsyncMock(
|
||||
side_effect=FakeSecurityViolationError("Access denied")
|
||||
return_value=mock_failed_result
|
||||
)
|
||||
|
||||
await sandbox.start()
|
||||
result = await sandbox.execute("import os; os.system('rm -rf /')")
|
||||
|
||||
assert result.status == SandboxStatus.FAILED
|
||||
assert "Security violation" in result.error
|
||||
assert "Access denied" in result.error
|
||||
assert "exit code 1" in result.error
|
||||
assert "Permission denied" in result.error
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_safe_sandbox_path_traversal_blocked(self):
|
||||
"""Test that path traversal attempts are blocked by _safe_sandbox_path."""
|
||||
mock_sandlock = Mock()
|
||||
mock_sandlock.is_available.return_value = True
|
||||
mock_sandlock.landlock_abi_version.return_value = 6 # >= 6, so available
|
||||
|
||||
sandbox = _make_sandbox(mock_sandlock)
|
||||
await sandbox.start()
|
||||
@@ -220,7 +223,7 @@ class TestSandlockSandbox:
|
||||
async def test_write_file_blocks_traversal(self):
|
||||
"""Test that write_file refuses paths that escape the sandbox root."""
|
||||
mock_sandlock = Mock()
|
||||
mock_sandlock.is_available.return_value = True
|
||||
mock_sandlock.landlock_abi_version.return_value = 6 # >= 6, so available
|
||||
|
||||
sandbox = _make_sandbox(mock_sandlock)
|
||||
await sandbox.start()
|
||||
@@ -229,3 +232,42 @@ class TestSandlockSandbox:
|
||||
assert success is False
|
||||
|
||||
await sandbox.stop()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_real_sandlock_integration(self):
|
||||
"""Integration test with real sandlock package if available.
|
||||
|
||||
This test uses the actual sandlock package (if available) to ensure
|
||||
the integration works with the real API surface, not just mocks.
|
||||
"""
|
||||
try:
|
||||
# Try to import real sandlock
|
||||
import sandlock
|
||||
|
||||
# Only run if Landlock is actually supported on this system
|
||||
if sandlock.landlock_abi_version() < 1:
|
||||
pytest.skip("Landlock not supported on this system")
|
||||
|
||||
# Test with real sandlock package
|
||||
from praisonai.sandbox.sandlock import SandlockSandbox
|
||||
|
||||
sandbox = SandlockSandbox()
|
||||
assert sandbox.is_available
|
||||
|
||||
await sandbox.start()
|
||||
|
||||
# Execute simple code that should succeed
|
||||
result = await sandbox.execute(
|
||||
"print('Real sandlock integration test')",
|
||||
limits=ResourceLimits(timeout_seconds=5, memory_mb=64)
|
||||
)
|
||||
|
||||
# Should complete successfully
|
||||
assert result.status == SandboxStatus.COMPLETED
|
||||
assert result.exit_code == 0
|
||||
assert "Real sandlock integration test" in result.stdout
|
||||
|
||||
await sandbox.stop()
|
||||
|
||||
except ImportError:
|
||||
pytest.skip("sandlock package not available for integration test")
|
||||
Generated
+1
-1
@@ -5090,7 +5090,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "praisonaiagents"
|
||||
version = "1.5.111"
|
||||
version = "1.5.112"
|
||||
source = { directory = "../praisonai-agents" }
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
|
||||
Reference in new issue
Block a user