Skip to content

Commit d6c367f

Browse files
fix: ManagedAgent provider overload conflates hosted-runtime vs LLM-routing (#1550)
* fix: ManagedAgent(provider=) overload conflates hosted-runtime vs LLM-routing — propose HostedAgent / LocalAgent split (fixes #1549) - Create new HostedAgent class for hosted runtimes (Anthropic managed infrastructure) - Create new LocalAgent class for local loops with optional cloud compute - Update ManagedAgent factory with deprecation warnings for LLM routing overload - Add proper error handling for compute provider misuse - Maintain full backward compatibility with all existing imports - Update examples to demonstrate new clear semantics - Add comprehensive tests for new backend semantics 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: MervinPraison <MervinPraison@users.noreply.github.com> * examples: update managed-agents provider examples to use new HostedAgent/LocalAgent classes - Create new canonical examples demonstrating HostedAgent vs LocalAgent usage - Update existing examples to use clear semantic distinctions - Add runtime_hosted_anthropic.py for true managed runtime usage - Add runtime_local_*.py examples for local execution with different LLMs - Update all_providers.py to use LocalAgent with compute backends - Update local_basic.py to use LocalAgent semantics - Add comprehensive test suite for backend semantics 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: MervinPraison <MervinPraison@users.noreply.github.com> * fix: resolve P1 backward compatibility and routing issues - Fix ManagedAgent compute provider routing: maintain backward compatibility for e2b/modal/etc by routing to LocalManagedAgent with deprecation warning instead of ValueError (fixes hard breaking change) - Fix LocalAgent provider preservation: preserve provider value for LLM routing to maintain _resolve_model() prefix behavior (ollama/, gemini/) - Fix spurious deprecation warnings: auto-detected provider=local no longer triggers warnings for users who never passed provider= - Remove unused os import from hosted_agent.py - Improve error messages with provider-specific guidance - Fix test fragility with exact warning counts and identity checks Addresses all P1 issues identified by Greptile and CodeRabbit reviewers. Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com> --------- Co-authored-by: praisonai-triage-agent[bot] <272766704+praisonai-triage-agent[bot]@users.noreply.github.com> Co-authored-by: MervinPraison <MervinPraison@users.noreply.github.com>
1 parent c373f14 commit d6c367f

12 files changed

Lines changed: 660 additions & 41 deletions

File tree

examples/python/managed-agents/provider/all_providers.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
"""All compute providers — comprehensive test across Local, Docker, E2B, and Modal.
22
3-
This example mirrors the Anthropic app.py but uses the local provider with
4-
various compute backends instead of Anthropic's managed infrastructure.
3+
This example demonstrates local agent loops with various compute backends for tool sandboxing.
4+
Uses the new LocalAgent class to clearly indicate local execution with optional cloud compute.
55
66
Requires:
77
- Docker running locally
88
- E2B_API_KEY set
99
- modal CLI configured (modal token set)
1010
"""
1111
import asyncio
12-
from praisonai import Agent, ManagedAgent, LocalManagedConfig
12+
from praisonai import Agent
13+
from praisonai.integrations import LocalAgent, LocalAgentConfig
1314

1415

1516
async def test_provider(name, compute, extra_provision_kwargs=None):
@@ -18,10 +19,9 @@ async def test_provider(name, compute, extra_provision_kwargs=None):
1819
print(f" PROVIDER: {name}")
1920
print(f"{'='*60}")
2021

21-
managed = ManagedAgent(
22-
provider="local",
22+
managed = LocalAgent(
2323
compute=compute,
24-
config=LocalManagedConfig(
24+
config=LocalAgentConfig(
2525
model="gpt-4o-mini",
2626
system="You are a helpful assistant. Be concise.",
2727
name=f"{name}Agent",

examples/python/managed-agents/provider/local_basic.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
"""Local provider — basic managed agent with gpt-4o-mini.
1+
"""Local agent — basic local execution with gpt-4o-mini.
22
33
No external infrastructure needed. Runs the agent loop locally.
4+
Uses the new canonical LocalAgent class for clarity.
45
"""
5-
from praisonai import Agent, ManagedAgent, LocalManagedConfig
6+
from praisonai import Agent
7+
from praisonai.integrations import LocalAgent, LocalAgentConfig
68

7-
# Create a local managed agent (auto-detects local when no ANTHROPIC_API_KEY)
8-
managed = ManagedAgent(
9-
provider="local",
10-
config=LocalManagedConfig(
9+
# Create a local agent (runs locally, no managed runtime)
10+
managed = LocalAgent(
11+
config=LocalAgentConfig(
1112
model="gpt-4o-mini",
1213
system="You are a helpful assistant. Be concise.",
1314
name="LocalAgent",
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"""Anthropic hosted runtime — entire agent runs on Anthropic's managed infrastructure.
2+
3+
Uses the new canonical HostedAgent class which clearly communicates that the entire
4+
agent loop runs in Anthropic's cloud, not locally.
5+
"""
6+
from praisonai import Agent
7+
from praisonai.integrations import HostedAgent, HostedAgentConfig
8+
9+
# Create a hosted agent running entirely on Anthropic's managed runtime
10+
hosted = HostedAgent(
11+
provider="anthropic",
12+
config=HostedAgentConfig(
13+
model="claude-3-5-sonnet-latest",
14+
system="You are a helpful coding assistant. Be concise.",
15+
name="AnthropicHostedAgent",
16+
tools=[{"type": "agent_toolset_20260401"}],
17+
),
18+
)
19+
20+
agent = Agent(name="anthropic-hosted", backend=hosted)
21+
22+
# 1. Basic execution - runs entirely in Anthropic's cloud
23+
print("[1] Hosted execution on Anthropic infrastructure...")
24+
result = agent.start("What is the capital of France? One word.", stream=True)
25+
print(f" Result: {result}")
26+
27+
# 2. Agent metadata from Anthropic's API
28+
print(f"\n[2] Agent ID: {hosted.agent_id}")
29+
print(f" Version: {hosted.agent_version}")
30+
print(f" Env ID: {hosted.environment_id}")
31+
print(f" Session: {hosted.session_id}")
32+
33+
# 3. Multi-turn (same session keeps context in Anthropic's cloud)
34+
print("\n[3] Multi-turn conversation...")
35+
result = agent.start("What country is that city in?", stream=True)
36+
print(f" Result: {result}")
37+
38+
# 4. Usage tracking from Anthropic's usage API
39+
info = hosted.retrieve_session()
40+
print(f"\n[4] Usage: in={info['usage']['input_tokens']}, out={info['usage']['output_tokens']}")
41+
42+
# 5. List all sessions for this agent
43+
sessions = hosted.list_sessions()
44+
print(f"\n[5] Sessions: {len(sessions)}")
45+
for s in sessions[:3]: # Show first 3
46+
print(f" {s['id']} | {s['status']}")
47+
48+
print("\nDone! Agent loop ran entirely on Anthropic's managed infrastructure.")
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""Local agent loop with Gemini LLM — runs locally, not in a managed runtime.
2+
3+
Uses the new canonical LocalAgent class which clearly communicates that only the
4+
agent loop runs locally. The LLM calls go to Google's Gemini API, but there's no managed runtime involved.
5+
"""
6+
from praisonai import Agent
7+
from praisonai.integrations import LocalAgent, LocalAgentConfig
8+
9+
# Create a local agent using Gemini LLM
10+
local = LocalAgent(
11+
config=LocalAgentConfig(
12+
model="gemini/gemini-2.0-flash", # Use Gemini with litellm routing prefix
13+
system="You are a helpful coding assistant. Be concise.",
14+
name="LocalGeminiAgent",
15+
tools=["execute_command", "read_file", "write_file"],
16+
),
17+
)
18+
19+
agent = Agent(name="local-gemini", backend=local)
20+
21+
# 1. Basic execution - agent loop runs locally, LLM calls go to Gemini
22+
print("[1] Local execution with Gemini LLM...")
23+
result = agent.start("What is the capital of France? One word.", stream=True)
24+
print(f" Result: {result}")
25+
26+
# 2. Agent metadata (locally generated UUIDs)
27+
print(f"\n[2] Agent ID: {local.agent_id}")
28+
print(f" Version: {local.agent_version}")
29+
print(f" Env ID: {local.environment_id}")
30+
print(f" Session: {local.session_id}")
31+
32+
# 3. Multi-turn conversation (session state maintained locally)
33+
print("\n[3] Multi-turn conversation...")
34+
result = agent.start("What country is that city in?", stream=True)
35+
print(f" Result: {result}")
36+
37+
# 4. Usage tracking (accumulated locally)
38+
info = local.retrieve_session()
39+
print(f"\n[4] Usage: in={info['usage']['input_tokens']}, out={info['usage']['output_tokens']}")
40+
41+
# 5. Tool execution (runs in local subprocess)
42+
print("\n[5] Tool execution example...")
43+
result = agent.start("Create a file called hello_gemini.txt with 'Hello from Gemini agent!'", stream=True)
44+
print(f" Tool result: {result}")
45+
46+
print("\nDone! Agent loop ran locally with Gemini LLM calls.")
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""Local agent loop with Ollama LLM — runs locally, not in a managed runtime.
2+
3+
Uses the new canonical LocalAgent class which clearly communicates that only the
4+
agent loop runs locally. The LLM calls go to a local Ollama instance, no managed runtime involved.
5+
"""
6+
from praisonai import Agent
7+
from praisonai.integrations import LocalAgent, LocalAgentConfig
8+
9+
# Create a local agent using Ollama LLM
10+
local = LocalAgent(
11+
config=LocalAgentConfig(
12+
model="ollama/llama3.2", # Use Ollama with litellm routing prefix
13+
system="You are a helpful coding assistant. Be concise.",
14+
name="LocalOllamaAgent",
15+
tools=["execute_command", "read_file", "write_file"],
16+
),
17+
)
18+
19+
agent = Agent(name="local-ollama", backend=local)
20+
21+
# 1. Basic execution - agent loop runs locally, LLM calls go to local Ollama
22+
print("[1] Local execution with Ollama LLM...")
23+
result = agent.start("What is the capital of France? One word.", stream=True)
24+
print(f" Result: {result}")
25+
26+
# 2. Agent metadata (locally generated UUIDs)
27+
print(f"\n[2] Agent ID: {local.agent_id}")
28+
print(f" Version: {local.agent_version}")
29+
print(f" Env ID: {local.environment_id}")
30+
print(f" Session: {local.session_id}")
31+
32+
# 3. Multi-turn conversation (session state maintained locally)
33+
print("\n[3] Multi-turn conversation...")
34+
result = agent.start("What country is that city in?", stream=True)
35+
print(f" Result: {result}")
36+
37+
# 4. Usage tracking (accumulated locally)
38+
info = local.retrieve_session()
39+
print(f"\n[4] Usage: in={info['usage']['input_tokens']}, out={info['usage']['output_tokens']}")
40+
41+
# 5. Tool execution (runs in local subprocess)
42+
print("\n[5] Tool execution example...")
43+
result = agent.start("Create a file called hello_ollama.txt with 'Hello from Ollama agent!'", stream=True)
44+
print(f" Tool result: {result}")
45+
46+
print("\nDone! Agent loop ran locally with Ollama LLM calls.")
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""Local agent loop with OpenAI LLM — runs locally, not in a managed runtime.
2+
3+
Uses the new canonical LocalAgent class which clearly communicates that only the
4+
agent loop runs locally. The LLM calls go to OpenAI, but there's no managed runtime involved.
5+
"""
6+
from praisonai import Agent
7+
from praisonai.integrations import LocalAgent, LocalAgentConfig
8+
9+
# Create a local agent using OpenAI's LLM
10+
local = LocalAgent(
11+
config=LocalAgentConfig(
12+
model="gpt-4o-mini",
13+
system="You are a helpful coding assistant. Be concise.",
14+
name="LocalOpenAIAgent",
15+
tools=["execute_command", "read_file", "write_file"],
16+
),
17+
)
18+
19+
agent = Agent(name="local-openai", backend=local)
20+
21+
# 1. Basic execution - agent loop runs locally, LLM calls go to OpenAI
22+
print("[1] Local execution with OpenAI LLM...")
23+
result = agent.start("What is the capital of France? One word.", stream=True)
24+
print(f" Result: {result}")
25+
26+
# 2. Agent metadata (locally generated UUIDs)
27+
print(f"\n[2] Agent ID: {local.agent_id}")
28+
print(f" Version: {local.agent_version}")
29+
print(f" Env ID: {local.environment_id}")
30+
print(f" Session: {local.session_id}")
31+
32+
# 3. Multi-turn conversation (session state maintained locally)
33+
print("\n[3] Multi-turn conversation...")
34+
result = agent.start("What country is that city in?", stream=True)
35+
print(f" Result: {result}")
36+
37+
# 4. Usage tracking (accumulated locally)
38+
info = local.retrieve_session()
39+
print(f"\n[4] Usage: in={info['usage']['input_tokens']}, out={info['usage']['output_tokens']}")
40+
41+
# 5. Tool execution (runs in local subprocess)
42+
print("\n[5] Tool execution example...")
43+
result = agent.start("Create a file called hello.txt with 'Hello from local agent!'", stream=True)
44+
print(f" Tool result: {result}")
45+
46+
print("\nDone! Agent loop ran locally with OpenAI LLM calls.")

src/praisonai/praisonai/__init__.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@
2727
'LocalManagedConfig', # backward compat alias
2828
'SandboxedAgent', # new honest name
2929
'SandboxedAgentConfig', # new honest name
30+
# New canonical agent backends
31+
'HostedAgent',
32+
'HostedAgentConfig',
33+
'LocalAgent',
34+
'LocalAgentConfig',
3035
]
3136

3237
# Telemetry initialization state
@@ -123,6 +128,19 @@ def __getattr__(name):
123128
elif name in ('ManagedConfig', 'ManagedBackendConfig'):
124129
from .integrations.managed_agents import ManagedConfig
125130
return ManagedConfig
131+
# New canonical agent backends
132+
elif name == 'HostedAgent':
133+
from .integrations.hosted_agent import HostedAgent
134+
return HostedAgent
135+
elif name == 'HostedAgentConfig':
136+
from .integrations.hosted_agent import HostedAgentConfig
137+
return HostedAgentConfig
138+
elif name == 'LocalAgent':
139+
from .integrations.local_agent import LocalAgent
140+
return LocalAgent
141+
elif name == 'LocalAgentConfig':
142+
from .integrations.local_agent import LocalAgentConfig
143+
return LocalAgentConfig
126144
elif name in ('DB', 'PraisonAIDB', 'PraisonDB'):
127145
from .db.adapter import DB
128146
return DB

src/praisonai/praisonai/integrations/__init__.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@
4343
'SandboxedAgentConfig', # new honest name
4444
'ManagedAgentIntegration', # backward compat alias
4545
'ManagedBackendConfig', # backward compat alias
46+
# New canonical agent backends
47+
'HostedAgent',
48+
'HostedAgentConfig',
49+
'LocalAgent',
50+
'LocalAgentConfig',
4651
'get_available_integrations',
4752
'ExternalAgentRegistry',
4853
'get_registry',
@@ -107,4 +112,17 @@ def __getattr__(name):
107112
elif name == 'create_integration':
108113
from .registry import create_integration
109114
return create_integration
115+
# New canonical agent backends
116+
elif name == 'HostedAgent':
117+
from .hosted_agent import HostedAgent
118+
return HostedAgent
119+
elif name == 'HostedAgentConfig':
120+
from .hosted_agent import HostedAgentConfig
121+
return HostedAgentConfig
122+
elif name == 'LocalAgent':
123+
from .local_agent import LocalAgent
124+
return LocalAgent
125+
elif name == 'LocalAgentConfig':
126+
from .local_agent import LocalAgentConfig
127+
return LocalAgentConfig
110128
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

0 commit comments

Comments
 (0)