Skip to content
Merged
Show file tree
Hide file tree
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
15 changes: 9 additions & 6 deletions src/ad_seller/crews/inventory_crews.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
create_upsell_agent,
)
from ..config import get_settings
from ..models.flow_state import ProposalReviewOutput
from ..tools.audience import (
AudienceCapabilityTool,
AudienceValidationTool,
Expand Down Expand Up @@ -412,19 +413,21 @@ def create_proposal_review_crew(proposal_data: dict) -> Crew:
- If Reject: Clear explanation and alternatives
- Audience coverage impact on delivery confidence
- Upsell opportunities to pursue""",
expected_output="""Final proposal recommendation with:
- Decision (Accept/Counter/Reject)
- Counter-terms if applicable (including audience modifications)
- Rejection reason and alternatives if applicable
- Audience validation summary
- Prioritized upsell opportunities""",
expected_output="""A structured proposal decision with:
- decision: accept, counter, or reject
- rationale: brief explanation
- counter_price_cpm and counter_terms if countering
- rejection_reason if rejecting
- audience_summary
- upsell_opportunities""",
agent=proposal_review_agent,
context=[
pricing_check_task,
availability_check_task,
audience_validation_task,
upsell_task,
],
output_pydantic=ProposalReviewOutput,
)

return Crew(
Expand Down
15 changes: 6 additions & 9 deletions src/ad_seller/flows/proposal_handling_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from ..models.flow_state import (
ExecutionStatus,
ProposalEvaluation,
ProposalReviewOutput,
SellerFlowState,
)
from ..models.ucp import AudienceCapability, SignalType
Expand Down Expand Up @@ -419,17 +420,13 @@ async def run_crew_evaluation(self) -> None:
crew = create_proposal_review_crew(self.state.proposal_data)

try:
result = crew.kickoff()
result = await crew.kickoff_async()

# Parse crew recommendation
result_text = str(result).lower()

if "accept" in result_text:
self.state.recommendation = "accept"
elif "counter" in result_text:
self.state.recommendation = "counter"
review: Optional[ProposalReviewOutput] = result.pydantic
if review is not None:
self.state.recommendation = review.decision.value
else:
self.state.recommendation = "reject"
self._fallback_evaluation()

# Emit proposal.evaluated event
await emit_event(
Expand Down
30 changes: 30 additions & 0 deletions src/ad_seller/models/flow_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,36 @@ class ProposalEvaluation(BaseModel):
upsell_opportunities: list[str] = Field(default_factory=list)


class ProposalDecision(str, Enum):
"""Final decision on a proposal, as returned by the review crew."""

ACCEPT = "accept"
COUNTER = "counter"
REJECT = "reject"


class ProposalReviewOutput(BaseModel):
"""Structured final decision from the proposal-review crew."""

decision: ProposalDecision = Field(
description="Final recommendation: accept, counter, or reject"
)
rationale: str = Field(description="Brief explanation of why this decision was reached")
counter_price_cpm: Optional[float] = Field(
default=None, description="Counter-offer CPM, only if decision is counter"
)
counter_terms: Optional[str] = Field(
default=None, description="Full counter-terms detail, only if decision is counter"
)
rejection_reason: Optional[str] = Field(
default=None, description="Reason and alternatives, only if decision is reject"
)
audience_summary: str = Field(description="Audience validation summary")
upsell_opportunities: list[str] = Field(
default_factory=list, description="Prioritized upsell opportunities"
)


class PricingDecision(BaseModel):
"""Pricing decision for a deal."""

Expand Down
Loading