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 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 tool_stop_action is False and len(batch_workflows) > 0:
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 or tool_stop_action:
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
- event_type: str | None = None,
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
- event_type: Optional type of event
251
- data: Event payload
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 http://localhost:8080)
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=None, # Use latest deployment
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=None, # Use latest active deployment
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
- not isinstance(error, StepExecutionError) and workflow.workflow_type != "tool"
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
@@ -63,6 +63,7 @@ class Tool(Workflow):
63
63
  id=id,
64
64
  func=func or self._default_execute,
65
65
  workflow_type="tool",
66
+ description=description or self.__doc__ or "",
66
67
  queue_name=queue_name,
67
68
  queue_concurrency_limit=queue_concurrency_limit,
68
69
  on_start=on_start,
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=Y1JZvhqrEoirwbMvP948G-95iWB4kJAv9qBswmXGxP8,26426
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=d6L2e7RmmN275tuAcugFiqRq7snUndgXoPGSoCBMR6g,25127
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=6uZcbbyk_zo7sR76PVw_9hXdgfpRyf5veDsoQwJcKrk,51700
11
- polos/core/workflow.py,sha256=xGGNye2bb5B7W2RWsVFmYb5fz8349sGeAWZXjJgUuD0,51093
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=7viS9ryCeEnOaAO4blsLH45ulBPknDa9ISfif0jmhig,14237
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=iiJOU4zeNHHv61uzWNO7TablP4IQFPWcROMXRoSaQd0,32984
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=5KF0IgknIFNDhnZiFx2m7MLUXysgmt_np-1dI5euFf0,56398
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=OmaNBCQgA8e5uRJTO88pNR3eP1BDQiK2jvxkrPYdrag,22483
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=5BvdBSXPKGwO0F3FubiXV8vuMyW3AC1-4Jwz3Bc7eik,5486
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.0.dist-info/METADATA,sha256=66gs2_-Sk0xinDRF7KD6pR8JfY8Q0I2zmOijV_aibFQ,16924
54
- polos_sdk-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
55
- polos_sdk-0.1.0.dist-info/RECORD,,
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