Skip to content

Commit f5bc74c

Browse files
Introduce unified run method for CLI and server
Adds a universal `run` method to the `Agent` class. This method intelligently detects whether to execute in CLI mode (e.g., `call`, `list`, `shell`, `help`) or to start the FastAPI server, providing a seamless developer experience. The example agent is updated to use this new `run` method, simplifying its main execution block and removing redundant server startup help text.
1 parent a748d60 commit f5bc74c

3 files changed

Lines changed: 473 additions & 17 deletions

File tree

examples/python_agent_nodes/hello_world/main.py

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,33 +17,37 @@
1717
node_id="hello-world",
1818
agentfield_server="http://localhost:8080",
1919
ai_config=AIConfig(
20-
model=os.getenv("SMALL_MODEL", "openai/gpt-4o-mini"),
21-
temperature=0.7
22-
)
20+
model=os.getenv("SMALL_MODEL", "openai/gpt-4o-mini"), temperature=0.7
21+
),
2322
)
2423

2524
# ============= SKILL (DETERMINISTIC) =============
2625

26+
2727
@app.skill()
2828
def get_greeting(name: str) -> dict:
2929
"""Returns a greeting template (deterministic - no AI)"""
3030
return {"message": f"Hello, {name}! Welcome to Agentfield."}
3131

32+
3233
# ============= REASONERS (AI-POWERED) =============
3334

35+
3436
class EmojiResult(BaseModel):
3537
"""Simple schema for emoji addition"""
38+
3639
text: str
3740
emoji: str
3841

42+
3943
@app.reasoner()
4044
async def add_emoji(text: str) -> EmojiResult:
4145
"""Uses AI to add an appropriate emoji to text"""
4246
return await app.ai(
43-
user=f"Add one appropriate emoji to this greeting: {text}",
44-
schema=EmojiResult
47+
user=f"Add one appropriate emoji to this greeting: {text}", schema=EmojiResult
4548
)
4649

50+
4751
@app.reasoner()
4852
async def say_hello(name: str) -> dict:
4953
"""
@@ -60,22 +64,15 @@ async def say_hello(name: str) -> dict:
6064
# Step 2: Add emoji using AI (reasoner)
6165
result = await add_emoji(greeting["message"])
6266

63-
return {
64-
"greeting": result.text,
65-
"emoji": result.emoji,
66-
"name": name
67-
}
67+
return {"greeting": result.text, "emoji": result.emoji, "name": name}
68+
6869

69-
# ============= START SERVER =============
70+
# ============= START SERVER OR CLI =============
7071

7172
if __name__ == "__main__":
7273
print("🚀 Hello World Agent")
7374
print("📍 Node: hello-world")
7475
print("🌐 Control Plane: http://localhost:8080")
75-
print("\n✨ Try it:")
76-
print(' curl -X POST http://localhost:8080/api/v1/execute/hello-world.say_hello \\')
77-
print(' -H "Content-Type: application/json" \\')
78-
print(' -d \'{"input": {"name": "Alice"}}\'')
79-
print()
8076

81-
app.serve(auto_port=True)
77+
# Universal entry point - auto-detects CLI vs server mode
78+
app.run(auto_port=True)

sdk/python/agentfield/agent.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
Literal,
2424
)
2525
from agentfield.agent_ai import AgentAI
26+
from agentfield.agent_cli import AgentCLI
2627
from agentfield.agent_field_handler import AgentFieldHandler
2728
from agentfield.agent_mcp import AgentMCP
2829
from agentfield.agent_registry import clear_current_agent, set_current_agent
@@ -504,6 +505,7 @@ def __init__(
504505

505506
# Initialize handlers
506507
self.ai_handler = AgentAI(self)
508+
self.cli_handler = AgentCLI(self)
507509
self.mcp_handler = AgentMCP(self)
508510
self.agentfield_handler = AgentFieldHandler(self)
509511
self.workflow_handler = AgentWorkflow(self)
@@ -3138,6 +3140,76 @@ def __del__(self) -> None: # pragma: no cover - destructor best effort
31383140
# Ignore errors in destructor to prevent warnings during garbage collection
31393141
pass
31403142

3143+
def run(self, **serve_kwargs):
3144+
"""
3145+
Universal entry point - auto-detects CLI vs server mode.
3146+
3147+
This method intelligently determines whether to run in CLI mode or server mode
3148+
based on command-line arguments. It provides a seamless developer experience
3149+
where the same code can be used for both interactive CLI usage and production
3150+
server deployment.
3151+
3152+
CLI mode is activated when sys.argv contains commands like:
3153+
- 'call': Execute a specific function
3154+
- 'list': List all available functions
3155+
- 'shell': Launch interactive IPython shell
3156+
- 'help': Show help for a specific function
3157+
3158+
Server mode is activated otherwise, starting the FastAPI server.
3159+
3160+
Args:
3161+
**serve_kwargs: Keyword arguments passed to serve() method in server mode.
3162+
Common options include:
3163+
- port: Server port (default: auto-detected)
3164+
- host: Server host (default: "0.0.0.0")
3165+
- dev: Enable development mode (default: False)
3166+
- auto_port: Auto-find available port (default: False)
3167+
3168+
Example:
3169+
```python
3170+
from agentfield import Agent
3171+
3172+
app = Agent(node_id="my_agent")
3173+
3174+
@app.reasoner()
3175+
async def analyze(text: str) -> dict:
3176+
return {"result": text.upper()}
3177+
3178+
@app.skill()
3179+
def get_status() -> dict:
3180+
return {"status": "active"}
3181+
3182+
if __name__ == "__main__":
3183+
# Single entry point for both CLI and server
3184+
app.run()
3185+
3186+
# CLI usage:
3187+
# python main.py list
3188+
# python main.py call analyze --text "hello world"
3189+
# python main.py shell
3190+
# python main.py help analyze
3191+
3192+
# Server usage:
3193+
# python main.py
3194+
# python main.py --port 8080 --dev
3195+
```
3196+
3197+
Note:
3198+
- CLI mode runs functions directly without starting a server
3199+
- Server mode starts the FastAPI server for production use
3200+
- The mode is automatically detected from command-line arguments
3201+
- No code changes needed to switch between modes
3202+
"""
3203+
import sys
3204+
3205+
# Check if CLI mode is requested
3206+
if len(sys.argv) > 1 and sys.argv[1] in ['call', 'list', 'shell', 'help']:
3207+
# Run in CLI mode
3208+
self.cli_handler.run_cli()
3209+
else:
3210+
# Run in server mode
3211+
self.serve(**serve_kwargs)
3212+
31413213
def serve( # pragma: no cover - requires full server runtime integration
31423214
self,
31433215
port: Optional[int] = None,

0 commit comments

Comments
 (0)