Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
bbb380e
feat: add cookbook
lennessyy Sep 25, 2025
08d11c7
chore: adjust typescript config
lennessyy Sep 26, 2025
c4e0df0
add Github source button functionality
lennessyy Sep 26, 2025
5e233b3
Merge branch 'main' into cookbook
lennessyy Sep 26, 2025
8b94f88
style the tiles
lennessyy Sep 26, 2025
f51889c
Merge branch 'cookbook' of https://github.com/temporalio/documentatio…
lennessyy Sep 26, 2025
ac1f106
adjust spacing
lennessyy Sep 26, 2025
396f5ea
add existing recipies
lennessyy Sep 26, 2025
0989a7b
add source
lennessyy Sep 26, 2025
cb27156
fix broken link issue
lennessyy Sep 26, 2025
c62b2bb
fix broken links issue
lennessyy Sep 26, 2025
ceb7cff
add source for retry example
lennessyy Sep 26, 2025
007e49f
Merge branch 'main' into cookbook
lennessyy Sep 26, 2025
cca3ff8
adjust box shadow
lennessyy Sep 26, 2025
966f35b
Merge branch 'cookbook' of https://github.com/temporalio/documentatio…
lennessyy Sep 26, 2025
2cc06ac
chore: add e2e tests
lennessyy Sep 30, 2025
adb866a
Merge branch 'main' into cookbook
lennessyy Sep 30, 2025
90708c6
add date and change tab behavior
lennessyy Oct 1, 2025
bb6eed5
add orderng mechanism
lennessyy Oct 1, 2025
0e0039f
rename to ai cookbook
lennessyy Oct 1, 2025
974c14b
add cookbook recipes
lennessyy Oct 3, 2025
3df7970
broken link fix
lennessyy Oct 3, 2025
8a06439
adjust tile order
lennessyy Oct 3, 2025
7b4f686
docs: add source for recipes
lennessyy Oct 3, 2025
c3c3a43
Update AI Cookbook heading and capitalize Workflow
angelazhou32 Oct 3, 2025
d8eafd7
change title
angelazhou32 Oct 3, 2025
9c353d3
changed heading levels, added you in the description, changed date st…
angelazhou32 Oct 3, 2025
c0a8cb3
Merge remote-tracking branch 'origin/main' into cookbook
angelazhou32 Oct 3, 2025
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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,7 @@ package-lock.json

# Env
.env
/assembly/.env
/assembly/.env

# Tests
test-results/*
188 changes: 188 additions & 0 deletions cookbook/basic-python.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
---
title: Hello World
description: Simple example demonstrating how to call an LLM from Temporal using the OpenAI Python API library.
tags: [foundations, openai, python]
source: https://github.com/temporalio/ai-cookbook/tree/main/foundations/hello_world_openai_responses_python
priority: 999
---

This is a simple example showing how to call an LLM from Temporal using the [OpenAI Python API library](https://github.com/openai/openai-python).

Being an external API call, the LLM invocation happens in a Temporal Activity.

This recipe highlights two key design decisions:

- A generic activity for invoking an LLM API. This activity can be re-used with different arguments throughout your codebase.
- Configuring the Temporal client with a `dataconverter` to allow serialization of Pydantic types.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to put a quick note in as to why this matters? Asking since we do that in line 17.

- Retries are handled by Temporal and not by the underlying libraries such as the OpenAI client. This is important because if you leave the client retires on they can interfere with correct and durable error handling and recovery.


## Create the Activity

We create wrapper for the `create` method of the `AsyncOpenAI` client object.
This is a generic activity that invokes the OpenAI LLM.

We set `max_retries=0` on when creating the `AsyncOpenAI` client.
This moves the responsibility for retries from the OpenAI client to Temporal.

In this implementation, we include only the `instructions` and `input` argument, but it could be extended to others.

*File: activities/openai_responses.py*
```python

from temporalio import activity
from openai import AsyncOpenAI
from openai.types.responses import Response
from dataclasses import dataclass

# Temporal best practice: Create a data structure to hold the request parameters.
@dataclass
class OpenAIResponsesRequest:
model: str
instructions: str
input: str

@activity.defn
async def create(request: OpenAIResponsesRequest) -> Response:
# Temporal best practice: Disable retry logic in OpenAI API client library.
client = AsyncOpenAI(max_retries=0)

resp = await client.responses.create(
model=request.model,
instructions=request.instructions,
input=request.input,
timeout=15,
)

return resp
```

## Create the Workflow

In this example, we take the user input and generate a response in haiku format, using the OpenAI Responses activity. The
Workflow returns `result.output_text` from the OpenAI `Response`.

As per usual, the activity retry configuration is set here in the Workflow. In this case, a retry policy is not specified
so the default retry policy is used (exponential backoff with 1s initial interval, 2.0 backoff coefficient, max interval
100× initial, unlimited attempts, no non-retryable errors).

*File: workflows/hello_world_workflow.py*
```python
from temporalio import workflow
from datetime import timedelta

from activities import openai_responses


@workflow.defn
class HelloWorld:
@workflow.run
async def run(self, input: str) -> str:
system_instructions = "You only respond in haikus."
result = await workflow.execute_activity(
openai_responses.create,
openai_responses.OpenAIResponsesRequest(
model="gpt-4o-mini",
instructions=system_instructions,
input=input,
),
start_to_close_timeout=timedelta(seconds=30),
)
return result.output_text
```

## Create the Worker

Create the process for executing Activities and Workflows.
We configure the Temporal client with `pydantic_data_converter` so Temporal can serialize/deserialize output of the OpenAI SDK.

*File: worker.py*
```python
import asyncio

from temporalio.client import Client
from temporalio.worker import Worker

from workflows.hello_world_workflow import HelloWorld
from activities import openai_responses
from temporalio.contrib.pydantic import pydantic_data_converter


async def main():
client = await Client.connect(
"localhost:7233",
data_converter=pydantic_data_converter,
)

worker = Worker(
client,
task_queue="hello-world-python-task-queue",
workflows=[
HelloWorld,
],
activities=[
openai_responses.create,
],
)
await worker.run()


if __name__ == "__main__":
asyncio.run(main())
```

## Create the Workflow Starter

The starter script submits the workflow to Temporal for execution, then waits for the result and prints it out.
It uses the `pydantic_data_converter` to match the Worker configuration.

*File: start_workflow.py*
```python
import asyncio

from temporalio.client import Client

from workflows.hello_world_workflow import HelloWorld
from temporalio.contrib.pydantic import pydantic_data_converter


async def main():
client = await Client.connect(
"localhost:7233",
data_converter=pydantic_data_converter,
)

# Submit the Hello World workflow for execution
result = await client.execute_workflow(
HelloWorld.run,
"Tell me about recursion in programming.",
id="my-workflow-id",
task_queue="hello-world-python-task-queue",
)
print(f"Result: {result}")


if __name__ == "__main__":
asyncio.run(main())

```

## Running

Start the Temporal Dev Server:

```bash
temporal server start-dev
```

Run the worker:

```bash
uv run python -m worker
```

Start execution:

```bash
uv run python -m start_workflow
```
Loading