Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions examples/temporal-direct/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,29 @@
import ai


MODEL_ID = "gateway:anthropic/claude-sonnet-4.6"


# ── Workflow-safe model placeholder ──────────────────────────────
#
# ``agent.run`` requires a ``Model``, but a real one can't be built
# inside the workflow: ``ai.get_model("gateway:...")`` constructs an
# ``httpx.AsyncClient`` at provider-init time, which imports
# httpcore/anyio and trips the Temporal sandbox (``threading.local``
# at module load). Our loop never calls the model directly anyway --
# every LLM call is delegated to ``llm_call_activity``, which runs
# outside the sandbox and resolves the real model by id there.
#
# So hand the workflow a placeholder ``Model`` whose provider builds
# no client. It carries the real model id (so the activity can
# resolve it) but is safe to construct inside the sandbox.
class WorkflowModelProvider(ai.Provider[Any]):
"""A clientless provider, safe to construct in a workflow sandbox."""

def __init__(self) -> None:
super().__init__(name="workflow-placeholder", base_url="")


# ── Tool definitions ─────────────────────────────────────────────
#
# Declared with @ai.tool so the framework can extract JSON schemas
Expand Down Expand Up @@ -93,6 +116,7 @@ async def get_population_activity(city: str) -> int:

@dataclasses.dataclass
class LLMParams:
model_id: str
messages: list[dict[str, Any]]
tool_schemas: list[dict[str, Any]]

Expand All @@ -105,7 +129,7 @@ class LLMResult:
@temporalio.activity.defn
async def llm_call_activity(params: LLMParams) -> LLMResult:
"""Call the LLM, drain the stream, return the final message."""
model = ai.get_model("gateway:anthropic/claude-sonnet-4.6")
model = ai.get_model(params.model_id)
messages = [ai.messages.Message.model_validate(m) for m in params.messages]
tools = [
ai.Tool(
Expand Down Expand Up @@ -148,6 +172,7 @@ async def loop(
result = await temporalio.workflow.execute_activity(
llm_call_activity,
LLMParams(
model_id=context.model.id,
messages=[m.model_dump() for m in context.messages],
tool_schemas=tool_schemas,
),
Expand Down Expand Up @@ -213,7 +238,7 @@ async def _call() -> ai.events.ToolCallResult:
class WeatherWorkflow:
@temporalio.workflow.run
async def run(self, user_query: str) -> str:
model = ai.get_model("gateway:anthropic/claude-sonnet-4.6")
model = ai.Model(MODEL_ID, provider=WorkflowModelProvider())
messages: list[ai.messages.Message] = [
ai.system_message(
"Answer questions using the weather and population tools."
Expand Down
Loading