Skip to content

Commit 78dcbc6

Browse files
authored
Merge pull request #33 from WorkflowAI/workflows
Workflows
2 parents 4fa1418 + 1725876 commit 78dcbc6

File tree

9 files changed

+1101
-0
lines changed

9 files changed

+1101
-0
lines changed

.github/workflows/examples.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ env:
1414

1515
jobs:
1616
examples:
17+
# TODO: we should run only in prod
18+
environment: staging
1719
runs-on: ubuntu-latest
1820
steps:
1921
- uses: actions/checkout@v3

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,10 @@ validation that is too strict can lead to invalid generations. In case of an inv
626626
def my_agent(_: Input) -> :...
627627
```
628628

629+
## Workflows
630+
631+
For advanced workflow patterns and examples, please refer to the [Workflows README](examples/workflows/README.md) for more details.
632+
629633
## Contributing
630634

631635
See the [CONTRIBUTING.md](./CONTRIBUTING.md) file for more details.

examples/workflows/README.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Workflows Patterns
2+
3+
This README describes the five main patterns used in our workflows built using the WorkflowAI SDK. These patterns provide a structured method for composing complex AI tasks from simpler components, and allow you to balance flexibility and control in your AI applications.
4+
5+
## 1. Sequential Processing (Chains)
6+
In this pattern, tasks are executed in a fixed sequence, where the output of one step becomes the input for the next. This is ideal for linear processes such as content generation, data transformation, or any task that benefits from a clear, step-by-step flow.
7+
8+
**Example:**
9+
- Generate an initial result (e.g., marketing copy).
10+
- Evaluate and refine that result through subsequent steps.
11+
12+
For an implementation example, see [chain.py](chain.py).
13+
14+
## 2. Routing
15+
The routing pattern directs work based on intermediate results. An initial decision or classification determines which specialized agent should handle the next part of the workflow. This allows the system to adapt its behavior based on context (for instance, routing customer queries to different support teams).
16+
17+
**Example:**
18+
- Classify a customer query (e.g., as general, refund, or technical).
19+
- Route the query to a specialized agent that handles that particular type.
20+
21+
For an implementation example, see [routing.py](routing.py).
22+
23+
## 3. Parallel Processing
24+
Parallel processing splits work into independent subtasks that run concurrently. This pattern is used when different aspects of an input can be handled independently, leading to faster overall processing times.
25+
26+
**Example:**
27+
- Perform security, performance, and maintainability reviews on code simultaneously.
28+
- Collect and aggregate the results after all tasks complete.
29+
30+
For an implementation example, see [parallel_processing.py](parallel_processing.py).
31+
32+
## 4. Orchestrator-Worker
33+
In the orchestrator-worker pattern, one agent (the orchestrator) plans and coordinates the work, while multiple worker agents execute the individual parts of the plan in parallel. This pattern is particularly useful when a task can be decomposed into distinct planning and execution phases.
34+
35+
**Example:**
36+
- An orchestrator analyzes a feature request and generates an implementation plan with details on file changes.
37+
- Worker agents then execute the planned changes concurrently.
38+
39+
For an implementation example, see [orchestrator_worker.py](orchestrator_worker.py).
40+
41+
## 5. Evaluator-Optimizer
42+
The evaluator-optimizer pattern employs an iterative feedback loop. An initial output is evaluated for quality, and based on the feedback, improvements are made. This cycle is repeated until the output reaches the desired quality, or a maximum number of iterations is met.
43+
44+
**Example:**
45+
- Translate text using a fast initial model.
46+
- Evaluate the translation's quality, tone, nuance, and cultural accuracy.
47+
- If needed, refine the translation based on detailed feedback and repeat the process.
48+
49+
For an implementation example, see [evaluator_optimizer.py](evaluator_optimizer.py).
50+
51+
## 6. Chain of Agents (Long Context Processing)
52+
The Chain of Agents pattern is designed for processing long documents or complex tasks that exceed the context window of a single model. In this pattern, multiple worker agents process different chunks of the input sequentially, passing their findings through the chain, while a manager agent synthesizes the final output.
53+
54+
**Example:**
55+
- Split a long document into manageable chunks
56+
- Worker agents process each chunk sequentially, building upon previous findings
57+
- A manager agent synthesizes all findings into a final, coherent response
58+
59+
For an implementation example, see [chain_of_agents.py](chain_of_agents.py).
60+
61+
---
62+
63+
These patterns were inspired by the workflow patterns described in the [Vercel AI SDK Documentation](https://sdk.vercel.ai/docs/foundations/agents#patterns-with-examples) and research from organizations like [Google Research](https://research.google/blog/chain-of-agents-large-language-models-collaborating-on-long-context-tasks/).
64+
65+
This README should serve as a high-level guide to understanding and using the different patterns available in our workflows.

examples/workflows/chain.py

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import asyncio
2+
from typing import TypedDict
3+
4+
from pydantic import BaseModel, Field # pyright: ignore [reportUnknownVariableType]
5+
6+
import workflowai
7+
from workflowai import Model
8+
9+
10+
class MarketingCopyInput(BaseModel):
11+
"""The product or concept for which to generate marketing copy."""
12+
13+
idea: str = Field(description="A short description or name of the product.")
14+
15+
16+
class MarketingCopyOutput(BaseModel):
17+
"""Contains the AI generated marketing copy text for the provided product or concept."""
18+
19+
marketing_text: str = Field(description="Persuasive marketing copy text.")
20+
21+
22+
@workflowai.agent(id="marketing-copy-generator", model=Model.GPT_4O_MINI_LATEST)
23+
async def generate_marketing_copy_agent(_: MarketingCopyInput) -> MarketingCopyOutput:
24+
"""
25+
Write persuasive marketing copy for the provided idea.
26+
Focus on benefits and emotional appeal.
27+
"""
28+
...
29+
30+
31+
class EvaluateCopyInput(BaseModel):
32+
"""Input type for evaluating the quality of marketing copy."""
33+
34+
marketing_text: str = Field(description="The marketing copy text to evaluate.")
35+
36+
37+
class EvaluateCopyOutput(BaseModel):
38+
"""Evaluation results for the marketing copy."""
39+
40+
has_call_to_action: bool = Field(description="Whether a call to action is present.")
41+
emotional_appeal: int = Field(description="Emotional appeal rating (1-10).")
42+
clarity: int = Field(description="Clarity rating (1-10).")
43+
44+
45+
# We use a smarter model (O1) to review the copy since evaluation requires more nuanced understanding
46+
@workflowai.agent(id="marketing-copy-evaluator", model=Model.O1_MINI_LATEST)
47+
async def evaluate_marketing_copy_agent(_: EvaluateCopyInput) -> EvaluateCopyOutput:
48+
"""
49+
Evaluate the marketing copy for:
50+
1) Presence of a call to action (true/false)
51+
2) Emotional appeal (1-10)
52+
3) Clarity (1-10)
53+
Return the results as a structured output.
54+
"""
55+
...
56+
57+
58+
class RewriteCopyInput(BaseModel):
59+
"""Input for rewriting the marketing copy with targeted improvements."""
60+
61+
original_copy: str = Field(default="", description="Original marketing copy.")
62+
add_call_to_action: bool = Field(default=False, description="Whether we need a clear call to action.")
63+
strengthen_emotional_appeal: bool = Field(default=False, description="Whether emotional appeal needs a boost.")
64+
improve_clarity: bool = Field(default=False, description="Whether clarity needs improvement.")
65+
66+
67+
class RewriteCopyOutput(BaseModel):
68+
"""Contains the improved marketing copy."""
69+
70+
marketing_text: str = Field(description="The improved marketing copy text.")
71+
72+
73+
# Claude 3.5 Sonnet is a more powerful model for copywriting
74+
@workflowai.agent(model=Model.CLAUDE_3_5_SONNET_LATEST)
75+
async def rewrite_marketing_copy_agent(_: RewriteCopyInput) -> RewriteCopyOutput:
76+
"""
77+
Rewrite the marketing copy with the specified improvements:
78+
- A clear CTA if requested
79+
- Stronger emotional appeal if requested
80+
- Improved clarity if requested
81+
"""
82+
...
83+
84+
85+
class MarketingResult(TypedDict):
86+
original_copy: str
87+
final_copy: str
88+
was_improved: bool
89+
quality_metrics: EvaluateCopyOutput
90+
91+
92+
async def generate_marketing_copy(idea: str) -> MarketingResult:
93+
"""
94+
Demonstrates a chain flow:
95+
1) Generate an initial marketing copy.
96+
2) Evaluate its quality.
97+
3) If the quality is lacking, request a rewrite with clearer CTA, stronger emotional appeal, and/or clarity.
98+
4) Return the final copy and metrics.
99+
"""
100+
# Step 1: Generate initial copy
101+
generation = await generate_marketing_copy_agent(MarketingCopyInput(idea=idea))
102+
original_copy = generation.marketing_text
103+
final_copy = original_copy
104+
105+
# Step 2: Evaluate the copy
106+
evaluation = await evaluate_marketing_copy_agent(EvaluateCopyInput(marketing_text=original_copy))
107+
108+
# Step 3: Check evaluation results. If below thresholds, rewrite
109+
needs_improvement = not evaluation.has_call_to_action or evaluation.emotional_appeal < 7 or evaluation.clarity < 7
110+
111+
if needs_improvement:
112+
rewrite = await rewrite_marketing_copy_agent(
113+
RewriteCopyInput(
114+
original_copy=original_copy,
115+
add_call_to_action=not evaluation.has_call_to_action,
116+
strengthen_emotional_appeal=evaluation.emotional_appeal < 7,
117+
improve_clarity=evaluation.clarity < 7,
118+
),
119+
)
120+
final_copy = rewrite.marketing_text
121+
122+
return {
123+
"original_copy": original_copy,
124+
"final_copy": final_copy,
125+
"was_improved": needs_improvement,
126+
"quality_metrics": evaluation,
127+
}
128+
129+
130+
if __name__ == "__main__":
131+
idea = "A open-source platform for AI agents"
132+
result = asyncio.run(generate_marketing_copy(idea))
133+
134+
print("\n=== Input Idea ===")
135+
print(idea)
136+
137+
print("\n=== Marketing Copy ===")
138+
print(result["original_copy"])
139+
140+
print("\n=== Quality Assessment ===")
141+
metrics = result["quality_metrics"]
142+
print(f"✓ Call to Action: {'Present' if metrics.has_call_to_action else 'Missing'}")
143+
print(f"✓ Emotional Appeal: {metrics.emotional_appeal}/10")
144+
print(f"✓ Clarity: {metrics.clarity}/10")
145+
146+
if result["was_improved"]:
147+
print("\n=== Improved Marketing Copy ===")
148+
print(result["final_copy"])
149+
print()

0 commit comments

Comments
 (0)