praval 0.7.6__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.
- praval/__init__.py +119 -0
- praval/composition.py +225 -0
- praval/core/__init__.py +1 -0
- praval/core/agent.py +621 -0
- praval/core/exceptions.py +31 -0
- praval/core/reef.py +568 -0
- praval/core/registry.py +86 -0
- praval/core/secure_reef.py +470 -0
- praval/core/secure_spore.py +373 -0
- praval/core/storage.py +115 -0
- praval/core/tool_registry.py +437 -0
- praval/core/transport.py +548 -0
- praval/decorators.py +345 -0
- praval/memory/__init__.py +29 -0
- praval/memory/embedded_store.py +796 -0
- praval/memory/episodic_memory.py +326 -0
- praval/memory/long_term_memory.py +391 -0
- praval/memory/memory_manager.py +525 -0
- praval/memory/memory_types.py +115 -0
- praval/memory/semantic_memory.py +431 -0
- praval/memory/short_term_memory.py +295 -0
- praval/providers/__init__.py +1 -0
- praval/providers/anthropic.py +129 -0
- praval/providers/cohere.py +160 -0
- praval/providers/factory.py +45 -0
- praval/providers/openai.py +251 -0
- praval/storage/__init__.py +56 -0
- praval/storage/base_provider.py +399 -0
- praval/storage/data_manager.py +496 -0
- praval/storage/decorators.py +321 -0
- praval/storage/exceptions.py +79 -0
- praval/storage/memory_integration.py +422 -0
- praval/storage/providers/__init__.py +20 -0
- praval/storage/providers/filesystem.py +497 -0
- praval/storage/providers/postgresql.py +428 -0
- praval/storage/providers/qdrant_provider.py +549 -0
- praval/storage/providers/redis_provider.py +483 -0
- praval/storage/providers/s3_provider.py +518 -0
- praval/storage/storage_registry.py +504 -0
- praval/tools.py +344 -0
- praval-0.7.6.dist-info/METADATA +1063 -0
- praval-0.7.6.dist-info/RECORD +45 -0
- praval-0.7.6.dist-info/WHEEL +5 -0
- praval-0.7.6.dist-info/licenses/LICENSE +21 -0
- praval-0.7.6.dist-info/top_level.txt +1 -0
praval/__init__.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Praval: A composable Python framework for LLM-based agents.
|
|
3
|
+
|
|
4
|
+
Inspired by coral ecosystems where simple organisms create complex structures
|
|
5
|
+
through collaboration, Praval enables simple agents to work together for
|
|
6
|
+
sophisticated behaviors.
|
|
7
|
+
|
|
8
|
+
Version 0.7.6 includes comprehensive tool system with @tool decorator,
|
|
9
|
+
PDF support for knowledge base, Unified Data Storage & Retrieval System with support for
|
|
10
|
+
PostgreSQL, Redis, S3, Qdrant, and filesystem storage providers, plus enhanced
|
|
11
|
+
Secure Spores Enterprise Edition and comprehensive memory system integration.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from .core.agent import Agent
|
|
15
|
+
from .core.registry import register_agent, get_registry
|
|
16
|
+
from .core.reef import get_reef, Spore, SporeType
|
|
17
|
+
from .decorators import chat, achat, broadcast, get_agent_info
|
|
18
|
+
from .composition import (
|
|
19
|
+
agent_pipeline, conditional_agent, throttled_agent,
|
|
20
|
+
AgentSession, start_agents
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# Enhanced agent decorator with memory support (v0.3.0)
|
|
24
|
+
from .decorators import agent, chat, achat, broadcast, get_agent_info
|
|
25
|
+
|
|
26
|
+
# Tool system imports (v0.7.2+)
|
|
27
|
+
try:
|
|
28
|
+
from .tools import (
|
|
29
|
+
tool, get_tool_info, is_tool, discover_tools, list_tools,
|
|
30
|
+
register_tool_with_agent, unregister_tool_from_agent, ToolCollection
|
|
31
|
+
)
|
|
32
|
+
from .core.tool_registry import ToolRegistry, Tool, ToolMetadata, get_tool_registry, reset_tool_registry
|
|
33
|
+
TOOLS_AVAILABLE = True
|
|
34
|
+
except ImportError:
|
|
35
|
+
TOOLS_AVAILABLE = False
|
|
36
|
+
tool = None
|
|
37
|
+
get_tool_info = None
|
|
38
|
+
is_tool = None
|
|
39
|
+
discover_tools = None
|
|
40
|
+
list_tools = None
|
|
41
|
+
register_tool_with_agent = None
|
|
42
|
+
unregister_tool_from_agent = None
|
|
43
|
+
ToolCollection = None
|
|
44
|
+
ToolRegistry = None
|
|
45
|
+
Tool = None
|
|
46
|
+
ToolMetadata = None
|
|
47
|
+
get_tool_registry = None
|
|
48
|
+
reset_tool_registry = None
|
|
49
|
+
|
|
50
|
+
# Memory system imports (optional - graceful fallback if dependencies missing)
|
|
51
|
+
try:
|
|
52
|
+
from .memory import MemoryManager, MemoryType, MemoryEntry, MemoryQuery
|
|
53
|
+
MEMORY_AVAILABLE = True
|
|
54
|
+
except ImportError:
|
|
55
|
+
MEMORY_AVAILABLE = False
|
|
56
|
+
MemoryManager = None
|
|
57
|
+
MemoryType = None
|
|
58
|
+
MemoryEntry = None
|
|
59
|
+
MemoryQuery = None
|
|
60
|
+
|
|
61
|
+
# Storage system imports (optional - graceful fallback if dependencies missing)
|
|
62
|
+
try:
|
|
63
|
+
from .storage import (
|
|
64
|
+
BaseStorageProvider, StorageRegistry, DataManager,
|
|
65
|
+
storage_enabled, requires_storage,
|
|
66
|
+
get_storage_registry, get_data_manager,
|
|
67
|
+
PostgreSQLProvider, RedisProvider, S3Provider,
|
|
68
|
+
FileSystemProvider, QdrantProvider,
|
|
69
|
+
DataReference, StorageResult, StorageType
|
|
70
|
+
)
|
|
71
|
+
STORAGE_AVAILABLE = True
|
|
72
|
+
except ImportError:
|
|
73
|
+
STORAGE_AVAILABLE = False
|
|
74
|
+
BaseStorageProvider = None
|
|
75
|
+
StorageRegistry = None
|
|
76
|
+
DataManager = None
|
|
77
|
+
storage_enabled = None
|
|
78
|
+
requires_storage = None
|
|
79
|
+
get_storage_registry = None
|
|
80
|
+
get_data_manager = None
|
|
81
|
+
PostgreSQLProvider = None
|
|
82
|
+
RedisProvider = None
|
|
83
|
+
S3Provider = None
|
|
84
|
+
FileSystemProvider = None
|
|
85
|
+
QdrantProvider = None
|
|
86
|
+
DataReference = None
|
|
87
|
+
StorageResult = None
|
|
88
|
+
StorageType = None
|
|
89
|
+
|
|
90
|
+
__version__ = "0.7.6"
|
|
91
|
+
__all__ = [
|
|
92
|
+
# Core classes
|
|
93
|
+
"Agent", "register_agent", "get_registry", "get_reef", "Spore", "SporeType",
|
|
94
|
+
|
|
95
|
+
# Enhanced decorator (now with memory support)
|
|
96
|
+
"agent",
|
|
97
|
+
|
|
98
|
+
# Communication and composition
|
|
99
|
+
"chat", "achat", "broadcast", "get_agent_info",
|
|
100
|
+
"agent_pipeline", "conditional_agent", "throttled_agent",
|
|
101
|
+
"AgentSession", "start_agents",
|
|
102
|
+
|
|
103
|
+
# Tool system (if available)
|
|
104
|
+
"tool", "get_tool_info", "is_tool", "discover_tools", "list_tools",
|
|
105
|
+
"register_tool_with_agent", "unregister_tool_from_agent", "ToolCollection",
|
|
106
|
+
"ToolRegistry", "Tool", "ToolMetadata", "get_tool_registry", "reset_tool_registry",
|
|
107
|
+
"TOOLS_AVAILABLE",
|
|
108
|
+
|
|
109
|
+
# Memory system (if available)
|
|
110
|
+
"MemoryManager", "MemoryType", "MemoryEntry", "MemoryQuery", "MEMORY_AVAILABLE",
|
|
111
|
+
|
|
112
|
+
# Storage system (if available)
|
|
113
|
+
"BaseStorageProvider", "StorageRegistry", "DataManager",
|
|
114
|
+
"storage_enabled", "requires_storage",
|
|
115
|
+
"get_storage_registry", "get_data_manager",
|
|
116
|
+
"PostgreSQLProvider", "RedisProvider", "S3Provider",
|
|
117
|
+
"FileSystemProvider", "QdrantProvider",
|
|
118
|
+
"DataReference", "StorageResult", "StorageType", "STORAGE_AVAILABLE"
|
|
119
|
+
]
|
praval/composition.py
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Composition utilities for decorator-based agents.
|
|
3
|
+
|
|
4
|
+
This module provides utilities for composing and orchestrating agents
|
|
5
|
+
decorated with the @agent decorator.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Callable, List, Dict, Any, Optional
|
|
9
|
+
import time
|
|
10
|
+
import threading
|
|
11
|
+
|
|
12
|
+
from .core.reef import get_reef
|
|
13
|
+
from .decorators import get_agent_info
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def agent_pipeline(*agents: Callable, channel: str = "pipeline") -> Callable:
|
|
17
|
+
"""
|
|
18
|
+
Compose agents into a pipeline that processes data sequentially.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
*agents: Functions decorated with @agent
|
|
22
|
+
channel: Channel name for pipeline communication
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Function that triggers the pipeline with initial data
|
|
26
|
+
|
|
27
|
+
Example:
|
|
28
|
+
pipeline = agent_pipeline(explorer, analyzer, reporter)
|
|
29
|
+
pipeline({"task": "analyze sentiment"})
|
|
30
|
+
"""
|
|
31
|
+
# Validate all functions are agents
|
|
32
|
+
for agent_func in agents:
|
|
33
|
+
if not hasattr(agent_func, '_praval_agent'):
|
|
34
|
+
raise ValueError(f"Function {agent_func.__name__} is not decorated with @agent")
|
|
35
|
+
|
|
36
|
+
def pipeline_trigger(initial_data: Dict[str, Any]) -> str:
|
|
37
|
+
"""Trigger the pipeline with initial data."""
|
|
38
|
+
# Subscribe all agents to pipeline channel
|
|
39
|
+
for agent_func in agents:
|
|
40
|
+
agent_info = get_agent_info(agent_func)
|
|
41
|
+
agent_info["underlying_agent"].subscribe_to_channel(channel)
|
|
42
|
+
|
|
43
|
+
# Broadcast initial data to start pipeline
|
|
44
|
+
reef = get_reef()
|
|
45
|
+
reef.create_channel(channel) # Ensure channel exists
|
|
46
|
+
return reef.system_broadcast(initial_data, channel)
|
|
47
|
+
|
|
48
|
+
# Store metadata
|
|
49
|
+
pipeline_trigger._praval_pipeline = True
|
|
50
|
+
pipeline_trigger._praval_agents = agents
|
|
51
|
+
pipeline_trigger._praval_channel = channel
|
|
52
|
+
|
|
53
|
+
return pipeline_trigger
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def conditional_agent(condition_func: Callable[[Any], bool]):
|
|
57
|
+
"""
|
|
58
|
+
Decorator for conditional agent execution.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
condition_func: Function that takes a spore and returns bool
|
|
62
|
+
|
|
63
|
+
Example:
|
|
64
|
+
@conditional_agent(lambda spore: spore.knowledge.get("priority") == "high")
|
|
65
|
+
@agent("urgent_processor")
|
|
66
|
+
def process_urgent(spore):
|
|
67
|
+
return {"processed": True}
|
|
68
|
+
"""
|
|
69
|
+
def decorator(agent_func: Callable) -> Callable:
|
|
70
|
+
if not hasattr(agent_func, '_praval_agent'):
|
|
71
|
+
raise ValueError("conditional_agent must be applied to @agent decorated functions")
|
|
72
|
+
|
|
73
|
+
# Get the original handler
|
|
74
|
+
underlying_agent = agent_func._praval_agent
|
|
75
|
+
original_handler = underlying_agent._custom_spore_handler
|
|
76
|
+
|
|
77
|
+
def conditional_handler(spore):
|
|
78
|
+
"""Only execute if condition is met."""
|
|
79
|
+
if condition_func(spore):
|
|
80
|
+
return original_handler(spore)
|
|
81
|
+
|
|
82
|
+
# Replace handler with conditional version
|
|
83
|
+
underlying_agent.set_spore_handler(conditional_handler)
|
|
84
|
+
|
|
85
|
+
return agent_func
|
|
86
|
+
|
|
87
|
+
return decorator
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def throttled_agent(delay_seconds: float):
|
|
91
|
+
"""
|
|
92
|
+
Decorator to throttle agent execution.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
delay_seconds: Minimum seconds between executions
|
|
96
|
+
|
|
97
|
+
Example:
|
|
98
|
+
@throttled_agent(2.0) # Max once every 2 seconds
|
|
99
|
+
@agent("slow_processor")
|
|
100
|
+
def process_slowly(spore):
|
|
101
|
+
return {"processed": True}
|
|
102
|
+
"""
|
|
103
|
+
def decorator(agent_func: Callable) -> Callable:
|
|
104
|
+
if not hasattr(agent_func, '_praval_agent'):
|
|
105
|
+
raise ValueError("throttled_agent must be applied to @agent decorated functions")
|
|
106
|
+
|
|
107
|
+
last_execution = {"time": 0}
|
|
108
|
+
lock = threading.Lock()
|
|
109
|
+
|
|
110
|
+
# Get the original handler
|
|
111
|
+
underlying_agent = agent_func._praval_agent
|
|
112
|
+
original_handler = underlying_agent._custom_spore_handler
|
|
113
|
+
|
|
114
|
+
def throttled_handler(spore):
|
|
115
|
+
"""Only execute if enough time has passed."""
|
|
116
|
+
with lock:
|
|
117
|
+
now = time.time()
|
|
118
|
+
if now - last_execution["time"] >= delay_seconds:
|
|
119
|
+
last_execution["time"] = now
|
|
120
|
+
return original_handler(spore)
|
|
121
|
+
|
|
122
|
+
# Replace handler with throttled version
|
|
123
|
+
underlying_agent.set_spore_handler(throttled_handler)
|
|
124
|
+
|
|
125
|
+
return agent_func
|
|
126
|
+
|
|
127
|
+
return decorator
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class AgentSession:
|
|
131
|
+
"""
|
|
132
|
+
Context manager for coordinated agent sessions.
|
|
133
|
+
|
|
134
|
+
Example:
|
|
135
|
+
with AgentSession("knowledge_mining") as session:
|
|
136
|
+
session.add_agents(explorer, analyzer, curator)
|
|
137
|
+
session.broadcast({"task": "mine concepts about AI"})
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
def __init__(self, session_name: str):
|
|
141
|
+
self.session_name = session_name
|
|
142
|
+
self.channel_name = f"session_{session_name}"
|
|
143
|
+
self.agents = []
|
|
144
|
+
|
|
145
|
+
def __enter__(self):
|
|
146
|
+
# Create session channel
|
|
147
|
+
reef = get_reef()
|
|
148
|
+
reef.create_channel(self.channel_name)
|
|
149
|
+
return self
|
|
150
|
+
|
|
151
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
152
|
+
# Cleanup if needed (agents will remain subscribed)
|
|
153
|
+
pass
|
|
154
|
+
|
|
155
|
+
def add_agent(self, agent_func: Callable) -> 'AgentSession':
|
|
156
|
+
"""Add an agent to this session."""
|
|
157
|
+
if not hasattr(agent_func, '_praval_agent'):
|
|
158
|
+
raise ValueError(f"Function {agent_func.__name__} is not decorated with @agent")
|
|
159
|
+
|
|
160
|
+
agent_info = get_agent_info(agent_func)
|
|
161
|
+
underlying_agent = agent_info["underlying_agent"]
|
|
162
|
+
|
|
163
|
+
# Subscribe agent to session channel
|
|
164
|
+
underlying_agent.subscribe_to_channel(self.channel_name)
|
|
165
|
+
self.agents.append(agent_func)
|
|
166
|
+
|
|
167
|
+
return self
|
|
168
|
+
|
|
169
|
+
def add_agents(self, *agent_funcs: Callable) -> 'AgentSession':
|
|
170
|
+
"""Add multiple agents to this session."""
|
|
171
|
+
for agent_func in agent_funcs:
|
|
172
|
+
self.add_agent(agent_func)
|
|
173
|
+
return self
|
|
174
|
+
|
|
175
|
+
def broadcast(self, data: Dict[str, Any]) -> str:
|
|
176
|
+
"""Broadcast data to all agents in this session."""
|
|
177
|
+
reef = get_reef()
|
|
178
|
+
return reef.system_broadcast(
|
|
179
|
+
{**data, "_session": self.session_name},
|
|
180
|
+
self.channel_name
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
184
|
+
"""Get session statistics."""
|
|
185
|
+
return {
|
|
186
|
+
"session_name": self.session_name,
|
|
187
|
+
"channel": self.channel_name,
|
|
188
|
+
"agent_count": len(self.agents),
|
|
189
|
+
"agent_names": [get_agent_info(agent)["name"] for agent in self.agents]
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def start_agents(*agent_funcs: Callable, initial_data: Optional[Dict[str, Any]] = None,
|
|
194
|
+
channel: str = "startup") -> str:
|
|
195
|
+
"""
|
|
196
|
+
Convenience function to start multiple agents with initial data.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
*agent_funcs: Functions decorated with @agent
|
|
200
|
+
initial_data: Initial data to broadcast (optional)
|
|
201
|
+
channel: Channel to use for startup communication
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
Spore ID of startup broadcast
|
|
205
|
+
|
|
206
|
+
Example:
|
|
207
|
+
start_agents(explorer, analyzer, curator,
|
|
208
|
+
initial_data={"task": "analyze market trends"})
|
|
209
|
+
"""
|
|
210
|
+
# Subscribe all agents to startup channel
|
|
211
|
+
reef = get_reef()
|
|
212
|
+
reef.create_channel(channel)
|
|
213
|
+
|
|
214
|
+
for agent_func in agent_funcs:
|
|
215
|
+
if not hasattr(agent_func, '_praval_agent'):
|
|
216
|
+
raise ValueError(f"Function {agent_func.__name__} is not decorated with @agent")
|
|
217
|
+
|
|
218
|
+
agent_info = get_agent_info(agent_func)
|
|
219
|
+
agent_info["underlying_agent"].subscribe_to_channel(channel)
|
|
220
|
+
|
|
221
|
+
# Broadcast initial data if provided
|
|
222
|
+
if initial_data:
|
|
223
|
+
return reef.system_broadcast(initial_data, channel)
|
|
224
|
+
else:
|
|
225
|
+
return reef.system_broadcast({"type": "agents_started"}, channel)
|
praval/core/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Core components for Praval framework."""
|