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.
Files changed (42) hide show
  1. agentfield/__init__.py +66 -0
  2. agentfield/agent.py +3569 -0
  3. agentfield/agent_ai.py +1125 -0
  4. agentfield/agent_cli.py +386 -0
  5. agentfield/agent_field_handler.py +494 -0
  6. agentfield/agent_mcp.py +534 -0
  7. agentfield/agent_registry.py +29 -0
  8. agentfield/agent_server.py +1185 -0
  9. agentfield/agent_utils.py +269 -0
  10. agentfield/agent_workflow.py +323 -0
  11. agentfield/async_config.py +278 -0
  12. agentfield/async_execution_manager.py +1227 -0
  13. agentfield/client.py +1447 -0
  14. agentfield/connection_manager.py +280 -0
  15. agentfield/decorators.py +527 -0
  16. agentfield/did_manager.py +337 -0
  17. agentfield/dynamic_skills.py +304 -0
  18. agentfield/execution_context.py +255 -0
  19. agentfield/execution_state.py +453 -0
  20. agentfield/http_connection_manager.py +429 -0
  21. agentfield/litellm_adapters.py +140 -0
  22. agentfield/logger.py +249 -0
  23. agentfield/mcp_client.py +204 -0
  24. agentfield/mcp_manager.py +340 -0
  25. agentfield/mcp_stdio_bridge.py +550 -0
  26. agentfield/memory.py +723 -0
  27. agentfield/memory_events.py +489 -0
  28. agentfield/multimodal.py +173 -0
  29. agentfield/multimodal_response.py +403 -0
  30. agentfield/pydantic_utils.py +227 -0
  31. agentfield/rate_limiter.py +280 -0
  32. agentfield/result_cache.py +441 -0
  33. agentfield/router.py +190 -0
  34. agentfield/status.py +70 -0
  35. agentfield/types.py +710 -0
  36. agentfield/utils.py +26 -0
  37. agentfield/vc_generator.py +464 -0
  38. agentfield/vision.py +198 -0
  39. agentfield-0.1.22rc2.dist-info/METADATA +102 -0
  40. agentfield-0.1.22rc2.dist-info/RECORD +42 -0
  41. agentfield-0.1.22rc2.dist-info/WHEEL +5 -0
  42. agentfield-0.1.22rc2.dist-info/top_level.txt +1 -0
@@ -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)