Files
hive/examples/templates/email_reply_agent/nodes/__init__.py
T
2026-04-07 13:42:39 -07:00

125 lines
4.6 KiB
Python

"""Node definitions for Email Reply Agent."""
from framework.orchestrator import NodeSpec
# Node 1: Intake (client-facing)
intake_node = NodeSpec(
id="intake",
name="Intake",
description="Gather email filter criteria from user",
node_type="event_loop",
client_facing=True,
max_node_visits=0,
input_keys=["batch_complete", "restart"],
output_keys=["filter_criteria"],
nullable_output_keys=["batch_complete", "restart"],
success_criteria="Filter criteria is specific enough to search Gmail (sender, subject, date range, or keywords).",
system_prompt="""\
You are an intake specialist for email replies. Your ONLY job is to gather filter criteria and call set_output.
If the user has already provided criteria in their message, IMMEDIATELY call:
set_output("filter_criteria", {"sender_pattern": "...", "date_range": "...", "max_results": 50, "tone_guidance": "..."})
DO NOT:
- Read files
- Search files
- List directories
- Ask for confirmation if criteria are already provided
If you need more information, ask ONE brief question. Otherwise, call set_output immediately.
After batch_complete or restart, acknowledge and ask for next criteria.
""",
tools=[],
)
# Node 2: Search (autonomous)
search_node = NodeSpec(
id="search",
name="Search Emails",
description="Search Gmail for unreplied emails matching filter criteria",
node_type="event_loop",
client_facing=False,
max_node_visits=0,
input_keys=["filter_criteria"],
output_keys=["email_list"],
nullable_output_keys=[],
success_criteria="Found unreplied emails matching criteria with sender, subject, snippet, message_id.",
system_prompt="""\
You are a Gmail search agent. Find unreplied emails matching the user's filter criteria.
## Workflow:
1. Build Gmail search query from filter_criteria:
- Use "is:unread" to find unreplied (standard proxy for unreplied)
- Add sender: from:(pattern) if sender_pattern provided
- Add subject: subject:(keywords) if subject_keywords provided
- Add after: after:YYYY/MM/DD if date_range provided
- Limit to max_results (default 50)
2. Call gmail_list_messages with the query
3. For each message_id, call gmail_get_message to get full content (sender, subject, body)
4. Build a structured list of emails
## Output:
set_output("email_list", JSON list with fields for each email:
- message_id
- sender (email address)
- sender_name (if available)
- subject
- snippet (first 200 chars of body)
- received_date (ISO format)
)
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 & Reply (client-facing)
confirm_draft_node = NodeSpec(
id="confirm-draft",
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 replies sent for each.",
system_prompt="""\
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 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 subject and snippet
2. Use tone_guidance from filter_criteria + any user-specified preferences
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
2. Call: set_output("restart", True)
Personalization rules:
- Reference specific details from their email (questions asked, topics mentioned)
- Match their formality level (formal→formal, casual→casual)
- If tone_guidance specifies style, follow it
- Keep replies concise but warm
""",
tools=["gmail_reply_email"],
)
__all__ = ["intake_node", "search_node", "confirm_draft_node"]