agentfield 0.1.22rc2__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.
- agentfield/__init__.py +66 -0
- agentfield/agent.py +3569 -0
- agentfield/agent_ai.py +1125 -0
- agentfield/agent_cli.py +386 -0
- agentfield/agent_field_handler.py +494 -0
- agentfield/agent_mcp.py +534 -0
- agentfield/agent_registry.py +29 -0
- agentfield/agent_server.py +1185 -0
- agentfield/agent_utils.py +269 -0
- agentfield/agent_workflow.py +323 -0
- agentfield/async_config.py +278 -0
- agentfield/async_execution_manager.py +1227 -0
- agentfield/client.py +1447 -0
- agentfield/connection_manager.py +280 -0
- agentfield/decorators.py +527 -0
- agentfield/did_manager.py +337 -0
- agentfield/dynamic_skills.py +304 -0
- agentfield/execution_context.py +255 -0
- agentfield/execution_state.py +453 -0
- agentfield/http_connection_manager.py +429 -0
- agentfield/litellm_adapters.py +140 -0
- agentfield/logger.py +249 -0
- agentfield/mcp_client.py +204 -0
- agentfield/mcp_manager.py +340 -0
- agentfield/mcp_stdio_bridge.py +550 -0
- agentfield/memory.py +723 -0
- agentfield/memory_events.py +489 -0
- agentfield/multimodal.py +173 -0
- agentfield/multimodal_response.py +403 -0
- agentfield/pydantic_utils.py +227 -0
- agentfield/rate_limiter.py +280 -0
- agentfield/result_cache.py +441 -0
- agentfield/router.py +190 -0
- agentfield/status.py +70 -0
- agentfield/types.py +710 -0
- agentfield/utils.py +26 -0
- agentfield/vc_generator.py +464 -0
- agentfield/vision.py +198 -0
- agentfield-0.1.22rc2.dist-info/METADATA +102 -0
- agentfield-0.1.22rc2.dist-info/RECORD +42 -0
- agentfield-0.1.22rc2.dist-info/WHEEL +5 -0
- agentfield-0.1.22rc2.dist-info/top_level.txt +1 -0
agentfield/agent_cli.py
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CLI functionality for AgentField Agent class.
|
|
3
|
+
|
|
4
|
+
Provides native command-line interface support for running agent functions
|
|
5
|
+
directly from the terminal without starting a server.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import asyncio
|
|
10
|
+
import inspect
|
|
11
|
+
import json
|
|
12
|
+
import sys
|
|
13
|
+
from typing import Any, Callable, Dict, List, Optional, get_type_hints
|
|
14
|
+
|
|
15
|
+
from agentfield.logger import log_error, log_warn
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AgentCLI:
|
|
19
|
+
"""CLI handler for Agent class"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, agent_instance):
|
|
22
|
+
"""
|
|
23
|
+
Initialize CLI handler with agent instance.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
agent_instance: The Agent instance to provide CLI for
|
|
27
|
+
"""
|
|
28
|
+
self.agent = agent_instance
|
|
29
|
+
|
|
30
|
+
def _get_all_functions(self) -> List[str]:
|
|
31
|
+
"""Get list of all available reasoners and skills"""
|
|
32
|
+
functions = []
|
|
33
|
+
|
|
34
|
+
# Add reasoners
|
|
35
|
+
for reasoner in self.agent.reasoners:
|
|
36
|
+
functions.append(reasoner["id"])
|
|
37
|
+
|
|
38
|
+
# Add skills
|
|
39
|
+
for skill in self.agent.skills:
|
|
40
|
+
functions.append(skill["id"])
|
|
41
|
+
|
|
42
|
+
return sorted(functions)
|
|
43
|
+
|
|
44
|
+
def _get_function(self, func_name: str) -> Optional[Callable]:
|
|
45
|
+
"""
|
|
46
|
+
Get function by name from agent.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
func_name: Name of the function to retrieve
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
The function if found, None otherwise
|
|
53
|
+
"""
|
|
54
|
+
if hasattr(self.agent, func_name):
|
|
55
|
+
func = getattr(self.agent, func_name)
|
|
56
|
+
# Get the original function if it's a tracked wrapper
|
|
57
|
+
if hasattr(func, "_original_func"):
|
|
58
|
+
return func._original_func
|
|
59
|
+
return func
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
def _get_function_metadata(self, func_name: str) -> Optional[Dict]:
|
|
63
|
+
"""
|
|
64
|
+
Get metadata for a function.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
func_name: Name of the function
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Metadata dict if found, None otherwise
|
|
71
|
+
"""
|
|
72
|
+
# Check reasoners
|
|
73
|
+
for reasoner in self.agent.reasoners:
|
|
74
|
+
if reasoner["id"] == func_name:
|
|
75
|
+
return {"type": "reasoner", **reasoner}
|
|
76
|
+
|
|
77
|
+
# Check skills
|
|
78
|
+
for skill in self.agent.skills:
|
|
79
|
+
if skill["id"] == func_name:
|
|
80
|
+
return {"type": "skill", **skill}
|
|
81
|
+
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
def _parse_function_args(
|
|
85
|
+
self, func: Callable, cli_args: List[str]
|
|
86
|
+
) -> Dict[str, Any]:
|
|
87
|
+
"""
|
|
88
|
+
Parse CLI arguments for a specific function.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
func: The function to parse arguments for
|
|
92
|
+
cli_args: List of CLI arguments
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Dictionary of parsed arguments
|
|
96
|
+
"""
|
|
97
|
+
sig = inspect.signature(func)
|
|
98
|
+
type_hints = get_type_hints(func)
|
|
99
|
+
|
|
100
|
+
# Create argument parser for this function
|
|
101
|
+
parser = argparse.ArgumentParser(
|
|
102
|
+
description=f"Arguments for {func.__name__}", add_help=False
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Add arguments based on function signature
|
|
106
|
+
for param_name, param in sig.parameters.items():
|
|
107
|
+
if param_name in ["self", "execution_context"]:
|
|
108
|
+
continue
|
|
109
|
+
|
|
110
|
+
param_type = type_hints.get(param_name, str)
|
|
111
|
+
|
|
112
|
+
# Handle different parameter types
|
|
113
|
+
if param_type is bool:
|
|
114
|
+
# Boolean flags
|
|
115
|
+
parser.add_argument(
|
|
116
|
+
f"--{param_name}",
|
|
117
|
+
action="store_true",
|
|
118
|
+
help=f"{param_name} (boolean flag)",
|
|
119
|
+
)
|
|
120
|
+
elif param_type is int:
|
|
121
|
+
parser.add_argument(
|
|
122
|
+
f"--{param_name}",
|
|
123
|
+
type=int,
|
|
124
|
+
required=param.default == inspect.Parameter.empty,
|
|
125
|
+
default=(
|
|
126
|
+
param.default
|
|
127
|
+
if param.default != inspect.Parameter.empty
|
|
128
|
+
else None
|
|
129
|
+
),
|
|
130
|
+
help=f"{param_name} (integer)",
|
|
131
|
+
)
|
|
132
|
+
elif param_type is float:
|
|
133
|
+
parser.add_argument(
|
|
134
|
+
f"--{param_name}",
|
|
135
|
+
type=float,
|
|
136
|
+
required=param.default == inspect.Parameter.empty,
|
|
137
|
+
default=(
|
|
138
|
+
param.default
|
|
139
|
+
if param.default != inspect.Parameter.empty
|
|
140
|
+
else None
|
|
141
|
+
),
|
|
142
|
+
help=f"{param_name} (float)",
|
|
143
|
+
)
|
|
144
|
+
elif param_type in [list, List]:
|
|
145
|
+
parser.add_argument(
|
|
146
|
+
f"--{param_name}",
|
|
147
|
+
type=str,
|
|
148
|
+
required=param.default == inspect.Parameter.empty,
|
|
149
|
+
default=(
|
|
150
|
+
param.default
|
|
151
|
+
if param.default != inspect.Parameter.empty
|
|
152
|
+
else None
|
|
153
|
+
),
|
|
154
|
+
help=f"{param_name} (JSON list)",
|
|
155
|
+
)
|
|
156
|
+
elif param_type in [dict, Dict]:
|
|
157
|
+
parser.add_argument(
|
|
158
|
+
f"--{param_name}",
|
|
159
|
+
type=str,
|
|
160
|
+
required=param.default == inspect.Parameter.empty,
|
|
161
|
+
default=(
|
|
162
|
+
param.default
|
|
163
|
+
if param.default != inspect.Parameter.empty
|
|
164
|
+
else None
|
|
165
|
+
),
|
|
166
|
+
help=f"{param_name} (JSON object)",
|
|
167
|
+
)
|
|
168
|
+
else:
|
|
169
|
+
# Default to string
|
|
170
|
+
parser.add_argument(
|
|
171
|
+
f"--{param_name}",
|
|
172
|
+
type=str,
|
|
173
|
+
required=param.default == inspect.Parameter.empty,
|
|
174
|
+
default=(
|
|
175
|
+
param.default
|
|
176
|
+
if param.default != inspect.Parameter.empty
|
|
177
|
+
else None
|
|
178
|
+
),
|
|
179
|
+
help=f"{param_name} (string)",
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# Parse arguments
|
|
183
|
+
try:
|
|
184
|
+
parsed_args = parser.parse_args(cli_args)
|
|
185
|
+
kwargs = vars(parsed_args)
|
|
186
|
+
|
|
187
|
+
# Convert JSON strings to objects
|
|
188
|
+
for param_name, param in sig.parameters.items():
|
|
189
|
+
if param_name in kwargs and kwargs[param_name] is not None:
|
|
190
|
+
param_type = type_hints.get(param_name, str)
|
|
191
|
+
if param_type in [list, List, dict, Dict]:
|
|
192
|
+
try:
|
|
193
|
+
kwargs[param_name] = json.loads(kwargs[param_name])
|
|
194
|
+
except json.JSONDecodeError:
|
|
195
|
+
log_warn(
|
|
196
|
+
f"Failed to parse JSON for {param_name}, using as string"
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
return kwargs
|
|
200
|
+
except SystemExit:
|
|
201
|
+
# argparse calls sys.exit on error, catch it
|
|
202
|
+
raise ValueError("Invalid arguments")
|
|
203
|
+
|
|
204
|
+
def _call_function(self, func_name: str, cli_args: List[str]) -> None:
|
|
205
|
+
"""
|
|
206
|
+
Call a function with parsed CLI arguments.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
func_name: Name of the function to call
|
|
210
|
+
cli_args: List of CLI arguments
|
|
211
|
+
"""
|
|
212
|
+
func = self._get_function(func_name)
|
|
213
|
+
if not func:
|
|
214
|
+
log_error(f"Function '{func_name}' not found")
|
|
215
|
+
sys.exit(1)
|
|
216
|
+
|
|
217
|
+
try:
|
|
218
|
+
# Parse arguments
|
|
219
|
+
kwargs = self._parse_function_args(func, cli_args)
|
|
220
|
+
|
|
221
|
+
# Call function
|
|
222
|
+
if inspect.iscoroutinefunction(func):
|
|
223
|
+
result = asyncio.run(func(**kwargs))
|
|
224
|
+
else:
|
|
225
|
+
result = func(**kwargs)
|
|
226
|
+
|
|
227
|
+
print(json.dumps(result, indent=2, default=str))
|
|
228
|
+
|
|
229
|
+
except ValueError as e:
|
|
230
|
+
log_error(f"Argument parsing failed: {e}")
|
|
231
|
+
self._show_function_help(func_name)
|
|
232
|
+
sys.exit(1)
|
|
233
|
+
except Exception as e:
|
|
234
|
+
log_error(f"Execution failed: {e}")
|
|
235
|
+
sys.exit(1)
|
|
236
|
+
|
|
237
|
+
def _show_function_help(self, func_name: str) -> None:
|
|
238
|
+
"""
|
|
239
|
+
Show help for a specific function.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
func_name: Name of the function
|
|
243
|
+
"""
|
|
244
|
+
func = self._get_function(func_name)
|
|
245
|
+
metadata = self._get_function_metadata(func_name)
|
|
246
|
+
|
|
247
|
+
if not func or not metadata:
|
|
248
|
+
log_error(f"Function '{func_name}' not found")
|
|
249
|
+
return
|
|
250
|
+
|
|
251
|
+
sig = inspect.signature(func)
|
|
252
|
+
doc = inspect.getdoc(func) or "No description available"
|
|
253
|
+
|
|
254
|
+
print(f"\n{func_name} ({metadata['type']})")
|
|
255
|
+
print("=" * 60)
|
|
256
|
+
print(f"\n{doc}\n")
|
|
257
|
+
print("Arguments:")
|
|
258
|
+
|
|
259
|
+
for param_name, param in sig.parameters.items():
|
|
260
|
+
if param_name in ["self", "execution_context"]:
|
|
261
|
+
continue
|
|
262
|
+
|
|
263
|
+
required = param.default == inspect.Parameter.empty
|
|
264
|
+
default = "" if required else f" (default: {param.default})"
|
|
265
|
+
req_str = "required" if required else "optional"
|
|
266
|
+
|
|
267
|
+
print(f" --{param_name:<20} {req_str}{default}")
|
|
268
|
+
|
|
269
|
+
print("\nExample:")
|
|
270
|
+
example_args = []
|
|
271
|
+
for param_name, param in sig.parameters.items():
|
|
272
|
+
if param_name in ["self", "execution_context"]:
|
|
273
|
+
continue
|
|
274
|
+
if param.default == inspect.Parameter.empty:
|
|
275
|
+
example_args.append(f'--{param_name} "value"')
|
|
276
|
+
|
|
277
|
+
print(f" python main.py call {func_name} {' '.join(example_args)}")
|
|
278
|
+
print()
|
|
279
|
+
|
|
280
|
+
def _list_functions(self) -> None:
|
|
281
|
+
"""List all available functions with their signatures"""
|
|
282
|
+
print(f"\nš Agent: {self.agent.node_id}\n")
|
|
283
|
+
|
|
284
|
+
if self.agent.reasoners:
|
|
285
|
+
print("Reasoners (AI-powered):")
|
|
286
|
+
for reasoner in self.agent.reasoners:
|
|
287
|
+
func = self._get_function(reasoner["id"])
|
|
288
|
+
if func:
|
|
289
|
+
sig = inspect.signature(func)
|
|
290
|
+
doc = inspect.getdoc(func) or "No description"
|
|
291
|
+
# Get first line of docstring
|
|
292
|
+
doc_first_line = doc.split("\n")[0]
|
|
293
|
+
print(f" ⢠{reasoner['id']}{sig}")
|
|
294
|
+
print(f" {doc_first_line}\n")
|
|
295
|
+
|
|
296
|
+
if self.agent.skills:
|
|
297
|
+
print("Skills (deterministic):")
|
|
298
|
+
for skill in self.agent.skills:
|
|
299
|
+
func = self._get_function(skill["id"])
|
|
300
|
+
if func:
|
|
301
|
+
sig = inspect.signature(func)
|
|
302
|
+
doc = inspect.getdoc(func) or "No description"
|
|
303
|
+
# Get first line of docstring
|
|
304
|
+
doc_first_line = doc.split("\n")[0]
|
|
305
|
+
print(f" ⢠{skill['id']}{sig}")
|
|
306
|
+
print(f" {doc_first_line}\n")
|
|
307
|
+
|
|
308
|
+
print(
|
|
309
|
+
f"Total: {len(self.agent.reasoners)} reasoners, {len(self.agent.skills)} skills\n"
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
def _interactive_shell(self) -> None:
|
|
313
|
+
"""Launch interactive shell with agent context"""
|
|
314
|
+
try:
|
|
315
|
+
from IPython import embed
|
|
316
|
+
|
|
317
|
+
# Prepare namespace with all functions
|
|
318
|
+
namespace = {
|
|
319
|
+
"agent": self.agent,
|
|
320
|
+
"asyncio": asyncio,
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
# Add all skills and reasoners to namespace
|
|
324
|
+
for reasoner in self.agent.reasoners:
|
|
325
|
+
func = self._get_function(reasoner["id"])
|
|
326
|
+
if func:
|
|
327
|
+
namespace[reasoner["id"]] = func
|
|
328
|
+
|
|
329
|
+
for skill in self.agent.skills:
|
|
330
|
+
func = self._get_function(skill["id"])
|
|
331
|
+
if func:
|
|
332
|
+
namespace[skill["id"]] = func
|
|
333
|
+
|
|
334
|
+
print(f"š Agent Shell: {self.agent.node_id}")
|
|
335
|
+
print(f"Available functions: {', '.join(self._get_all_functions())}")
|
|
336
|
+
print("\nTip: Use 'await function_name(args)' for async functions")
|
|
337
|
+
print(" Use 'function_name(args)' for sync functions\n")
|
|
338
|
+
|
|
339
|
+
embed(user_ns=namespace)
|
|
340
|
+
except ImportError:
|
|
341
|
+
log_error("IPython not installed. Install with: pip install ipython")
|
|
342
|
+
sys.exit(1)
|
|
343
|
+
|
|
344
|
+
def run_cli(self) -> None:
|
|
345
|
+
"""
|
|
346
|
+
Main CLI entry point - parses commands and executes.
|
|
347
|
+
"""
|
|
348
|
+
parser = argparse.ArgumentParser(
|
|
349
|
+
description=f"Agent CLI: {self.agent.node_id}",
|
|
350
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
354
|
+
|
|
355
|
+
# 'call' command
|
|
356
|
+
call_parser = subparsers.add_parser("call", help="Call a function")
|
|
357
|
+
call_parser.add_argument("function", help="Function name to call")
|
|
358
|
+
|
|
359
|
+
# 'list' command
|
|
360
|
+
subparsers.add_parser("list", help="List all functions")
|
|
361
|
+
|
|
362
|
+
# 'shell' command
|
|
363
|
+
subparsers.add_parser("shell", help="Interactive shell")
|
|
364
|
+
|
|
365
|
+
# 'help' command
|
|
366
|
+
help_parser = subparsers.add_parser("help", help="Show help for a function")
|
|
367
|
+
help_parser.add_argument("function", help="Function name")
|
|
368
|
+
|
|
369
|
+
# Parse known args to separate command from function args
|
|
370
|
+
args, unknown = parser.parse_known_args()
|
|
371
|
+
|
|
372
|
+
if not args.command:
|
|
373
|
+
parser.print_help()
|
|
374
|
+
sys.exit(0)
|
|
375
|
+
|
|
376
|
+
if args.command == "call":
|
|
377
|
+
self._call_function(args.function, unknown)
|
|
378
|
+
elif args.command == "list":
|
|
379
|
+
self._list_functions()
|
|
380
|
+
elif args.command == "shell":
|
|
381
|
+
self._interactive_shell()
|
|
382
|
+
elif args.command == "help":
|
|
383
|
+
self._show_function_help(args.function)
|
|
384
|
+
else:
|
|
385
|
+
parser.print_help()
|
|
386
|
+
sys.exit(1)
|