Skip to content

Commit 59c5728

Browse files
committed
Merge remote-tracking branch 'origin/main' into last_completion_result
2 parents 7c18d5b + c8b0b78 commit 59c5728

File tree

16 files changed

+1278
-1097
lines changed

16 files changed

+1278
-1097
lines changed

.github/workflows/build-binaries.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ on:
44
branches:
55
- main
66
- "releases/*"
7+
- fix-build-binaries
78

89
jobs:
910
# Compile the binaries and upload artifacts
@@ -65,7 +66,7 @@ jobs:
6566
if [ "$RUNNER_OS" = "Windows" ]; then
6667
bindir=Scripts
6768
fi
68-
./.venv/$bindir/pip install 'protobuf>=3.20,<6' 'types-protobuf>=3.20,<6' 'typing-extensions>=4.2.0,<5' pytest pytest_asyncio grpcio 'nexus-rpc>=1.1.0' pydantic opentelemetry-api opentelemetry-sdk python-dateutil openai-agents
69+
./.venv/$bindir/pip install 'protobuf>=3.20,<6' 'types-protobuf>=3.20,<6' 'typing-extensions>=4.2.0,<5' pytest pytest_asyncio grpcio 'nexus-rpc>=1.1.0' pydantic opentelemetry-api opentelemetry-sdk python-dateutil 'openai-agents>=0.2.3,<=0.2.9'
6970
./.venv/$bindir/pip install --no-index --find-links=../dist temporalio
7071
./.venv/$bindir/python -m pytest -s -k test_workflow_hello
7172

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "temporalio"
3-
version = "1.16.0"
3+
version = "1.17.0"
44
description = "Temporal.io Python SDK"
55
authors = [{ name = "Temporal Technologies Inc", email = "[email protected]" }]
66
requires-python = ">=3.9"
@@ -26,7 +26,7 @@ opentelemetry = [
2626
]
2727
pydantic = ["pydantic>=2.0.0,<3"]
2828
openai-agents = [
29-
"openai-agents >= 0.2.3,<0.3",
29+
"openai-agents>=0.2.3,<=0.2.9", # 0.2.10 doesn't work: https://github.com/openai/openai-agents-python/issues/1639
3030
"eval-type-backport>=0.2.2; python_version < '3.10'"
3131
]
3232

@@ -57,7 +57,7 @@ dev = [
5757
"pytest-cov>=6.1.1",
5858
"httpx>=0.28.1",
5959
"pytest-pretty>=1.3.0",
60-
"openai-agents[litellm] >= 0.2.3,<0.3"
60+
"openai-agents[litellm]>=0.2.3,<=0.2.9", # 0.2.10 doesn't work: https://github.com/openai/openai-agents-python/issues/1639
6161
]
6262

6363
[tool.poe.tasks]

temporalio/bridge/Cargo.lock

Lines changed: 12 additions & 34 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

temporalio/contrib/openai_agents/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@
1717
from temporalio.contrib.openai_agents._trace_interceptor import (
1818
OpenAIAgentsTracingInterceptor,
1919
)
20+
from temporalio.contrib.openai_agents.workflow import AgentsWorkflowError
2021

2122
from . import workflow
2223

2324
__all__ = [
25+
"AgentsWorkflowError",
2426
"OpenAIAgentsPlugin",
2527
"ModelActivityParameters",
2628
"workflow",

temporalio/contrib/openai_agents/_openai_runner.py

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from agents import (
77
Agent,
8+
AgentsException,
89
Handoff,
910
RunConfig,
1011
RunContextWrapper,
@@ -21,6 +22,7 @@
2122
from temporalio import workflow
2223
from temporalio.contrib.openai_agents._model_parameters import ModelActivityParameters
2324
from temporalio.contrib.openai_agents._temporal_model_stub import _TemporalModelStub
25+
from temporalio.contrib.openai_agents.workflow import AgentsWorkflowError
2426

2527

2628
class TemporalOpenAIRunner(AgentRunner):
@@ -136,16 +138,28 @@ async def on_invoke(
136138
handoffs=new_handoffs,
137139
)
138140

139-
return await self._runner.run(
140-
starting_agent=convert_agent(starting_agent, None),
141-
input=input,
142-
context=context,
143-
max_turns=max_turns,
144-
hooks=hooks,
145-
run_config=run_config,
146-
previous_response_id=previous_response_id,
147-
session=session,
148-
)
141+
try:
142+
return await self._runner.run(
143+
starting_agent=convert_agent(starting_agent, None),
144+
input=input,
145+
context=context,
146+
max_turns=max_turns,
147+
hooks=hooks,
148+
run_config=run_config,
149+
previous_response_id=previous_response_id,
150+
session=session,
151+
)
152+
except AgentsException as e:
153+
# In order for workflow failures to properly fail the workflow, we need to rewrap them in
154+
# a Temporal error
155+
if e.__cause__ and workflow.is_failure_exception(e.__cause__):
156+
reraise = AgentsWorkflowError(
157+
f"Workflow failure exception in Agents Framework: {e}"
158+
)
159+
reraise.__traceback__ = e.__traceback__
160+
raise reraise from e.__cause__
161+
else:
162+
raise e
149163

150164
def run_sync(
151165
self,

temporalio/contrib/openai_agents/_temporal_openai_agents.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,19 @@
2424

2525
import temporalio.client
2626
import temporalio.worker
27-
from temporalio.client import ClientConfig, Plugin
27+
from temporalio.client import ClientConfig
2828
from temporalio.contrib.openai_agents._invoke_model_activity import ModelActivity
2929
from temporalio.contrib.openai_agents._model_parameters import ModelActivityParameters
30-
from temporalio.contrib.openai_agents._openai_runner import TemporalOpenAIRunner
30+
from temporalio.contrib.openai_agents._openai_runner import (
31+
TemporalOpenAIRunner,
32+
)
3133
from temporalio.contrib.openai_agents._temporal_trace_provider import (
3234
TemporalTraceProvider,
3335
)
3436
from temporalio.contrib.openai_agents._trace_interceptor import (
3537
OpenAIAgentsTracingInterceptor,
3638
)
39+
from temporalio.contrib.openai_agents.workflow import AgentsWorkflowError
3740
from temporalio.contrib.pydantic import (
3841
PydanticPayloadConverter,
3942
ToJsonOptions,
@@ -284,6 +287,9 @@ def configure_worker(self, config: WorkerConfig) -> WorkerConfig:
284287
config["activities"] = list(config.get("activities") or []) + [
285288
ModelActivity(self._model_provider).invoke_model_activity
286289
]
290+
config["workflow_failure_exception_types"] = list(
291+
config.get("workflow_failure_exception_types") or []
292+
) + [AgentsWorkflowError]
287293
return self.next_worker_plugin.configure_worker(config)
288294

289295
async def run_worker(self, worker: Worker) -> None:

temporalio/contrib/openai_agents/workflow.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,3 +263,12 @@ class ToolSerializationError(TemporalError):
263263
To fix this error, ensure your tool returns string-convertible values or
264264
modify the tool to return a string representation of the result.
265265
"""
266+
267+
268+
class AgentsWorkflowError(TemporalError):
269+
"""Error that occurs when the agents SDK raises an error which should terminate the calling workflow or update.
270+
271+
.. warning::
272+
This exception is experimental and may change in future versions.
273+
Use with caution in production environments.
274+
"""

temporalio/converter.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1590,7 +1590,9 @@ def value_to_type(
15901590
elif key_type is type(None):
15911591
key = {"null": None}[key]
15921592

1593-
if not isinstance(key, key_type):
1593+
# Can't call isinstance if key_type is a newtype
1594+
is_newtype = getattr(key_type, "__supertype__", None)
1595+
if is_newtype or not isinstance(key, key_type):
15941596
key = value_to_type(key_type, key, custom_converters)
15951597
except Exception as err:
15961598
raise TypeError(

temporalio/service.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import temporalio.exceptions
2727
import temporalio.runtime
2828

29-
__version__ = "1.16.0"
29+
__version__ = "1.17.0"
3030

3131
ServiceRequest = TypeVar("ServiceRequest", bound=google.protobuf.message.Message)
3232
ServiceResponse = TypeVar("ServiceResponse", bound=google.protobuf.message.Message)

temporalio/worker/_workflow_instance.py

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ def activate(
420420
# We want some errors during activation, like those that can happen
421421
# during payload conversion, to be able to fail the workflow not the
422422
# task
423-
if self._is_workflow_failure_exception(err):
423+
if self.workflow_is_failure_exception(err):
424424
try:
425425
self._set_workflow_failure(err)
426426
except Exception as inner_err:
@@ -635,7 +635,7 @@ async def run_update() -> None:
635635
# Validation failures are always update failures. We reuse
636636
# workflow failure logic to decide task failure vs update
637637
# failure after validation.
638-
if not past_validation or self._is_workflow_failure_exception(err):
638+
if not past_validation or self.workflow_is_failure_exception(err):
639639
if command is None:
640640
command = self._add_command()
641641
command.update_response.protocol_instance_id = (
@@ -1692,6 +1692,23 @@ def workflow_set_current_details(self, details: str):
16921692
self._assert_not_read_only("set current details")
16931693
self._current_details = details
16941694

1695+
def workflow_is_failure_exception(self, err: BaseException) -> bool:
1696+
# An exception is a failure instead of a task fail if it's already a
1697+
# failure error or if it is a timeout error or if it is an instance of
1698+
# any of the failure types in the worker or workflow-level setting
1699+
wf_failure_exception_types = self._defn.failure_exception_types
1700+
if self._dynamic_failure_exception_types is not None:
1701+
wf_failure_exception_types = self._dynamic_failure_exception_types
1702+
return (
1703+
isinstance(err, temporalio.exceptions.FailureError)
1704+
or isinstance(err, asyncio.TimeoutError)
1705+
or any(isinstance(err, typ) for typ in wf_failure_exception_types)
1706+
or any(
1707+
isinstance(err, typ)
1708+
for typ in self._worker_level_failure_exception_types
1709+
)
1710+
)
1711+
16951712
def workflow_has_last_completion_result(self) -> bool:
16961713
return len(self._last_completion_result.payloads) > 0
16971714

@@ -1976,7 +1993,7 @@ def _convert_payloads(
19761993
# Don't wrap payload conversion errors that would fail the workflow
19771994
raise
19781995
except Exception as err:
1979-
if self._is_workflow_failure_exception(err):
1996+
if self.workflow_is_failure_exception(err):
19801997
raise
19811998
raise RuntimeError("Failed decoding arguments") from err
19821999

@@ -2019,23 +2036,6 @@ def _instantiate_workflow_object(self) -> Any:
20192036

20202037
return workflow_instance
20212038

2022-
def _is_workflow_failure_exception(self, err: BaseException) -> bool:
2023-
# An exception is a failure instead of a task fail if it's already a
2024-
# failure error or if it is a timeout error or if it is an instance of
2025-
# any of the failure types in the worker or workflow-level setting
2026-
wf_failure_exception_types = self._defn.failure_exception_types
2027-
if self._dynamic_failure_exception_types is not None:
2028-
wf_failure_exception_types = self._dynamic_failure_exception_types
2029-
return (
2030-
isinstance(err, temporalio.exceptions.FailureError)
2031-
or isinstance(err, asyncio.TimeoutError)
2032-
or any(isinstance(err, typ) for typ in wf_failure_exception_types)
2033-
or any(
2034-
isinstance(err, typ)
2035-
for typ in self._worker_level_failure_exception_types
2036-
)
2037-
)
2038-
20392039
def _warn_if_unfinished_handlers(self) -> None:
20402040
def warnable(handler_executions: Iterable[HandlerExecution]):
20412041
return [
@@ -2229,7 +2229,7 @@ async def _run_top_level_workflow_function(self, coro: Awaitable[None]) -> None:
22292229
err
22302230
):
22312231
self._add_command().cancel_workflow_execution.SetInParent()
2232-
elif self._is_workflow_failure_exception(err):
2232+
elif self.workflow_is_failure_exception(err):
22332233
# All other failure errors fail the workflow
22342234
self._set_workflow_failure(err)
22352235
else:

0 commit comments

Comments
 (0)