mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-07-30 10:52:15 +00:00
268 lines
8.4 KiB
Python
268 lines
8.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Smart Inbox — message triage and auto-reply with an orchestrator agent.
|
|
|
|
Usage:
|
|
python examples/messaging_hub/smart_inbox.py --demo
|
|
python examples/messaging_hub/smart_inbox.py --demo --model gpt-4o --engine cloud
|
|
python examples/messaging_hub/smart_inbox.py --channel slack
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
|
|
import click
|
|
|
|
DEMO_MESSAGES = [
|
|
"URGENT: Server is down in production, need immediate help!",
|
|
"Hey, just wanted to share this interesting article about AI agents.",
|
|
"Can you review my PR #42 by end of day? It's blocking the release.",
|
|
"Meeting reminder: Team standup at 10am tomorrow.",
|
|
"Buy now! Limited time offer on premium widgets!!!",
|
|
]
|
|
|
|
CLASSIFICATION_PROMPT = (
|
|
"You are a smart inbox assistant. Classify the following message into "
|
|
"exactly one category: URGENT, ACTION_REQUIRED, FYI, or SPAM.\n"
|
|
"Then draft a short reply if appropriate (not for SPAM).\n\n"
|
|
"Respond in this exact format:\n"
|
|
"CATEGORY: <category>\n"
|
|
"REPLY: <reply or N/A>\n\n"
|
|
"Message:\n{message}"
|
|
)
|
|
|
|
SUMMARY_PROMPT = (
|
|
"You previously triaged the following messages and their classifications:\n\n"
|
|
"{triage_log}\n\n"
|
|
"Produce a concise end-of-day summary. Group by category "
|
|
"(URGENT, ACTION_REQUIRED, FYI, SPAM) and highlight any items "
|
|
"that still need attention."
|
|
)
|
|
|
|
|
|
def _parse_classification(response: str) -> tuple[str, str]:
|
|
"""Extract category and reply from the agent response."""
|
|
category = "UNKNOWN"
|
|
reply = "N/A"
|
|
for line in response.splitlines():
|
|
stripped = line.strip()
|
|
if stripped.upper().startswith("CATEGORY:"):
|
|
category = stripped.split(":", 1)[1].strip().upper()
|
|
elif stripped.upper().startswith("REPLY:"):
|
|
reply = stripped.split(":", 1)[1].strip()
|
|
return category, reply
|
|
|
|
|
|
def _print_table(results: list[dict[str, str]]) -> None:
|
|
"""Print triage results as a formatted table."""
|
|
# Column widths
|
|
cat_w = max(len("Category"), max((len(r["category"]) for r in results), default=0))
|
|
msg_w = min(
|
|
50,
|
|
max(len("Message"), max((len(r["message"]) for r in results), default=0)),
|
|
)
|
|
rep_w = min(
|
|
40,
|
|
max(len("Reply"), max((len(r["reply"]) for r in results), default=0)),
|
|
)
|
|
|
|
header = (
|
|
f" {'#':<3} {'Category':<{cat_w}} {'Message':<{msg_w}} {'Reply':<{rep_w}}"
|
|
)
|
|
separator = " " + "-" * (len(header) - 2)
|
|
|
|
click.echo()
|
|
click.echo(header)
|
|
click.echo(separator)
|
|
|
|
for i, r in enumerate(results, 1):
|
|
msg_display = r["message"][:msg_w]
|
|
rep_display = r["reply"][:rep_w]
|
|
row = (
|
|
f" {i:<3} {r['category']:<{cat_w}}"
|
|
f" {msg_display:<{msg_w}} {rep_display:<{rep_w}}"
|
|
)
|
|
click.echo(row)
|
|
|
|
click.echo(separator)
|
|
click.echo()
|
|
|
|
|
|
def _run_demo(model: str, engine_key: str) -> None:
|
|
"""Process sample messages through the agent for classification."""
|
|
try:
|
|
from openjarvis import Jarvis
|
|
except ImportError:
|
|
click.echo(
|
|
"Error: openjarvis is not installed. "
|
|
"Install it with: uv sync --extra dev",
|
|
err=True,
|
|
)
|
|
sys.exit(1)
|
|
|
|
tools = ["think", "memory_store", "memory_search"]
|
|
|
|
click.echo("Smart Inbox — Demo Mode")
|
|
click.echo(f"Model: {model} | Engine: {engine_key}")
|
|
click.echo("=" * 60)
|
|
click.echo(f"Processing {len(DEMO_MESSAGES)} messages...\n")
|
|
|
|
try:
|
|
j = Jarvis(model=model, engine_key=engine_key)
|
|
except Exception as exc:
|
|
click.echo(
|
|
f"Error: could not initialize Jarvis — {exc}\n\n"
|
|
"Make sure your engine is running. For Ollama:\n"
|
|
" ollama serve\n"
|
|
" ollama pull qwen3:8b\n\n"
|
|
"For cloud engines, ensure API keys are set in your .env file.",
|
|
err=True,
|
|
)
|
|
sys.exit(1)
|
|
|
|
results: list[dict[str, str]] = []
|
|
|
|
try:
|
|
for idx, message in enumerate(DEMO_MESSAGES, 1):
|
|
click.echo(f" [{idx}/{len(DEMO_MESSAGES)}] Classifying: {message[:60]}...")
|
|
|
|
prompt = CLASSIFICATION_PROMPT.format(message=message)
|
|
response = j.ask(
|
|
prompt,
|
|
agent="orchestrator",
|
|
tools=tools,
|
|
temperature=0.3,
|
|
)
|
|
|
|
category, reply = _parse_classification(response)
|
|
results.append(
|
|
{"message": message, "category": category, "reply": reply}
|
|
)
|
|
click.echo(f" -> {category}")
|
|
|
|
# Print results table
|
|
_print_table(results)
|
|
|
|
# Generate end-of-day summary
|
|
click.echo("Generating end-of-day summary...\n")
|
|
triage_log = "\n".join(
|
|
f"- [{r['category']}] {r['message']}" for r in results
|
|
)
|
|
summary_prompt = SUMMARY_PROMPT.format(triage_log=triage_log)
|
|
summary = j.ask(
|
|
summary_prompt,
|
|
agent="orchestrator",
|
|
tools=tools,
|
|
temperature=0.3,
|
|
)
|
|
click.echo("End-of-Day Summary")
|
|
click.echo("-" * 40)
|
|
click.echo(summary)
|
|
|
|
except Exception as exc:
|
|
click.echo(f"Error during triage: {exc}", err=True)
|
|
sys.exit(1)
|
|
finally:
|
|
j.close()
|
|
|
|
|
|
def _run_channel(channel: str, model: str, engine_key: str) -> None:
|
|
"""Connect to a real messaging channel for live triage.
|
|
|
|
This mode requires channel credentials to be configured. See the
|
|
README for setup instructions for each supported channel.
|
|
"""
|
|
click.echo("Smart Inbox — Live Channel Mode")
|
|
click.echo(f"Channel: {channel} | Model: {model} | Engine: {engine_key}")
|
|
click.echo("=" * 60)
|
|
click.echo()
|
|
|
|
# Channel setup guidance
|
|
setup_help = {
|
|
"slack": (
|
|
"To set up Slack:\n"
|
|
" 1. Run: jarvis add slack\n"
|
|
" 2. Set SLACK_BOT_TOKEN and SLACK_APP_TOKEN in your .env\n"
|
|
" 3. Invite the bot to your target channel\n"
|
|
),
|
|
"whatsapp": (
|
|
"To set up WhatsApp:\n"
|
|
" 1. Ensure Node.js 22+ is installed\n"
|
|
" 2. Configure WhatsApp Baileys bridge (see channel docs)\n"
|
|
" 3. Scan the QR code to authenticate\n"
|
|
),
|
|
}
|
|
|
|
help_text = setup_help.get(
|
|
channel,
|
|
f"Channel '{channel}' requires appropriate credentials.\n"
|
|
f"Run: jarvis channel list to see available channels.\n",
|
|
)
|
|
|
|
click.echo(help_text)
|
|
click.echo(
|
|
"Once configured, incoming messages will be triaged automatically.\n"
|
|
"Use --demo to test with sample messages without channel setup.\n"
|
|
)
|
|
|
|
# Demonstrate how the channel integration would work
|
|
click.echo("Example integration code:\n")
|
|
click.echo(" from openjarvis import Jarvis")
|
|
click.echo(f' j = Jarvis(model="{model}", engine_key="{engine_key}")')
|
|
click.echo(" # Listen for incoming messages on the channel")
|
|
click.echo(f' # See: jarvis channel status (to verify "{channel}" is connected)')
|
|
click.echo(' response = j.ask(message, agent="orchestrator",')
|
|
click.echo(' tools=["think", "memory_store", "memory_search"])')
|
|
click.echo()
|
|
|
|
|
|
@click.command()
|
|
@click.option(
|
|
"--channel",
|
|
default="slack",
|
|
show_default=True,
|
|
help="Messaging channel to connect to (slack, whatsapp, etc.).",
|
|
)
|
|
@click.option(
|
|
"--model",
|
|
default="qwen3:8b",
|
|
show_default=True,
|
|
help="Model to use for message triage.",
|
|
)
|
|
@click.option(
|
|
"--engine",
|
|
"engine_key",
|
|
default="ollama",
|
|
show_default=True,
|
|
help="Engine backend (ollama, cloud, vllm, etc.).",
|
|
)
|
|
@click.option(
|
|
"--demo",
|
|
is_flag=True,
|
|
default=False,
|
|
help="Run in demo mode with sample messages (no channel required).",
|
|
)
|
|
def main(channel: str, model: str, engine_key: str, demo: bool) -> None:
|
|
"""Smart inbox assistant — classify and reply to messages.
|
|
|
|
Processes incoming messages through an orchestrator agent that classifies
|
|
each message as URGENT, ACTION_REQUIRED, FYI, or SPAM, drafts concise
|
|
replies, and stores key information for end-of-day summaries.
|
|
|
|
\b
|
|
Demo mode (no engine required for --help):
|
|
python examples/messaging_hub/smart_inbox.py --demo
|
|
|
|
\b
|
|
Live channel mode:
|
|
python examples/messaging_hub/smart_inbox.py --channel slack
|
|
"""
|
|
if demo:
|
|
_run_demo(model, engine_key)
|
|
else:
|
|
_run_channel(channel, model, engine_key)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|