Skip to content

Commit f7e4769

Browse files
committed
update example to make the devex better, added reference to env var to reduce grpc warning
Signed-off-by: Filinto Duran <[email protected]>
1 parent 1c99128 commit f7e4769

File tree

4 files changed

+193
-48
lines changed

4 files changed

+193
-48
lines changed

docs/sessions/dapr_session.md

Lines changed: 119 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,6 @@ First, start the Dapr sidecar with a state store. The simplest way is using Redi
7878
```bash
7979
# Start Redis (if not already running)
8080
docker run -d -p 6379:6379 redis:7-alpine
81-
82-
# Start Dapr sidecar with Redis state store
83-
dapr run --app-id myapp --dapr-grpc-port 50001 --components-path ./components
8481
```
8582

8683
Create a component configuration file at `./components/statestore.yaml`:
@@ -100,43 +97,75 @@ spec:
10097
value: ""
10198
```
10299
103-
Now use DaprSession in your code:
100+
Create your Python application (save as `example.py`):
104101

105102
```python
103+
import asyncio
104+
import os
105+
106106
from agents import Agent, Runner
107107
from agents.extensions.memory import DaprSession
108108
109-
# Create agent
110-
agent = Agent(
111-
name="Assistant",
112-
instructions="Reply very concisely.",
113-
)
109+
grpc_port = os.environ.get("DAPR_GRPC_PORT", "50001")
114110
115-
# Connect to Dapr sidecar (using default gRPC port 50001)
116-
session = DaprSession.from_address(
117-
session_id="user-123",
118-
state_store_name="statestore",
119-
dapr_address="localhost:50001", # Default Dapr gRPC port
120-
)
121111
122-
# First turn
123-
result = await Runner.run(
124-
agent,
125-
"What city is the Golden Gate Bridge in?",
126-
session=session
127-
)
128-
print(result.final_output) # "San Francisco"
112+
async def main():
113+
# Create agent
114+
agent = Agent(
115+
name="Assistant",
116+
instructions="Reply very concisely.",
117+
)
129118
130-
# Second turn - agent remembers context
131-
result = await Runner.run(
132-
agent,
133-
"What state is it in?",
134-
session=session
135-
)
136-
print(result.final_output) # "California"
119+
# Connect to Dapr sidecar (using default gRPC port 50001)
120+
session = DaprSession.from_address(
121+
session_id="user-123",
122+
state_store_name="statestore",
123+
dapr_address=f"localhost:{grpc_port}",
124+
)
125+
126+
try:
127+
# First turn
128+
result = await Runner.run(
129+
agent,
130+
"What city is the Golden Gate Bridge in?",
131+
session=session
132+
)
133+
print(f"Agent: {result.final_output}") # "San Francisco"
134+
135+
# Second turn - agent remembers context
136+
result = await Runner.run(
137+
agent,
138+
"What state is it in?",
139+
session=session
140+
)
141+
print(f"Agent: {result.final_output}") # "California"
142+
143+
finally:
144+
# Always clean up
145+
await session.close()
146+
147+
148+
if __name__ == "__main__":
149+
asyncio.run(main())
150+
```
151+
152+
Now run your application **with the Dapr sidecar**:
137153

138-
# Clean up
139-
await session.close()
154+
**Option 1: Run with Dapr (recommended)**
155+
156+
```bash
157+
# Dapr starts the sidecar AND runs your application
158+
dapr run --app-id myapp --dapr-http-port 3500 --dapr-grpc-port 50001 --resources-path ./components -- python example.py
159+
```
160+
161+
**Option 2: Run separately (for development)**
162+
163+
```bash
164+
# Terminal 1: Start Dapr sidecar
165+
dapr run --app-id myapp --dapr-http-port 3500 --dapr-grpc-port 50001 --resources-path ./components
166+
167+
# Terminal 2: Run your application (in a separate terminal)
168+
python example.py
140169
```
141170

142171
**What's happening here?**
@@ -145,6 +174,12 @@ await session.close()
145174
3. The agent can retrieve previous messages across multiple turns
146175
4. When you scale to multiple instances, all instances share the same session state
147176

177+
**Want a more complete example?** See [`examples/memory/dapr_session_example.py`](https://github.com/openai/openai-agents-python/blob/main/examples/memory/dapr_session_example.py) which demonstrates:
178+
- Multi-turn conversations with context
179+
- Session isolation and multi-tenancy
180+
- TTL and consistency levels
181+
- All with detailed output and comments
182+
148183
## Usage patterns
149184

150185
### Dapr ports
@@ -155,17 +190,20 @@ The `DaprSession` communicates with the Dapr sidecar using network ports. Dapr e
155190
- **HTTP port: 3500** - For REST API and health checks
156191
- **Metrics port: 9090** - For Prometheus metrics (monitoring)
157192

158-
When starting the Dapr sidecar, you can specify custom ports if needed:
193+
When starting the Dapr sidecar, explicitly specify both ports to avoid connection issues:
159194

160195
```bash
161196
# Using default ports (recommended)
162-
dapr run --app-id myapp --components-path ./components
197+
dapr run --app-id myapp \
198+
--dapr-http-port 3500 \
199+
--dapr-grpc-port 50001 \
200+
--resources-path ./components
163201
164-
# Using custom ports
202+
# Using custom ports (in case of conflicts with other services)
165203
dapr run --app-id myapp \
166-
--dapr-grpc-port 50002 \
167204
--dapr-http-port 3501 \
168-
--components-path ./components
205+
--dapr-grpc-port 50002 \
206+
--resources-path ./components
169207
```
170208

171209
If using custom ports, specify them in your session connection:
@@ -556,9 +594,27 @@ else:
556594
```
557595

558596
Common issues:
597+
598+
**Health check connection refused (port 3500)**:
599+
- **Cause**: The `--dapr-http-port` was not specified, so Dapr used a random port
600+
- **Solution 1**: Always explicitly set `--dapr-http-port 3500` when starting Dapr (recommended)
601+
- **Solution 2**: Set the HTTP endpoint via environment variable before creating the session:
602+
```python
603+
import os
604+
# Option A: Set full endpoint
605+
os.environ["DAPR_HTTP_ENDPOINT"] = "http://localhost:3500"
606+
607+
# Option B: Set just the port (SDK will construct http://localhost:PORT)
608+
os.environ["DAPR_HTTP_PORT"] = "3500"
609+
610+
# Then create your session
611+
session = DaprSession.from_address(...)
612+
```
613+
614+
**Other issues**:
559615
- Check that `dapr run` is active (use `dapr list` to see running sidecars)
560616
- Verify the gRPC port matches (default: 50001)
561-
- Ensure no firewall blocking the port
617+
- Ensure no firewall blocking the ports
562618
- Check that the Dapr sidecar finished initializing (check logs with `dapr logs --app-id myapp`)
563619

564620
### Configuration error: State store not found
@@ -570,13 +626,38 @@ Common issues:
570626
**Solution**: Ensure your component YAML file is in the components directory specified when starting Dapr:
571627

572628
```bash
573-
dapr run --app-id myapp --dapr-grpc-port 50001 --components-path ./components
629+
dapr run --app-id myapp \
630+
--dapr-http-port 3500 \
631+
--dapr-grpc-port 50001 \
632+
--resources-path ./components
574633
```
575634

576635
### TTL not working
577636

578637
Verify your state store [supports TTL](https://docs.dapr.io/reference/components-reference/supported-state-stores/) and is properly configured.
579638

639+
### gRPC fork warnings on first API call
640+
641+
**Symptom**: You see informational messages like `This process is fork-safe due to gRPC...` on the first OpenAI API call.
642+
643+
**Cause**: When `DaprSession` initializes, it creates gRPC background threads. The first OpenAI API call then initializes HTTP worker threads, and gRPC detects this concurrent thread creation and logs safety information.
644+
645+
**Impact**: These are informational messages, not errors. They only appear once and don't affect functionality.
646+
647+
**Solution** (optional): If you want to suppress these messages, set the gRPC verbosity level before creating the session:
648+
649+
```python
650+
import os
651+
652+
# Suppress gRPC informational messages
653+
os.environ["GRPC_VERBOSITY"] = "ERROR"
654+
655+
# Now create your session
656+
session = DaprSession.from_address(...)
657+
```
658+
659+
**Note**: These warnings are expected when using gRPC-based libraries (like Dapr) alongside other async HTTP libraries. They indicate that gRPC's fork handlers are working correctly to ensure thread safety.
660+
580661
## API reference
581662

582663
For detailed API documentation, see:

examples/memory/dapr_session_example.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616
"""
1717

1818
import asyncio
19+
import os
20+
21+
os.environ["GRPC_VERBOSITY"] = (
22+
"ERROR" # Suppress gRPC warnings caused by the Dapr Python SDK gRPC connection.
23+
)
1924

2025
from agents import Agent, Runner
2126
from agents.extensions.memory import (
@@ -24,6 +29,8 @@
2429
DaprSession,
2530
)
2631

32+
grpc_port = os.environ.get("DAPR_GRPC_PORT", "50001")
33+
2734

2835
async def main():
2936
# Create an agent
@@ -35,7 +42,7 @@ async def main():
3542
print("=== Dapr Session Example ===")
3643
print("This example requires Dapr sidecar to be running")
3744
print(
38-
"Start Dapr with: dapr run --app-id myapp --dapr-grpc-port 50001 --components-path ./components"
45+
"Start Dapr with: dapr run --app-id myapp --dapr-http-port 3500 --dapr-grpc-port 50001 --resources-path ./components"
3946
) # noqa: E501
4047
print()
4148

@@ -45,15 +52,15 @@ async def main():
4552
session = DaprSession.from_address(
4653
session_id,
4754
state_store_name="statestore",
48-
dapr_address="localhost:50001",
55+
dapr_address=f"localhost:{grpc_port}",
4956
)
5057

5158
# Test Dapr connectivity
5259
if not await session.ping():
5360
print("Dapr sidecar is not available!")
5461
print("Please start Dapr sidecar and try again.")
5562
print(
56-
"Command: dapr run --app-id myapp --dapr-grpc-port 50001 --components-path ./components"
63+
"Command: dapr run --app-id myapp --dapr-http-port 3500 --dapr-grpc-port 50001 --resources-path ./components"
5764
) # noqa: E501
5865
return
5966

@@ -118,7 +125,7 @@ async def main():
118125
new_session = DaprSession.from_address(
119126
"different_conversation_456",
120127
state_store_name="statestore",
121-
dapr_address="localhost:50001",
128+
dapr_address=f"localhost:{grpc_port}",
122129
)
123130

124131
print("Creating a new session with different ID...")
@@ -146,7 +153,7 @@ async def main():
146153
except Exception as e:
147154
print(f"Error: {e}")
148155
print(
149-
"Make sure Dapr sidecar is running with: dapr run --app-id myapp --dapr-grpc-port 50001 --components-path ./components"
156+
"Make sure Dapr sidecar is running with: dapr run --app-id myapp --dapr-http-port 3500 --dapr-grpc-port 50001 --resources-path ./components"
150157
) # noqa: E501
151158

152159

@@ -160,7 +167,7 @@ async def demonstrate_advanced_features():
160167
ttl_session = DaprSession.from_address(
161168
"ttl_demo_session",
162169
state_store_name="statestore",
163-
dapr_address="localhost:50001",
170+
dapr_address=f"localhost:{grpc_port}",
164171
ttl=3600, # 1 hour TTL
165172
)
166173

@@ -182,15 +189,15 @@ async def demonstrate_advanced_features():
182189
eventual_session = DaprSession.from_address(
183190
"eventual_session",
184191
state_store_name="statestore",
185-
dapr_address="localhost:50001",
192+
dapr_address=f"localhost:{grpc_port}",
186193
consistency=DAPR_CONSISTENCY_EVENTUAL,
187194
)
188195

189196
# Strong consistency (guaranteed read-after-write)
190197
strong_session = DaprSession.from_address(
191198
"strong_session",
192199
state_store_name="statestore",
193-
dapr_address="localhost:50001",
200+
dapr_address=f"localhost:{grpc_port}",
194201
consistency=DAPR_CONSISTENCY_STRONG,
195202
)
196203

@@ -213,7 +220,7 @@ def get_tenant_session(tenant_id: str, user_id: str) -> DaprSession:
213220
return DaprSession.from_address(
214221
session_id,
215222
state_store_name="statestore",
216-
dapr_address="localhost:50001",
223+
dapr_address=f"localhost:{grpc_port}",
217224
)
218225

219226
tenant_a_session = get_tenant_session("tenant-a", "user-123")
@@ -254,7 +261,9 @@ async def setup_instructions():
254261
print("\n3. Start Redis:")
255262
print(" docker run -d -p 6379:6379 redis:7-alpine")
256263
print("\n4. Start Dapr sidecar:")
257-
print(" dapr run --app-id myapp --dapr-grpc-port 50001 --components-path ./components")
264+
print(
265+
" dapr run --app-id myapp --dapr-http-port 3500 --dapr-grpc-port 50001 --resources-path ./components"
266+
)
258267
print("\n5. Run this example:")
259268
print(" python examples/memory/dapr_session_example.py")
260269
print("\nAlternatively, you can use other state stores supported by Dapr:")

src/agents/extensions/memory/dapr_session.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,12 @@ def from_address(
104104
105105
Returns:
106106
DaprSession: An instance of DaprSession connected to the specified Dapr sidecar.
107+
108+
Note:
109+
The Dapr Python SDK performs health checks on the HTTP endpoint (default: http://localhost:3500).
110+
Ensure the Dapr sidecar is started with --dapr-http-port 3500. Alternatively, set one of
111+
these environment variables: DAPR_HTTP_ENDPOINT (e.g., "http://localhost:3500") or
112+
DAPR_HTTP_PORT (e.g., "3500") to avoid connection errors.
107113
"""
108114
dapr_client = DaprClient(address=dapr_address)
109115
session = cls(

test.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import asyncio
2+
import os
3+
os.environ["GRPC_VERBOSITY"] = (
4+
"ERROR" # Suppress gRPC warnings caused by the Dapr Python SDK gRPC connection.
5+
)
6+
from agents import Agent, Runner
7+
from agents.extensions.memory import DaprSession
8+
9+
grpc_port = os.environ.get("DAPR_GRPC_PORT", "50001")
10+
11+
12+
async def main():
13+
# Create agent
14+
agent = Agent(
15+
name="Assistant",
16+
instructions="Reply very concisely.",
17+
)
18+
19+
# Connect to Dapr sidecar (using default gRPC port 50001)
20+
session = DaprSession.from_address(
21+
session_id="user-123",
22+
state_store_name="statestore",
23+
dapr_address=f"localhost:{grpc_port}",
24+
)
25+
26+
try:
27+
# First turn
28+
result = await Runner.run(
29+
agent,
30+
"What city is the Golden Gate Bridge in?",
31+
session=session
32+
)
33+
print(f"Agent: {result.final_output}") # "San Francisco"
34+
35+
# Second turn - agent remembers context
36+
result = await Runner.run(
37+
agent,
38+
"What state is it in?",
39+
session=session
40+
)
41+
print(f"Agent: {result.final_output}") # "California"
42+
43+
finally:
44+
# Always clean up
45+
await session.close()
46+
47+
48+
if __name__ == "__main__":
49+
asyncio.run(main())

0 commit comments

Comments
 (0)