Skip to content

Commit a5f5adb

Browse files
committed
python-examples: starter examples for agents and workflows
1 parent d907d01 commit a5f5adb

File tree

106 files changed

+10059
-7
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

106 files changed

+10059
-7
lines changed

orchestrator/src/main.rs

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -786,13 +786,36 @@ async fn main() -> anyhow::Result<()> {
786786
.allow_credentials(true)
787787
.expose_headers([axum::http::header::CONTENT_TYPE]);
788788

789-
// Use the exact origin from CORS_ORIGIN env var
790-
let cors_origin = std::env::var("CORS_ORIGIN")
791-
.unwrap_or_else(|_| "http://localhost:5173".to_string());
792-
let origin_value: axum::http::HeaderValue = cors_origin
793-
.parse()
794-
.unwrap_or_else(|_| "http://localhost:5173".parse().unwrap());
795-
cors_layer.allow_origin(tower_http::cors::AllowOrigin::exact(origin_value))
789+
// In local mode, allow both localhost and 127.0.0.1 variants
790+
let local_mode = std::env::var("POLOS_LOCAL_MODE")
791+
.map(|v| v.to_lowercase() == "true")
792+
.unwrap_or(false);
793+
794+
if local_mode {
795+
// Extract port from CORS_ORIGIN or use default
796+
let cors_origin = std::env::var("CORS_ORIGIN")
797+
.unwrap_or_else(|_| "http://localhost:5173".to_string());
798+
let port = cors_origin
799+
.rsplit(':')
800+
.next()
801+
.and_then(|p| p.parse::<u16>().ok())
802+
.unwrap_or(5173);
803+
804+
// Allow both localhost and 127.0.0.1
805+
let origins = [
806+
format!("http://localhost:{}", port).parse().unwrap(),
807+
format!("http://127.0.0.1:{}", port).parse().unwrap(),
808+
];
809+
cors_layer.allow_origin(tower_http::cors::AllowOrigin::list(origins))
810+
} else {
811+
// Production: use exact origin from CORS_ORIGIN env var
812+
let cors_origin = std::env::var("CORS_ORIGIN")
813+
.unwrap_or_else(|_| "http://localhost:5173".to_string());
814+
let origin_value: axum::http::HeaderValue = cors_origin
815+
.parse()
816+
.unwrap_or_else(|_| "http://localhost:5173".parse().unwrap());
817+
cors_layer.allow_origin(tower_http::cors::AllowOrigin::exact(origin_value))
818+
}
796819
})
797820
.with_state(state);
798821

python-examples/.gitignore

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Environment
2+
.env
3+
.venv/
4+
venv/
5+
6+
# Python
7+
__pycache__/
8+
*.py[cod]
9+
*.egg-info/
10+
dist/
11+
build/
12+
13+
# uv
14+
uv.lock
15+
16+
# IDE
17+
.idea/
18+
.vscode/
19+
*.swp
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Required - indicates that we are running Polos locally in dev mode
2+
POLOS_LOCAL_MODE=true
3+
4+
# Required: Polos project ID.
5+
# You can get this from the output printed by `polos-server start` or from the UI page at
6+
# http://localhost:5173/projects/settings (the ID will be below the project name 'default')
7+
POLOS_PROJECT_ID=your-project-id
8+
9+
# Required: OpenAI API key for the agent
10+
OPENAI_API_KEY=sk-...
11+
12+
# Optional: Orchestrator URL (defaults to http://localhost:8080)
13+
# POLOS_API_URL=http://localhost:8080
14+
15+
# Optional: Deployment ID for routing workflows to specific workers
16+
POLOS_DEPLOYMENT_ID=python-examples
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# Weather agent Example
2+
3+
A simple example demonstrating the basics of Polos:
4+
- Creating agent with tool
5+
- Running a worker to execute the agent
6+
7+
## Prerequisites
8+
9+
1. **Polos Server** - The orchestrator that manages workflow execution
10+
2. **Python 3.10+**
11+
3. **Polos project ID** - Get this from the UI page http://localhost:5173/settings
12+
3. **OpenAI API Key** - For the weather agent
13+
14+
## Setup
15+
16+
### 1. Start the Polos Server
17+
18+
Install and start the Polos server (orchestrator):
19+
20+
```bash
21+
# Install polos-server
22+
curl -fsSL https://install.polos.dev/install.sh | bash
23+
24+
# Start the server
25+
polos-server start
26+
```
27+
28+
Example output:
29+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
30+
🎉 Polos server is running!
31+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
32+
📡 Orchestrator API: http://127.0.0.1:8080
33+
🌐 UI: http://127.0.0.1:5173
34+
🔑 Project ID: 8348be0b-bee2-4b28-bd7a-3d5a873c01ab
35+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
36+
37+
38+
The server runs at `http://localhost:8080` by default.
39+
40+
### 2. Install Dependencies
41+
42+
```bash
43+
# Using uv (recommended)
44+
uv sync
45+
46+
# Or using pip
47+
pip install -e .
48+
```
49+
50+
### 3. Configure Environment
51+
52+
Create a `.env` file:
53+
54+
```bash
55+
# Required: Your project ID.
56+
# You can get this from the output printed by `polos-server start` or from the UI page at
57+
# http://localhost:5173/projects/settings (the ID will be below the project name 'default')
58+
POLOS_PROJECT_ID=my-project
59+
60+
# Required: OpenAI API key for the agent
61+
OPENAI_API_KEY=sk-...
62+
63+
# Optional: Orchestrator URL (defaults to http://localhost:8080)
64+
# POLOS_API_URL=http://localhost:8080
65+
```
66+
67+
## Running the Example
68+
69+
### Terminal 1: Start the Worker
70+
71+
The worker executes workflows and agents:
72+
73+
```bash
74+
python worker.py
75+
```
76+
77+
You should see:
78+
```
79+
Starting worker...
80+
Project ID: <my-project>
81+
Agents: ['weather_agent']
82+
Press Ctrl+C to stop
83+
```
84+
85+
### Terminal 2: Run the Example
86+
87+
In a separate terminal, run:
88+
89+
```bash
90+
python main.py
91+
```
92+
93+
You should see:
94+
```
95+
Invoking weather_agent...
96+
Result: 'The weather in New York is...'
97+
```
98+
99+
## What's Happening?
100+
101+
1. **main.py** invokes `weather_agent` with input "What's the weather like in New York?"
102+
2. **Polos Server** receives the workflow (agent) and queues it for execution
103+
3. **worker.py** picks up the agent request and executes it:
104+
- Starts the weather agent to get weather information
105+
- The agent uses the `get_weather` tool
106+
4. The result is returned through Polos Server to the caller
107+
108+
## Files
109+
110+
- `agents.py` - Defines the `weather_agent` agent
111+
- `tools.py` - Defines the `get_weather` tool with mock weather data
112+
- `worker.py` - Runs the worker that executes workflows
113+
- `main.py` - Invokes the workflow and displays results
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"""Example workflows and agents for the Hello World example."""
2+
3+
from polos import Agent, max_steps, MaxStepsConfig
4+
from tools import get_weather
5+
6+
7+
# Define a weather agent that can look up weather information
8+
weather_agent = Agent(
9+
id="weather_agent",
10+
provider="openai",
11+
model="gpt-4o-mini",
12+
system_prompt="You are a helpful weather assistant. Use the get_weather tool to look up weather information when asked.",
13+
tools=[get_weather],
14+
stop_conditions=[
15+
max_steps(MaxStepsConfig(limit=10)),
16+
],
17+
)
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
"""
2+
Run the weather agent.
3+
4+
This script invokes the weather agent and waits for the result.
5+
6+
Run with:
7+
python main.py
8+
9+
Environment variables:
10+
POLOS_PROJECT_ID - Your project ID (required)
11+
POLOS_API_URL - Orchestrator URL (default: http://localhost:8080)
12+
"""
13+
14+
import asyncio
15+
import os
16+
17+
from dotenv import load_dotenv
18+
from polos import PolosClient
19+
20+
from agents import weather_agent
21+
22+
load_dotenv()
23+
24+
25+
async def main():
26+
"""Run the weather agent."""
27+
# Get project_id from environment
28+
project_id = os.getenv("POLOS_PROJECT_ID")
29+
if not project_id:
30+
raise ValueError(
31+
"POLOS_PROJECT_ID environment variable is required. "
32+
"Set it to your project ID (e.g., export POLOS_PROJECT_ID=my-project). "
33+
"You can get this from the output printed by `polos-server start` or from the UI page at "
34+
"http://localhost:5173/projects/settings (the ID will be below the project name 'default')"
35+
)
36+
37+
# Create Polos client
38+
client = PolosClient(
39+
project_id=project_id,
40+
api_url=os.getenv("POLOS_API_URL", "http://localhost:8080"),
41+
deployment_id="python-examples",
42+
)
43+
44+
print("Invoking weather_agent...")
45+
46+
result = await weather_agent.run(
47+
client, "What's the weather like in New York?"
48+
)
49+
50+
print(result.result)
51+
52+
53+
if __name__ == "__main__":
54+
asyncio.run(main())
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[project]
2+
name = "polos-hello-world"
3+
version = "0.1.0"
4+
description = "Hello World example for Polos SDK"
5+
requires-python = ">=3.10"
6+
dependencies = [
7+
"polos-sdk[openai]",
8+
"python-dotenv>=1.0.0",
9+
]
10+
11+
[dependency-groups]
12+
dev = [
13+
"ruff>=0.1.0",
14+
]
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
"""Example tools for agents."""
2+
3+
from pydantic import BaseModel
4+
from polos import tool, WorkflowContext
5+
6+
7+
# Pre-canned weather data for various cities
8+
WEATHER_DATA = {
9+
"new york": {
10+
"city": "New York",
11+
"temperature": 72,
12+
"condition": "Partly Cloudy",
13+
"humidity": 65,
14+
"wind_speed": 10,
15+
"unit": "F",
16+
},
17+
"san francisco": {
18+
"city": "San Francisco",
19+
"temperature": 68,
20+
"condition": "Foggy",
21+
"humidity": 80,
22+
"wind_speed": 8,
23+
"unit": "F",
24+
},
25+
"london": {
26+
"city": "London",
27+
"temperature": 15,
28+
"condition": "Rainy",
29+
"humidity": 85,
30+
"wind_speed": 12,
31+
"unit": "C",
32+
},
33+
"tokyo": {
34+
"city": "Tokyo",
35+
"temperature": 22,
36+
"condition": "Sunny",
37+
"humidity": 60,
38+
"wind_speed": 5,
39+
"unit": "C",
40+
},
41+
"paris": {
42+
"city": "Paris",
43+
"temperature": 18,
44+
"condition": "Cloudy",
45+
"humidity": 70,
46+
"wind_speed": 9,
47+
"unit": "C",
48+
},
49+
}
50+
51+
52+
class WeatherInput(BaseModel):
53+
"""Input schema for weather tool."""
54+
55+
city: str
56+
57+
58+
class WeatherOutput(BaseModel):
59+
"""Output schema for weather tool."""
60+
61+
city: str
62+
temperature: int
63+
condition: str
64+
humidity: int
65+
wind_speed: int
66+
unit: str
67+
error: str | None = None
68+
69+
70+
@tool(description="Get the current weather information for a given city")
71+
async def get_weather(ctx: WorkflowContext, input: WeatherInput) -> WeatherOutput:
72+
"""
73+
Tool that returns weather information for a city.
74+
75+
This is a simple example tool that the agent can call.
76+
In a real scenario, this would query a weather API.
77+
"""
78+
city = input.city.strip().lower()
79+
weather = WEATHER_DATA.get(city)
80+
81+
if not weather:
82+
# Try to find a partial match
83+
for key, value in WEATHER_DATA.items():
84+
if city in key or key in city:
85+
weather = value
86+
break
87+
88+
if not weather:
89+
return WeatherOutput(
90+
city=city.title(),
91+
temperature=0,
92+
condition="Unknown",
93+
humidity=0,
94+
wind_speed=0,
95+
unit="C",
96+
error=f"Weather data not available for '{city}'.",
97+
)
98+
99+
return WeatherOutput(
100+
city=weather["city"],
101+
temperature=weather["temperature"],
102+
condition=weather["condition"],
103+
humidity=weather["humidity"],
104+
wind_speed=weather["wind_speed"],
105+
unit=weather["unit"],
106+
error=None,
107+
)

0 commit comments

Comments
 (0)