Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 38 additions & 11 deletions examples/python/handoff/handoff_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,70 @@
This example shows how agents can hand off tasks to specialized agents.
"""

from pydantic import BaseModel
from typing import TypedDict, Optional
from praisonaiagents import Agent
Comment on lines +7 to 9

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Import the handoff helper function from praisonaiagents to configure the typed handoffs.

Suggested change
from pydantic import BaseModel
from typing import TypedDict, Optional
from praisonaiagents import Agent
from pydantic import BaseModel
from typing import TypedDict, Optional
from praisonaiagents import Agent, handoff

Comment on lines +7 to 9

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 Both issues above (the broken input_payload_schema parameter and the unsupported tuple format) can be fixed using the existing handoff() helper, which already supports input_type= and is correctly processed by _process_handoffs. Here is the corrected version using the real API.

Suggested change
from pydantic import BaseModel
from typing import TypedDict, Optional
from praisonaiagents import Agent
from pydantic import BaseModel
from typing import Optional
from praisonaiagents import Agent
from praisonaiagents.agent.handoff import handoff

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!


# Create specialized agents
# Define typed payload contracts for each handoff
class BillingPayload(BaseModel):
account_id: str
invoice_amount: Optional[float] = None
issue_type: str = "billing"

class RefundPayload(TypedDict):
transaction_id: str
amount: float
reason: str
Comment on lines +17 to +20

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 TypedDict does not provide runtime validation

The PR description explicitly states the fix "enabling runtime validation", but TypedDict is a static-analysis-only construct — at runtime a TypedDict instance is an ordinary dict with no field-presence or type checking. Only BaseModel (Pydantic) enforces validation on instantiation. Using TypedDict here for RefundPayload contradicts the stated goal and would be misleading to readers trying to follow the pattern. Consider replacing it with a second BaseModel subclass for consistency and actual runtime enforcement.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!


class TechnicalPayload(BaseModel):
error_code: Optional[str] = None
device_info: str
user_agent: str
Comment on lines +12 to +25

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Required payload fields are stricter than the sample inputs can satisfy.

Line 13, Lines 18-20, and Lines 24-25 define mandatory fields that are absent from the plain-text sample requests on Lines 79-82. In a validated handoff flow, this either fails routing or pushes agents to invent data.

Proposed example-safe schema adjustment
 class BillingPayload(BaseModel):
-    account_id: str
+    account_id: Optional[str] = None
     invoice_amount: Optional[float] = None
     issue_type: str = "billing"

-class RefundPayload(TypedDict):
+class RefundPayload(TypedDict, total=False):
     transaction_id: str
     amount: float
     reason: str

 class TechnicalPayload(BaseModel):
     error_code: Optional[str] = None
-    device_info: str
-    user_agent: str
+    device_info: Optional[str] = None
+    user_agent: Optional[str] = None
-    When handing off, populate the appropriate payload with extracted data.""",
+    When handing off, populate only fields explicitly present in the request. Do not invent missing values.""",

Also applies to: 78-82

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/python/handoff/handoff_basic.py` around lines 12 - 25, The schemas
are too strict for the sample plain-text requests: relax required fields so the
example inputs validate. Update BillingPayload (make account_id Optional[str] or
provide a default), adjust TechnicalPayload fields device_info and user_agent to
Optional[str] (or give defaults), and change RefundPayload from a fully required
TypedDict to an optional/partial TypedDict (use total=False or NotRequired on
transaction_id/amount/reason) so missing fields in the sample requests won't
break validation; keep the class/typing names BillingPayload, TechnicalPayload,
and RefundPayload when making these changes.


# Create specialized agents that expect typed payloads from the triage
# (In a real scenario, the agents would validate and use the payload.)
billing_agent = Agent(
name="Billing Agent",
role="Billing Specialist",
goal="Handle all billing-related inquiries and tasks",
backstory="I am an expert in billing systems, payment processing, and invoice management."
goal="Handle all billing-related inquiries and tasks using structured data",
backstory="I am an expert in billing systems, payment processing, and invoice management.",
input_payload_schema=BillingPayload # hypothetical but consistent with design
)
Comment on lines +34 to 35

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Locate Agent class and constructor signatures =="
rg -n --type=py -C3 '\bclass\s+Agent\b|def __init__\('

echo "== Find input_payload_schema declaration and consumption =="
rg -n --type=py -C4 '\binput_payload_schema\b'

echo "== Find handoff parsing logic and tuple/schema handling =="
rg -n --type=py -C4 '\bhandoffs\b|HandoffValidationError|model_validate|TypeAdapter|TypedDict|isinstance\([^)]*tuple'

Repository: MervinPraison/PraisonAI

Length of output: 50380


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Search for input_payload_schema usage =="
rg -n --type=py --hidden --glob '!**/node_modules/**' --glob '!**/.venv/**' "input_payload_schema" . || true

echo
echo "== Search for handoffs usage =="
rg -n --type=py --hidden --glob '!**/node_modules/**' --glob '!**/.venv/**' "\bhandoffs\b" . || true

echo
echo "== Locate the real Agent class implementation =="
rg -n --type=py --hidden --glob '!**/node_modules/**' --glob '!**/.venv/**' "class\s+Agent\b" src examples || true

echo
echo "== Locate Agent.__init__ in implementation files =="
# Try to find __init__ near the Agent class definition lines we found above
rg -n --type=py "class\s+Agent\b" -S src examples | cut -d: -f1 | sort -u | while read -r f; do
  echo "--- $f : __init__ signatures near Agent ---"
  rg -n --type=py "def __init__\(" "$f" -n || true
done

Repository: MervinPraison/PraisonAI

Length of output: 16284


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Show handoff_basic example around Lines 25-90 =="
nl -ba examples/python/handoff/handoff_basic.py | sed -n '1,120p'

echo
echo "== Search for input_payload_schema in implementation =="
rg -n --type=py "input_payload_schema" src/praisonai-agents src/praisonai examples || true

echo
echo "== Inspect Agent.__init__ signature and handoff-related params =="
nl -ba src/praisonai-agents/praisonaiagents/agent/agent.py | sed -n '480,860p'

echo
echo "== Inspect Agent handoff processing code =="
nl -ba src/praisonai-agents/praisonaiagents/agent/agent.py | sed -n '1800,1925p'
nl -ba src/praisonai-agents/praisonaiagents/agent/agent.py | sed -n '4520,4660p'

echo
echo "== Inspect Handoff implementation for schema/tuple handling =="
nl -ba src/praisonai-agents/praisonaiagents/agent/handoff.py | sed -n '1,220p'
nl -ba src/praisonai-agents/praisonaiagents/agent/handoff.py | sed -n '640,880p'

echo
echo "== Search for tuple parsing or (agent, schema) patterns =="
rg -n --type=py "isinstance\([^)]*tuple|Tuple\[|\(.*\,.*\)" src/praisonai-agents/praisonaiagents/agent/handoff.py src/praisonai-agents/praisonaiagents/agent/agent.py || true

Repository: MervinPraison/PraisonAI

Length of output: 180


🏁 Script executed:

#!/bin/bash
set -euo pipefail

py_print_range () {
  local path="$1"; local start="$2"; local end="$3";
  python3 - "$path" "$start" "$end" <<'PY'
import sys
path,start,end=sys.argv[1],int(sys.argv[2]),int(sys.argv[3])
with open(path,'r',encoding='utf-8') as f:
    for i,line in enumerate(f, start=1):
        if start <= i <= end:
            print(f"{i:5d} {line.rstrip()}")
        elif i > end:
            break
PY
}

echo "== handoff_basic.py (lines ~20-90) =="
py_print_range "examples/python/handoff/handoff_basic.py" 20 95

echo
echo "== Agent implementation: signature area (agent.py ~480-860) =="
py_print_range "src/praisonai-agents/praisonaiagents/agent/agent.py" 480 860

echo
echo "== Agent handoff processing (agent.py ~1800-1925) =="
py_print_range "src/praisonai-agents/praisonaiagents/agent/agent.py" 1820 1925

echo
echo "== Agent 'Process handoffs' function (agent.py ~4520-4660) =="
py_print_range "src/praisonai-agents/praisonaiagents/agent/agent.py" 4510 4665

echo
echo "== Handoff implementation: top (handoff.py ~1-220) =="
py_print_range "src/praisonai-agents/praisonaiagents/agent/handoff.py" 1 220

echo
echo "== Handoff implementation: config/schema bits (~640-880) =="
py_print_range "src/praisonai-agents/praisonaiagents/agent/handoff.py" 640 880

echo
echo "== Search for tuple parsing/typing in handoff.py =="
rg -n --type=py "tuple|Tuple\[|isinstance\([^)]*tuple|schema|input_payload_schema" src/praisonai-agents/praisonaiagents/agent/handoff.py || true

echo
echo "== Search for input_payload_schema in Agent+handoff code =="
rg -n --type=py "input_payload_schema" src/praisonai-agents/praisonaiagents/agent/agent.py src/praisonai-agents/praisonaiagents/agent/handoff.py || true

Repository: MervinPraison/PraisonAI

Length of output: 50379


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "== Search for input_payload_schema across codebase =="
rg -n --type=py "input_payload_schema" . || true

echo
echo "== Search for helper function handoff(...) in handoff.py =="
rg -n --type=py "def\s+handoff\s*\(" src/praisonai-agents/praisonaiagents/agent/handoff.py || true

echo
echo "== Show handoff(...) function definition (if present) =="
python3 - <<'PY'
import re, itertools
path="src/praisonai-agents/praisonaiagents/agent/handoff.py"
with open(path,'r',encoding='utf-8') as f:
    lines=f.readlines()
for i,l in enumerate(lines, start=1):
    if re.search(r"def\s+handoff\s*\(", l):
        start=max(1,i-20); end=min(len(lines), i+120)
        for j in range(start, end+1):
            print(f"{j:5d} {lines[j-1].rstrip()}")
        break
PY

echo
echo "== Search for input_type usage in handoff.py =="
rg -n --type=py "input_type|input_filter|__annotations__" src/praisonai-agents/praisonaiagents/agent/handoff.py || true

echo
echo "== Show the part where handoff tool params are built (input_type -> tool schema) =="
python3 - <<'PY'
import re
path="src/praisonai-agents/praisonaiagents/agent/handoff.py"
with open(path,'r',encoding='utf-8') as f:
    lines=f.readlines()
# find first occurrence of "input_type" near where tool definition generation happens
hits=[]
for i,l in enumerate(lines, start=1):
    if "input_type" in l and ("if self.input_type" in l or "Add input type annotations" in l):
        hits.append(i)
if not hits:
    # fallback: show around "Add input type annotations"
    for i,l in enumerate(lines, start=1):
        if "Add input type annotations" in l:
            hits.append(i)
if hits:
    i=hits[0]
    start=max(1,i-60); end=min(len(lines), i+220)
    for j in range(start, end+1):
        print(f"{j:5d} {lines[j-1].rstrip()}")
else:
    print("No matching blocks found.")
PY

echo
echo "== Agent._process_handoffs tuple handling evidence =="
python3 - <<'PY'
path="src/praisonai-agents/praisonaiagents/agent/agent.py"
with open(path,'r',encoding='utf-8') as f:
    lines=f.readlines()
# locate def _process_handoffs
import re
idx=None
for i,l in enumerate(lines, start=1):
    if re.search(r"def\s+_process_handoffs\s*\(", l):
        idx=i; break
if idx:
    start=max(1,idx-30); end=min(len(lines), idx+80)
    for j in range(start, end+1):
        print(f"{j:5d} {lines[j-1].rstrip()}")
else:
    print("Couldn't find _process_handoffs")
PY

echo
echo "== Verify Agent __init__ wrapper in src/praisonai/praisonai/agent.py accepts/forwards unknown kwargs =="
python3 - <<'PY'
path="src/praisonai/praisonai/agent.py"
with open(path,'r',encoding='utf-8') as f:
    lines=f.readlines()
for i,l in enumerate(lines, start=1):
    if "class Agent" in l:
        # print some range that includes __init__
        pass
# find __init__
import re
for i,l in enumerate(lines, start=1):
    if re.search(r"def\s+__init__\s*\(", l) and "cli_backend" in l:
        start=max(1,i-20); end=min(len(lines), i+80)
        for j in range(start,end+1):
            print(f"{j:5d} {lines[j-1].rstrip()}")
        break
PY

Repository: MervinPraison/PraisonAI

Length of output: 29734


Fix typed handoff API usage in handoff_basic.py (no input_payload_schema, tuples won’t be processed)

  • praisonaiagents.Agent doesn’t accept/handle input_payload_schema; this kwarg is only present in the example.
  • Agent(handoffs=...) processes only Agent instances (via name + chat) or Handoff instances; entries like (billing_agent, BillingPayload) are invalid and will be skipped, so the typed payload routing won’t work.
  • Replace the tuples/input_payload_schema= with supported handoff config, e.g. handoffs=[handoff(billing_agent, input_type=BillingPayload), ...] (or Handoff(..., input_type=...)).

File: examples/python/handoff/handoff_basic.py (lines 34-35, 42-43, 50-51, 66-70)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/python/handoff/handoff_basic.py` around lines 34 - 35, The current
example uses an unsupported pattern: passing tuples like (billing_agent,
BillingPayload) or the kwarg input_payload_schema to Agent, which Agent.handoffs
processing will skip; update the handoff configuration to use supported handoff
objects instead (e.g., call handoff(billing_agent, input_type=BillingPayload) or
construct Handoff(billing_agent, input_type=BillingPayload)) and pass those in
Agent(handoffs=[...]) so the billing_agent and BillingPayload are properly
registered; locate uses of Agent(handoffs=...), the tuple entries
(billing_agent, BillingPayload), and input_payload_schema in handoff_basic.py
and replace them with handoff(...) or Handoff(..., input_type=...).


refund_agent = Agent(
name="Refund Agent",
role="Refund Specialist",
goal="Process refund requests and handle refund-related issues",
backstory="I specialize in processing refunds, evaluating refund eligibility, and ensuring customer satisfaction."
goal="Process refund requests and handle refund-related issues with validated data",
backstory="I specialize in processing refunds, evaluating refund eligibility, and ensuring customer satisfaction.",
input_payload_schema=RefundPayload
)

technical_support_agent = Agent(
name="Technical Support",
role="Technical Support Specialist",
goal="Resolve technical issues and provide technical assistance",
backstory="I am skilled in troubleshooting technical problems and providing solutions."
goal="Resolve technical issues and provide technical assistance with structured context",
backstory="I am skilled in troubleshooting technical problems and providing solutions.",
input_payload_schema=TechnicalPayload
)
Comment on lines 29 to 51

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The Agent class constructor does not accept an input_payload_schema parameter. Passing this parameter will result in a TypeError at runtime. The typed payload contract should instead be defined on the handoff itself using the handoff helper function with the input_type parameter.

billing_agent = Agent(
    name="Billing Agent",
    role="Billing Specialist",
    goal="Handle all billing-related inquiries and tasks using structured data",
    backstory="I am an expert in billing systems, payment processing, and invoice management."
)

refund_agent = Agent(
    name="Refund Agent",
    role="Refund Specialist",
    goal="Process refund requests and handle refund-related issues with validated data",
    backstory="I specialize in processing refunds, evaluating refund eligibility, and ensuring customer satisfaction."
)

technical_support_agent = Agent(
    name="Technical Support",
    role="Technical Support Specialist",
    goal="Resolve technical issues and provide technical assistance with structured context",
    backstory="I am skilled in troubleshooting technical problems and providing solutions."
)

Comment on lines 29 to 51

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 input_payload_schema causes TypeError at startup

Agent.__init__ has no input_payload_schema parameter and does not accept **kwargs, so every agent construction in this file will raise TypeError: __init__() got an unexpected keyword argument 'input_payload_schema'. The example cannot be run at all. The inline comment even acknowledges this is "hypothetical" — but that means it should not be in runnable example code. The correct, already-supported mechanism for attaching a typed schema to a handoff is handoff(agent, input_type=PayloadClass) from praisonaiagents.agent.handoff.


# Create a triage agent with handoffs to specialized agents
# Create a triage agent with typed handoffs
triage_agent = Agent(
name="Triage Agent",
role="Customer Service Triage",
goal="Understand customer needs and route them to the appropriate specialist",
goal="Understand customer needs and route them to the appropriate specialist with a validated payload",
backstory="I analyze customer requests and direct them to the most suitable specialist for efficient resolution.",
instructions="""Analyze the customer's request and determine which specialist can best help:
- For billing questions, payment issues, or invoices, transfer to the Billing Agent
- For refund requests or refund status inquiries, transfer to the Refund Agent
- For technical problems or product issues, transfer to Technical Support

Always explain why you're transferring the customer before doing so.""",
handoffs=[billing_agent, refund_agent, technical_support_agent]
Always explain why you're transferring the customer before doing so.
When handing off, populate the appropriate payload with extracted data.""",
handoffs=[
(billing_agent, BillingPayload),
(refund_agent, RefundPayload),
(technical_support_agent, TechnicalPayload)
]
Comment on lines +66 to +70

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The handoffs list expects either Agent or Handoff instances. Passing tuples like (billing_agent, BillingPayload) will not be processed correctly by _process_handoffs and will trigger a warning, meaning the handoff tools will not be registered. Use the handoff helper function with the input_type parameter to correctly define typed handoffs.

Suggested change
handoffs=[
(billing_agent, BillingPayload),
(refund_agent, RefundPayload),
(technical_support_agent, TechnicalPayload)
]
handoffs=[
handoff(billing_agent, input_type=BillingPayload),
handoff(refund_agent, input_type=RefundPayload),
handoff(technical_support_agent, input_type=TechnicalPayload)
]

Comment on lines +66 to +70

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 Tuple format in handoffs is silently discarded

_process_handoffs only recognises Handoff instances or objects that have name and chat attributes. A (agent, PayloadClass) tuple matches neither branch and triggers logging.warning("Invalid handoff item type: <class 'tuple'>..."), so all three handoffs are registered as no-ops — the triage agent will have no transfer tools. The existing API for typed handoffs is handoff(agent, input_type=PayloadClass) (from praisonaiagents.agent.handoff), which is already fully wired into _process_handoffs and to_tool_function.

)

# Example usage
Expand Down
Loading