-
Notifications
You must be signed in to change notification settings - Fork 276
feat: add cookbook #3870
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
feat: add cookbook #3870
Changes from all commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
bbb380e
feat: add cookbook
lennessyy 08d11c7
chore: adjust typescript config
lennessyy c4e0df0
add Github source button functionality
lennessyy 5e233b3
Merge branch 'main' into cookbook
lennessyy 8b94f88
style the tiles
lennessyy f51889c
Merge branch 'cookbook' of https://github.com/temporalio/documentatio…
lennessyy ac1f106
adjust spacing
lennessyy 396f5ea
add existing recipies
lennessyy 0989a7b
add source
lennessyy cb27156
fix broken link issue
lennessyy c62b2bb
fix broken links issue
lennessyy ceb7cff
add source for retry example
lennessyy 007e49f
Merge branch 'main' into cookbook
lennessyy cca3ff8
adjust box shadow
lennessyy 966f35b
Merge branch 'cookbook' of https://github.com/temporalio/documentatio…
lennessyy 2cc06ac
chore: add e2e tests
lennessyy adb866a
Merge branch 'main' into cookbook
lennessyy 90708c6
add date and change tab behavior
lennessyy bb6eed5
add orderng mechanism
lennessyy 0e0039f
rename to ai cookbook
lennessyy 974c14b
add cookbook recipes
lennessyy 3df7970
broken link fix
lennessyy 8a06439
adjust tile order
lennessyy 7b4f686
docs: add source for recipes
lennessyy c3c3a43
Update AI Cookbook heading and capitalize Workflow
angelazhou32 d8eafd7
change title
angelazhou32 9c353d3
changed heading levels, added you in the description, changed date st…
angelazhou32 c0a8cb3
Merge remote-tracking branch 'origin/main' into cookbook
angelazhou32 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,4 +34,7 @@ package-lock.json | |
|
||
# Env | ||
.env | ||
/assembly/.env | ||
/assembly/.env | ||
|
||
# Tests | ||
test-results/* |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | ||
- 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 | ||
``` |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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.