mirror of
https://github.com/MervinPraison/PraisonAI.git
synced 2026-11-07 12:47:55 -10:00
fix: lazy-load server_registry, goal judge and encrypted session store (fixes #6167)
Move three opt-in-only imports off module top level: - agent/execution_mixin.py: _server_registry now imported in the launch path and cleanup_launch_registration(). - goal/loop.py: judge_goal/_tail imported inside _goal_gate(). - session/__init__.py: EncryptedSessionStore & co. served via __getattr__. Tests now patch praisonaiagents.goal.judge.judge_goal (the lookup site). Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
1d68f23cfb
commit
f8768debb4
6 files changed
+30
-28
No files matched your search
@@ -22,12 +22,6 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
from typing import List, Optional, Any, Dict, Union, Generator, TYPE_CHECKING
|
||||
|
||||
# Shared HTTP launch state — the process-wide registry is defined in
|
||||
# agent/server_registry.py and shared with PraisonAIAgents.launch() so a
|
||||
# standalone Agent and a team launched on the same port cannot double-bind it or
|
||||
# hide each other's endpoints.
|
||||
from .server_registry import _server_registry
|
||||
|
||||
|
||||
class _TimeoutCancelToken:
|
||||
"""cancel_token for caller-mode ``start(timeout=...)``.
|
||||
@@ -76,6 +70,8 @@ def cleanup_launch_registration(agent_id: str) -> None:
|
||||
``_AgentServerRegistry.teardown_routes_for``) so cleanup cannot race a
|
||||
concurrent launch that reserves and registers a route on the same port.
|
||||
"""
|
||||
from .server_registry import _server_registry # lazy: launch path only
|
||||
|
||||
_server_registry.teardown_routes_for(agent_id)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -2394,7 +2390,11 @@ Write the complete compiled report:"""
|
||||
host = resolve_launch_host(host)
|
||||
|
||||
# Get or create the shared FastAPI app for this port (shared with
|
||||
# PraisonAIAgents.launch via the same _server_registry singleton).
|
||||
# PraisonAIAgents.launch via the same _server_registry singleton, so a
|
||||
# standalone Agent and a team on one port cannot double-bind it).
|
||||
# Imported lazily: only the HTTP launch path needs the registry.
|
||||
from .server_registry import _server_registry
|
||||
|
||||
app, is_new_app = _server_registry.get_or_create_app(
|
||||
port,
|
||||
title=f"PraisonAI Agents API (Port {port})",
|
||||
|
||||
@@ -14,7 +14,6 @@ from typing import Any, Optional, Tuple, TYPE_CHECKING
|
||||
|
||||
from praisonaiagents._logging import get_logger
|
||||
|
||||
from .judge import judge_goal, _tail
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .models import GoalCriteria, GoalState
|
||||
@@ -168,6 +167,8 @@ class GoalLoopMixin:
|
||||
self._persist_goal_state()
|
||||
return "continue", reason
|
||||
|
||||
from .judge import judge_goal, _tail # lazy: goal loop is opt-in
|
||||
|
||||
state.turns_used += 1
|
||||
verdict, reason = judge_goal(
|
||||
state,
|
||||
|
||||
@@ -39,6 +39,7 @@ if TYPE_CHECKING:
|
||||
from .sqlite_transcript_store import SqliteTranscriptStore
|
||||
from .protocols import SessionStoreProtocol, SessionMirrorProtocol
|
||||
from .hierarchy import HierarchicalSessionStore, SessionSnapshot, ExtendedSessionData
|
||||
from .encrypted_store import EncryptedSessionStore, SessionEncryptionError, generate_session_key
|
||||
|
||||
# Lazy loading for zero import overhead
|
||||
_module_cache = {}
|
||||
@@ -207,6 +208,12 @@ def __getattr__(name: str):
|
||||
_module_cache[name] = wrap_inter_agent
|
||||
return wrap_inter_agent
|
||||
|
||||
if name in ("EncryptedSessionStore", "SessionEncryptionError", "generate_session_key"):
|
||||
from . import encrypted_store
|
||||
value = getattr(encrypted_store, name)
|
||||
_module_cache[name] = value
|
||||
return value
|
||||
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
||||
|
||||
@@ -249,9 +256,3 @@ __all__ = [
|
||||
"SessionEncryptionError",
|
||||
"generate_session_key",
|
||||
]
|
||||
|
||||
from .encrypted_store import ( # noqa: E402
|
||||
EncryptedSessionStore,
|
||||
SessionEncryptionError,
|
||||
generate_session_key,
|
||||
)
|
||||
@@ -173,7 +173,7 @@ class TestGoalLoopGate:
|
||||
verdicts = iter([("continue", "not yet"), ("done", "goal met")])
|
||||
|
||||
with patch.object(agent, "chat", side_effect=_unique_chat()), \
|
||||
patch("praisonaiagents.goal.loop.judge_goal",
|
||||
patch("praisonaiagents.goal.judge.judge_goal",
|
||||
side_effect=lambda *a, **k: next(verdicts)):
|
||||
result = agent.run_goal("do the task", goal="finish it", max_turns=10)
|
||||
|
||||
@@ -193,7 +193,7 @@ class TestGoalLoopGate:
|
||||
return f"still working {counter['n']}"
|
||||
|
||||
with patch.object(agent, "chat", side_effect=fake_chat), \
|
||||
patch("praisonaiagents.goal.loop.judge_goal",
|
||||
patch("praisonaiagents.goal.judge.judge_goal",
|
||||
side_effect=lambda *a, **k: next(verdicts)):
|
||||
result = agent.run_goal("task", goal="g", max_turns=10)
|
||||
|
||||
@@ -205,7 +205,7 @@ class TestGoalLoopGate:
|
||||
def test_budget_exhaust_pauses_recoverably(self):
|
||||
agent = _make_agent()
|
||||
with patch.object(agent, "chat", side_effect=_unique_chat()), \
|
||||
patch("praisonaiagents.goal.loop.judge_goal",
|
||||
patch("praisonaiagents.goal.judge.judge_goal",
|
||||
return_value=("continue", "never done")):
|
||||
result = agent.run_goal("task", goal="g", max_turns=3)
|
||||
|
||||
@@ -218,7 +218,7 @@ class TestGoalLoopGate:
|
||||
agent = _make_agent()
|
||||
# judge_goal itself fails open to ("continue", "judge unavailable")
|
||||
with patch.object(agent, "chat", side_effect=_unique_chat()), \
|
||||
patch("praisonaiagents.goal.loop.judge_goal",
|
||||
patch("praisonaiagents.goal.judge.judge_goal",
|
||||
return_value=("continue", "judge unavailable")):
|
||||
result = agent.run_goal("task", goal="g", max_turns=2)
|
||||
assert result.completion_reason == "budget_paused"
|
||||
@@ -226,7 +226,7 @@ class TestGoalLoopGate:
|
||||
def test_consecutive_parse_failures_autopause(self):
|
||||
agent = _make_agent()
|
||||
with patch.object(agent, "chat", side_effect=_unique_chat()), \
|
||||
patch("praisonaiagents.goal.loop.judge_goal",
|
||||
patch("praisonaiagents.goal.judge.judge_goal",
|
||||
return_value=("continue", "judge response unparseable")):
|
||||
result = agent.run_goal("task", goal="g", max_turns=20)
|
||||
# auto-pause after 3 consecutive parse failures, well before max_turns
|
||||
@@ -259,7 +259,7 @@ class TestConstraintViolation:
|
||||
)
|
||||
# Simulate a judge that respects constraints: constraint violated → continue
|
||||
with patch.object(agent, "chat", side_effect=_unique_chat()), \
|
||||
patch("praisonaiagents.goal.loop.judge_goal",
|
||||
patch("praisonaiagents.goal.judge.judge_goal",
|
||||
return_value=("continue", "constraint violated")):
|
||||
result = agent.run_goal("task", goal="g", criteria=criteria, max_turns=2)
|
||||
assert result.completion_reason == "budget_paused"
|
||||
@@ -282,7 +282,7 @@ class TestAsyncGoalGate:
|
||||
state = GoalState(goal="g", max_turns=5)
|
||||
state.status = "active"
|
||||
agent._goal_state = state
|
||||
with patch("praisonaiagents.goal.loop.judge_goal",
|
||||
with patch("praisonaiagents.goal.judge.judge_goal",
|
||||
return_value=("done", "goal met")):
|
||||
outcome = asyncio.run(agent._goal_gate_async("final response"))
|
||||
assert outcome == ("done", "goal met")
|
||||
@@ -314,7 +314,7 @@ class TestAsyncGoalGate:
|
||||
return ("done", "eventually met")
|
||||
|
||||
async def scenario():
|
||||
with patch("praisonaiagents.goal.loop.judge_goal",
|
||||
with patch("praisonaiagents.goal.judge.judge_goal",
|
||||
side_effect=slow_judge):
|
||||
task = asyncio.ensure_future(agent._goal_gate_async("resp"))
|
||||
await asyncio.to_thread(started.wait, 2.0)
|
||||
@@ -330,7 +330,7 @@ class TestAsyncGoalGate:
|
||||
def test_run_until_with_goal_delegates(self):
|
||||
agent = _make_agent()
|
||||
with patch.object(agent, "chat", side_effect=_unique_chat()), \
|
||||
patch("praisonaiagents.goal.loop.judge_goal",
|
||||
patch("praisonaiagents.goal.judge.judge_goal",
|
||||
return_value=("done", "met")):
|
||||
result = agent.run_until("task", goal="reach the goal", max_iterations=5)
|
||||
assert result.completion_reason == "goal_met"
|
||||
@@ -7,7 +7,7 @@ at ``turns_used=0``. A crashed run (still ``active``) was also never resumed.
|
||||
|
||||
import pytest
|
||||
|
||||
import praisonaiagents.goal.loop as goal_loop
|
||||
import praisonaiagents.goal.judge as goal_judge
|
||||
from praisonaiagents import Agent
|
||||
from praisonaiagents.goal.models import GoalState
|
||||
from praisonaiagents.model_harness import ScriptedModel
|
||||
@@ -20,7 +20,7 @@ GOAL = "all tests pass"
|
||||
def _isolated_home(tmp_path, monkeypatch):
|
||||
monkeypatch.setenv("PRAISONAI_HOME", str(tmp_path / ".praisonai"))
|
||||
monkeypatch.setattr(
|
||||
goal_loop, "judge_goal", lambda state, *a, **k: ("continue", "not yet")
|
||||
goal_judge, "judge_goal", lambda state, *a, **k: ("continue", "not yet")
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -228,7 +228,7 @@ class TestGoalLoopEvidence:
|
||||
|
||||
with patch.object(agent, "chat",
|
||||
side_effect=lambda p: f"step {counter.__setitem__('n', counter['n'] + 1) or counter['n']}"), \
|
||||
patch("praisonaiagents.goal.loop.judge_goal", side_effect=fake_judge):
|
||||
patch("praisonaiagents.goal.judge.judge_goal", side_effect=fake_judge):
|
||||
result = agent.run_goal("task", goal="g", max_turns=2)
|
||||
|
||||
# Blocking hook fails every turn → judge never consulted, ends paused.
|
||||
@@ -247,7 +247,7 @@ class TestGoalLoopEvidence:
|
||||
return ("done", "met")
|
||||
|
||||
with patch.object(agent, "chat", side_effect=lambda pr: "did it"), \
|
||||
patch("praisonaiagents.goal.loop.judge_goal", side_effect=fake_judge):
|
||||
patch("praisonaiagents.goal.judge.judge_goal", side_effect=fake_judge):
|
||||
result = agent.run_goal("task", goal="g", max_turns=5)
|
||||
|
||||
assert result.completion_reason == "goal_met"
|
||||
@@ -283,7 +283,7 @@ class TestGoalLoopEvidence:
|
||||
return "trying"
|
||||
|
||||
with patch.object(agent, "chat", side_effect=fake_chat), \
|
||||
patch("praisonaiagents.goal.loop.judge_goal",
|
||||
patch("praisonaiagents.goal.judge.judge_goal",
|
||||
side_effect=lambda *a, **k: ("done", "met")):
|
||||
result = agent.run_goal("task", goal="g", max_turns=2)
|
||||
|
||||
|
||||
Reference in new issue
Block a user