fix: re-export ManagedBackendProtocol and unify retrieve_session schema (fixes #1429)#1439
fix: re-export ManagedBackendProtocol and unify retrieve_session schema (fixes #1429)#1439MervinPraison merged 4 commits intomainfrom
Conversation
…ma (fixes #1429) - Add lazy re-export of ManagedBackendProtocol in praisonaiagents.managed - Create SessionInfo dataclass with unified schema (id, status, title, usage) - Update AnthropicManagedAgent.retrieve_session to use SessionInfo - Update LocalManagedAgent.retrieve_session to use SessionInfo - Add comprehensive unit tests for schema equality - Maintain backward compatibility with existing dict-based access Fixes #1429 Co-authored-by: MervinPraison <MervinPraison@users.noreply.github.com>
There was a problem hiding this comment.
MervinPraison has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThis change re-exports Changes
Sequence Diagram(s)sequenceDiagram
participant Caller as Caller
participant Anthropic as AnthropicManagedAgent
participant API as ExternalClient
participant SI as SessionInfo
Caller->>Anthropic: retrieve_session()
Anthropic->>API: beta.sessions.retrieve(session_id)
API-->>Anthropic: session payload (id,status,title,usage?)
Anthropic->>SI: SessionInfo(...usage normalized...)
SI-->>Anthropic: dict
Anthropic-->>Caller: unified session dict
sequenceDiagram
participant Caller as Caller
participant Local as LocalManagedAgent
participant Store as LocalStore
participant SI as SessionInfo
Caller->>Local: retrieve_session()
Local->>Store: read session id/totals
Store-->>Local: session data (id or None, totals)
Local->>SI: SessionInfo(...usage normalized...)
SI-->>Local: dict
Local-->>Caller: unified session dict
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@copilot Do a thorough review of this PR. Read ALL existing reviewer comments above from Qodo, Coderabbit, and Gemini first — incorporate their findings. Review areas:
|
There was a problem hiding this comment.
Code Review
This pull request introduces a unified SessionInfo schema to standardize session metadata across different managed agent backends, including the addition of a SessionInfo dataclass and updates to the retrieve_session methods. Review feedback identified a logic error in the SessionInfo post-initialization that could result in incomplete usage data, an inconsistency in status strings for local agents compared to the Anthropic implementation, and a need for safer attribute extraction to handle explicit null values from API responses.
|
|
||
| session_info = SessionInfo( | ||
| id=self._session_id, | ||
| status="idle" if self._session_id else "none", |
There was a problem hiding this comment.
Using "none" as the status when no session exists is inconsistent with the AnthropicManagedAgent implementation, which returns "unknown" (the default in SessionInfo) in the same scenario. Furthermore, the updated test test_retrieve_session_no_session in test_managed_backend.py explicitly asserts that the status should be "unknown". This inconsistency will cause that test to fail when running against the local backend.
| status="idle" if self._session_id else "none", | |
| status="idle" if self._session_id else "unknown", |
| elif "input_tokens" not in self.usage: | ||
| self.usage["input_tokens"] = 0 | ||
| elif "output_tokens" not in self.usage: | ||
| self.usage["output_tokens"] = 0 |
There was a problem hiding this comment.
The elif logic in __post_init__ is flawed. If both input_tokens and output_tokens are missing from the usage dictionary (for example, if usage={} is passed), only input_tokens will be initialized because the elif chain will stop after the first match. This results in an incomplete usage dictionary, which contradicts the goal of a unified schema where all fields are always present.
| elif "input_tokens" not in self.usage: | |
| self.usage["input_tokens"] = 0 | |
| elif "output_tokens" not in self.usage: | |
| self.usage["output_tokens"] = 0 | |
| else: | |
| if "input_tokens" not in self.usage: | |
| self.usage["input_tokens"] = 0 | |
| if "output_tokens" not in self.usage: | |
| self.usage["output_tokens"] = 0 |
| id=getattr(sess, "id", self._session_id), | ||
| status=getattr(sess, "status", "unknown"), | ||
| title=getattr(sess, "title", ""), | ||
| usage=usage_dict if usage_dict else None, |
There was a problem hiding this comment.
Using getattr(sess, "status", "unknown") (and similar for title) can return None if the attribute exists on the sess object but its value is explicitly None (which is common in API responses for optional fields). Since the SessionInfo dataclass hints these fields as str, receiving None might cause issues for consumers expecting string methods (e.g., .lower()). It is safer to use or to ensure the default value is applied when the attribute value is falsy.
| id=getattr(sess, "id", self._session_id), | |
| status=getattr(sess, "status", "unknown"), | |
| title=getattr(sess, "title", ""), | |
| usage=usage_dict if usage_dict else None, | |
| id=getattr(sess, "id", None) or self._session_id, | |
| status=getattr(sess, "status", None) or "unknown", | |
| title=getattr(sess, "title", None) or "", | |
| usage=usage_dict if usage_dict else None, |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (3)
src/praisonai-agents/tests/managed/test_session_info_schema.py (2)
1-10: Test directory placement.Per the repo's test layout guidance, tests should live under
unit/,integration/, ore2e/. This file sits directly intests/managed/; consider moving totests/unit/managed/for consistency with the rest of the suite.As per coding guidelines: "structure tests into unit/, integration/, and e2e/ categories".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/praisonai-agents/tests/managed/test_session_info_schema.py` around lines 1 - 10, This test file lives under tests/managed/ instead of the repo's standardized test directories; move src/praisonai-agents/tests/managed/test_session_info_schema.py into the unit test tree (e.g., tests/unit/managed/test_session_info_schema.py) so it follows the unit/integration/e2e layout, update any relative imports or test discovery references if present, and ensure test runners/CI configuration (if explicitly listing paths) include the new location; verify the test still imports the same symbols (e.g., SessionInfo schema checks in this module) and passes discovery after relocation.
139-164:test_schema_equality_between_backendsonly exercises one backend.Despite the name, this test never instantiates
AnthropicManagedAgentor compares the two results. Consider mocking the Anthropic client (as intest_anthropic_retrieve_session_schema) and assertingset(local_result) == set(anthropic_result)with matching value types, so the equality claim is actually verified.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/praisonai-agents/tests/managed/test_session_info_schema.py` around lines 139 - 164, The test test_schema_equality_between_backends currently only exercises LocalManagedAgent; update it to also exercise AnthropicManagedAgent (or mock its client like in test_anthropic_retrieve_session_schema) so you actually compare schemas: instantiate or mock AnthropicManagedAgent to produce anthropic_result, then assert set(local_result.keys()) == set(anthropic_result.keys()) and validate matching value types for fields like "id", "status", "title", and that "usage" is a dict containing "input_tokens" and "output_tokens"; keep LocalManagedAgent usage and token setup as-is and mirror the same non-None checks for the anthropic_result.src/praisonai/praisonai/integrations/_session_info.py (1)
29-30: Type annotation should reflect theNonesentinel.
usage: Dict[str, int] = Noneis typed as non-Optional but defaults toNone. Type checkers will complain; preferOptional[Dict[str, int]] = Noneand usefield(default_factory=...)or keep the__post_init__normalization. Also, the string after a field isn't a real docstring — it's a bare string expression (harmless but not picked up by tooling).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/praisonai/praisonai/integrations/_session_info.py` around lines 29 - 30, The field `usage` is declared as `usage: Dict[str, int] = None` which mismatches its type and leaves a bare string after the field; change the annotation to `Optional[Dict[str, int]] = None` (import Optional from typing) if you intend to keep the None sentinel and rely on `__post_init__` normalization, or better replace it with `usage: Dict[str, int] = field(default_factory=dict)` (import field from dataclasses) to avoid None entirely; also remove or convert the trailing bare string into a proper class docstring or a comment so tooling picks it up.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/praisonai-agents/tests/managed/test_session_info_schema.py`:
- Around line 186-201: The test test_empty_session_local is asserting a
backend-specific status ("none") instead of the canonical schema; update it to
match the Anthropic test expectations by asserting the same defaults produced by
SessionInfo().to_dict() (e.g., status "unknown", id "", title "", and usage
{"input_tokens": 0, "output_tokens": 0}) so LocalManagedAgent.retrieve_session()
and test_empty_session_anthropic use identical schema assertions; locate the
test function test_empty_session_local and replace the status and related
assertions to mirror test_empty_session_anthropic (or assert equality against
SessionInfo().to_dict()).
In `@src/praisonai/praisonai/integrations/_session_info.py`:
- Around line 32-39: The __post_init__ method in SessionInfo currently uses an
elif chain that prevents adding "output_tokens" when both keys are missing;
update SessionInfo.__post_init__ to initialize usage with defaults using
independent checks or usage.setdefault for both "input_tokens" and
"output_tokens" (e.g., if self.usage is None: set full default dict, else call
self.usage.setdefault("input_tokens", 0) and
self.usage.setdefault("output_tokens", 0)) so both keys are guaranteed; add a
unit test constructing SessionInfo(usage={}) and asserting both tokens exist
with 0 to prevent regression.
In `@src/praisonai/praisonai/integrations/managed_agents.py`:
- Around line 593-599: The SessionInfo construction uses getattr(sess, "status",
"unknown") and getattr(sess, "title", "") which don't guard against attributes
that exist but are None; update the coercion to treat None as the default by
using expressions like (getattr(sess, "status", None) or "unknown") and
(getattr(sess, "title", None) or "") and similarly ensure the id uses
(getattr(sess, "id", None) or self._session_id) so SessionInfo(id=...,
status=..., title=...) always receives non-None strings; leave usage_dict
handling as-is and then return session_info.to_dict().
In `@src/praisonai/praisonai/integrations/managed_local.py`:
- Around line 693-715: The retrieve_session method in ManagedLocal is diverging
from Anthropic’s unified defaults by returning id=None and status="none" when no
session exists; change SessionInfo construction in retrieve_session so id uses
an empty string when self._session_id is falsy (e.g. id=self._session_id or "")
and status uses "idle" when there is a session else "unknown" (e.g.
status="idle" if self._session_id else "unknown"), keeping usage the same and
then return session_info.to_dict(); also update the corresponding test
(test_empty_session_local) to assert id == "" and status == "unknown".
---
Nitpick comments:
In `@src/praisonai-agents/tests/managed/test_session_info_schema.py`:
- Around line 1-10: This test file lives under tests/managed/ instead of the
repo's standardized test directories; move
src/praisonai-agents/tests/managed/test_session_info_schema.py into the unit
test tree (e.g., tests/unit/managed/test_session_info_schema.py) so it follows
the unit/integration/e2e layout, update any relative imports or test discovery
references if present, and ensure test runners/CI configuration (if explicitly
listing paths) include the new location; verify the test still imports the same
symbols (e.g., SessionInfo schema checks in this module) and passes discovery
after relocation.
- Around line 139-164: The test test_schema_equality_between_backends currently
only exercises LocalManagedAgent; update it to also exercise
AnthropicManagedAgent (or mock its client like in
test_anthropic_retrieve_session_schema) so you actually compare schemas:
instantiate or mock AnthropicManagedAgent to produce anthropic_result, then
assert set(local_result.keys()) == set(anthropic_result.keys()) and validate
matching value types for fields like "id", "status", "title", and that "usage"
is a dict containing "input_tokens" and "output_tokens"; keep LocalManagedAgent
usage and token setup as-is and mirror the same non-None checks for the
anthropic_result.
In `@src/praisonai/praisonai/integrations/_session_info.py`:
- Around line 29-30: The field `usage` is declared as `usage: Dict[str, int] =
None` which mismatches its type and leaves a bare string after the field; change
the annotation to `Optional[Dict[str, int]] = None` (import Optional from
typing) if you intend to keep the None sentinel and rely on `__post_init__`
normalization, or better replace it with `usage: Dict[str, int] =
field(default_factory=dict)` (import field from dataclasses) to avoid None
entirely; also remove or convert the trailing bare string into a proper class
docstring or a comment so tooling picks it up.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 8e79ed63-5415-483d-8a17-467458dde2a6
📒 Files selected for processing (6)
src/praisonai-agents/praisonaiagents/managed/__init__.pysrc/praisonai-agents/tests/managed/test_session_info_schema.pysrc/praisonai-agents/tests/unit/test_managed_backend.pysrc/praisonai/praisonai/integrations/_session_info.pysrc/praisonai/praisonai/integrations/managed_agents.pysrc/praisonai/praisonai/integrations/managed_local.py
Agent-Logs-Url: https://github.com/MervinPraison/PraisonAI/sessions/2663770e-de83-461c-8b12-3d3571e7e9ee Co-authored-by: MervinPraison <454862+MervinPraison@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR addresses issue #1429 by improving managed-agents discoverability and unifying the retrieve_session() return schema across managed backends.
Changes:
- Lazily re-export
ManagedBackendProtocolfrompraisonaiagents.managedto supportfrom praisonaiagents.managed import ManagedBackendProtocol. - Introduce a shared
SessionInfodataclass and update both Anthropic + Local backends to return a consistent session-info dict viaSessionInfo.to_dict(). - Add/adjust tests to validate the unified schema and updated no-session behavior.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
src/praisonai-agents/praisonaiagents/managed/__init__.py |
Adds lazy re-export of ManagedBackendProtocol and includes it in __all__. |
src/praisonai/praisonai/integrations/_session_info.py |
New SessionInfo dataclass intended to enforce a unified schema and defaults. |
src/praisonai/praisonai/integrations/managed_agents.py |
Updates AnthropicManagedAgent.retrieve_session() to return SessionInfo.to_dict(), including defaults when no session. |
src/praisonai/praisonai/integrations/managed_local.py |
Updates LocalManagedAgent.retrieve_session() to return SessionInfo.to_dict() with the unified keys. |
src/praisonai-agents/tests/unit/test_managed_backend.py |
Updates the “no session” unit test to assert the unified default schema. |
src/praisonai-agents/tests/managed/test_session_info_schema.py |
New tests for SessionInfo defaults and backend schema shape consistency. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| usage: Dict[str, int] = None | ||
| """Token usage tracking with input_tokens and output_tokens""" | ||
|
|
||
| def __post_init__(self): | ||
| """Ensure usage field has proper defaults.""" | ||
| if self.id is None: | ||
| self.id = "" | ||
| if self.status is None: | ||
| self.status = "unknown" | ||
| if self.title is None: | ||
| self.title = "" |
There was a problem hiding this comment.
SessionInfo.post_init uses an elif chain when filling missing token keys, so usage={} will only get input_tokens and still miss output_tokens. This contradicts the stated invariant that both token fields are always present. Consider normalizing with two independent checks (or setdefault) after ensuring usage is a dict. Also, usage is annotated as Dict[str, int] but defaults to None; update the type to Optional[Dict[str, int]] (or use field(default_factory=...)) to avoid type inconsistencies.
| "usage": { | ||
|
|
||
| session_info = SessionInfo( | ||
| id=self._session_id or "", |
There was a problem hiding this comment.
LocalManagedAgent._session_id is declared as Optional[str] and is initialized to None, but retrieve_session() passes it straight into SessionInfo as id=self._session_id. When no session exists this will return {"id": None, ...} rather than the unified schema's string default. Coerce id to an empty string (e.g., self._session_id or "") or have SessionInfo normalize id=None to "" in post_init.
| id=self._session_id or "", | |
| id=self._session_id or "", |
| id=self._session_id or "", | ||
| status="idle" if self._session_id else "none", | ||
| title="", # Local agent doesn't track titles |
There was a problem hiding this comment.
This implementation returns status="none" when there is no session, while the unified SessionInfo defaults (and the Anthropic backend's no-session behavior) use status="unknown" per the #1429 acceptance criteria. To keep schema semantics consistent across backends, align the no-session status value (or document/encode backend-specific statuses explicitly if intentional).
| assert result["status"] == "idle" | ||
| assert result["title"] == "" | ||
| assert result["usage"] == {"input_tokens": 200, "output_tokens": 100} | ||
|
|
||
|
|
||
| def test_schema_equality_between_backends(): | ||
| """Test that both backends return schemas with identical structure.""" | ||
| from praisonai.integrations.managed_local import LocalManagedAgent | ||
|
|
||
| # Test LocalManagedAgent (easier to set up) | ||
| local_agent = LocalManagedAgent() | ||
| local_agent._session_id = "test-session" | ||
| local_agent.total_input_tokens = 0 | ||
| local_agent.total_output_tokens = 0 | ||
|
|
||
| local_result = local_agent.retrieve_session() |
There was a problem hiding this comment.
test_schema_equality_between_backends claims to validate equality between Anthropic and Local backends, but it only exercises LocalManagedAgent and checks its keys. This won't catch drift between implementations. Consider either (1) mocking AnthropicManagedAgent similarly to test_anthropic_retrieve_session_schema and asserting the two dicts have identical key sets + value types/defaults, or (2) renaming the test to reflect what it actually verifies.
| m = ManagedAgent(api_key="test-key") | ||
| assert m.retrieve_session() == {} | ||
| info = m.retrieve_session() | ||
| assert info["id"] == "" | ||
| assert info["status"] == "unknown" | ||
| assert info["usage"] == {"input_tokens": 0, "output_tokens": 0} |
There was a problem hiding this comment.
This test constructs ManagedAgent(api_key="test-key") without forcing provider="anthropic" or patching ANTHROPIC_API_KEY/CLAUDE_API_KEY. Since ManagedAgent() auto-detects provider based on environment variables (and does not currently switch to Anthropic just because api_key is passed), this assertion can fail in environments where those env vars are unset (it would return LocalManagedAgent, which reports a different no-session status). Make the test deterministic by either passing provider="anthropic" or patching the env vars in this test.
There was a problem hiding this comment.
MervinPraison has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.
|
@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAI (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on Python packages (praisonaiagents, praisonai). Do NOT modify praisonai-rust or praisonai-ts. Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding. Phase 1: Review per AGENTS.md
Phase 2: FIX Valid Issues Phase 3: Final Verdict |
|
Claude finished @MervinPraison's task —— View job 🔍 Final Architecture Review & Bug FixesTodo List:
🏗️ Architecture Review (AGENTS.md Compliance):✅ Protocol-driven: Changes properly use SessionInfo dataclass as protocol 🔧 Issues Fixed & Pushed:✅ SessionInfo.post_init bug: Fixed Files Modified:
✅ Final Verdict:APPROVED — All critical issues identified by reviewers have been resolved. The PR successfully:
This PR is ready for merge. All acceptance criteria from issue #1429 have been met. |
Completed a full review and applied a targeted fix in Findings:
Reviewer feedback context:
Suggested improvement (optional follow-up): # _session_info.py
from dataclasses import dataclass, field
usage: Dict[str, int] = field(default_factory=lambda: {"input_tokens": 0, "output_tokens": 0})This would remove |
- Fix SessionInfo.__post_init__ elif chain bug preventing output_tokens initialization - Unify LocalManagedAgent status to 'unknown' (matching Anthropic backend) - Fix type annotation: usage: Optional[Dict[str, int]] = None - Improve getattr() None handling in AnthropicManagedAgent - Update test expectations for unified schema consistency Addresses review feedback from Gemini, CodeRabbit, Qodo, and Copilot. Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>
There was a problem hiding this comment.
MervinPraison has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.
Focused re-do of the #1429 scope from the closed monolithic PR #1437, rebased onto current main.
Scope (5 files)
praisonaiagents/managed/__init__.py— re-exportsManagedBackendProtocollazily sofrom praisonaiagents.managed import ManagedBackendProtocolworks.praisonai/integrations/_session_info.py(new) —SessionInfodataclass (id, status, title, usage) with.to_dict().praisonai/integrations/managed_agents.py—AnthropicManagedAgent.retrieve_sessionreturnsSessionInfo.to_dict().praisonai/integrations/managed_local.py—LocalManagedAgent.retrieve_sessionreturnsSessionInfo.to_dict().tests/managed/test_session_info_schema.py(new) — 8 tests.Local validation
tests/managed/test_session_info_schema.py— 8/8 pass.test_retrieve_session_no_sessionadapted to the new unified schema per Managed Agents: re-export ManagedBackendProtocol and unify retrieve_session schema #1429 acceptance criteria; committed in 62496c3).from praisonaiagents.managed import ManagedBackendProtocolconfirmed working.Acceptance criteria matched
SessionInfodataclass with all fields always present.id/status/usagekeys kept).Closes #1429.
Summary by CodeRabbit
New Features
Bug Fixes