feat: use send instead of draft for email reply agent
This commit is contained in:
@@ -18,7 +18,7 @@ from .nodes import intake_node, search_node, confirm_draft_node
|
||||
goal = Goal(
|
||||
id="email-reply-goal",
|
||||
name="Email Reply Agent",
|
||||
description="Filter unreplied emails by user criteria, confirm recipients, draft personalized replies.",
|
||||
description="Filter unreplied emails by user criteria, confirm recipients, send personalized replies.",
|
||||
success_criteria=[
|
||||
SuccessCriterion(
|
||||
id="sc-filter",
|
||||
@@ -29,15 +29,15 @@ goal = Goal(
|
||||
),
|
||||
SuccessCriterion(
|
||||
id="sc-confirm",
|
||||
description="User confirms recipient list before drafting",
|
||||
description="User confirms recipient list before sending",
|
||||
metric="Confirmation rate",
|
||||
target="100%",
|
||||
weight=0.25,
|
||||
),
|
||||
SuccessCriterion(
|
||||
id="sc-personalize",
|
||||
description="Drafts are personalized based on email content and tone guidance",
|
||||
metric="User satisfaction with draft relevance",
|
||||
description="Replies are personalized based on email content and tone guidance",
|
||||
metric="User satisfaction with reply relevance",
|
||||
target="85%",
|
||||
weight=0.40,
|
||||
),
|
||||
@@ -45,7 +45,7 @@ goal = Goal(
|
||||
constraints=[
|
||||
Constraint(
|
||||
id="c-privacy",
|
||||
description="Never auto-send emails; all replies go to drafts for user review",
|
||||
description="Never send emails without explicit user confirmation; always present recipient list and get approval first",
|
||||
constraint_type="hard",
|
||||
category="functional",
|
||||
),
|
||||
@@ -103,7 +103,7 @@ terminal_nodes = []
|
||||
|
||||
# Module-level vars read by AgentRunner.load()
|
||||
conversation_mode = "continuous"
|
||||
identity_prompt = "You are a helpful email reply assistant that filters unreplied emails and drafts personalized responses."
|
||||
identity_prompt = "You are a helpful email reply assistant that filters unreplied emails and sends personalized responses."
|
||||
loop_config = {
|
||||
"max_iterations": 100,
|
||||
"max_tool_calls_per_turn": 30,
|
||||
|
||||
@@ -36,7 +36,7 @@ default_config = RuntimeConfig()
|
||||
class AgentMetadata:
|
||||
name: str = "Email Reply Agent"
|
||||
version: str = "1.0.0"
|
||||
description: str = "Filter unreplied emails, confirm recipients, draft personalized replies."
|
||||
description: str = "Filter unreplied emails, confirm recipients, send personalized replies."
|
||||
intro_message: str = "Tell me which emails you want to reply to (e.g., 'emails from @company.com in the last week')."
|
||||
|
||||
|
||||
|
||||
@@ -74,39 +74,39 @@ If no emails found, set empty array: set_output("email_list", [])
|
||||
tools=["gmail_list_messages", "gmail_get_message", "gmail_batch_get_messages"],
|
||||
)
|
||||
|
||||
# Node 3: Confirm & Draft (client-facing)
|
||||
# Node 3: Confirm & Reply (client-facing)
|
||||
confirm_draft_node = NodeSpec(
|
||||
id="confirm-draft",
|
||||
name="Confirm & Draft",
|
||||
description="Present emails for confirmation, draft personalized replies",
|
||||
name="Confirm & Reply",
|
||||
description="Present emails for confirmation, send personalized replies",
|
||||
node_type="event_loop",
|
||||
client_facing=True,
|
||||
max_node_visits=0,
|
||||
input_keys=["email_list", "filter_criteria"],
|
||||
output_keys=["batch_complete", "restart"],
|
||||
nullable_output_keys=["batch_complete", "restart"],
|
||||
success_criteria="User confirmed recipients and personalized drafts created for each.",
|
||||
success_criteria="User confirmed recipients and personalized replies sent for each.",
|
||||
system_prompt="""\
|
||||
You are a Gmail reply drafter. Present emails for confirmation, then draft personalized replies.
|
||||
You are a Gmail reply assistant. Present emails for confirmation, then send personalized replies.
|
||||
|
||||
**STEP 1 — Present for confirmation (text only, NO tool calls):**
|
||||
1. Show the email list in readable format:
|
||||
- #. Sender Name <email> - Subject (Date)
|
||||
- Snippet: first 150 chars
|
||||
2. Ask: "These are the people to reply to. Confirm? Any tone preferences or specific messages?"
|
||||
2. Ask: "These are the emails to reply to. Confirm? Any tone preferences or specific messages?"
|
||||
3. Wait for user response
|
||||
|
||||
**STEP 2 — Handle user response:**
|
||||
|
||||
If user CONFIRMS (says yes, go ahead, sounds good, etc.):
|
||||
For EACH email in email_list:
|
||||
1. Read the sender, subject, and snippet
|
||||
1. Read the subject and snippet
|
||||
2. Use tone_guidance from filter_criteria + any user-specified preferences
|
||||
3. Call gmail_create_draft with:
|
||||
- to: sender email
|
||||
- subject: "Re: " + original_subject
|
||||
- body: personalized 2-4 sentence reply based on email context
|
||||
4. After all drafts created, call: set_output("batch_complete", True)
|
||||
3. Call gmail_reply_email with:
|
||||
- message_id: the email's message_id
|
||||
- html: personalized 2-4 sentence reply based on email context
|
||||
(The tool automatically handles recipient, subject, and threading)
|
||||
4. After all replies sent, call: set_output("batch_complete", True)
|
||||
|
||||
If user wants to CHANGE LOGIC/FILTER (says change filter, different criteria, not these emails, wrong emails, etc.):
|
||||
1. Acknowledge their request
|
||||
@@ -118,7 +118,7 @@ Personalization rules:
|
||||
- If tone_guidance specifies style, follow it
|
||||
- Keep replies concise but warm
|
||||
""",
|
||||
tools=["gmail_create_draft"],
|
||||
tools=["gmail_reply_email"],
|
||||
)
|
||||
|
||||
__all__ = ["intake_node", "search_node", "confirm_draft_node"]
|
||||
|
||||
@@ -60,10 +60,10 @@ class TestAgentStructure:
|
||||
assert "gmail_list_messages" in search_node.tools
|
||||
assert "gmail_get_message" in search_node.tools
|
||||
|
||||
def test_confirm_draft_node_has_draft_tool(self, agent_module):
|
||||
"""Confirm-draft node has draft creation tool."""
|
||||
def test_confirm_draft_node_has_reply_tool(self, agent_module):
|
||||
"""Confirm-draft node has reply tool."""
|
||||
draft_node = next(n for n in agent_module.nodes if n.id == "confirm-draft")
|
||||
assert "gmail_create_draft" in draft_node.tools
|
||||
assert "gmail_reply_email" in draft_node.tools
|
||||
|
||||
def test_confirm_draft_node_has_restart_output(self, agent_module):
|
||||
"""Confirm-draft node has restart output key for logic changes."""
|
||||
|
||||
Reference in New Issue
Block a user