fix actions (#3)

This commit is contained in:
Adrian Lyjak
2025-09-27 00:38:41 -04:00
committed by GitHub
parent 38c8f032fe
commit c5e703a2e3
6 changed files with 262 additions and 2 deletions
+52 -2
View File
@@ -1,2 +1,52 @@
# template-workflow-basic
Llama Index Workflow Template
# Email Workflow Example
This example demonstrates how to build an event-driven, async-first workflow for sending emails using [llama-index-workflows](https://github.com/run-llama/llama-index-workflows). The workflow uses an LLM to generate email content and sends emails to internal recipients only.
## Installation
Install all required dependencies (including llama-index-workflows and OpenAI LLM support):
```bash
pip install -e .
```
## Usage
Run the workflow from the command line:
```bash
python -m basic.workflow \
--sender you@mycompany.com \
--receiver recipient1@mycompany.com \
--receiver recipient2@mycompany.com \
--subject "Quarterly Update" \
--draft "Here's a draft for the quarterly update email."
```
**Note:**
- The sender and all receivers must use `@mycompany.com` emails.
- You must set your `OPENAI_API_KEY` in the environment before running.
## Workflow Overview
- **prepare_email**:
Initializes the email client and uses an LLM to generate a fully-formed email from your draft and subject.
Emits a `PrepareEmail` event for each receiver.
- **send_email**:
Sends the generated email to each receiver using the internal email client.
Updates email statistics.
- **collect_email_stats**:
Collects results from all send attempts and outputs a summary of successes and failures.
## Customization
- Replace the `EmailClient` logic with your own email sending implementation.
- Extend the workflow with additional steps or validation as needed.
## References
- [llama-index-workflows documentation](https://github.com/run-llama/llama-index-workflows)
- [OpenAI LLM integration](https://github.com/run-llama/llama-index-llms-openai)
+5
View File
@@ -0,0 +1,5 @@
_exclude:
- ".git"
- ".github"
- "copier.yaml"
- ".venv"
+31
View File
@@ -0,0 +1,31 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "basic"
version = "0.1.0"
description = "A base example that showcases usage patterns for workflows"
requires-python = ">=3.10"
readme = "README.md"
dependencies = ["llama-index-workflows", "llama-index-llms-openai"]
[tool.hatch.envs.default.scripts]
format = "ruff format ."
format-check = "ruff format --check ."
lint = "ruff check --fix ."
lint-check = ["ruff check ."]
typecheck = "ty check src"
test = "pytest"
all-check = ["format-check", "lint-check", "test"]
all-fix = ["format", "lint", "test"]
[dependency-groups]
dev = ["hatch>=1.14.2", "pytest>=8.4.2", "ruff>=0.13.2", "ty>=0.0.1a21"]
[tool.llamadeploy]
env_files = [".env"]
[tool.llamadeploy.workflows]
default = "basic.workflow:workflow"
View File
+172
View File
@@ -0,0 +1,172 @@
from pydantic import BaseModel, ConfigDict
from workflows import Workflow, step, Context
from workflows.events import (
Event,
StartEvent,
StopEvent,
)
from typing import Annotated, Optional
from workflows.resource import Resource
from llama_index.llms.openai import OpenAI
# replace with an actual email sending client
class EmailClient:
def __init__(self, sender_email: str):
if not self._internal_email(sender_email):
print("Sorry, you are not allowed to use this email client")
return
self.sender_email = sender_email
def send(self, receiver_email: str, subject: str, content: str) -> bool:
if not self._internal_email(receiver_email):
print(
"Sorry, we cannot send an email to a person outside of the organization"
)
return False
print(
f"Sent an email from {self.sender_email} to {receiver_email} with subject '{subject}' and content:\n{content}"
)
return True
def _internal_email(self, email: str) -> bool:
return email.endswith("@mycompany.com")
class EmailStats:
def __init__(self):
self.success = 0
self.fail = 0
def update(self, result: bool):
if result:
self.success += 1
else:
self.fail += 1
class PrepareEmail(Event):
receiver: str
subject: str
content: str
class SendEmail(Event):
success: bool
class EmailFlowState(BaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True)
email_num: int = 0
email_client: Optional[EmailClient] = None
async def get_llm(*args, **kwargs) -> OpenAI:
return OpenAI("gpt-4.1")
async def get_email_stats(*args, **kwargs) -> EmailStats:
return EmailStats()
class EmailFlow(Workflow):
@step
async def prepare_email(
self,
ev: StartEvent,
ctx: Context[EmailFlowState],
llm: Annotated[OpenAI, Resource(get_llm)],
) -> PrepareEmail | StopEvent | None:
async with ctx.store.edit_state() as state:
cl = EmailClient(sender_email=ev.sender)
if hasattr(cl, "sender_email"):
state.email_client = cl
state.email_num = len(ev.receivers)
else:
return StopEvent(
result="It is not possible to send emails from your current address: please use a mycompany.com address and try again."
)
email_content = await llm.acomplete(
f"Given this email draft: {ev.draft} and subject: {ev.subject}, can you please create an fully-formed email message to send?"
)
for receiver in ev.receivers:
ctx.send_event(
PrepareEmail(
receiver=receiver, subject=ev.subject, content=email_content.text
)
)
@step
async def send_email(
self,
ev: PrepareEmail,
ctx: Context[EmailFlowState],
stats: Annotated[EmailStats, Resource(get_email_stats)],
) -> SendEmail:
state = await ctx.store.get_state()
succ = state.email_client.send(ev.receiver, ev.subject, ev.content) # type: ignore
stats.update(succ)
return SendEmail(success=succ)
@step
async def collect_email_stats(
self,
ev: SendEmail,
ctx: Context[EmailFlowState],
stats: Annotated[EmailStats, Resource(get_email_stats)],
) -> StopEvent | None:
state = await ctx.store.get_state()
evs = ctx.collect_events(ev, [SendEmail] * state.email_num)
if evs:
return StopEvent(
result=f"Sent {stats.success} emails, failed to send {stats.fail} emails"
)
async def main(sender: str, receivers: list[str], subject: str, draft: str) -> None:
w = EmailFlow(timeout=60, verbose=False)
result = await w.run(
sender=sender, receivers=receivers, subject=subject, draft=draft
)
print(str(result))
workflow = EmailFlow(timeout=None)
if __name__ == "__main__":
import asyncio
import os
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument(
"-s",
"--sender",
required=True,
help="Sender email (must end with @mycompany.com)",
)
parser.add_argument(
"-r",
"--receiver",
required=True,
action="append",
help="Email for the receiver (must end with @mycompany.com). Can be repeated",
)
parser.add_argument("-t", "--subject", required=True, help="Subject of the email")
parser.add_argument("-d", "--draft", required=True, help="Draft for the email")
args = parser.parse_args()
if not os.getenv("OPENAI_API_KEY", None):
raise ValueError(
"You need to set OPENAI_API_KEY in your environment before using this workflow"
)
asyncio.run(
main(
sender=args.sender,
receivers=args.receiver,
subject=args.subject,
draft=args.draft,
)
)
+2
View File
@@ -0,0 +1,2 @@
# Changes here will be overwritten by Copier; NEVER EDIT MANUALLY
{{ _copier_answers|to_nice_yaml -}}