Skip to content

Commit ced63fe

Browse files
author
Pierre
authored
Merge pull request #40 from WorkflowAI/more-examples
More examples
2 parents 58b3abc + 761d59d commit ced63fe

19 files changed

+898
-35
lines changed

CONTRIBUTING.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,14 @@ poetry run pytest tests/e2e
3838

3939
#### Configuring VSCode
4040

41-
Suggested extensions are available in the [.vscode/extensions.json](.vscode/extensions.json) file.
41+
Suggested extensions are available in the [.vscode/extensions.json](.vscode/extensions.json) file. When you open this project in VSCode or Cursor, you'll be prompted to install these recommended extensions automatically.
42+
43+
To manually install recommended extensions:
44+
1. Open VSCode/Cursor Command Palette (Cmd/Ctrl + Shift + P)
45+
2. Type "Show Recommended Extensions"
46+
3. Install the ones marked with @recommended
47+
48+
These extensions will help ensure consistent code quality and style across all contributors.
4249

4350
### Dependencies
4451

README.md

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ client = WorkflowAI(
6464

6565
# Use the client to create and run agents
6666
@client.agent()
67-
def my_agent(task_input: Input) -> Output:
67+
def my_agent(agent_input: Input) -> Output:
6868
...
6969
```
7070

@@ -154,16 +154,16 @@ feedback_input = CallFeedbackInput(
154154
)
155155

156156
# Analyze the feedback
157-
result = await analyze_call_feedback(feedback_input)
157+
run = await analyze_call_feedback(feedback_input)
158158

159159
# Print the analysis
160160
print("\nPositive Points:")
161-
for point in result.positive_points:
161+
for point in run.positive_points:
162162
print(f"\n{point.point}")
163163
print(f" Quote [{point.timestamp}]: \"{point.quote}\"")
164164

165165
print("\nNegative Points:")
166-
for point in result.negative_points:
166+
for point in run.negative_points:
167167
print(f"\n{point.point}")
168168
print(f" Quote [{point.timestamp}]: \"{point.quote}\"")
169169
```
@@ -354,7 +354,33 @@ An example of using a PDF as input is available in [pdf_answer.py](./examples/pd
354354

355355
### Audio
356356

357-
[todo]
357+
Use the `File` class to pass audio files as input to an agent. Note that only some models support audio input.
358+
359+
```python
360+
from workflowai.fields import File
361+
...
362+
363+
class AudioInput(BaseModel):
364+
audio: File = Field(description="The audio recording to analyze for spam/robocall detection")
365+
366+
class AudioClassification(BaseModel):
367+
is_spam: bool = Field(description="Whether the audio is classified as spam/robocall")
368+
369+
@workflowai.agent(id="audio-classifier", model=Model.GEMINI_1_5_FLASH_LATEST)
370+
async def classify_audio(input: AudioInput) -> AudioClassification:
371+
...
372+
373+
# Example 1: Using base64 encoded data
374+
audio = File(content_type='audio/mp3', data='<base 64 encoded data>')
375+
376+
# Example 2: Using a URL
377+
# audio = File(url='https://example.com/audio/call.mp3')
378+
379+
run = await classify_audio(AudioInput(audio=audio))
380+
print(run)
381+
```
382+
383+
See an example of audio classification in [audio_classifier.py](./examples/04_audio_classifier.py).
358384

359385
### Caching
360386

@@ -590,7 +616,7 @@ async def analyze_call_feedback_strict(input: CallFeedbackInput) -> CallFeedback
590616
...
591617

592618
try:
593-
result = await analyze_call_feedback_strict(
619+
run = await analyze_call_feedback_strict(
594620
CallFeedbackInput(
595621
transcript="[00:01:15] Customer: The product is great!",
596622
call_date=date(2024, 1, 15)
@@ -608,13 +634,13 @@ async def analyze_call_feedback_tolerant(input: CallFeedbackInput) -> CallFeedba
608634
...
609635

610636
# The invalid_generation is less likely
611-
result = await analyze_call_feedback_tolerant(
637+
run = await analyze_call_feedback_tolerant(
612638
CallFeedbackInput(
613639
transcript="[00:01:15] Customer: The product is great!",
614640
call_date=date(2024, 1, 15)
615641
)
616642
)
617-
if not result.positive_points and not result.negative_points:
643+
if not run.positive_points and not run.negative_points:
618644
print("No feedback points were generated!")
619645
```
620646

@@ -630,15 +656,14 @@ absent will cause `AttributeError` when queried.
630656
async def analyze_call_feedback_stream(input: CallFeedbackInput) -> AsyncIterator[CallFeedbackOutput]:
631657
...
632658

633-
async for result in analyze_call_feedback_stream(
659+
async for run in analyze_call_feedback_stream(
634660
CallFeedbackInput(
635661
transcript="[00:01:15] Customer: The product is great!",
636662
call_date=date(2024, 1, 15)
637663
)
638664
):
639-
# With default values, we can safely check the points as they stream in
640-
print(f"Positive points so far: {len(result.positive_points)}")
641-
print(f"Negative points so far: {len(result.negative_points)}")
665+
print(f"Positive points so far: {len(run.positive_points)}")
666+
print(f"Negative points so far: {len(run.negative_points)}")
642667
```
643668

644669
#### Field properties

examples/01_basic_agent.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"""
2+
This example demonstrates how to create a basic WorkflowAI agent that takes a city name
3+
and returns information about the capital of its country. It showcases:
4+
5+
1. Basic agent creation with input/output models
6+
2. Field descriptions and examples
7+
3. Cost and latency tracking
8+
"""
9+
10+
import asyncio
11+
12+
from pydantic import BaseModel, Field
13+
14+
import workflowai
15+
from workflowai import Model, Run
16+
17+
18+
class CityInput(BaseModel):
19+
"""Input model for the city-to-capital agent."""
20+
city: str = Field(
21+
description="The name of the city for which to find the country's capital",
22+
examples=["Paris", "New York", "Tokyo"],
23+
)
24+
25+
26+
class CapitalOutput(BaseModel):
27+
"""Output model containing information about the capital city."""
28+
country: str = Field(
29+
description="The country where the input city is located",
30+
examples=["France", "United States", "Japan"],
31+
)
32+
capital: str = Field(
33+
description="The capital city of the country",
34+
examples=["Paris", "Washington D.C.", "Tokyo"],
35+
)
36+
fun_fact: str = Field(
37+
description="An interesting fact about the capital city",
38+
examples=["Paris has been the capital of France since 508 CE"],
39+
)
40+
41+
42+
@workflowai.agent(
43+
id="city-to-capital",
44+
model=Model.CLAUDE_3_5_SONNET_LATEST,
45+
)
46+
async def get_capital_info(city_input: CityInput) -> Run[CapitalOutput]:
47+
"""
48+
Find the capital city of the country where the input city is located.
49+
50+
Guidelines:
51+
1. First identify the country where the input city is located
52+
2. Then provide the capital city of that country
53+
3. Include an interesting historical or cultural fact about the capital
54+
4. Be accurate and precise with geographical information
55+
5. If the input city is itself the capital, still provide the information
56+
"""
57+
...
58+
59+
60+
async def main():
61+
# Example 1: Basic usage with Paris
62+
print("\nExample 1: Basic usage with Paris")
63+
print("-" * 50)
64+
run = await get_capital_info(CityInput(city="Paris"))
65+
print(run)
66+
67+
# Example 2: Using Tokyo
68+
print("\nExample 2: Using Tokyo")
69+
print("-" * 50)
70+
run = await get_capital_info(CityInput(city="Tokyo"))
71+
print(run)
72+
73+
74+
if __name__ == "__main__":
75+
asyncio.run(main())

examples/03_caching.py

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
"""
2+
This example demonstrates the different caching options in WorkflowAI:
3+
1. 'auto' - Cache only when temperature is 0 (default)
4+
2. 'always' - Always use cache if available
5+
3. 'never' - Never use cache, always execute new runs
6+
7+
The example uses a medical SOAP notes extractor to show how caching affects:
8+
- Response consistency (important for medical documentation)
9+
- Cost efficiency
10+
- Execution time
11+
"""
12+
13+
import asyncio
14+
import time
15+
from typing import Literal, TypedDict
16+
17+
from pydantic import BaseModel, Field
18+
19+
import workflowai
20+
from workflowai import Model, Run
21+
22+
# Import CacheUsage type
23+
CacheUsage = Literal["auto", "always", "never"]
24+
25+
26+
class SOAPInput(BaseModel):
27+
"""Input containing a medical consultation transcript."""
28+
transcript: str = Field(
29+
description="The medical consultation transcript to analyze",
30+
)
31+
32+
33+
class SOAPNote(BaseModel):
34+
"""Structured SOAP note components."""
35+
subjective: list[str] = Field(
36+
description="Patient's symptoms, complaints, and history as reported",
37+
examples=["Patient reports severe headache for 3 days", "Denies fever or nausea"],
38+
)
39+
objective: list[str] = Field(
40+
description="Observable, measurable findings from examination",
41+
examples=["BP 120/80", "Temperature 98.6°F", "No visible inflammation"],
42+
)
43+
assessment: list[str] = Field(
44+
description="Diagnosis or clinical impressions",
45+
examples=["Tension headache", "Rule out migraine"],
46+
)
47+
plan: list[str] = Field(
48+
description="Treatment plan and next steps",
49+
examples=["Prescribed ibuprofen 400mg", "Follow up in 2 weeks"],
50+
)
51+
52+
53+
@workflowai.agent(
54+
id="soap-extractor",
55+
model=Model.LLAMA_3_3_70B,
56+
)
57+
async def extract_soap_notes(soap_input: SOAPInput) -> Run[SOAPNote]:
58+
"""
59+
Extract SOAP notes from a medical consultation transcript.
60+
61+
Guidelines:
62+
1. Analyze the transcript to identify key medical information
63+
2. Organize information into SOAP format:
64+
- Subjective: Patient's symptoms, complaints, and history
65+
- Objective: Physical examination findings and test results
66+
- Assessment: Diagnosis or clinical impression
67+
- Plan: Treatment plan and next steps
68+
69+
3. Be thorough but concise
70+
4. Use medical terminology appropriately
71+
5. Include all relevant information from the transcript
72+
"""
73+
...
74+
75+
76+
class ResultMetrics(TypedDict):
77+
option: str
78+
duration: float
79+
cost: float
80+
81+
82+
async def demonstrate_caching(transcript: str):
83+
"""Run the same transcript with different caching options and compare results."""
84+
85+
print("\nComparing caching options")
86+
print("-" * 50)
87+
88+
cache_options: list[CacheUsage] = ["auto", "always", "never"]
89+
results: list[ResultMetrics] = []
90+
91+
for cache_option in cache_options:
92+
start_time = time.time()
93+
94+
run = await extract_soap_notes(
95+
SOAPInput(transcript=transcript),
96+
use_cache=cache_option,
97+
)
98+
99+
duration = time.time() - start_time
100+
101+
# Store metrics for comparison
102+
results.append({
103+
"option": cache_option,
104+
"duration": duration,
105+
"cost": float(run.cost_usd or 0.0), # Convert None to 0.0
106+
})
107+
108+
# Print comparison table
109+
print("\nResults Comparison:")
110+
print("-" * 50)
111+
print(f"{'Cache Option':<12} {'Duration':<10} {'Cost':<8}")
112+
print("-" * 50)
113+
114+
for r in results:
115+
print(
116+
f"{r['option']:<12} "
117+
f"{r['duration']:.2f}s{'*' if r['duration'] < 0.1 else '':<8} "
118+
f"${r['cost']:<7}",
119+
)
120+
121+
print("-" * 50)
122+
print("* Very fast response indicates cached result")
123+
124+
125+
async def main():
126+
# Example medical consultation transcript
127+
transcript = """
128+
Patient is a 45-year-old female presenting with severe headache for the past 3 days.
129+
She describes the pain as throbbing, primarily on the right side of her head.
130+
Pain level reported as 7/10. Denies fever, nausea, or visual disturbances.
131+
Previous history of migraines, but states this feels different.
132+
133+
Vital signs stable: BP 120/80, HR 72, Temp 98.6°F.
134+
Physical exam shows mild tenderness in right temporal area.
135+
No neurological deficits noted.
136+
Eye examination normal, no papilledema.
137+
138+
Assessment suggests tension headache, but need to rule out migraine.
139+
No red flags for secondary causes identified.
140+
141+
Plan: Prescribed ibuprofen 400mg q6h for pain.
142+
Recommended stress reduction techniques.
143+
Patient education provided regarding headache triggers.
144+
Follow up in 2 weeks, sooner if symptoms worsen.
145+
Return precautions discussed.
146+
"""
147+
148+
print("\nDemonstrating different caching options")
149+
print("=" * 50)
150+
print("This example shows how caching affects the agent's behavior:")
151+
print("- 'auto': Caches only when temperature is 0 (default)")
152+
print("- 'always': Reuses cached results when available")
153+
print("- 'never': Generates new results every time")
154+
155+
await demonstrate_caching(transcript)
156+
157+
158+
if __name__ == "__main__":
159+
asyncio.run(main())

0 commit comments

Comments
 (0)