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.
Files changed (55) hide show
  1. examples/calculator_agent_example.py +77 -0
  2. examples/multi_agent_workflow.py +132 -0
  3. examples/research_agent_example.py +76 -0
  4. jarviscore/__init__.py +54 -0
  5. jarviscore/cli/__init__.py +7 -0
  6. jarviscore/cli/__main__.py +33 -0
  7. jarviscore/cli/check.py +404 -0
  8. jarviscore/cli/smoketest.py +371 -0
  9. jarviscore/config/__init__.py +7 -0
  10. jarviscore/config/settings.py +128 -0
  11. jarviscore/core/__init__.py +7 -0
  12. jarviscore/core/agent.py +163 -0
  13. jarviscore/core/mesh.py +463 -0
  14. jarviscore/core/profile.py +64 -0
  15. jarviscore/docs/API_REFERENCE.md +932 -0
  16. jarviscore/docs/CONFIGURATION.md +753 -0
  17. jarviscore/docs/GETTING_STARTED.md +600 -0
  18. jarviscore/docs/TROUBLESHOOTING.md +424 -0
  19. jarviscore/docs/USER_GUIDE.md +983 -0
  20. jarviscore/execution/__init__.py +94 -0
  21. jarviscore/execution/code_registry.py +298 -0
  22. jarviscore/execution/generator.py +268 -0
  23. jarviscore/execution/llm.py +430 -0
  24. jarviscore/execution/repair.py +283 -0
  25. jarviscore/execution/result_handler.py +332 -0
  26. jarviscore/execution/sandbox.py +555 -0
  27. jarviscore/execution/search.py +281 -0
  28. jarviscore/orchestration/__init__.py +18 -0
  29. jarviscore/orchestration/claimer.py +101 -0
  30. jarviscore/orchestration/dependency.py +143 -0
  31. jarviscore/orchestration/engine.py +292 -0
  32. jarviscore/orchestration/status.py +96 -0
  33. jarviscore/p2p/__init__.py +23 -0
  34. jarviscore/p2p/broadcaster.py +353 -0
  35. jarviscore/p2p/coordinator.py +364 -0
  36. jarviscore/p2p/keepalive.py +361 -0
  37. jarviscore/p2p/swim_manager.py +290 -0
  38. jarviscore/profiles/__init__.py +6 -0
  39. jarviscore/profiles/autoagent.py +264 -0
  40. jarviscore/profiles/customagent.py +137 -0
  41. jarviscore_framework-0.1.0.dist-info/METADATA +136 -0
  42. jarviscore_framework-0.1.0.dist-info/RECORD +55 -0
  43. jarviscore_framework-0.1.0.dist-info/WHEEL +5 -0
  44. jarviscore_framework-0.1.0.dist-info/licenses/LICENSE +21 -0
  45. jarviscore_framework-0.1.0.dist-info/top_level.txt +3 -0
  46. tests/conftest.py +44 -0
  47. tests/test_agent.py +165 -0
  48. tests/test_autoagent.py +140 -0
  49. tests/test_autoagent_day4.py +186 -0
  50. tests/test_customagent.py +248 -0
  51. tests/test_integration.py +293 -0
  52. tests/test_llm_fallback.py +185 -0
  53. tests/test_mesh.py +356 -0
  54. tests/test_p2p_integration.py +375 -0
  55. 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)