polos-sdk 0.1.0__py3-none-any.whl → 0.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- polos/agents/agent.py +3 -0
- polos/agents/stream.py +19 -3
- polos/core/step.py +7 -0
- polos/core/workflow.py +6 -0
- polos/features/events.py +5 -10
- polos/runtime/client.py +8 -4
- polos/runtime/worker.py +5 -3
- polos/tools/tool.py +1 -0
- polos/utils/serializer.py +21 -1
- polos_sdk-0.1.2.dist-info/METADATA +121 -0
- {polos_sdk-0.1.0.dist-info → polos_sdk-0.1.2.dist-info}/RECORD +12 -12
- polos_sdk-0.1.0.dist-info/METADATA +0 -650
- {polos_sdk-0.1.0.dist-info → polos_sdk-0.1.2.dist-info}/WHEEL +0 -0
polos/agents/agent.py
CHANGED
|
@@ -351,6 +351,7 @@ class Agent(Workflow):
|
|
|
351
351
|
id: str, # Required: task ID
|
|
352
352
|
provider: str,
|
|
353
353
|
model: str,
|
|
354
|
+
description: str | None = None, # Description for team coordination
|
|
354
355
|
system_prompt: str | None = None,
|
|
355
356
|
tools: list[Any] | None = None,
|
|
356
357
|
temperature: float | None = None,
|
|
@@ -388,6 +389,7 @@ class Agent(Workflow):
|
|
|
388
389
|
super().__init__(
|
|
389
390
|
id=id,
|
|
390
391
|
func=self._agent_execute,
|
|
392
|
+
description=description,
|
|
391
393
|
workflow_type="agent",
|
|
392
394
|
queue_name=queue_name,
|
|
393
395
|
queue_concurrency_limit=queue_concurrency_limit,
|
|
@@ -689,6 +691,7 @@ class Agent(Workflow):
|
|
|
689
691
|
"input": input,
|
|
690
692
|
"session_id": session_id,
|
|
691
693
|
"user_id": user_id,
|
|
694
|
+
"conversation_id": conversation_id,
|
|
692
695
|
"streaming": True,
|
|
693
696
|
"provider_kwargs": kwargs, # Pass kwargs to provider
|
|
694
697
|
},
|
polos/agents/stream.py
CHANGED
|
@@ -56,7 +56,6 @@ async def _agent_stream_function(ctx: AgentContext, payload: dict[str, Any]) ->
|
|
|
56
56
|
agent_run_id = ctx.execution_id # Use execution_id from context
|
|
57
57
|
agent_config = payload["agent_config"]
|
|
58
58
|
streaming = payload.get("streaming", True) # Default to True for backward compatibility
|
|
59
|
-
tool_stop_action = False
|
|
60
59
|
input_data = payload.get("input")
|
|
61
60
|
|
|
62
61
|
result = {
|
|
@@ -206,6 +205,23 @@ async def _agent_stream_function(ctx: AgentContext, payload: dict[str, Any]) ->
|
|
|
206
205
|
"tool_results": tool_results, # Tool results from previous iteration
|
|
207
206
|
},
|
|
208
207
|
)
|
|
208
|
+
|
|
209
|
+
if guardrails and streaming and llm_result.get("content"):
|
|
210
|
+
# Emit one text_delta event with full response for clients who are streaming
|
|
211
|
+
await ctx.step.publish_event(
|
|
212
|
+
f"llm_generate:text_delta:{agent_step}",
|
|
213
|
+
topic=f"workflow:{agent_run_id}",
|
|
214
|
+
event_type="text_delta",
|
|
215
|
+
data={
|
|
216
|
+
"step": agent_step,
|
|
217
|
+
"chunk_index": 1,
|
|
218
|
+
"content": llm_result.get("content"),
|
|
219
|
+
"_metadata": {
|
|
220
|
+
"execution_id": agent_run_id,
|
|
221
|
+
"workflow_id": ctx.workflow_id,
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
)
|
|
209
225
|
else:
|
|
210
226
|
# No guardrails - use streaming
|
|
211
227
|
llm_result = await _llm_stream(
|
|
@@ -313,7 +329,7 @@ async def _agent_stream_function(ctx: AgentContext, payload: dict[str, Any]) ->
|
|
|
313
329
|
)
|
|
314
330
|
|
|
315
331
|
# Execute all tools in batch
|
|
316
|
-
if
|
|
332
|
+
if len(batch_workflows) > 0:
|
|
317
333
|
tool_results_list: list[BatchStepResult] = await ctx.step.batch_invoke_and_wait(
|
|
318
334
|
f"execute_tools:step_{agent_step}", batch_workflows
|
|
319
335
|
)
|
|
@@ -453,7 +469,7 @@ async def _agent_stream_function(ctx: AgentContext, payload: dict[str, Any]) ->
|
|
|
453
469
|
raise StepExecutionError(hook_result.error_message or "Hook execution failed")
|
|
454
470
|
|
|
455
471
|
# No tool results, we're done
|
|
456
|
-
if tool_results is None or len(tool_results) == 0
|
|
472
|
+
if tool_results is None or len(tool_results) == 0:
|
|
457
473
|
end_steps = True
|
|
458
474
|
|
|
459
475
|
# Evaluate stop conditions (if any)
|
polos/core/step.py
CHANGED
|
@@ -134,6 +134,13 @@ class Step:
|
|
|
134
134
|
# Extract full module path for Pydantic class
|
|
135
135
|
# (e.g., "polos.llm.providers.base.LLMResponse")
|
|
136
136
|
output_schema_name = f"{result.__class__.__module__}.{result.__class__.__name__}"
|
|
137
|
+
elif isinstance(result, list) and result and isinstance(result[0], BaseModel):
|
|
138
|
+
# Handle list of Pydantic models
|
|
139
|
+
outputs = [item.model_dump(mode="json") for item in result]
|
|
140
|
+
# Store schema name of the list item type for deserialization
|
|
141
|
+
output_schema_name = (
|
|
142
|
+
f"list[{result[0].__class__.__module__}.{result[0].__class__.__name__}]"
|
|
143
|
+
)
|
|
137
144
|
else:
|
|
138
145
|
outputs = result
|
|
139
146
|
|
polos/core/workflow.py
CHANGED
|
@@ -80,6 +80,7 @@ class Workflow:
|
|
|
80
80
|
self,
|
|
81
81
|
id: str,
|
|
82
82
|
func: Callable,
|
|
83
|
+
description: str | None = None, # Description for team coordination
|
|
83
84
|
workflow_type: str | None = "workflow",
|
|
84
85
|
queue_name: str | None = None,
|
|
85
86
|
queue_concurrency_limit: int | None = None,
|
|
@@ -94,6 +95,7 @@ class Workflow:
|
|
|
94
95
|
state_schema: type[BaseModel] | None = None,
|
|
95
96
|
):
|
|
96
97
|
self.id = id
|
|
98
|
+
self.description = description # Description for team coordination
|
|
97
99
|
self.func = func
|
|
98
100
|
self.is_async = asyncio.iscoroutinefunction(func)
|
|
99
101
|
# Check if function has a payload parameter
|
|
@@ -849,6 +851,7 @@ class Workflow:
|
|
|
849
851
|
|
|
850
852
|
def workflow(
|
|
851
853
|
id: str | None = None,
|
|
854
|
+
description: str | None = None, # Description for team coordination
|
|
852
855
|
queue: str | Queue | dict[str, Any] | None = None,
|
|
853
856
|
trigger_on_event: str | None = None,
|
|
854
857
|
batch_size: int = 1,
|
|
@@ -938,6 +941,8 @@ def workflow(
|
|
|
938
941
|
|
|
939
942
|
Args:
|
|
940
943
|
id: Optional workflow ID (defaults to function name)
|
|
944
|
+
description: Optional description for team coordination. Used when this
|
|
945
|
+
workflow is added to a Team so the coordinator LLM understands its purpose.
|
|
941
946
|
queue: Optional queue configuration. Can be:
|
|
942
947
|
- str: Queue name
|
|
943
948
|
- Queue: Queue object
|
|
@@ -1158,6 +1163,7 @@ def workflow(
|
|
|
1158
1163
|
workflow_obj = Workflow(
|
|
1159
1164
|
id=workflow_id,
|
|
1160
1165
|
func=func,
|
|
1166
|
+
description=description,
|
|
1161
1167
|
queue_name=queue_name,
|
|
1162
1168
|
queue_concurrency_limit=queue_concurrency_limit,
|
|
1163
1169
|
trigger_on_event=trigger_on_event,
|
polos/features/events.py
CHANGED
|
@@ -235,8 +235,7 @@ async def batch_publish(
|
|
|
235
235
|
async def publish(
|
|
236
236
|
client: PolosClient,
|
|
237
237
|
topic: str,
|
|
238
|
-
|
|
239
|
-
data: dict[str, Any] = None,
|
|
238
|
+
event_data: EventData,
|
|
240
239
|
execution_id: str | None = None,
|
|
241
240
|
root_execution_id: str | None = None,
|
|
242
241
|
) -> int:
|
|
@@ -247,20 +246,16 @@ async def publish(
|
|
|
247
246
|
Args:
|
|
248
247
|
client: PolosClient instance
|
|
249
248
|
topic: Event topic
|
|
250
|
-
|
|
251
|
-
|
|
249
|
+
event_data: EventData
|
|
250
|
+
execution_id: Optional execution ID
|
|
251
|
+
root_execution_id: Optional root execution ID
|
|
252
252
|
|
|
253
253
|
Returns:
|
|
254
254
|
sequence_id: Global sequence ID for the event
|
|
255
255
|
"""
|
|
256
256
|
sequence_ids = await batch_publish(
|
|
257
257
|
topic=topic,
|
|
258
|
-
events=[
|
|
259
|
-
EventData(
|
|
260
|
-
event_type=event_type,
|
|
261
|
-
data=data or {},
|
|
262
|
-
)
|
|
263
|
-
],
|
|
258
|
+
events=[event_data],
|
|
264
259
|
execution_id=execution_id,
|
|
265
260
|
root_execution_id=root_execution_id,
|
|
266
261
|
client=client,
|
polos/runtime/client.py
CHANGED
|
@@ -48,17 +48,22 @@ class PolosClient:
|
|
|
48
48
|
api_url: str | None = None,
|
|
49
49
|
api_key: str | None = None,
|
|
50
50
|
project_id: str | None = None,
|
|
51
|
+
deployment_id: str | None = None,
|
|
51
52
|
):
|
|
52
53
|
"""Initialize Polos client.
|
|
53
54
|
|
|
54
55
|
Args:
|
|
55
|
-
api_url: Orchestrator API URL (default: from POLOS_API_URL env var or
|
|
56
|
+
api_url: Orchestrator API URL (default: from POLOS_API_URL env var or
|
|
57
|
+
http://localhost:8080)
|
|
56
58
|
api_key: API key for authentication (default: from POLOS_API_KEY env var)
|
|
57
59
|
project_id: Project ID for multi-tenancy (default: from POLOS_PROJECT_ID env var)
|
|
60
|
+
deployment_id: Deployment ID for workflow invocations (default: from
|
|
61
|
+
POLOS_DEPLOYMENT_ID env var, None uses latest deployment)
|
|
58
62
|
"""
|
|
59
63
|
self.api_url = api_url or os.getenv("POLOS_API_URL", "http://localhost:8080")
|
|
60
64
|
self.api_key = api_key or os.getenv("POLOS_API_KEY")
|
|
61
65
|
self.project_id = project_id or os.getenv("POLOS_PROJECT_ID")
|
|
66
|
+
self.deployment_id = deployment_id or os.getenv("POLOS_DEPLOYMENT_ID")
|
|
62
67
|
|
|
63
68
|
# Validate required fields (with local mode support)
|
|
64
69
|
local_mode_requested = os.getenv("POLOS_LOCAL_MODE", "False").lower() == "true"
|
|
@@ -158,7 +163,6 @@ class PolosClient:
|
|
|
158
163
|
Args:
|
|
159
164
|
workflow_id: The workflow identifier
|
|
160
165
|
payload: The workflow payload
|
|
161
|
-
deployment_id: Optional deployment ID (if not provided, uses latest active)
|
|
162
166
|
queue_name: Optional queue name (if not provided, defaults to workflow_id)
|
|
163
167
|
queue_concurrency_limit: Optional concurrency limit for queue creation
|
|
164
168
|
concurrency_key: Optional concurrency key for per-tenant queuing
|
|
@@ -172,7 +176,7 @@ class PolosClient:
|
|
|
172
176
|
"""
|
|
173
177
|
return await self._submit_workflow(
|
|
174
178
|
workflow_id=workflow_id,
|
|
175
|
-
deployment_id=
|
|
179
|
+
deployment_id=self.deployment_id, # Use client's deployment_id (None uses latest)
|
|
176
180
|
payload=payload,
|
|
177
181
|
queue_name=queue_name,
|
|
178
182
|
queue_concurrency_limit=queue_concurrency_limit,
|
|
@@ -235,7 +239,7 @@ class PolosClient:
|
|
|
235
239
|
# Submit all workflows in a single batch using the batch endpoint
|
|
236
240
|
handles = await self._submit_workflows(
|
|
237
241
|
workflows=workflow_requests,
|
|
238
|
-
deployment_id=
|
|
242
|
+
deployment_id=self.deployment_id, # Use client's deployment_id (None uses latest)
|
|
239
243
|
parent_execution_id=None,
|
|
240
244
|
root_execution_id=None,
|
|
241
245
|
step_key=None, # Not invoked from a step, so no step_key
|
polos/runtime/worker.py
CHANGED
|
@@ -187,8 +187,8 @@ class Worker:
|
|
|
187
187
|
|
|
188
188
|
# Build workflow registry
|
|
189
189
|
self.workflows_registry: dict[str, Workflow] = {}
|
|
190
|
-
self.agents: list[Agent] = [a for a in agents if isinstance(a, Agent)] or []
|
|
191
|
-
self.tools: list[Tool] = [t for t in tools if isinstance(t, Tool)] or []
|
|
190
|
+
self.agents: list[Agent] = [a for a in (agents or []) if isinstance(a, Agent)] or []
|
|
191
|
+
self.tools: list[Tool] = [t for t in (tools or []) if isinstance(t, Tool)] or []
|
|
192
192
|
self.agent_ids: list[str] = []
|
|
193
193
|
self.tool_ids: list[str] = []
|
|
194
194
|
self.workflow_ids: list[str] = []
|
|
@@ -938,7 +938,9 @@ class Worker:
|
|
|
938
938
|
# Check if error is StepExecutionError - if so, mark as non-retryable
|
|
939
939
|
# Tools are not retryable by default. We feed the error back to the LLM to handle.
|
|
940
940
|
retryable = (
|
|
941
|
-
|
|
941
|
+
workflow
|
|
942
|
+
and not isinstance(error, StepExecutionError)
|
|
943
|
+
and workflow.workflow_type != "tool"
|
|
942
944
|
)
|
|
943
945
|
await self._report_failure(
|
|
944
946
|
workflow_data["execution_id"], error_message, stack_trace, retryable=retryable
|
polos/tools/tool.py
CHANGED
polos/utils/serializer.py
CHANGED
|
@@ -87,11 +87,31 @@ async def deserialize(obj: Any, output_schema_name: str | None = None) -> Any:
|
|
|
87
87
|
|
|
88
88
|
Args:
|
|
89
89
|
obj: Object to deserialize
|
|
90
|
-
output_schema_name: The name of the output schema
|
|
90
|
+
output_schema_name: The name of the output schema (can be
|
|
91
|
+
"list[module.ClassName]" for lists)
|
|
91
92
|
|
|
92
93
|
Returns:
|
|
93
94
|
Deserialized object
|
|
94
95
|
"""
|
|
96
|
+
# Handle list of Pydantic models (schema format: "list[module.ClassName]")
|
|
97
|
+
if output_schema_name and output_schema_name.startswith("list[") and isinstance(obj, list):
|
|
98
|
+
try:
|
|
99
|
+
# Extract the inner class name from "list[module.ClassName]"
|
|
100
|
+
inner_schema = output_schema_name[5:-1] # Remove "list[" and "]"
|
|
101
|
+
module_path, class_name = inner_schema.rsplit(".", 1)
|
|
102
|
+
module = __import__(module_path, fromlist=[class_name])
|
|
103
|
+
model_class = getattr(module, class_name)
|
|
104
|
+
|
|
105
|
+
# Validate each item in the list back to the Pydantic model
|
|
106
|
+
if issubclass(model_class, BaseModel):
|
|
107
|
+
obj = [model_class.model_validate(item) for item in obj]
|
|
108
|
+
except (ImportError, AttributeError, ValueError, TypeError) as e:
|
|
109
|
+
raise Exception(
|
|
110
|
+
f"Failed to reconstruct Pydantic model list from output_schema_name: "
|
|
111
|
+
f"{output_schema_name}. Error: {str(e)}"
|
|
112
|
+
) from e
|
|
113
|
+
return obj
|
|
114
|
+
|
|
95
115
|
# If output_schema_name is present, try to reconstruct the Pydantic model
|
|
96
116
|
if output_schema_name and isinstance(obj, dict):
|
|
97
117
|
try:
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: polos-sdk
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Polos SDK for Python - Durable Agent Execution
|
|
5
|
+
Project-URL: Homepage, https://github.com/polos-dev/polos
|
|
6
|
+
Project-URL: Repository, https://github.com/polos-dev/polos
|
|
7
|
+
Project-URL: Documentation, https://docs.polos.dev
|
|
8
|
+
Project-URL: Issues, https://github.com/polos-dev/polos/issues
|
|
9
|
+
Author: Polos Team
|
|
10
|
+
License: Apache-2.0
|
|
11
|
+
Keywords: agents,ai,async,durable-execution,llm,orchestration,workflow
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: anyio>=4.0.0
|
|
23
|
+
Requires-Dist: fastapi>=0.104.0
|
|
24
|
+
Requires-Dist: httpx>=0.24.0
|
|
25
|
+
Requires-Dist: opentelemetry-api>=1.20.0
|
|
26
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.20.0
|
|
27
|
+
Requires-Dist: opentelemetry-sdk>=1.20.0
|
|
28
|
+
Requires-Dist: pydantic>=2.0.0
|
|
29
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
30
|
+
Requires-Dist: uvicorn>=0.24.0
|
|
31
|
+
Provides-Extra: anthropic
|
|
32
|
+
Requires-Dist: anthropic>=0.39.0; extra == 'anthropic'
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: pre-commit>=3.0.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
37
|
+
Requires-Dist: pytest-mock>=3.10.0; extra == 'dev'
|
|
38
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
39
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
40
|
+
Provides-Extra: fireworks
|
|
41
|
+
Requires-Dist: openai>=1.0.0; extra == 'fireworks'
|
|
42
|
+
Provides-Extra: gemini
|
|
43
|
+
Requires-Dist: openai>=1.0.0; extra == 'gemini'
|
|
44
|
+
Provides-Extra: groq
|
|
45
|
+
Requires-Dist: openai>=1.0.0; extra == 'groq'
|
|
46
|
+
Provides-Extra: openai
|
|
47
|
+
Requires-Dist: openai>=1.0.0; extra == 'openai'
|
|
48
|
+
Provides-Extra: together
|
|
49
|
+
Requires-Dist: openai>=1.0.0; extra == 'together'
|
|
50
|
+
Description-Content-Type: text/markdown
|
|
51
|
+
|
|
52
|
+
# Polos Python SDK
|
|
53
|
+
|
|
54
|
+
Durable execution engine for Python. Build reliable AI agents and workflows that can survive failures, handle long-running tasks, and coordinate complex processes.
|
|
55
|
+
|
|
56
|
+
## Features
|
|
57
|
+
|
|
58
|
+
- 🤖 **AI Agents** - Build LLM-powered agents with tool calling, streaming, and conversation history
|
|
59
|
+
- 🔄 **Durable Workflows** - Workflows survive failures and resume from checkpoints
|
|
60
|
+
- ⏰ **Long-Running** - Execute workflows that run for hours or days
|
|
61
|
+
- 🔗 **Workflow Orchestration** - Chain workflows together and build complex processes
|
|
62
|
+
- 🛠️ **Tools** - Define reusable tools that agents can call
|
|
63
|
+
- 🐍 **Native Python** - Async/await support, type hints, and Pythonic APIs
|
|
64
|
+
- 📊 **Observability** - Built-in tracing, events, and monitoring
|
|
65
|
+
|
|
66
|
+
## Installation
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install polos-sdk
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Or with UV (recommended):
|
|
73
|
+
```bash
|
|
74
|
+
uv add polos-sdk
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Optional Dependencies
|
|
78
|
+
|
|
79
|
+
Install provider-specific dependencies for LLM support:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# OpenAI
|
|
83
|
+
pip install polos-sdk[openai]
|
|
84
|
+
|
|
85
|
+
# Anthropic
|
|
86
|
+
pip install polos-sdk[anthropic]
|
|
87
|
+
|
|
88
|
+
# Google Gemini
|
|
89
|
+
pip install polos-sdk[gemini]
|
|
90
|
+
|
|
91
|
+
# Groq
|
|
92
|
+
pip install polos-sdk[groq]
|
|
93
|
+
|
|
94
|
+
# Fireworks
|
|
95
|
+
pip install polos-sdk[fireworks]
|
|
96
|
+
|
|
97
|
+
# Together AI
|
|
98
|
+
pip install polos-sdk[together]
|
|
99
|
+
|
|
100
|
+
# All providers
|
|
101
|
+
pip install polos-sdk[openai,anthropic,gemini,groq,fireworks,together]
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Quick Start
|
|
105
|
+
|
|
106
|
+
Use the quickstart guide at [https://docs.polos.dev](https://docs.polos.dev) to get started in minutes.
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
|
|
110
|
+
Apache-2.0 - see [LICENSE](../../LICENSE) for details.
|
|
111
|
+
|
|
112
|
+
## Support
|
|
113
|
+
|
|
114
|
+
- 📖 [Documentation](https://docs.polos.dev)
|
|
115
|
+
- 💬 [Discord Community](https://discord.gg/ZAxHKMPwFG)
|
|
116
|
+
- 🐛 [Issue Tracker](https://github.com/polos-dev/polos/issues)
|
|
117
|
+
- 📧 [Email Support](mailto:support@polos.dev)
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
Built with ❤️ by the Polos team
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
polos/__init__.py,sha256=WTVHY01DfbioRF7A130k7SLizsC9UJZoySmz9lJ_VnA,2402
|
|
2
2
|
polos/agents/__init__.py,sha256=z6Pyyzbd8fKf2oA49hQ_1srj0nyCrS8XsuWAMYExz3c,122
|
|
3
|
-
polos/agents/agent.py,sha256=
|
|
3
|
+
polos/agents/agent.py,sha256=f3nXltSr28M9_sFaxndHjFx6NhO5bD5kqLTyEpu0vVQ,26592
|
|
4
4
|
polos/agents/conversation_history.py,sha256=W4efQU4p7yyMAkAw8jFkA9YEkQn-7giZKr0lkyBUJ-w,3918
|
|
5
5
|
polos/agents/stop_conditions.py,sha256=2qV27F4J8SfOHDZ3iGqUUELuBLmLIK08Fj7WOapo8cQ,9456
|
|
6
|
-
polos/agents/stream.py,sha256=
|
|
6
|
+
polos/agents/stream.py,sha256=XHF6N2od1DCVLHZlpdwRBgMwf_7Kv8rvSbg8k4wtyg0,25819
|
|
7
7
|
polos/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
8
|
polos/core/context.py,sha256=vkwM4cQ93p1-K_muT6obxYV_dzgsPDOC3HT-OKNF6jE,5053
|
|
9
9
|
polos/core/state.py,sha256=MKWaGM1kLQEw0Q2VWD-PXB3CkK1tiqoTmeoQRwIpngg,774
|
|
10
|
-
polos/core/step.py,sha256=
|
|
11
|
-
polos/core/workflow.py,sha256=
|
|
10
|
+
polos/core/step.py,sha256=BmSMXyPYVnoLEOz4TDyJgslFg0jLFmy_Ey6keN1MIwI,52116
|
|
11
|
+
polos/core/workflow.py,sha256=VNxTUObkdtjxftGaeUF7hSpcr-rYioO2-cQva7L2tYs,51524
|
|
12
12
|
polos/features/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
-
polos/features/events.py,sha256=
|
|
13
|
+
polos/features/events.py,sha256=8Ixel43iRe2rHB6bdLMMpYt5qy_jxDpfOmL7saVa0Fg,14145
|
|
14
14
|
polos/features/schedules.py,sha256=SpnQLOHKShZJA-Hteoqto5eTiq3Sin4-y8-K2RTQouw,3077
|
|
15
15
|
polos/features/tracing.py,sha256=xOaj9WLqK7B9IwRUDyCDVZ7IGhVPDxQ1YB1UQ3aFK2w,22736
|
|
16
16
|
polos/features/wait.py,sha256=c2NUz1BPG56EGIdxQwuDhydnyztqD5B9KBTlaimdofE,2504
|
|
@@ -33,12 +33,12 @@ polos/middleware/hook.py,sha256=qhzjQP5I6DkL4GbdIrauDTesUQh067whyt1PxbP8qH8,4780
|
|
|
33
33
|
polos/middleware/hook_executor.py,sha256=732Kw89deLjDZPJyKQ0ZDlmOinp7dksBZenBdPSkpfg,3669
|
|
34
34
|
polos/runtime/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
35
35
|
polos/runtime/batch.py,sha256=hrzHi2oP5jJ6SJemESnkWI22v3m_GIMgh38fOscmM8k,2879
|
|
36
|
-
polos/runtime/client.py,sha256=
|
|
36
|
+
polos/runtime/client.py,sha256=52N2l86V7z91RYrE4glMC83yhZRKkrZxsK79PEA24ME,33257
|
|
37
37
|
polos/runtime/queue.py,sha256=QeLgCVnMemchj7FdT_K5zsy-86iNTOqf2Wm3s1Cg-lA,1257
|
|
38
|
-
polos/runtime/worker.py,sha256=
|
|
38
|
+
polos/runtime/worker.py,sha256=Eo6HeJPSY0dpfUZ6U261oNDgK2guaNET53LoTtWE04U,56459
|
|
39
39
|
polos/runtime/worker_server.py,sha256=HtWCyufL184R-aAIU_p0vVhwlWH4PDseQjZ2qPKpEns,10120
|
|
40
40
|
polos/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
|
-
polos/tools/tool.py,sha256=
|
|
41
|
+
polos/tools/tool.py,sha256=6MMTjPmc-j6ihjO-oQUpM2yDvFiagoOnDL--HI7Xliw,22542
|
|
42
42
|
polos/types/__init__.py,sha256=Va3Yg1JbuDIzXFt5czG2hrN7DZL19k04DNBguUf-JlA,360
|
|
43
43
|
polos/types/types.py,sha256=wqYKLqm8nZ2Ba5BEDJpMgS88XzzeGV9zzX_ntGP8lpw,2919
|
|
44
44
|
polos/utils/__init__.py,sha256=E9PPg23MCxh-82TlQeD4-VNo0gQo3rJDQxaw7EVbVfo,636
|
|
@@ -47,9 +47,9 @@ polos/utils/client_context.py,sha256=drSXD_4zPwn83KaNbp3B7vQcDH9IUbni1hT7TSG2DGU
|
|
|
47
47
|
polos/utils/config.py,sha256=VXfccQgtVTX5ieFnHosB8xqcoHecDT2hJ09rJMnKvWw,399
|
|
48
48
|
polos/utils/output_schema.py,sha256=wJSZviqPCcqWrDfUlBFHCKYmuOqNZTqw4hYhqGZFI10,14235
|
|
49
49
|
polos/utils/retry.py,sha256=Igz4degC0IUWnsJsRKVIrkNjYm57DG467EcLbmMOMuM,1398
|
|
50
|
-
polos/utils/serializer.py,sha256=
|
|
50
|
+
polos/utils/serializer.py,sha256=XfnZ7vIbTk_xly116De1xJiNncJdb1GIjAERys7ZV10,6554
|
|
51
51
|
polos/utils/tracing.py,sha256=SkBktU12KyIQcl8q1wjyTX7QfGwyzdzEcBY8QTX_t6Y,1021
|
|
52
52
|
polos/utils/worker_singleton.py,sha256=7-MOx9VFlyC-8waB7OMg6xER5NB7Ld7ofMku9yoitDo,1081
|
|
53
|
-
polos_sdk-0.1.
|
|
54
|
-
polos_sdk-0.1.
|
|
55
|
-
polos_sdk-0.1.
|
|
53
|
+
polos_sdk-0.1.2.dist-info/METADATA,sha256=whWoDVWW56tKr819JhVzi6lhOvd4jF4MI2te01_7U-M,3870
|
|
54
|
+
polos_sdk-0.1.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
55
|
+
polos_sdk-0.1.2.dist-info/RECORD,,
|
|
@@ -1,650 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: polos-sdk
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: Polos SDK for Python - Durable Agent Execution
|
|
5
|
-
Project-URL: Homepage, https://github.com/polos-dev/polos
|
|
6
|
-
Project-URL: Repository, https://github.com/polos-dev/polos
|
|
7
|
-
Project-URL: Documentation, https://docs.polos.dev
|
|
8
|
-
Project-URL: Issues, https://github.com/polos-dev/polos/issues
|
|
9
|
-
Author: Polos Team
|
|
10
|
-
License: Apache-2.0
|
|
11
|
-
Keywords: agents,ai,async,durable-execution,llm,orchestration,workflow
|
|
12
|
-
Classifier: Development Status :: 4 - Beta
|
|
13
|
-
Classifier: Intended Audience :: Developers
|
|
14
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
-
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
-
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
20
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
-
Requires-Python: >=3.10
|
|
22
|
-
Requires-Dist: anyio>=4.0.0
|
|
23
|
-
Requires-Dist: fastapi>=0.104.0
|
|
24
|
-
Requires-Dist: httpx>=0.24.0
|
|
25
|
-
Requires-Dist: opentelemetry-api>=1.20.0
|
|
26
|
-
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.20.0
|
|
27
|
-
Requires-Dist: opentelemetry-sdk>=1.20.0
|
|
28
|
-
Requires-Dist: pydantic>=2.0.0
|
|
29
|
-
Requires-Dist: python-dotenv>=1.0.0
|
|
30
|
-
Requires-Dist: uvicorn>=0.24.0
|
|
31
|
-
Provides-Extra: anthropic
|
|
32
|
-
Requires-Dist: anthropic>=0.39.0; extra == 'anthropic'
|
|
33
|
-
Provides-Extra: dev
|
|
34
|
-
Requires-Dist: pre-commit>=3.0.0; extra == 'dev'
|
|
35
|
-
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
36
|
-
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
37
|
-
Requires-Dist: pytest-mock>=3.10.0; extra == 'dev'
|
|
38
|
-
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
39
|
-
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
40
|
-
Provides-Extra: fireworks
|
|
41
|
-
Requires-Dist: openai>=1.0.0; extra == 'fireworks'
|
|
42
|
-
Provides-Extra: gemini
|
|
43
|
-
Requires-Dist: openai>=1.0.0; extra == 'gemini'
|
|
44
|
-
Provides-Extra: groq
|
|
45
|
-
Requires-Dist: openai>=1.0.0; extra == 'groq'
|
|
46
|
-
Provides-Extra: openai
|
|
47
|
-
Requires-Dist: openai>=1.0.0; extra == 'openai'
|
|
48
|
-
Provides-Extra: together
|
|
49
|
-
Requires-Dist: openai>=1.0.0; extra == 'together'
|
|
50
|
-
Description-Content-Type: text/markdown
|
|
51
|
-
|
|
52
|
-
# Polos Python SDK
|
|
53
|
-
|
|
54
|
-
Durable execution engine for Python. Build reliable AI agents and workflows that can survive failures, handle long-running tasks, and coordinate complex processes.
|
|
55
|
-
|
|
56
|
-
## Features
|
|
57
|
-
|
|
58
|
-
- 🤖 **AI Agents** - Build LLM-powered agents with tool calling, streaming, and conversation history
|
|
59
|
-
- 🔄 **Durable Workflows** - Workflows survive failures and resume from checkpoints
|
|
60
|
-
- ⏰ **Long-Running** - Execute workflows that run for hours or days
|
|
61
|
-
- 🔗 **Workflow Orchestration** - Chain workflows together and build complex processes
|
|
62
|
-
- 🛠️ **Tools** - Define reusable tools that agents can call
|
|
63
|
-
- 🐍 **Native Python** - Async/await support, type hints, and Pythonic APIs
|
|
64
|
-
- 📊 **Observability** - Built-in tracing, events, and monitoring
|
|
65
|
-
- 🔒 **Isolated Execution** - Each workflow runs in a secure environment
|
|
66
|
-
|
|
67
|
-
## Installation
|
|
68
|
-
|
|
69
|
-
```bash
|
|
70
|
-
pip install polos-worker
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
Or with UV (recommended):
|
|
74
|
-
```bash
|
|
75
|
-
uv add polos-worker
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
### Optional Dependencies
|
|
79
|
-
|
|
80
|
-
Install provider-specific dependencies for LLM support:
|
|
81
|
-
|
|
82
|
-
```bash
|
|
83
|
-
# OpenAI
|
|
84
|
-
pip install polos-worker[openai]
|
|
85
|
-
|
|
86
|
-
# Anthropic
|
|
87
|
-
pip install polos-worker[anthropic]
|
|
88
|
-
|
|
89
|
-
# Google Gemini
|
|
90
|
-
pip install polos-worker[gemini]
|
|
91
|
-
|
|
92
|
-
# Groq
|
|
93
|
-
pip install polos-worker[groq]
|
|
94
|
-
|
|
95
|
-
# Fireworks
|
|
96
|
-
pip install polos-worker[fireworks]
|
|
97
|
-
|
|
98
|
-
# Together AI
|
|
99
|
-
pip install polos-worker[together]
|
|
100
|
-
|
|
101
|
-
# All providers
|
|
102
|
-
pip install polos-worker[openai,anthropic,gemini,groq,fireworks,together]
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
## Quick Start
|
|
106
|
-
|
|
107
|
-
### 1. Configure the SDK
|
|
108
|
-
|
|
109
|
-
```python
|
|
110
|
-
from polos import configure
|
|
111
|
-
|
|
112
|
-
configure(
|
|
113
|
-
api_url='http://localhost:8080',
|
|
114
|
-
api_key='your-api-key', # Optional if running in dev mode
|
|
115
|
-
project_id='your-project-id'
|
|
116
|
-
)
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
Or use environment variables:
|
|
120
|
-
```bash
|
|
121
|
-
export POLOS_API_URL=http://localhost:8080
|
|
122
|
-
export POLOS_API_KEY=your-api-key
|
|
123
|
-
export POLOS_PROJECT_ID=your-project-id
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### 2. Define a Workflow
|
|
127
|
-
|
|
128
|
-
```python
|
|
129
|
-
from polos import workflow, WorkflowContext
|
|
130
|
-
|
|
131
|
-
@workflow(id="hello_world")
|
|
132
|
-
async def hello_world(ctx: WorkflowContext, payload: dict):
|
|
133
|
-
"""A simple workflow."""
|
|
134
|
-
name = payload.get('name', 'World')
|
|
135
|
-
return {'message': f'Hello, {name}!'}
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
### 3. Create an Agent
|
|
139
|
-
|
|
140
|
-
```python
|
|
141
|
-
from polos import Agent
|
|
142
|
-
|
|
143
|
-
weather_agent = Agent(
|
|
144
|
-
id="weather-agent",
|
|
145
|
-
provider="openai",
|
|
146
|
-
model="gpt-5-mini",
|
|
147
|
-
system_prompt="You are a helpful weather assistant",
|
|
148
|
-
tools=[get_weather] # Your custom tools
|
|
149
|
-
)
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
### 4. Run Your Code
|
|
153
|
-
|
|
154
|
-
Start a worker to execute workflows and agents:
|
|
155
|
-
|
|
156
|
-
```bash
|
|
157
|
-
# Using the Python worker
|
|
158
|
-
python -m polos.runtime.worker
|
|
159
|
-
|
|
160
|
-
# Or use the Node.js worker (supports Python workflows)
|
|
161
|
-
npm install -g polos-worker
|
|
162
|
-
polos-worker start
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
## Core Concepts
|
|
166
|
-
|
|
167
|
-
### Workflows
|
|
168
|
-
|
|
169
|
-
Workflows are durable functions that can survive failures and resume execution:
|
|
170
|
-
|
|
171
|
-
```python
|
|
172
|
-
from polos import workflow, WorkflowContext, WorkflowState
|
|
173
|
-
|
|
174
|
-
class MyState(WorkflowState):
|
|
175
|
-
counter: int = 0
|
|
176
|
-
messages: list[str] = []
|
|
177
|
-
|
|
178
|
-
@workflow(id="my_workflow", state_schema=MyState)
|
|
179
|
-
async def my_workflow(ctx: WorkflowContext, payload: dict):
|
|
180
|
-
# Access state
|
|
181
|
-
ctx.state.counter += 1
|
|
182
|
-
ctx.state.messages.append(payload.get('message', ''))
|
|
183
|
-
|
|
184
|
-
# Use step.run() for durable execution
|
|
185
|
-
result = await ctx.step.run("process_data", process_data, payload)
|
|
186
|
-
|
|
187
|
-
return {'result': result, 'counter': ctx.state.counter}
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
### Agents
|
|
191
|
-
|
|
192
|
-
Agents are LLM-powered workflows with tool calling capabilities:
|
|
193
|
-
|
|
194
|
-
```python
|
|
195
|
-
from polos import Agent, max_steps, executed_tool
|
|
196
|
-
|
|
197
|
-
# Define a tool
|
|
198
|
-
from polos import tool
|
|
199
|
-
|
|
200
|
-
@tool
|
|
201
|
-
def get_weather(ctx, location: str) -> str:
|
|
202
|
-
"""Get weather for a location."""
|
|
203
|
-
# Your weather API call here
|
|
204
|
-
return f"Weather in {location}: 72°F, sunny"
|
|
205
|
-
|
|
206
|
-
# Create an agent
|
|
207
|
-
weather_agent = Agent(
|
|
208
|
-
id="weather-agent",
|
|
209
|
-
provider="openai",
|
|
210
|
-
model="gpt-5-mini",
|
|
211
|
-
system_prompt="You are a helpful weather assistant",
|
|
212
|
-
tools=[get_weather],
|
|
213
|
-
stop_conditions=[max_steps(10), executed_tool("get_weather")]
|
|
214
|
-
)
|
|
215
|
-
|
|
216
|
-
# Use the agent
|
|
217
|
-
result = await weather_agent.run("What's the weather in NYC?")
|
|
218
|
-
print(result.result) # Agent's response
|
|
219
|
-
|
|
220
|
-
# Or stream the response
|
|
221
|
-
stream_result = await weather_agent.stream("What's the weather in NYC?")
|
|
222
|
-
async for event in stream_result.events:
|
|
223
|
-
if event.event_type == "text_delta":
|
|
224
|
-
print(event.data.get("content", ""), end="", flush=True)
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
### Tools
|
|
228
|
-
|
|
229
|
-
Tools are functions that agents can call:
|
|
230
|
-
|
|
231
|
-
```python
|
|
232
|
-
from polos import tool, WorkflowContext
|
|
233
|
-
|
|
234
|
-
@tool
|
|
235
|
-
def calculate(ctx: WorkflowContext, expression: str) -> float:
|
|
236
|
-
"""Evaluate a mathematical expression."""
|
|
237
|
-
return eval(expression) # In production, use a safe evaluator
|
|
238
|
-
|
|
239
|
-
@tool
|
|
240
|
-
def search_web(ctx: WorkflowContext, query: str) -> str:
|
|
241
|
-
"""Search the web for information."""
|
|
242
|
-
# Your search implementation
|
|
243
|
-
return f"Results for: {query}"
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
### Hooks
|
|
247
|
-
|
|
248
|
-
Hooks allow you to intercept workflow execution:
|
|
249
|
-
|
|
250
|
-
```python
|
|
251
|
-
from polos import hook, HookContext, HookResult, HookAction
|
|
252
|
-
|
|
253
|
-
@hook
|
|
254
|
-
def log_execution(ctx: WorkflowContext, hook_ctx: HookContext) -> HookResult:
|
|
255
|
-
"""Log workflow execution."""
|
|
256
|
-
print(f"Workflow {ctx.workflow_id} started")
|
|
257
|
-
return HookResult.continue_with()
|
|
258
|
-
|
|
259
|
-
@workflow(id="my_workflow", on_start=log_execution)
|
|
260
|
-
async def my_workflow(ctx: WorkflowContext, payload: dict):
|
|
261
|
-
return {'result': 'done'}
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
### Guardrails
|
|
265
|
-
|
|
266
|
-
Guardrails validate and modify agent outputs:
|
|
267
|
-
|
|
268
|
-
```python
|
|
269
|
-
from polos import guardrail, GuardrailContext, GuardrailResult
|
|
270
|
-
|
|
271
|
-
@guardrail
|
|
272
|
-
def check_profanity(ctx: GuardrailContext) -> GuardrailResult:
|
|
273
|
-
"""Check for profanity in agent output."""
|
|
274
|
-
content = ctx.content or ""
|
|
275
|
-
if any(word in content.lower() for word in ["bad", "word"]):
|
|
276
|
-
return GuardrailResult.fail("Content contains inappropriate language")
|
|
277
|
-
return GuardrailResult.continue_with()
|
|
278
|
-
|
|
279
|
-
weather_agent = Agent(
|
|
280
|
-
id="weather-agent",
|
|
281
|
-
provider="openai",
|
|
282
|
-
model="gpt-5-mini",
|
|
283
|
-
guardrails=[check_profanity]
|
|
284
|
-
)
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
## Advanced Features
|
|
288
|
-
|
|
289
|
-
### State Management
|
|
290
|
-
|
|
291
|
-
Workflows can maintain state across executions:
|
|
292
|
-
|
|
293
|
-
```python
|
|
294
|
-
from polos import workflow, WorkflowContext, WorkflowState
|
|
295
|
-
|
|
296
|
-
class CounterState(WorkflowState):
|
|
297
|
-
count: int = 0
|
|
298
|
-
history: list[str] = []
|
|
299
|
-
|
|
300
|
-
@workflow(id="counter", state_schema=CounterState)
|
|
301
|
-
async def counter(ctx: WorkflowContext, payload: dict):
|
|
302
|
-
ctx.state.count += 1
|
|
303
|
-
ctx.state.history.append(payload.get('action', ''))
|
|
304
|
-
return {'count': ctx.state.count}
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
### Step Execution
|
|
308
|
-
|
|
309
|
-
Use `ctx.step` for durable execution:
|
|
310
|
-
|
|
311
|
-
```python
|
|
312
|
-
@workflow(id="my_workflow")
|
|
313
|
-
async def my_workflow(ctx: WorkflowContext, payload: dict):
|
|
314
|
-
# Run a step (durable)
|
|
315
|
-
result = await ctx.step.run("step_name", my_function, arg1, arg2)
|
|
316
|
-
|
|
317
|
-
# Invoke another workflow and wait
|
|
318
|
-
result = await ctx.step.invoke_and_wait("other_workflow", other_workflow, payload)
|
|
319
|
-
|
|
320
|
-
# Invoke another workflow (fire and forget)
|
|
321
|
-
handle = await ctx.step.invoke("other_workflow", other_workflow, payload)
|
|
322
|
-
|
|
323
|
-
# Wait for a workflow to complete
|
|
324
|
-
result = await ctx.step.wait_for(handle)
|
|
325
|
-
|
|
326
|
-
return result
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
### Events
|
|
330
|
-
|
|
331
|
-
Publish and subscribe to events:
|
|
332
|
-
|
|
333
|
-
```python
|
|
334
|
-
from polos import events
|
|
335
|
-
|
|
336
|
-
# Publish an event
|
|
337
|
-
await events.publish(
|
|
338
|
-
topic="user/123",
|
|
339
|
-
event_type="message",
|
|
340
|
-
data={"text": "Hello"}
|
|
341
|
-
)
|
|
342
|
-
|
|
343
|
-
# Stream events
|
|
344
|
-
async for event in events.stream_topic("user/123"):
|
|
345
|
-
print(f"Event: {event.event_type} - {event.data}")
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
### Scheduled Workflows
|
|
349
|
-
|
|
350
|
-
Schedule workflows to run on a schedule:
|
|
351
|
-
|
|
352
|
-
```python
|
|
353
|
-
@workflow(
|
|
354
|
-
id="daily_report",
|
|
355
|
-
schedule="0 9 * * *" # 9 AM daily
|
|
356
|
-
)
|
|
357
|
-
async def daily_report(ctx: WorkflowContext, payload: dict):
|
|
358
|
-
# Generate daily report
|
|
359
|
-
return {'report': '...'}
|
|
360
|
-
```
|
|
361
|
-
|
|
362
|
-
### Queues
|
|
363
|
-
|
|
364
|
-
Use queues for concurrency control:
|
|
365
|
-
|
|
366
|
-
```python
|
|
367
|
-
from polos import Queue
|
|
368
|
-
|
|
369
|
-
my_queue = Queue(name="processing", concurrency_limit=5)
|
|
370
|
-
|
|
371
|
-
@workflow(id="processor", queue=my_queue)
|
|
372
|
-
async def processor(ctx: WorkflowContext, payload: dict):
|
|
373
|
-
# Process item
|
|
374
|
-
return {'status': 'processed'}
|
|
375
|
-
```
|
|
376
|
-
|
|
377
|
-
### Batch Execution
|
|
378
|
-
|
|
379
|
-
Execute workflows in batch:
|
|
380
|
-
|
|
381
|
-
```python
|
|
382
|
-
from polos import batch
|
|
383
|
-
|
|
384
|
-
results = await batch.run(
|
|
385
|
-
workflow_id="my_workflow",
|
|
386
|
-
inputs=[
|
|
387
|
-
{"value": 1},
|
|
388
|
-
{"value": 2},
|
|
389
|
-
{"value": 3}
|
|
390
|
-
]
|
|
391
|
-
)
|
|
392
|
-
|
|
393
|
-
for result in results:
|
|
394
|
-
print(result.result)
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
## Testing
|
|
398
|
-
|
|
399
|
-
The SDK includes comprehensive unit tests. Run tests with:
|
|
400
|
-
|
|
401
|
-
```bash
|
|
402
|
-
# Install test dependencies
|
|
403
|
-
uv sync --dev
|
|
404
|
-
|
|
405
|
-
# Run all tests
|
|
406
|
-
uv run pytest
|
|
407
|
-
|
|
408
|
-
# Run with coverage
|
|
409
|
-
uv run pytest --cov=polos --cov-report=html
|
|
410
|
-
|
|
411
|
-
# Run specific test file
|
|
412
|
-
uv run pytest tests/unit/test_core/test_workflow.py
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
See [`TESTING_PLAN.md`](./TESTING_PLAN.md) for testing guidelines.
|
|
416
|
-
|
|
417
|
-
## Development Setup
|
|
418
|
-
|
|
419
|
-
### Using UV (Recommended)
|
|
420
|
-
|
|
421
|
-
```bash
|
|
422
|
-
# Fork the repository on GitHub first: https://github.com/polos-dev/polos
|
|
423
|
-
# Then clone your fork
|
|
424
|
-
git clone https://github.com/YOUR_USERNAME/polos.git
|
|
425
|
-
cd polos/sdk/python
|
|
426
|
-
|
|
427
|
-
# Install dependencies (creates venv automatically)
|
|
428
|
-
uv sync
|
|
429
|
-
|
|
430
|
-
# Format code with Ruff
|
|
431
|
-
uv run ruff format .
|
|
432
|
-
|
|
433
|
-
# Lint code
|
|
434
|
-
uv run ruff check .
|
|
435
|
-
|
|
436
|
-
# Run tests
|
|
437
|
-
uv run pytest
|
|
438
|
-
|
|
439
|
-
# Build package
|
|
440
|
-
uv build
|
|
441
|
-
```
|
|
442
|
-
|
|
443
|
-
### Using pip
|
|
444
|
-
|
|
445
|
-
```bash
|
|
446
|
-
# Create virtual environment
|
|
447
|
-
python -m venv venv
|
|
448
|
-
source venv/bin/activate # Windows: venv\Scripts\activate
|
|
449
|
-
|
|
450
|
-
# Install in development mode
|
|
451
|
-
pip install -e ".[dev]"
|
|
452
|
-
|
|
453
|
-
# Format code
|
|
454
|
-
ruff format .
|
|
455
|
-
|
|
456
|
-
# Lint code
|
|
457
|
-
ruff check .
|
|
458
|
-
|
|
459
|
-
# Run tests
|
|
460
|
-
pytest
|
|
461
|
-
```
|
|
462
|
-
|
|
463
|
-
### Pre-commit Hooks
|
|
464
|
-
|
|
465
|
-
The project uses pre-commit hooks for code quality:
|
|
466
|
-
|
|
467
|
-
```bash
|
|
468
|
-
# Install pre-commit
|
|
469
|
-
pip install pre-commit
|
|
470
|
-
|
|
471
|
-
# Install hooks
|
|
472
|
-
pre-commit install
|
|
473
|
-
|
|
474
|
-
# Run hooks manually
|
|
475
|
-
pre-commit run --all-files
|
|
476
|
-
```
|
|
477
|
-
|
|
478
|
-
## Project Structure
|
|
479
|
-
|
|
480
|
-
```
|
|
481
|
-
sdk/python/
|
|
482
|
-
├── polos/ # Main package
|
|
483
|
-
│ ├── agents/ # Agent implementation
|
|
484
|
-
│ ├── core/ # Core workflow/step/context
|
|
485
|
-
│ ├── features/ # Events, schedules, tracing
|
|
486
|
-
│ ├── llm/ # LLM providers
|
|
487
|
-
│ ├── middleware/ # Hooks and guardrails
|
|
488
|
-
│ ├── runtime/ # Worker and client
|
|
489
|
-
│ ├── tools/ # Tool implementation
|
|
490
|
-
│ ├── types/ # Type definitions
|
|
491
|
-
│ └── utils/ # Utility functions
|
|
492
|
-
├── tests/ # Test suite
|
|
493
|
-
│ └── unit/ # Unit tests
|
|
494
|
-
├── pyproject.toml # Project configuration
|
|
495
|
-
└── README.md # This file
|
|
496
|
-
```
|
|
497
|
-
|
|
498
|
-
## API Reference
|
|
499
|
-
|
|
500
|
-
### Core Functions
|
|
501
|
-
|
|
502
|
-
#### `@workflow(id, queue, schedule, state_schema, ...)`
|
|
503
|
-
|
|
504
|
-
Decorator to register a workflow function.
|
|
505
|
-
|
|
506
|
-
**Parameters:**
|
|
507
|
-
- `id` (str): Unique workflow identifier
|
|
508
|
-
- `queue` (str | Queue | dict, optional): Queue configuration
|
|
509
|
-
- `schedule` (str | dict, optional): Cron schedule or schedule config
|
|
510
|
-
- `state_schema` (type[WorkflowState], optional): State schema class
|
|
511
|
-
- `on_start` (Callable | list, optional): Hooks to run on start
|
|
512
|
-
- `on_end` (Callable | list, optional): Hooks to run on end
|
|
513
|
-
|
|
514
|
-
**Example:**
|
|
515
|
-
```python
|
|
516
|
-
@workflow(id="my_workflow", state_schema=MyState)
|
|
517
|
-
async def my_workflow(ctx: WorkflowContext, payload: dict):
|
|
518
|
-
return {'result': 'done'}
|
|
519
|
-
```
|
|
520
|
-
|
|
521
|
-
#### `Agent(id, provider, model, ...)`
|
|
522
|
-
|
|
523
|
-
Create an AI agent.
|
|
524
|
-
|
|
525
|
-
**Parameters:**
|
|
526
|
-
- `id` (str): Unique agent identifier
|
|
527
|
-
- `provider` (str): LLM provider ("openai", "anthropic", etc.)
|
|
528
|
-
- `model` (str): Model name (e.g., "gpt-4o", "claude-sonnet-4-5")
|
|
529
|
-
- `system_prompt` (str, optional): System prompt
|
|
530
|
-
- `tools` (list, optional): List of tools
|
|
531
|
-
- `stop_conditions` (list, optional): Stop condition callables
|
|
532
|
-
- `guardrails` (list, optional): Guardrail callables
|
|
533
|
-
|
|
534
|
-
**Example:**
|
|
535
|
-
```python
|
|
536
|
-
agent = Agent(
|
|
537
|
-
id="my-agent",
|
|
538
|
-
provider="openai",
|
|
539
|
-
model="gpt-5-mini",
|
|
540
|
-
tools=[my_tool]
|
|
541
|
-
)
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
#### `@tool`
|
|
545
|
-
|
|
546
|
-
Decorator to register a tool function.
|
|
547
|
-
|
|
548
|
-
**Example:**
|
|
549
|
-
```python
|
|
550
|
-
@tool
|
|
551
|
-
def my_tool(ctx: WorkflowContext, arg: str) -> str:
|
|
552
|
-
return f"Processed: {arg}"
|
|
553
|
-
```
|
|
554
|
-
|
|
555
|
-
### Context Objects
|
|
556
|
-
|
|
557
|
-
#### `WorkflowContext`
|
|
558
|
-
|
|
559
|
-
Context object passed to workflow functions.
|
|
560
|
-
|
|
561
|
-
**Properties:**
|
|
562
|
-
- `workflow_id` (str): Workflow identifier
|
|
563
|
-
- `execution_id` (str): Current execution ID
|
|
564
|
-
- `root_execution_id` (str): Root execution ID
|
|
565
|
-
- `state` (WorkflowState): Workflow state
|
|
566
|
-
- `step` (Step): Step helper for durable execution
|
|
567
|
-
|
|
568
|
-
#### `AgentContext`
|
|
569
|
-
|
|
570
|
-
Context object passed to agent functions (extends `WorkflowContext`).
|
|
571
|
-
|
|
572
|
-
**Additional Properties:**
|
|
573
|
-
- `agent_id` (str): Agent identifier
|
|
574
|
-
- `conversation_id` (str, optional): Conversation ID for history
|
|
575
|
-
|
|
576
|
-
## Environment Variables
|
|
577
|
-
|
|
578
|
-
- `POLOS_API_URL` - Orchestrator URL (default: `http://localhost:8080`)
|
|
579
|
-
- `POLOS_API_KEY` - API key for authentication
|
|
580
|
-
- `POLOS_PROJECT_ID` - Project ID for multi-project support
|
|
581
|
-
|
|
582
|
-
## Architecture
|
|
583
|
-
|
|
584
|
-
Polos uses a push-based worker architecture:
|
|
585
|
-
|
|
586
|
-
```
|
|
587
|
-
┌─────────────┐
|
|
588
|
-
│ Client │ ── trigger ──> ┌──────────────┐
|
|
589
|
-
└─────────────┘ │ Orchestrator │
|
|
590
|
-
│ (Rust API) │
|
|
591
|
-
└──────────────┘
|
|
592
|
-
│
|
|
593
|
-
invokes
|
|
594
|
-
↓
|
|
595
|
-
┌─────────────┐ ┌──────────────┐
|
|
596
|
-
│ Executor │ <── pushes ──│ Worker │
|
|
597
|
-
│ (Python) │ │ (Python) │
|
|
598
|
-
└─────────────┘ └──────────────┘
|
|
599
|
-
```
|
|
600
|
-
|
|
601
|
-
1. **Client** triggers workflow/agent via SDK
|
|
602
|
-
2. **Orchestrator** queues execution in database
|
|
603
|
-
3. **Orchestrator** pushes execution to worker via HTTP
|
|
604
|
-
4. **Worker** receives execution request and executes workflow/agent in process
|
|
605
|
-
5. **Worker** reports result back to orchestrator
|
|
606
|
-
6. **Orchestrator** marks execution complete
|
|
607
|
-
7. **Client** receives result
|
|
608
|
-
|
|
609
|
-
## Contributing
|
|
610
|
-
|
|
611
|
-
We welcome contributions! See [CONTRIBUTING.md](../../CONTRIBUTING.md) for guidelines.
|
|
612
|
-
|
|
613
|
-
### Development Workflow
|
|
614
|
-
|
|
615
|
-
1. **Fork the repository** on GitHub: https://github.com/polos-dev/polos
|
|
616
|
-
2. **Clone your fork**:
|
|
617
|
-
```bash
|
|
618
|
-
git clone https://github.com/YOUR_USERNAME/polos.git
|
|
619
|
-
cd polos/sdk/python
|
|
620
|
-
```
|
|
621
|
-
3. **Add upstream remote** (optional, for syncing with main repo):
|
|
622
|
-
```bash
|
|
623
|
-
git remote add upstream https://github.com/polos-dev/polos.git
|
|
624
|
-
```
|
|
625
|
-
4. Create a feature branch
|
|
626
|
-
5. Make your changes
|
|
627
|
-
6. Run tests: `uv run pytest`
|
|
628
|
-
7. Format code: `uv run ruff format .`
|
|
629
|
-
8. Lint code: `uv run ruff check .`
|
|
630
|
-
9. Submit a pull request to `polos-dev/polos`
|
|
631
|
-
|
|
632
|
-
## License
|
|
633
|
-
|
|
634
|
-
MIT License - see [LICENSE](../../LICENSE) for details.
|
|
635
|
-
|
|
636
|
-
## Support
|
|
637
|
-
|
|
638
|
-
- 📖 [Documentation](https://docs.polos.dev)
|
|
639
|
-
- 💬 [Discord Community](https://discord.gg/polos)
|
|
640
|
-
- 🐛 [Issue Tracker](https://github.com/polos-dev/polos/issues)
|
|
641
|
-
- 📧 [Email Support](mailto:support@polos.dev)
|
|
642
|
-
|
|
643
|
-
## Related Projects
|
|
644
|
-
|
|
645
|
-
- [Polos Orchestrator](../../orchestrator) - Rust-based orchestrator
|
|
646
|
-
- [Polos UI](../../ui) - Web interface for monitoring and management
|
|
647
|
-
|
|
648
|
-
---
|
|
649
|
-
|
|
650
|
-
Built with ❤️ by the Polos team
|
|
File without changes
|