jarviscore-framework 0.1.0__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.
- examples/calculator_agent_example.py +77 -0
- examples/multi_agent_workflow.py +132 -0
- examples/research_agent_example.py +76 -0
- jarviscore/__init__.py +54 -0
- jarviscore/cli/__init__.py +7 -0
- jarviscore/cli/__main__.py +33 -0
- jarviscore/cli/check.py +404 -0
- jarviscore/cli/smoketest.py +371 -0
- jarviscore/config/__init__.py +7 -0
- jarviscore/config/settings.py +128 -0
- jarviscore/core/__init__.py +7 -0
- jarviscore/core/agent.py +163 -0
- jarviscore/core/mesh.py +463 -0
- jarviscore/core/profile.py +64 -0
- jarviscore/docs/API_REFERENCE.md +932 -0
- jarviscore/docs/CONFIGURATION.md +753 -0
- jarviscore/docs/GETTING_STARTED.md +600 -0
- jarviscore/docs/TROUBLESHOOTING.md +424 -0
- jarviscore/docs/USER_GUIDE.md +983 -0
- jarviscore/execution/__init__.py +94 -0
- jarviscore/execution/code_registry.py +298 -0
- jarviscore/execution/generator.py +268 -0
- jarviscore/execution/llm.py +430 -0
- jarviscore/execution/repair.py +283 -0
- jarviscore/execution/result_handler.py +332 -0
- jarviscore/execution/sandbox.py +555 -0
- jarviscore/execution/search.py +281 -0
- jarviscore/orchestration/__init__.py +18 -0
- jarviscore/orchestration/claimer.py +101 -0
- jarviscore/orchestration/dependency.py +143 -0
- jarviscore/orchestration/engine.py +292 -0
- jarviscore/orchestration/status.py +96 -0
- jarviscore/p2p/__init__.py +23 -0
- jarviscore/p2p/broadcaster.py +353 -0
- jarviscore/p2p/coordinator.py +364 -0
- jarviscore/p2p/keepalive.py +361 -0
- jarviscore/p2p/swim_manager.py +290 -0
- jarviscore/profiles/__init__.py +6 -0
- jarviscore/profiles/autoagent.py +264 -0
- jarviscore/profiles/customagent.py +137 -0
- jarviscore_framework-0.1.0.dist-info/METADATA +136 -0
- jarviscore_framework-0.1.0.dist-info/RECORD +55 -0
- jarviscore_framework-0.1.0.dist-info/WHEEL +5 -0
- jarviscore_framework-0.1.0.dist-info/licenses/LICENSE +21 -0
- jarviscore_framework-0.1.0.dist-info/top_level.txt +3 -0
- tests/conftest.py +44 -0
- tests/test_agent.py +165 -0
- tests/test_autoagent.py +140 -0
- tests/test_autoagent_day4.py +186 -0
- tests/test_customagent.py +248 -0
- tests/test_integration.py +293 -0
- tests/test_llm_fallback.py +185 -0
- tests/test_mesh.py +356 -0
- tests/test_p2p_integration.py +375 -0
- tests/test_remote_sandbox.py +116 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Execution Engine - Complete code generation and execution pipeline
|
|
3
|
+
|
|
4
|
+
Zero-config components:
|
|
5
|
+
- UnifiedLLMClient: Multi-provider LLM (vLLM, Azure, Gemini, Claude)
|
|
6
|
+
- InternetSearch: Web search and content extraction (DuckDuckGo)
|
|
7
|
+
- CodeGenerator: Natural language → Python code
|
|
8
|
+
- SandboxExecutor: Safe code execution with limits
|
|
9
|
+
- AutonomousRepair: Automatic error fixing
|
|
10
|
+
|
|
11
|
+
Everything works out of the box - just pass config dict.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
# LLM Client
|
|
15
|
+
from .llm import (
|
|
16
|
+
UnifiedLLMClient,
|
|
17
|
+
LLMProvider,
|
|
18
|
+
TOKEN_PRICING,
|
|
19
|
+
create_llm_client
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# Internet Search
|
|
23
|
+
from .search import (
|
|
24
|
+
InternetSearch,
|
|
25
|
+
create_search_client
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Code Generator
|
|
29
|
+
from .generator import (
|
|
30
|
+
CodeGenerator,
|
|
31
|
+
create_code_generator
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Sandbox Executor
|
|
35
|
+
from .sandbox import (
|
|
36
|
+
SandboxExecutor,
|
|
37
|
+
ExecutionTimeout,
|
|
38
|
+
create_sandbox_executor
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# Autonomous Repair
|
|
42
|
+
from .repair import (
|
|
43
|
+
AutonomousRepair,
|
|
44
|
+
create_autonomous_repair
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Result Handler
|
|
48
|
+
from .result_handler import (
|
|
49
|
+
ResultHandler,
|
|
50
|
+
ResultStatus,
|
|
51
|
+
ErrorCategory,
|
|
52
|
+
create_result_handler
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Code Registry
|
|
56
|
+
from .code_registry import (
|
|
57
|
+
CodeRegistry,
|
|
58
|
+
create_code_registry
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
__all__ = [
|
|
62
|
+
# LLM
|
|
63
|
+
'UnifiedLLMClient',
|
|
64
|
+
'LLMProvider',
|
|
65
|
+
'TOKEN_PRICING',
|
|
66
|
+
'create_llm_client',
|
|
67
|
+
|
|
68
|
+
# Search
|
|
69
|
+
'InternetSearch',
|
|
70
|
+
'create_search_client',
|
|
71
|
+
|
|
72
|
+
# Generator
|
|
73
|
+
'CodeGenerator',
|
|
74
|
+
'create_code_generator',
|
|
75
|
+
|
|
76
|
+
# Sandbox
|
|
77
|
+
'SandboxExecutor',
|
|
78
|
+
'ExecutionTimeout',
|
|
79
|
+
'create_sandbox_executor',
|
|
80
|
+
|
|
81
|
+
# Repair
|
|
82
|
+
'AutonomousRepair',
|
|
83
|
+
'create_autonomous_repair',
|
|
84
|
+
|
|
85
|
+
# Result Handler
|
|
86
|
+
'ResultHandler',
|
|
87
|
+
'ResultStatus',
|
|
88
|
+
'ErrorCategory',
|
|
89
|
+
'create_result_handler',
|
|
90
|
+
|
|
91
|
+
# Code Registry
|
|
92
|
+
'CodeRegistry',
|
|
93
|
+
'create_code_registry',
|
|
94
|
+
]
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Code Registry - Store and retrieve generated code functions
|
|
3
|
+
|
|
4
|
+
Purpose:
|
|
5
|
+
- Store successfully generated code for reuse
|
|
6
|
+
- Index by agent_id, capabilities, task patterns
|
|
7
|
+
- Retrieve similar code for future tasks
|
|
8
|
+
- File-based storage (no external dependencies)
|
|
9
|
+
|
|
10
|
+
Storage Structure:
|
|
11
|
+
./logs/code_registry/
|
|
12
|
+
├─ index.json # Metadata index for fast search
|
|
13
|
+
└─ functions/
|
|
14
|
+
├─ {function_id}__{hash}.py # Generated code files
|
|
15
|
+
└─ ...
|
|
16
|
+
"""
|
|
17
|
+
import json
|
|
18
|
+
import hashlib
|
|
19
|
+
import logging
|
|
20
|
+
import re
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Dict, Any, List, Optional
|
|
23
|
+
from datetime import datetime
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class CodeRegistry:
|
|
29
|
+
"""
|
|
30
|
+
Registry for storing and retrieving generated code.
|
|
31
|
+
|
|
32
|
+
Features:
|
|
33
|
+
- Auto-register successful executions
|
|
34
|
+
- Search by agent capabilities and task patterns
|
|
35
|
+
- File-based storage with JSON index
|
|
36
|
+
- No external dependencies
|
|
37
|
+
|
|
38
|
+
Example:
|
|
39
|
+
registry = CodeRegistry()
|
|
40
|
+
|
|
41
|
+
# Register generated code
|
|
42
|
+
registry.register(
|
|
43
|
+
code="result = 2 + 2",
|
|
44
|
+
agent_id="calculator-123",
|
|
45
|
+
task="Calculate 2+2",
|
|
46
|
+
capabilities=["math", "arithmetic"],
|
|
47
|
+
output=4
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Search for similar code
|
|
51
|
+
matches = registry.search(
|
|
52
|
+
capabilities=["math"],
|
|
53
|
+
task_pattern="factorial"
|
|
54
|
+
)
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(self, registry_dir: str = "./logs/code_registry"):
|
|
58
|
+
"""
|
|
59
|
+
Initialize code registry.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
registry_dir: Base directory for code storage
|
|
63
|
+
"""
|
|
64
|
+
self.registry_dir = Path(registry_dir)
|
|
65
|
+
self.functions_dir = self.registry_dir / "functions"
|
|
66
|
+
self.index_file = self.registry_dir / "index.json"
|
|
67
|
+
|
|
68
|
+
# Create directories
|
|
69
|
+
self.functions_dir.mkdir(parents=True, exist_ok=True)
|
|
70
|
+
|
|
71
|
+
# Load or create index
|
|
72
|
+
self._index = self._load_index()
|
|
73
|
+
|
|
74
|
+
logger.info(f"CodeRegistry initialized: {self.registry_dir}")
|
|
75
|
+
logger.info(f"Indexed functions: {len(self._index)}")
|
|
76
|
+
|
|
77
|
+
def register(
|
|
78
|
+
self,
|
|
79
|
+
code: str,
|
|
80
|
+
agent_id: str,
|
|
81
|
+
task: str,
|
|
82
|
+
capabilities: List[str],
|
|
83
|
+
output: Any,
|
|
84
|
+
result_id: Optional[str] = None,
|
|
85
|
+
metadata: Optional[Dict] = None
|
|
86
|
+
) -> str:
|
|
87
|
+
"""
|
|
88
|
+
Register generated code in the registry.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
code: Generated Python code
|
|
92
|
+
agent_id: ID of agent that generated code
|
|
93
|
+
task: Original task description
|
|
94
|
+
capabilities: Agent capabilities list
|
|
95
|
+
output: Execution output (for verification)
|
|
96
|
+
result_id: Optional result_id from execution
|
|
97
|
+
metadata: Additional metadata
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
function_id: Unique identifier for registered function
|
|
101
|
+
"""
|
|
102
|
+
# Generate function ID
|
|
103
|
+
code_hash = self._hash_code(code)
|
|
104
|
+
function_id = f"{agent_id}_{code_hash[:8]}"
|
|
105
|
+
|
|
106
|
+
# Check if already registered
|
|
107
|
+
if function_id in self._index:
|
|
108
|
+
logger.debug(f"Code already registered: {function_id}")
|
|
109
|
+
return function_id
|
|
110
|
+
|
|
111
|
+
# Store code to file
|
|
112
|
+
code_file = self.functions_dir / f"{function_id}.py"
|
|
113
|
+
code_file.write_text(code)
|
|
114
|
+
|
|
115
|
+
# Add to index
|
|
116
|
+
self._index[function_id] = {
|
|
117
|
+
"function_id": function_id,
|
|
118
|
+
"agent_id": agent_id,
|
|
119
|
+
"task": task,
|
|
120
|
+
"capabilities": capabilities,
|
|
121
|
+
"code_hash": code_hash,
|
|
122
|
+
"code_file": str(code_file.relative_to(self.registry_dir)),
|
|
123
|
+
"output_sample": str(output)[:100], # First 100 chars
|
|
124
|
+
"result_id": result_id,
|
|
125
|
+
"registered_at": datetime.now().isoformat(),
|
|
126
|
+
"metadata": metadata or {},
|
|
127
|
+
# Extract task keywords for search
|
|
128
|
+
"task_keywords": self._extract_keywords(task)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# Save index
|
|
132
|
+
self._save_index()
|
|
133
|
+
|
|
134
|
+
logger.info(f"Registered code: {function_id} (task: {task[:50]}...)")
|
|
135
|
+
return function_id
|
|
136
|
+
|
|
137
|
+
def get(self, function_id: str) -> Optional[Dict[str, Any]]:
|
|
138
|
+
"""
|
|
139
|
+
Get function metadata and code by ID.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
function_id: Function identifier
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Dictionary with metadata and code, or None if not found
|
|
146
|
+
"""
|
|
147
|
+
if function_id not in self._index:
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
entry = self._index[function_id].copy()
|
|
151
|
+
|
|
152
|
+
# Load code from file
|
|
153
|
+
code_file = self.registry_dir / entry["code_file"]
|
|
154
|
+
if code_file.exists():
|
|
155
|
+
entry["code"] = code_file.read_text()
|
|
156
|
+
else:
|
|
157
|
+
logger.warning(f"Code file missing for {function_id}")
|
|
158
|
+
entry["code"] = None
|
|
159
|
+
|
|
160
|
+
return entry
|
|
161
|
+
|
|
162
|
+
def search(
|
|
163
|
+
self,
|
|
164
|
+
capabilities: Optional[List[str]] = None,
|
|
165
|
+
task_pattern: Optional[str] = None,
|
|
166
|
+
agent_id: Optional[str] = None,
|
|
167
|
+
limit: int = 10
|
|
168
|
+
) -> List[Dict[str, Any]]:
|
|
169
|
+
"""
|
|
170
|
+
Search for matching code in registry.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
capabilities: Filter by agent capabilities
|
|
174
|
+
task_pattern: Search in task descriptions
|
|
175
|
+
agent_id: Filter by agent ID
|
|
176
|
+
limit: Maximum results to return
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
List of matching function metadata (sorted by relevance)
|
|
180
|
+
"""
|
|
181
|
+
matches = []
|
|
182
|
+
|
|
183
|
+
for function_id, entry in self._index.items():
|
|
184
|
+
score = 0
|
|
185
|
+
|
|
186
|
+
# Filter by agent_id
|
|
187
|
+
if agent_id and entry["agent_id"] != agent_id:
|
|
188
|
+
continue
|
|
189
|
+
|
|
190
|
+
# Score by capability overlap
|
|
191
|
+
if capabilities:
|
|
192
|
+
entry_caps = set(entry["capabilities"])
|
|
193
|
+
query_caps = set(capabilities)
|
|
194
|
+
overlap = len(entry_caps & query_caps)
|
|
195
|
+
if overlap == 0:
|
|
196
|
+
continue # Must have at least one matching capability
|
|
197
|
+
score += overlap * 10
|
|
198
|
+
|
|
199
|
+
# Score by task keyword match
|
|
200
|
+
if task_pattern:
|
|
201
|
+
pattern_keywords = self._extract_keywords(task_pattern)
|
|
202
|
+
entry_keywords = set(entry["task_keywords"])
|
|
203
|
+
keyword_overlap = len(set(pattern_keywords) & entry_keywords)
|
|
204
|
+
if keyword_overlap > 0:
|
|
205
|
+
score += keyword_overlap * 5
|
|
206
|
+
|
|
207
|
+
# Add to matches with score
|
|
208
|
+
match = entry.copy()
|
|
209
|
+
match["_score"] = score
|
|
210
|
+
matches.append(match)
|
|
211
|
+
|
|
212
|
+
# Sort by score (descending)
|
|
213
|
+
matches.sort(key=lambda x: x["_score"], reverse=True)
|
|
214
|
+
|
|
215
|
+
# Return top matches
|
|
216
|
+
return matches[:limit]
|
|
217
|
+
|
|
218
|
+
def list_all(self, limit: int = 100) -> List[Dict[str, Any]]:
|
|
219
|
+
"""
|
|
220
|
+
List all registered functions (most recent first).
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
limit: Maximum results to return
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
List of function metadata
|
|
227
|
+
"""
|
|
228
|
+
entries = list(self._index.values())
|
|
229
|
+
|
|
230
|
+
# Sort by registration time (most recent first)
|
|
231
|
+
entries.sort(
|
|
232
|
+
key=lambda x: x.get("registered_at", ""),
|
|
233
|
+
reverse=True
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
return entries[:limit]
|
|
237
|
+
|
|
238
|
+
def _hash_code(self, code: str) -> str:
|
|
239
|
+
"""Generate hash of code for deduplication."""
|
|
240
|
+
# Normalize code (remove whitespace variations)
|
|
241
|
+
normalized = re.sub(r'\s+', ' ', code.strip())
|
|
242
|
+
return hashlib.md5(normalized.encode()).hexdigest()
|
|
243
|
+
|
|
244
|
+
def _extract_keywords(self, text: str) -> List[str]:
|
|
245
|
+
"""Extract meaningful keywords from text."""
|
|
246
|
+
# Convert to lowercase
|
|
247
|
+
text = text.lower()
|
|
248
|
+
|
|
249
|
+
# Remove common words
|
|
250
|
+
stopwords = {
|
|
251
|
+
'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for',
|
|
252
|
+
'of', 'with', 'by', 'from', 'as', 'is', 'was', 'are', 'were', 'been',
|
|
253
|
+
'be', 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would',
|
|
254
|
+
'should', 'could', 'may', 'might', 'must', 'can'
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
# Extract words (alphanumeric only)
|
|
258
|
+
words = re.findall(r'\b[a-z0-9]+\b', text)
|
|
259
|
+
|
|
260
|
+
# Filter stopwords and short words
|
|
261
|
+
keywords = [w for w in words if w not in stopwords and len(w) > 2]
|
|
262
|
+
|
|
263
|
+
return keywords
|
|
264
|
+
|
|
265
|
+
def _load_index(self) -> Dict:
|
|
266
|
+
"""Load index from file."""
|
|
267
|
+
if self.index_file.exists():
|
|
268
|
+
try:
|
|
269
|
+
with open(self.index_file, 'r') as f:
|
|
270
|
+
index = json.load(f)
|
|
271
|
+
logger.debug(f"Loaded index with {len(index)} entries")
|
|
272
|
+
return index
|
|
273
|
+
except Exception as e:
|
|
274
|
+
logger.error(f"Failed to load index: {e}")
|
|
275
|
+
return {}
|
|
276
|
+
return {}
|
|
277
|
+
|
|
278
|
+
def _save_index(self):
|
|
279
|
+
"""Save index to file."""
|
|
280
|
+
try:
|
|
281
|
+
with open(self.index_file, 'w') as f:
|
|
282
|
+
json.dump(self._index, f, indent=2)
|
|
283
|
+
logger.debug(f"Saved index with {len(self._index)} entries")
|
|
284
|
+
except Exception as e:
|
|
285
|
+
logger.error(f"Failed to save index: {e}")
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def create_code_registry(registry_dir: str = "./logs/code_registry") -> CodeRegistry:
|
|
289
|
+
"""
|
|
290
|
+
Factory function to create code registry.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
registry_dir: Directory for code storage
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
CodeRegistry instance
|
|
297
|
+
"""
|
|
298
|
+
return CodeRegistry(registry_dir)
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Code Generator - LLM-based Python code generation from natural language
|
|
3
|
+
Auto-injects internet search capabilities when needed
|
|
4
|
+
"""
|
|
5
|
+
import ast
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Dict, Any, Optional
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CodeGenerator:
|
|
13
|
+
"""
|
|
14
|
+
Zero-config code generator using LLM.
|
|
15
|
+
|
|
16
|
+
Philosophy:
|
|
17
|
+
- Developer writes natural language task
|
|
18
|
+
- Framework generates executable Python code
|
|
19
|
+
- Auto-injects search tools if task needs web data
|
|
20
|
+
- Validates syntax before returning
|
|
21
|
+
|
|
22
|
+
Example:
|
|
23
|
+
gen = CodeGenerator(llm_client, search_client)
|
|
24
|
+
code = await gen.generate(
|
|
25
|
+
task="Search for Python tutorials and count results",
|
|
26
|
+
system_prompt="You are a Python expert"
|
|
27
|
+
)
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, llm_client, search_client=None):
|
|
31
|
+
"""
|
|
32
|
+
Initialize code generator.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
llm_client: UnifiedLLMClient instance
|
|
36
|
+
search_client: Optional InternetSearch instance (auto-injects if provided)
|
|
37
|
+
"""
|
|
38
|
+
self.llm = llm_client
|
|
39
|
+
self.search = search_client
|
|
40
|
+
|
|
41
|
+
# Prompt templates
|
|
42
|
+
self.base_template = self._load_base_template()
|
|
43
|
+
self.search_template = self._load_search_template()
|
|
44
|
+
|
|
45
|
+
def _load_base_template(self) -> str:
|
|
46
|
+
"""Base prompt template for code generation."""
|
|
47
|
+
return """You are an expert Python programmer. Generate clean, working Python code to accomplish the given task.
|
|
48
|
+
|
|
49
|
+
CRITICAL REQUIREMENTS:
|
|
50
|
+
1. Write ONLY executable Python code (no explanations, markdown, or comments outside code)
|
|
51
|
+
2. Store the final result in a variable named 'result'
|
|
52
|
+
3. Use standard libraries when possible (requests, json, re, etc.)
|
|
53
|
+
4. Handle errors gracefully with try/except
|
|
54
|
+
5. Do NOT use input() or any blocking operations
|
|
55
|
+
6. Do NOT print anything unless explicitly requested
|
|
56
|
+
7. Make the code self-contained and complete
|
|
57
|
+
|
|
58
|
+
AVAILABLE TOOLS:
|
|
59
|
+
- Standard library: os, sys, json, re, datetime, math, random, etc.
|
|
60
|
+
- HTTP requests: import requests
|
|
61
|
+
- Data processing: import pandas (if installed)
|
|
62
|
+
- Any pip-installable package (assume it's available)
|
|
63
|
+
|
|
64
|
+
TASK:
|
|
65
|
+
{task}
|
|
66
|
+
|
|
67
|
+
AGENT CONTEXT:
|
|
68
|
+
{system_prompt}
|
|
69
|
+
|
|
70
|
+
Generate the Python code now (code only, no explanations):
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def _load_search_template(self) -> str:
|
|
74
|
+
"""Template when internet search is available."""
|
|
75
|
+
return """You are an expert Python programmer with internet search capabilities. Generate clean, working Python code to accomplish the given task.
|
|
76
|
+
|
|
77
|
+
CRITICAL REQUIREMENTS:
|
|
78
|
+
1. Write ONLY executable Python code (no explanations, markdown, or comments outside code)
|
|
79
|
+
2. Store the final result in a variable named 'result'
|
|
80
|
+
3. Use standard libraries when possible
|
|
81
|
+
4. Handle errors gracefully with try/except
|
|
82
|
+
5. Do NOT use input() or any blocking operations
|
|
83
|
+
6. Do NOT print anything unless explicitly requested
|
|
84
|
+
|
|
85
|
+
INTERNET SEARCH AVAILABLE:
|
|
86
|
+
You have access to a 'search' object with these async methods:
|
|
87
|
+
- await search.search(query, max_results=5) -> List[Dict] with 'title', 'snippet', 'url'
|
|
88
|
+
- await search.extract_content(url, max_length=10000) -> Dict with 'title', 'content', 'success'
|
|
89
|
+
- await search.search_and_extract(query, num_results=3) -> List[Dict] combined results
|
|
90
|
+
|
|
91
|
+
Example using search (define async main, sandbox will execute it):
|
|
92
|
+
```python
|
|
93
|
+
async def main():
|
|
94
|
+
# Search the web
|
|
95
|
+
results = await search.search("Python async tutorial")
|
|
96
|
+
|
|
97
|
+
# Extract content from first result
|
|
98
|
+
if results:
|
|
99
|
+
content = await search.extract_content(results[0]['url'])
|
|
100
|
+
|
|
101
|
+
# Process and return result
|
|
102
|
+
result = {{
|
|
103
|
+
'title': content['title'],
|
|
104
|
+
'summary': content['content'][:500]
|
|
105
|
+
}}
|
|
106
|
+
return result
|
|
107
|
+
return None
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
IMPORTANT:
|
|
111
|
+
- Define async def main() - the sandbox will call it automatically
|
|
112
|
+
- Do NOT use asyncio.run() - you're already in an async context
|
|
113
|
+
- Store final result by returning from main() or assigning to 'result' variable
|
|
114
|
+
|
|
115
|
+
TASK:
|
|
116
|
+
{task}
|
|
117
|
+
|
|
118
|
+
AGENT CONTEXT:
|
|
119
|
+
{system_prompt}
|
|
120
|
+
|
|
121
|
+
Generate the Python code now (code only, no explanations):
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
async def generate(
|
|
125
|
+
self,
|
|
126
|
+
task: Dict[str, Any],
|
|
127
|
+
system_prompt: str,
|
|
128
|
+
context: Optional[Dict] = None,
|
|
129
|
+
enable_search: bool = True
|
|
130
|
+
) -> str:
|
|
131
|
+
"""
|
|
132
|
+
Generate Python code for a task.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
task: Task dict with 'task' key (natural language description)
|
|
136
|
+
system_prompt: Agent's system prompt (role/expertise)
|
|
137
|
+
context: Optional context (dependencies, previous results)
|
|
138
|
+
enable_search: Auto-inject search tools if available (default True)
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Generated Python code as string
|
|
142
|
+
|
|
143
|
+
Raises:
|
|
144
|
+
ValueError: If generated code has syntax errors
|
|
145
|
+
RuntimeError: If LLM generation fails
|
|
146
|
+
|
|
147
|
+
Example:
|
|
148
|
+
code = await gen.generate(
|
|
149
|
+
task={"task": "Calculate factorial of 10"},
|
|
150
|
+
system_prompt="You are a math expert"
|
|
151
|
+
)
|
|
152
|
+
"""
|
|
153
|
+
task_description = task.get('task', '')
|
|
154
|
+
logger.info(f"Generating code for: {task_description[:80]}...")
|
|
155
|
+
|
|
156
|
+
# Decide whether to inject search tools
|
|
157
|
+
use_search = enable_search and self.search is not None
|
|
158
|
+
if use_search:
|
|
159
|
+
logger.debug("Search tools available - using search template")
|
|
160
|
+
template = self.search_template
|
|
161
|
+
else:
|
|
162
|
+
template = self.base_template
|
|
163
|
+
|
|
164
|
+
# Build prompt
|
|
165
|
+
prompt = template.format(
|
|
166
|
+
task=task_description,
|
|
167
|
+
system_prompt=system_prompt
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# Add context if provided
|
|
171
|
+
if context:
|
|
172
|
+
prompt += f"\n\nCONTEXT FROM PREVIOUS STEPS:\n{self._format_context(context)}\n"
|
|
173
|
+
|
|
174
|
+
# Generate code via LLM
|
|
175
|
+
try:
|
|
176
|
+
response = await self.llm.generate(
|
|
177
|
+
prompt=prompt,
|
|
178
|
+
temperature=0.3, # Lower temp for code generation
|
|
179
|
+
max_tokens=4000
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
code = response['content']
|
|
183
|
+
logger.debug(f"Generated {len(code)} chars of code")
|
|
184
|
+
|
|
185
|
+
# Clean up code (remove markdown blocks if present)
|
|
186
|
+
code = self._clean_code(code)
|
|
187
|
+
|
|
188
|
+
# Validate syntax
|
|
189
|
+
self._validate_code(code)
|
|
190
|
+
|
|
191
|
+
logger.info("Code generation successful")
|
|
192
|
+
return code
|
|
193
|
+
|
|
194
|
+
except Exception as e:
|
|
195
|
+
logger.error(f"Code generation failed: {e}")
|
|
196
|
+
raise RuntimeError(f"Failed to generate code: {e}")
|
|
197
|
+
|
|
198
|
+
def _clean_code(self, code: str) -> str:
|
|
199
|
+
"""
|
|
200
|
+
Clean up generated code (remove markdown, explanations).
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
code: Raw LLM output
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
Clean Python code
|
|
207
|
+
"""
|
|
208
|
+
# Remove markdown code blocks
|
|
209
|
+
import re
|
|
210
|
+
|
|
211
|
+
# Pattern: ```python ... ``` or ```...```
|
|
212
|
+
pattern = r'```(?:python)?\s*\n(.*?)\n```'
|
|
213
|
+
matches = re.findall(pattern, code, re.DOTALL)
|
|
214
|
+
|
|
215
|
+
if matches:
|
|
216
|
+
# Use the first code block found
|
|
217
|
+
code = matches[0]
|
|
218
|
+
|
|
219
|
+
# Remove leading/trailing whitespace
|
|
220
|
+
code = code.strip()
|
|
221
|
+
|
|
222
|
+
# Remove any lines that are just comments at the start
|
|
223
|
+
lines = code.split('\n')
|
|
224
|
+
while lines and lines[0].strip().startswith('#'):
|
|
225
|
+
lines.pop(0)
|
|
226
|
+
|
|
227
|
+
code = '\n'.join(lines)
|
|
228
|
+
|
|
229
|
+
return code
|
|
230
|
+
|
|
231
|
+
def _validate_code(self, code: str):
|
|
232
|
+
"""
|
|
233
|
+
Validate Python syntax.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
code: Python code string
|
|
237
|
+
|
|
238
|
+
Raises:
|
|
239
|
+
ValueError: If code has syntax errors
|
|
240
|
+
"""
|
|
241
|
+
try:
|
|
242
|
+
ast.parse(code)
|
|
243
|
+
logger.debug("Code syntax validation passed")
|
|
244
|
+
except SyntaxError as e:
|
|
245
|
+
logger.error(f"Syntax error in generated code: {e}")
|
|
246
|
+
logger.error(f"Problematic code:\n{code}")
|
|
247
|
+
raise ValueError(f"Generated code has syntax errors: {e}")
|
|
248
|
+
|
|
249
|
+
def _format_context(self, context: Dict) -> str:
|
|
250
|
+
"""Format context from previous steps for prompt."""
|
|
251
|
+
parts = []
|
|
252
|
+
for key, value in context.items():
|
|
253
|
+
parts.append(f"{key}: {value}")
|
|
254
|
+
return "\n".join(parts)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def create_code_generator(llm_client, search_client=None) -> CodeGenerator:
|
|
258
|
+
"""
|
|
259
|
+
Factory function to create code generator.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
llm_client: LLM client instance
|
|
263
|
+
search_client: Optional search client (auto-injected if provided)
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
CodeGenerator instance
|
|
267
|
+
"""
|
|
268
|
+
return CodeGenerator(llm_client, search_client)
|