jarviscore-framework 0.1.0__py3-none-any.whl → 0.1.1__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.
- jarviscore/__init__.py +37 -5
- jarviscore/adapter/__init__.py +34 -0
- jarviscore/adapter/decorator.py +332 -0
- jarviscore/cli/check.py +18 -13
- jarviscore/cli/scaffold.py +178 -0
- jarviscore/context/__init__.py +40 -0
- jarviscore/context/dependency.py +160 -0
- jarviscore/context/jarvis_context.py +207 -0
- jarviscore/context/memory.py +155 -0
- jarviscore/data/.env.example +146 -0
- jarviscore/data/__init__.py +7 -0
- jarviscore/data/examples/calculator_agent_example.py +77 -0
- jarviscore/data/examples/multi_agent_workflow.py +132 -0
- jarviscore/data/examples/research_agent_example.py +76 -0
- jarviscore/docs/CONFIGURATION.md +6 -2
- jarviscore/docs/GETTING_STARTED.md +7 -4
- jarviscore/docs/TROUBLESHOOTING.md +11 -7
- jarviscore/docs/USER_GUIDE.md +6 -2
- jarviscore/execution/llm.py +23 -16
- {jarviscore_framework-0.1.0.dist-info → jarviscore_framework-0.1.1.dist-info}/METADATA +10 -9
- {jarviscore_framework-0.1.0.dist-info → jarviscore_framework-0.1.1.dist-info}/RECORD +26 -12
- tests/test_context.py +467 -0
- tests/test_decorator.py +622 -0
- {jarviscore_framework-0.1.0.dist-info → jarviscore_framework-0.1.1.dist-info}/WHEEL +0 -0
- {jarviscore_framework-0.1.0.dist-info → jarviscore_framework-0.1.1.dist-info}/licenses/LICENSE +0 -0
- {jarviscore_framework-0.1.0.dist-info → jarviscore_framework-0.1.1.dist-info}/top_level.txt +0 -0
jarviscore/__init__.py
CHANGED
|
@@ -4,11 +4,12 @@ JarvisCore - P2P Distributed Agent Framework
|
|
|
4
4
|
A production-grade framework for building autonomous agent systems with:
|
|
5
5
|
- Event-sourced state management (crash recovery, HITL support)
|
|
6
6
|
- P2P coordination via SWIM protocol
|
|
7
|
-
-
|
|
7
|
+
- Three execution profiles:
|
|
8
8
|
* AutoAgent: LLM code generation (3 lines of user code)
|
|
9
9
|
* CustomAgent: Framework-agnostic (LangChain, MCP, raw Python)
|
|
10
|
+
* @jarvis_agent: Decorator to wrap existing agents (1 line)
|
|
10
11
|
|
|
11
|
-
Quick Start:
|
|
12
|
+
Quick Start (AutoAgent):
|
|
12
13
|
from jarviscore import Mesh, AutoAgent
|
|
13
14
|
|
|
14
15
|
class ScraperAgent(AutoAgent):
|
|
@@ -20,12 +21,33 @@ Quick Start:
|
|
|
20
21
|
mesh.add(ScraperAgent)
|
|
21
22
|
await mesh.start()
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
Quick Start (Custom Profile with decorator):
|
|
25
|
+
from jarviscore import Mesh, jarvis_agent, JarvisContext
|
|
26
|
+
|
|
27
|
+
@jarvis_agent(role="processor", capabilities=["processing"])
|
|
28
|
+
class DataProcessor:
|
|
29
|
+
def run(self, data):
|
|
30
|
+
return {"processed": data * 2}
|
|
31
|
+
|
|
32
|
+
# With context access
|
|
33
|
+
@jarvis_agent(role="aggregator", capabilities=["aggregation"])
|
|
34
|
+
class Aggregator:
|
|
35
|
+
def run(self, task, ctx: JarvisContext):
|
|
36
|
+
prev = ctx.previous("step1")
|
|
37
|
+
return {"result": prev}
|
|
38
|
+
|
|
39
|
+
mesh = Mesh(mode="autonomous")
|
|
40
|
+
mesh.add(DataProcessor)
|
|
41
|
+
mesh.add(Aggregator)
|
|
42
|
+
await mesh.start()
|
|
43
|
+
|
|
44
|
+
results = await mesh.workflow("pipeline", [
|
|
45
|
+
{"agent": "processor", "task": "Process", "params": {"data": [1,2,3]}},
|
|
46
|
+
{"agent": "aggregator", "task": "Aggregate", "depends_on": [0]}
|
|
25
47
|
])
|
|
26
48
|
"""
|
|
27
49
|
|
|
28
|
-
__version__ = "0.1.
|
|
50
|
+
__version__ = "0.1.1"
|
|
29
51
|
__author__ = "JarvisCore Contributors"
|
|
30
52
|
__license__ = "MIT"
|
|
31
53
|
|
|
@@ -38,6 +60,10 @@ from jarviscore.core.mesh import Mesh, MeshMode
|
|
|
38
60
|
from jarviscore.profiles.autoagent import AutoAgent
|
|
39
61
|
from jarviscore.profiles.customagent import CustomAgent
|
|
40
62
|
|
|
63
|
+
# Custom Profile: Decorator and Context
|
|
64
|
+
from jarviscore.adapter import jarvis_agent
|
|
65
|
+
from jarviscore.context import JarvisContext, MemoryAccessor, DependencyAccessor
|
|
66
|
+
|
|
41
67
|
__all__ = [
|
|
42
68
|
# Version
|
|
43
69
|
"__version__",
|
|
@@ -51,4 +77,10 @@ __all__ = [
|
|
|
51
77
|
# Profiles
|
|
52
78
|
"AutoAgent",
|
|
53
79
|
"CustomAgent",
|
|
80
|
+
|
|
81
|
+
# Custom Profile (decorator approach)
|
|
82
|
+
"jarvis_agent",
|
|
83
|
+
"JarvisContext",
|
|
84
|
+
"MemoryAccessor",
|
|
85
|
+
"DependencyAccessor",
|
|
54
86
|
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Adapter module for JarvisCore Custom Profile.
|
|
3
|
+
|
|
4
|
+
Provides utilities to wrap existing agents for use with JarvisCore:
|
|
5
|
+
- @jarvis_agent: Decorator to convert any class into a JarvisCore agent
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
from jarviscore import jarvis_agent, Mesh, JarvisContext
|
|
9
|
+
|
|
10
|
+
@jarvis_agent(role="processor", capabilities=["processing"])
|
|
11
|
+
class DataProcessor:
|
|
12
|
+
def run(self, data):
|
|
13
|
+
return {"processed": data * 2}
|
|
14
|
+
|
|
15
|
+
# With context access
|
|
16
|
+
@jarvis_agent(role="aggregator", capabilities=["aggregation"])
|
|
17
|
+
class Aggregator:
|
|
18
|
+
def run(self, task, ctx: JarvisContext):
|
|
19
|
+
prev = ctx.previous("step1")
|
|
20
|
+
return {"aggregated": prev}
|
|
21
|
+
|
|
22
|
+
mesh = Mesh(mode="autonomous")
|
|
23
|
+
mesh.add(DataProcessor)
|
|
24
|
+
mesh.add(Aggregator)
|
|
25
|
+
await mesh.start()
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from .decorator import jarvis_agent, detect_execute_method, EXECUTE_METHODS
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
'jarvis_agent',
|
|
32
|
+
'detect_execute_method',
|
|
33
|
+
'EXECUTE_METHODS',
|
|
34
|
+
]
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@jarvis_agent decorator - Convert any class into a JarvisCore agent.
|
|
3
|
+
|
|
4
|
+
Wraps existing classes (LangChain, CrewAI, raw Python) to work with
|
|
5
|
+
JarvisCore orchestration without requiring them to inherit from CustomAgent.
|
|
6
|
+
"""
|
|
7
|
+
from typing import List, Optional, Type, Any
|
|
8
|
+
import inspect
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
from jarviscore.profiles.customagent import CustomAgent
|
|
12
|
+
from jarviscore.context import JarvisContext, create_context
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Common method names to auto-detect
|
|
18
|
+
EXECUTE_METHODS = [
|
|
19
|
+
"run", # Most common
|
|
20
|
+
"invoke", # LangChain
|
|
21
|
+
"execute", # Generic
|
|
22
|
+
"call", # Callable pattern
|
|
23
|
+
"__call__", # Callable objects
|
|
24
|
+
"process", # Processing agents
|
|
25
|
+
"handle", # Handler pattern
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def detect_execute_method(cls: Type) -> Optional[str]:
|
|
30
|
+
"""
|
|
31
|
+
Auto-detect the execute method on a class.
|
|
32
|
+
|
|
33
|
+
Checks for common method names in order of preference.
|
|
34
|
+
Only detects methods defined on the class itself, not inherited
|
|
35
|
+
from object (like __call__ from type).
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
cls: Class to inspect
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Method name or None if not found
|
|
42
|
+
"""
|
|
43
|
+
for method_name in EXECUTE_METHODS:
|
|
44
|
+
# Check if method is defined on the class itself (not inherited from object)
|
|
45
|
+
# We walk the MRO but stop before 'object'
|
|
46
|
+
for klass in cls.__mro__:
|
|
47
|
+
if klass is object:
|
|
48
|
+
break
|
|
49
|
+
if method_name in klass.__dict__:
|
|
50
|
+
attr = getattr(cls, method_name)
|
|
51
|
+
if callable(attr):
|
|
52
|
+
return method_name
|
|
53
|
+
break
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def jarvis_agent(
|
|
58
|
+
role: str,
|
|
59
|
+
capabilities: List[str],
|
|
60
|
+
execute_method: Optional[str] = None
|
|
61
|
+
):
|
|
62
|
+
"""
|
|
63
|
+
Decorator to convert any class into a JarvisCore agent.
|
|
64
|
+
|
|
65
|
+
The decorated class can use any framework (LangChain, CrewAI, raw Python).
|
|
66
|
+
JarvisCore provides orchestration (data handoff, dependencies, memory).
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
role: Agent role identifier (used for step routing)
|
|
70
|
+
capabilities: List of capabilities (used for step matching)
|
|
71
|
+
execute_method: Method name to call for execution (auto-detected if not provided)
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Wrapped class that extends CustomAgent
|
|
75
|
+
|
|
76
|
+
Example:
|
|
77
|
+
# Simple agent
|
|
78
|
+
@jarvis_agent(role="processor", capabilities=["data_processing"])
|
|
79
|
+
class DataProcessor:
|
|
80
|
+
def run(self, data):
|
|
81
|
+
return {"processed": data * 2}
|
|
82
|
+
|
|
83
|
+
# Agent with context access
|
|
84
|
+
@jarvis_agent(role="aggregator", capabilities=["aggregation"])
|
|
85
|
+
class Aggregator:
|
|
86
|
+
def run(self, task, ctx: JarvisContext):
|
|
87
|
+
prev = ctx.previous("step1")
|
|
88
|
+
return {"result": prev}
|
|
89
|
+
|
|
90
|
+
# Agent with custom method name
|
|
91
|
+
@jarvis_agent(role="researcher", capabilities=["research"], execute_method="invoke")
|
|
92
|
+
class Researcher:
|
|
93
|
+
def invoke(self, query):
|
|
94
|
+
return {"findings": search(query)}
|
|
95
|
+
|
|
96
|
+
Usage:
|
|
97
|
+
mesh = Mesh(mode="autonomous")
|
|
98
|
+
mesh.add(DataProcessor)
|
|
99
|
+
await mesh.start()
|
|
100
|
+
results = await mesh.workflow("pipeline", [...])
|
|
101
|
+
"""
|
|
102
|
+
def decorator(cls: Type) -> Type:
|
|
103
|
+
# Detect execute method if not provided
|
|
104
|
+
method_name = execute_method or detect_execute_method(cls)
|
|
105
|
+
if not method_name:
|
|
106
|
+
raise ValueError(
|
|
107
|
+
f"Could not detect execute method on {cls.__name__}. "
|
|
108
|
+
f"Please specify execute_method parameter or add one of: {EXECUTE_METHODS}"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# Verify method exists
|
|
112
|
+
if not hasattr(cls, method_name):
|
|
113
|
+
raise ValueError(
|
|
114
|
+
f"{cls.__name__} has no method '{method_name}'"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Check if method expects context parameter
|
|
118
|
+
method = getattr(cls, method_name)
|
|
119
|
+
sig = inspect.signature(method)
|
|
120
|
+
params = list(sig.parameters.keys())
|
|
121
|
+
|
|
122
|
+
# Remove 'self' from params
|
|
123
|
+
if params and params[0] == 'self':
|
|
124
|
+
params = params[1:]
|
|
125
|
+
|
|
126
|
+
expects_context = 'ctx' in params or 'context' in params
|
|
127
|
+
|
|
128
|
+
logger.debug(
|
|
129
|
+
f"Wrapping {cls.__name__}: method={method_name}, "
|
|
130
|
+
f"expects_context={expects_context}, params={params}"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Create wrapped CustomAgent subclass
|
|
134
|
+
class WrappedAgent(CustomAgent):
|
|
135
|
+
"""Wrapped agent created by @jarvis_agent decorator."""
|
|
136
|
+
|
|
137
|
+
# Override class attributes from decorator
|
|
138
|
+
# (Can't use nonlocal role/capabilities directly due to scoping)
|
|
139
|
+
pass
|
|
140
|
+
|
|
141
|
+
# Set class attributes (must do this after class creation)
|
|
142
|
+
WrappedAgent.role = role
|
|
143
|
+
WrappedAgent.capabilities = capabilities
|
|
144
|
+
|
|
145
|
+
# Store metadata for introspection
|
|
146
|
+
WrappedAgent._wrapped_class = cls
|
|
147
|
+
WrappedAgent._execute_method = method_name
|
|
148
|
+
WrappedAgent._expects_context = expects_context
|
|
149
|
+
WrappedAgent._original_params = params
|
|
150
|
+
|
|
151
|
+
# Override __init__
|
|
152
|
+
original_init = WrappedAgent.__init__
|
|
153
|
+
|
|
154
|
+
def new_init(self, agent_id=None, **kwargs):
|
|
155
|
+
# Call CustomAgent init
|
|
156
|
+
CustomAgent.__init__(self, agent_id)
|
|
157
|
+
|
|
158
|
+
# Instantiate the wrapped class
|
|
159
|
+
try:
|
|
160
|
+
self._instance = cls(**kwargs) if kwargs else cls()
|
|
161
|
+
except TypeError:
|
|
162
|
+
# Class might not accept kwargs
|
|
163
|
+
self._instance = cls()
|
|
164
|
+
|
|
165
|
+
self._logger.debug(f"Created wrapped instance of {cls.__name__}")
|
|
166
|
+
|
|
167
|
+
WrappedAgent.__init__ = new_init
|
|
168
|
+
|
|
169
|
+
# Override setup
|
|
170
|
+
async def new_setup(self):
|
|
171
|
+
await CustomAgent.setup(self)
|
|
172
|
+
|
|
173
|
+
# Call wrapped class setup if it exists
|
|
174
|
+
if hasattr(self._instance, 'setup'):
|
|
175
|
+
setup_fn = self._instance.setup
|
|
176
|
+
if inspect.iscoroutinefunction(setup_fn):
|
|
177
|
+
await setup_fn()
|
|
178
|
+
else:
|
|
179
|
+
setup_fn()
|
|
180
|
+
|
|
181
|
+
self._logger.debug(f"Setup complete for wrapped {cls.__name__}")
|
|
182
|
+
|
|
183
|
+
WrappedAgent.setup = new_setup
|
|
184
|
+
|
|
185
|
+
# Override execute_task
|
|
186
|
+
async def new_execute_task(self, task: dict) -> dict:
|
|
187
|
+
"""Execute by calling the wrapped class's method."""
|
|
188
|
+
# Get the execute method from instance
|
|
189
|
+
method = getattr(self._instance, method_name)
|
|
190
|
+
|
|
191
|
+
# Build context if method expects it
|
|
192
|
+
ctx = None
|
|
193
|
+
if expects_context:
|
|
194
|
+
# Get memory from mesh/workflow engine
|
|
195
|
+
memory_dict = {}
|
|
196
|
+
dep_manager = None
|
|
197
|
+
|
|
198
|
+
if self._mesh:
|
|
199
|
+
engine = getattr(self._mesh, '_workflow_engine', None)
|
|
200
|
+
if engine:
|
|
201
|
+
memory_dict = engine.memory
|
|
202
|
+
dep_manager = getattr(engine, 'dependency_manager', None)
|
|
203
|
+
|
|
204
|
+
ctx = create_context(
|
|
205
|
+
workflow_id=task.get('context', {}).get('workflow_id', 'unknown'),
|
|
206
|
+
step_id=task.get('context', {}).get('step_id', task.get('id', 'unknown')),
|
|
207
|
+
task=task.get('task', ''),
|
|
208
|
+
params=task.get('params', {}),
|
|
209
|
+
memory_dict=memory_dict,
|
|
210
|
+
dependency_manager=dep_manager
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
# Prepare arguments based on method signature
|
|
214
|
+
args = self._prepare_args(task, ctx, params)
|
|
215
|
+
|
|
216
|
+
# Call the method
|
|
217
|
+
try:
|
|
218
|
+
result = method(*args)
|
|
219
|
+
|
|
220
|
+
# Handle async methods
|
|
221
|
+
if inspect.isawaitable(result):
|
|
222
|
+
result = await result
|
|
223
|
+
|
|
224
|
+
except Exception as e:
|
|
225
|
+
self._logger.error(f"Error in {cls.__name__}.{method_name}: {e}")
|
|
226
|
+
return {
|
|
227
|
+
'status': 'failure',
|
|
228
|
+
'error': str(e),
|
|
229
|
+
'agent': self.agent_id
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
# Normalize result to expected format
|
|
233
|
+
return self._normalize_result(result)
|
|
234
|
+
|
|
235
|
+
WrappedAgent.execute_task = new_execute_task
|
|
236
|
+
|
|
237
|
+
# Helper to prepare arguments
|
|
238
|
+
def prepare_args(self, task: dict, ctx: Optional[JarvisContext], param_names: List[str]) -> tuple:
|
|
239
|
+
"""Prepare arguments for the wrapped method."""
|
|
240
|
+
args = []
|
|
241
|
+
|
|
242
|
+
# Get params, handling both dict and non-dict cases
|
|
243
|
+
task_params = task.get('params', {})
|
|
244
|
+
params_is_dict = isinstance(task_params, dict)
|
|
245
|
+
|
|
246
|
+
for param in param_names:
|
|
247
|
+
if param in ('ctx', 'context'):
|
|
248
|
+
args.append(ctx)
|
|
249
|
+
elif param == 'task':
|
|
250
|
+
args.append(task.get('task', task))
|
|
251
|
+
elif param == 'data':
|
|
252
|
+
# Try to get data from params or context
|
|
253
|
+
if params_is_dict:
|
|
254
|
+
args.append(task_params.get('data', task_params))
|
|
255
|
+
else:
|
|
256
|
+
args.append(task_params)
|
|
257
|
+
elif param == 'params':
|
|
258
|
+
args.append(task_params)
|
|
259
|
+
elif param in ('input', 'query', 'text', 'message'):
|
|
260
|
+
# Common input parameter names
|
|
261
|
+
if params_is_dict:
|
|
262
|
+
args.append(task.get('task', task_params.get(param, '')))
|
|
263
|
+
else:
|
|
264
|
+
args.append(task.get('task', task_params))
|
|
265
|
+
else:
|
|
266
|
+
# Try to get from params, fall back to task dict
|
|
267
|
+
if params_is_dict and param in task_params:
|
|
268
|
+
args.append(task_params[param])
|
|
269
|
+
elif param in task:
|
|
270
|
+
args.append(task[param])
|
|
271
|
+
else:
|
|
272
|
+
# Pass whole params as fallback
|
|
273
|
+
args.append(task_params)
|
|
274
|
+
break # Only do this once
|
|
275
|
+
|
|
276
|
+
# If no args were determined, pass the task/params
|
|
277
|
+
if not args:
|
|
278
|
+
if 'params' in task:
|
|
279
|
+
args.append(task_params)
|
|
280
|
+
else:
|
|
281
|
+
args.append(task)
|
|
282
|
+
|
|
283
|
+
return tuple(args)
|
|
284
|
+
|
|
285
|
+
WrappedAgent._prepare_args = prepare_args
|
|
286
|
+
|
|
287
|
+
# Helper to normalize result
|
|
288
|
+
def normalize_result(self, result: Any) -> dict:
|
|
289
|
+
"""Normalize result to expected format."""
|
|
290
|
+
if isinstance(result, dict):
|
|
291
|
+
if 'status' not in result:
|
|
292
|
+
return {
|
|
293
|
+
'status': 'success',
|
|
294
|
+
'output': result,
|
|
295
|
+
'agent': self.agent_id
|
|
296
|
+
}
|
|
297
|
+
if 'agent' not in result:
|
|
298
|
+
result['agent'] = self.agent_id
|
|
299
|
+
return result
|
|
300
|
+
else:
|
|
301
|
+
return {
|
|
302
|
+
'status': 'success',
|
|
303
|
+
'output': result,
|
|
304
|
+
'agent': self.agent_id
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
WrappedAgent._normalize_result = normalize_result
|
|
308
|
+
|
|
309
|
+
# Override teardown
|
|
310
|
+
async def new_teardown(self):
|
|
311
|
+
# Call wrapped class teardown if it exists
|
|
312
|
+
if hasattr(self._instance, 'teardown'):
|
|
313
|
+
teardown_fn = self._instance.teardown
|
|
314
|
+
if inspect.iscoroutinefunction(teardown_fn):
|
|
315
|
+
await teardown_fn()
|
|
316
|
+
else:
|
|
317
|
+
teardown_fn()
|
|
318
|
+
|
|
319
|
+
await CustomAgent.teardown(self)
|
|
320
|
+
self._logger.debug(f"Teardown complete for wrapped {cls.__name__}")
|
|
321
|
+
|
|
322
|
+
WrappedAgent.teardown = new_teardown
|
|
323
|
+
|
|
324
|
+
# Copy class metadata
|
|
325
|
+
WrappedAgent.__name__ = cls.__name__
|
|
326
|
+
WrappedAgent.__qualname__ = cls.__qualname__
|
|
327
|
+
WrappedAgent.__doc__ = cls.__doc__ or f"Wrapped agent from {cls.__name__}"
|
|
328
|
+
WrappedAgent.__module__ = cls.__module__
|
|
329
|
+
|
|
330
|
+
return WrappedAgent
|
|
331
|
+
|
|
332
|
+
return decorator
|
jarviscore/cli/check.py
CHANGED
|
@@ -222,14 +222,17 @@ class HealthChecker:
|
|
|
222
222
|
return False
|
|
223
223
|
|
|
224
224
|
async def _test_gemini(self) -> bool:
|
|
225
|
-
"""Test Gemini connectivity."""
|
|
225
|
+
"""Test Gemini connectivity using the new google.genai SDK."""
|
|
226
226
|
try:
|
|
227
|
-
|
|
227
|
+
from google import genai
|
|
228
228
|
|
|
229
|
-
genai.
|
|
230
|
-
|
|
229
|
+
client = genai.Client(api_key=os.getenv('GEMINI_API_KEY'))
|
|
230
|
+
model_name = os.getenv('GEMINI_MODEL', 'gemini-2.0-flash')
|
|
231
231
|
|
|
232
|
-
response = await
|
|
232
|
+
response = await client.aio.models.generate_content(
|
|
233
|
+
model=model_name,
|
|
234
|
+
contents="Reply with just 'OK'"
|
|
235
|
+
)
|
|
233
236
|
return 'OK' in response.text.upper()
|
|
234
237
|
|
|
235
238
|
except Exception:
|
|
@@ -291,13 +294,15 @@ class HealthChecker:
|
|
|
291
294
|
print("\n" + "="*70)
|
|
292
295
|
print(" Next Steps")
|
|
293
296
|
print("="*70)
|
|
294
|
-
print("\n1.
|
|
297
|
+
print("\n1. Initialize project (creates .env.example and examples):")
|
|
298
|
+
print(" python -m jarviscore.cli.scaffold --examples")
|
|
299
|
+
print("\n2. Configure your environment:")
|
|
295
300
|
print(" cp .env.example .env")
|
|
296
|
-
print("
|
|
297
|
-
print("
|
|
298
|
-
print("
|
|
299
|
-
print("
|
|
300
|
-
print("
|
|
301
|
+
print(" # Edit .env and add one of:")
|
|
302
|
+
print(" # CLAUDE_API_KEY=sk-ant-...")
|
|
303
|
+
print(" # AZURE_API_KEY=...")
|
|
304
|
+
print(" # GEMINI_API_KEY=...")
|
|
305
|
+
print(" # LLM_ENDPOINT=http://localhost:8000 (for local vLLM)")
|
|
301
306
|
print("\n3. Run health check again:")
|
|
302
307
|
print(" python -m jarviscore.cli.check --validate-llm")
|
|
303
308
|
print("\n4. Try the smoke test:")
|
|
@@ -309,8 +314,8 @@ class HealthChecker:
|
|
|
309
314
|
print("\n✓ All checks passed! Ready to use JarvisCore.\n")
|
|
310
315
|
print("Next steps:")
|
|
311
316
|
print(" 1. Run smoke test: python -m jarviscore.cli.smoketest")
|
|
312
|
-
print(" 2.
|
|
313
|
-
print(" 3.
|
|
317
|
+
print(" 2. Get examples: python -m jarviscore.cli.scaffold --examples")
|
|
318
|
+
print(" 3. Try example: python examples/calculator_agent_example.py")
|
|
314
319
|
print()
|
|
315
320
|
|
|
316
321
|
return True
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""
|
|
2
|
+
JarvisCore Project Initialization CLI
|
|
3
|
+
|
|
4
|
+
Scaffolds a new JarvisCore project with configuration and examples.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
python -m jarviscore.cli.scaffold # Create .env from template
|
|
8
|
+
python -m jarviscore.cli.scaffold --examples # Also copy example files
|
|
9
|
+
python -m jarviscore.cli.scaffold --force # Overwrite existing files
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
import shutil
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from importlib import resources
|
|
16
|
+
import argparse
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_data_path() -> Path:
|
|
20
|
+
"""Get path to the data directory within the package."""
|
|
21
|
+
# Python 3.9+ approach using importlib.resources
|
|
22
|
+
try:
|
|
23
|
+
# resources.files() returns a Traversable, convert to Path
|
|
24
|
+
data_path = resources.files('jarviscore.data')
|
|
25
|
+
return Path(str(data_path))
|
|
26
|
+
except (TypeError, AttributeError):
|
|
27
|
+
# Fallback for older Python or if resources.files doesn't work
|
|
28
|
+
import jarviscore.data
|
|
29
|
+
return Path(jarviscore.data.__file__).parent
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def copy_env_example(dest_dir: Path, force: bool = False) -> bool:
|
|
33
|
+
"""
|
|
34
|
+
Copy .env.example to destination directory.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
dest_dir: Destination directory
|
|
38
|
+
force: Overwrite if exists
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
True if copied, False if skipped
|
|
42
|
+
"""
|
|
43
|
+
data_path = get_data_path()
|
|
44
|
+
src = data_path / '.env.example'
|
|
45
|
+
dest = dest_dir / '.env.example'
|
|
46
|
+
|
|
47
|
+
if not src.exists():
|
|
48
|
+
print(f"✗ Source file not found: {src}")
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
if dest.exists() and not force:
|
|
52
|
+
print(f"⚠ {dest.name} already exists (use --force to overwrite)")
|
|
53
|
+
return False
|
|
54
|
+
|
|
55
|
+
shutil.copy2(src, dest)
|
|
56
|
+
print(f"✓ Created {dest.name}")
|
|
57
|
+
return True
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def copy_examples(dest_dir: Path, force: bool = False) -> bool:
|
|
61
|
+
"""
|
|
62
|
+
Copy example files to destination directory.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
dest_dir: Destination directory
|
|
66
|
+
force: Overwrite if exists
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
True if copied, False if skipped
|
|
70
|
+
"""
|
|
71
|
+
data_path = get_data_path()
|
|
72
|
+
src = data_path / 'examples'
|
|
73
|
+
dest = dest_dir / 'examples'
|
|
74
|
+
|
|
75
|
+
if not src.exists():
|
|
76
|
+
print(f"✗ Examples directory not found: {src}")
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
if dest.exists() and not force:
|
|
80
|
+
print(f"⚠ examples/ directory already exists (use --force to overwrite)")
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
if dest.exists() and force:
|
|
84
|
+
shutil.rmtree(dest)
|
|
85
|
+
|
|
86
|
+
shutil.copytree(src, dest)
|
|
87
|
+
|
|
88
|
+
# Count files copied
|
|
89
|
+
file_count = sum(1 for _ in dest.glob('*.py'))
|
|
90
|
+
print(f"✓ Created examples/ directory ({file_count} files)")
|
|
91
|
+
return True
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def print_header():
|
|
95
|
+
"""Print initialization header."""
|
|
96
|
+
print("\n" + "=" * 60)
|
|
97
|
+
print(" JarvisCore Project Initialization")
|
|
98
|
+
print("=" * 60 + "\n")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def print_next_steps(env_created: bool, examples_created: bool):
|
|
102
|
+
"""Print next steps after initialization."""
|
|
103
|
+
print("\n" + "=" * 60)
|
|
104
|
+
print(" Next Steps")
|
|
105
|
+
print("=" * 60)
|
|
106
|
+
|
|
107
|
+
steps = []
|
|
108
|
+
|
|
109
|
+
if env_created:
|
|
110
|
+
steps.append("1. Copy and configure your environment:\n"
|
|
111
|
+
" cp .env.example .env\n"
|
|
112
|
+
" # Edit .env and add your LLM API key")
|
|
113
|
+
|
|
114
|
+
steps.append(f"{'2' if env_created else '1'}. Validate your setup:\n"
|
|
115
|
+
" python -m jarviscore.cli.check --validate-llm")
|
|
116
|
+
|
|
117
|
+
steps.append(f"{'3' if env_created else '2'}. Run smoke test:\n"
|
|
118
|
+
" python -m jarviscore.cli.smoketest")
|
|
119
|
+
|
|
120
|
+
if examples_created:
|
|
121
|
+
steps.append(f"{'4' if env_created else '3'}. Try an example:\n"
|
|
122
|
+
" python examples/calculator_agent_example.py")
|
|
123
|
+
|
|
124
|
+
for step in steps:
|
|
125
|
+
print(f"\n{step}")
|
|
126
|
+
|
|
127
|
+
print()
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def main():
|
|
131
|
+
"""CLI entry point."""
|
|
132
|
+
parser = argparse.ArgumentParser(
|
|
133
|
+
description='Initialize a new JarvisCore project'
|
|
134
|
+
)
|
|
135
|
+
parser.add_argument(
|
|
136
|
+
'--examples',
|
|
137
|
+
action='store_true',
|
|
138
|
+
help='Also copy example agent files'
|
|
139
|
+
)
|
|
140
|
+
parser.add_argument(
|
|
141
|
+
'--force',
|
|
142
|
+
action='store_true',
|
|
143
|
+
help='Overwrite existing files'
|
|
144
|
+
)
|
|
145
|
+
parser.add_argument(
|
|
146
|
+
'--dir',
|
|
147
|
+
type=str,
|
|
148
|
+
default='.',
|
|
149
|
+
help='Target directory (default: current directory)'
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
args = parser.parse_args()
|
|
153
|
+
dest_dir = Path(args.dir).resolve()
|
|
154
|
+
|
|
155
|
+
print_header()
|
|
156
|
+
print(f"Initializing in: {dest_dir}\n")
|
|
157
|
+
|
|
158
|
+
# Ensure destination exists
|
|
159
|
+
dest_dir.mkdir(parents=True, exist_ok=True)
|
|
160
|
+
|
|
161
|
+
# Copy files
|
|
162
|
+
env_created = copy_env_example(dest_dir, args.force)
|
|
163
|
+
|
|
164
|
+
examples_created = False
|
|
165
|
+
if args.examples:
|
|
166
|
+
examples_created = copy_examples(dest_dir, args.force)
|
|
167
|
+
|
|
168
|
+
# Summary
|
|
169
|
+
if env_created or examples_created:
|
|
170
|
+
print_next_steps(env_created, examples_created)
|
|
171
|
+
sys.exit(0)
|
|
172
|
+
else:
|
|
173
|
+
print("\n⚠ No files were created. Use --force to overwrite existing files.")
|
|
174
|
+
sys.exit(1)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
if __name__ == '__main__':
|
|
178
|
+
main()
|