Skip to content

Commit 1e52758

Browse files
bibryammarcduikeryaron2
authored
Dapr university preparation (#122)
* Created an example with dapr-based conversation history agent Signed-off-by: Bilgin Ibryam <[email protected]> * Added a new example with AssistantAgent Signed-off-by: Bilgin Ibryam <[email protected]> * Fix for linting errors Signed-off-by: Bilgin Ibryam <[email protected]> --------- Signed-off-by: Bilgin Ibryam <[email protected]> Co-authored-by: Marc Duiker <[email protected]> Co-authored-by: Yaron Schneider <[email protected]>
1 parent b7b4a98 commit 1e52758

File tree

9 files changed

+283
-3
lines changed

9 files changed

+283
-3
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Stateful Augmented LLM Pattern demonstrates:
4+
1. Memory - remembering user preferences
5+
2. Tool use - accessing external data
6+
3. LLM abstraction
7+
4. Durable execution of tools as workflow actions
8+
"""
9+
import asyncio
10+
import logging
11+
from typing import List
12+
from pydantic import BaseModel, Field
13+
from dapr_agents import tool, AssistantAgent
14+
from dapr_agents.memory import ConversationDaprStateMemory
15+
from dotenv import load_dotenv
16+
17+
18+
# Define tool output model
19+
class FlightOption(BaseModel):
20+
airline: str = Field(description="Airline name")
21+
price: float = Field(description="Price in USD")
22+
23+
24+
# Define tool input model
25+
class DestinationSchema(BaseModel):
26+
destination: str = Field(description="Destination city name")
27+
28+
29+
# Define flight search tool
30+
@tool(args_model=DestinationSchema)
31+
def search_flights(destination: str) -> List[FlightOption]:
32+
"""Search for flights to the specified destination."""
33+
# Mock flight data (would be an external API call in a real app)
34+
return [
35+
FlightOption(airline="SkyHighAir", price=450.00),
36+
FlightOption(airline="GlobalWings", price=375.50),
37+
]
38+
39+
40+
async def main():
41+
try:
42+
# Initialize TravelBuddy agent
43+
travel_planner = AssistantAgent(
44+
name="TravelBuddy",
45+
role="Travel Planner",
46+
goal="Help users find flights and remember preferences",
47+
instructions=[
48+
"Find flights to destinations",
49+
"Remember user preferences",
50+
"Provide clear flight info",
51+
],
52+
tools=[search_flights],
53+
message_bus_name="messagepubsub",
54+
state_store_name="workflowstatestore",
55+
state_key="workflow_state",
56+
agents_registry_store_name="registrystatestore",
57+
agents_registry_key="agents_registry",
58+
memory=ConversationDaprStateMemory(
59+
store_name="conversationstore", session_id="my-unique-id"
60+
),
61+
)
62+
63+
travel_planner.as_service(port=8001)
64+
await travel_planner.start()
65+
print("Travel Planner Agent is running")
66+
67+
except Exception as e:
68+
print(f"Error starting service: {e}")
69+
70+
71+
if __name__ == "__main__":
72+
load_dotenv()
73+
logging.basicConfig(level=logging.INFO)
74+
asyncio.run(main())
File renamed without changes.

quickstarts/01-hello-world/README.md

Lines changed: 123 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,14 +183,135 @@ if __name__ == "__main__":
183183

184184
**Expected output:** The agent will first check the weather in London, find it's rainy, and then recommend visiting museums.
185185

186-
### 4. Simple Workflow
186+
### 4. Stateful Assistant Agent
187187

188188
Make sure Dapr is initialized on your system:
189189

190190
```bash
191191
dapr init
192192
```
193193

194+
Run the assistant agent example to see how to create a stateful agent with persistent memory:
195+
196+
```bash
197+
dapr run --app-id stateful-llm --app-port 8001 --dapr-http-port 3500 --resources-path components/ -- python 04_assistant_agent.py
198+
```
199+
200+
This example demonstrates a stateful travel planning assistant that:
201+
1. Remembers user context persistently (across restarts)
202+
2. Uses a tool to search for flight options
203+
3. Exposes a REST API for workflow interaction
204+
4. Stores execution state in Dapr workflow state stores
205+
206+
```python
207+
#!/usr/bin/env python3
208+
"""
209+
Stateful Augmented LLM Pattern demonstrates:
210+
1. Memory - remembering user preferences
211+
2. Tool use - accessing external data
212+
3. LLM abstraction
213+
4. Durable execution of tools as workflow actions
214+
"""
215+
import asyncio
216+
import logging
217+
from typing import List
218+
from pydantic import BaseModel, Field
219+
from dapr_agents import tool, AssistantAgent
220+
from dapr_agents.memory import ConversationDaprStateMemory
221+
from dotenv import load_dotenv
222+
223+
# Define tool output model
224+
class FlightOption(BaseModel):
225+
airline: str = Field(description="Airline name")
226+
price: float = Field(description="Price in USD")
227+
228+
# Define tool input model
229+
class DestinationSchema(BaseModel):
230+
destination: str = Field(description="Destination city name")
231+
232+
# Define flight search tool
233+
@tool(args_model=DestinationSchema)
234+
def search_flights(destination: str) -> List[FlightOption]:
235+
"""Search for flights to the specified destination."""
236+
# Mock flight data (would be an external API call in a real app)
237+
return [
238+
FlightOption(airline="SkyHighAir", price=450.00),
239+
FlightOption(airline="GlobalWings", price=375.50)
240+
]
241+
242+
async def main():
243+
try:
244+
# Initialize TravelBuddy agent
245+
travel_planner = AssistantAgent(
246+
name="TravelBuddy",
247+
role="Travel Planner",
248+
goal="Help users find flights and remember preferences",
249+
instructions=[
250+
"Find flights to destinations",
251+
"Remember user preferences",
252+
"Provide clear flight info"
253+
],
254+
tools=[search_flights],
255+
message_bus_name="messagepubsub",
256+
state_store_name="workflowstatestore",
257+
state_key="workflow_state",
258+
agents_registry_store_name="registrystatestore",
259+
agents_registry_key="agents_registry",
260+
memory=ConversationDaprStateMemory(
261+
store_name="conversationstore", session_id="my-unique-id"
262+
)
263+
)
264+
265+
travel_planner.as_service(port=8001)
266+
await travel_planner.start()
267+
print("Travel Planner Agent is running")
268+
269+
except Exception as e:
270+
print(f"Error starting service: {e}")
271+
272+
if __name__ == "__main__":
273+
load_dotenv()
274+
logging.basicConfig(level=logging.INFO)
275+
asyncio.run(main())
276+
277+
### Interacting with the Agent
278+
279+
Unlike simpler agents, this stateful agent exposes a REST API for workflow interactions:
280+
281+
#### Start a new workflow:
282+
283+
```bash
284+
curl -i -X POST http://localhost:8001/start-workflow \
285+
-H "Content-Type: application/json" \
286+
-d '{"task": "I want to find flights to Paris"}'
287+
```
288+
289+
You'll receive a workflow ID in response, which you can use to track progress.
290+
291+
#### Check workflow status:
292+
293+
```bash
294+
# Replace WORKFLOW_ID with the ID from the previous response
295+
curl -i -X GET http://localhost:3500/v1.0/workflows/durableTaskHub/WORKFLOW_ID
296+
```
297+
298+
### How It Works
299+
300+
The key components of this implementation are:
301+
302+
1. **Persistent Memory**: The agent stores conversation state in Dapr's state store, enabling it to remember context across sessions and system restarts.
303+
304+
2. **Workflow Orchestration**: Long-running tasks are managed through Dapr's workflow system, providing:
305+
- Durability - workflows survive process crashes
306+
- Observability - track status and progress
307+
- Recoverability - automatic retry on failures
308+
309+
3. **Tool Integration**: A flight search tool is defined using the `@tool` decorator, which automatically handles input validation and type conversion.
310+
311+
4. **Service Exposure**: The agent exposes REST endpoints to start and manage workflows.
312+
313+
### 5. Simple Workflow
314+
194315
Run the workflow example to see how to create a multi-step LLM process:
195316

196317
<!-- STEP
@@ -202,7 +323,7 @@ expected_stdout_lines:
202323
output_match_mode: substring
203324
-->
204325
```bash
205-
dapr run --app-id dapr-agent-wf -- python 04_chain_tasks.py
326+
dapr run --app-id dapr-agent-wf -- python 05_chain_tasks.py
206327
```
207328
<!-- END_STEP -->
208329

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
apiVersion: dapr.io/v1alpha1
2+
kind: Component
3+
metadata:
4+
name: conversationstore
5+
spec:
6+
type: state.redis
7+
version: v1
8+
metadata:
9+
- name: redisHost
10+
value: localhost:6379
11+
- name: redisPassword
12+
value: ""
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
apiVersion: dapr.io/v1alpha1
2+
kind: Component
3+
metadata:
4+
name: messagepubsub
5+
spec:
6+
type: pubsub.redis
7+
version: v1
8+
metadata:
9+
- name: redisHost
10+
value: localhost:6379
11+
- name: redisPassword
12+
value: ""
13+
- name: consumerID
14+
value: "travel-planner-group"
15+
- name: enableTLS
16+
value: "false"
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
apiVersion: dapr.io/v1alpha1
2+
kind: Component
3+
metadata:
4+
name: registrystatestore
5+
spec:
6+
type: state.redis
7+
version: v1
8+
metadata:
9+
- name: redisHost
10+
value: localhost:6379
11+
- name: redisPassword
12+
value: ""
13+
- name: enableTLS
14+
value: "false"
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
apiVersion: dapr.io/v1alpha1
2+
kind: Component
3+
metadata:
4+
name: workflowstatestore
5+
spec:
6+
type: state.redis
7+
version: v1
8+
metadata:
9+
- name: redisHost
10+
value: localhost:6379
11+
- name: redisPassword
12+
value: ""
13+
- name: actorStateStore
14+
value: "true"

quickstarts/03-agent-tool-call/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ if __name__ == "__main__":
231231
3. Run the agent with Dapr
232232

233233
```bash
234-
dapr run --app-id weatheragent --resources-path ./components -- python weather_agent.py
234+
dapr run --app-id weatheragent --resources-path ./components -- python weather_agent_dapr.py
235235
```
236236

237237
## Available Agent Types
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import asyncio
2+
from weather_tools import tools
3+
from dapr_agents import Agent
4+
from dotenv import load_dotenv
5+
from dapr_agents.memory import ConversationDaprStateMemory
6+
7+
load_dotenv()
8+
9+
AIAgent = Agent(
10+
name="Stevie",
11+
role="Weather Assistant",
12+
goal="Assist Humans with weather related tasks.",
13+
instructions=[
14+
"Get accurate weather information",
15+
"From time to time, you can also jump after answering the weather question.",
16+
],
17+
memory=ConversationDaprStateMemory(store_name="historystore", session_id="some-id"),
18+
pattern="toolcalling",
19+
tools=tools,
20+
)
21+
22+
23+
# Wrap your async call
24+
async def main():
25+
await AIAgent.run("What is the weather in Virginia, New York and Washington DC?")
26+
27+
28+
if __name__ == "__main__":
29+
asyncio.run(main())

0 commit comments

Comments
 (0)