Skip to content

Add LiteLLM activity sample - #343

Draft
Abhinav0905 wants to merge 1 commit into
temporalio:mainfrom
Abhinav0905:litellm-workflow-sample
Draft

Add LiteLLM activity sample#343
Abhinav0905 wants to merge 1 commit into
temporalio:mainfrom
Abhinav0905:litellm-workflow-sample

Conversation

@Abhinav0905

Copy link
Copy Markdown

What changed

  • Added a standalone litellm_activity sample with serializable Workflow inputs.
  • Kept LiteLLM network I/O in a Temporal Activity so Workflow replay remains deterministic.
  • Added Activity timeouts, Temporal-controlled retries, provider configuration, and setup documentation.
  • Added deterministic Activity and Workflow tests with no live provider calls.
  • Added the LiteLLM dependency group, package metadata, root sample listing, and lockfile entries.

Why

LLM provider calls are nondeterministic network operations and must run outside Workflow code. This sample demonstrates the Activity boundary directly while making timeouts and retry behavior visible in Temporal Event History.

Closes #239.

User impact

Users can run the sample against any LiteLLM-supported provider by setting provider credentials on the Worker and selecting a model with LITELLM_MODEL. Credentials are not passed through the Workflow or stored in Event History.

Validation

  • Ruff import ordering and lint checks
  • Ruff formatting check
  • MyPy on the new sample and tests
  • pytest tests/litellm_activity (2 passed)

cc @brianstrauch

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


abhinav seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

Comment on lines +25 to +26
if not content:
raise ValueError("LiteLLM returned an empty response")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does this happen often? I would opt to remove this and make the example less defensive.

Comment on lines +21 to +22
if not isinstance(response, ModelResponse):
raise TypeError("Expected a non-streaming LiteLLM response")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Optional, but it would be great to see a streaming example as well, where we demonstrate how to integrate LiteLLM with Temporal Workflow Streams.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds a new litellm_activity sample that demonstrates the Temporal best practice of performing nondeterministic LLM/provider network calls inside an Activity (not Workflow code), along with dependency wiring and deterministic tests to validate behavior without live provider calls.

Changes:

  • Introduces the litellm_activity sample (Workflow + Activity + worker/starter) with Activity timeouts and Temporal-managed retries.
  • Adds deterministic unit tests for both the Activity wrapper and the Workflow-to-Activity boundary.
  • Registers the new sample in packaging metadata and documentation, and adds a litellm dependency group plus lockfile entries.

Reviewed changes

Copilot reviewed 11 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
uv.lock Adds lockfile entries for the new litellm dependency group.
README.md Adds the new litellm_activity sample to the repo’s sample index.
pyproject.toml Registers the litellm dependency group and includes litellm_activity as a package.
litellm_activity/init.py Declares the new sample package.
litellm_activity/activities.py Implements the LiteLLM provider call as a Temporal Activity with a client timeout and LiteLLM retries disabled.
litellm_activity/shared.py Adds a serializable request dataclass shared across starter/workflow/activity.
litellm_activity/workflow.py Adds a Workflow that calls the Activity with timeouts and a bounded retry policy.
litellm_activity/worker.py Adds a runnable Worker hosting the workflow and activity.
litellm_activity/starter.py Adds a runnable starter to execute the workflow with CLI/env-provided inputs.
litellm_activity/README.md Documents setup, provider configuration, running, and tests for the new sample.
tests/litellm_activity/init.py Initializes the test package for the new sample.
tests/litellm_activity/activity_test.py Unit-tests the Activity wrapper via monkeypatched LiteLLM call (no network).
tests/litellm_activity/workflow_test.py Tests the Workflow execution path with a mocked Activity implementation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

"Why should LLM calls run in Temporal Activities?"
```

The Activity gives each provider call a 30-second client timeout. The Workflow gives each Activity attempt 45 seconds, limits the entire Activity execution to two minutes, and retries failures up to three times with exponential backoff. LiteLLM's own retries are disabled so Temporal records and controls every attempt.
Comment on lines +21 to +22
if not isinstance(response, ModelResponse):
raise TypeError("Expected a non-streaming LiteLLM response")
from litellm_activity.activities import call_litellm
from litellm_activity.workflow import LiteLLMWorkflow

TASK_QUEUE = "litellm-activity-task-queue"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Move to shared.py since it's used here and in starter.py?

prompt = " ".join(sys.argv[1:]) or "Explain Temporal in one sentence."
request = LLMRequest(
prompt=prompt,
model=os.getenv("LITELLM_MODEL", "openai/gpt-4o-mini"),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Declare "openai/gpt-4o-mini" as a constant in shared.py and import it here to reduce duplication

from litellm_activity.shared import LLMRequest


async def test_call_litellm(monkeypatch: Any) -> None:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does this work?

Suggested change
async def test_call_litellm(monkeypatch: Any) -> None:
async def test_call_litellm(monkeypatch: pytest.MonkeyPatch) -> None:

Comment on lines +35 to +40
# Terminal 1: run the Worker
uv run --group litellm python -m litellm_activity.worker

# Terminal 2: start a Workflow
uv run --group litellm python -m litellm_activity.starter \
"Why should LLM calls run in Temporal Activities?"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
# Terminal 1: run the Worker
uv run --group litellm python -m litellm_activity.worker
# Terminal 2: start a Workflow
uv run --group litellm python -m litellm_activity.starter \
"Why should LLM calls run in Temporal Activities?"
# Terminal 1: run the Worker
uv run --group litellm litellm_activity.worker
# Terminal 2: start a Workflow
uv run --group litellm litellm_activity.starter \
"Why should LLM calls run in Temporal Activities?"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature Request] Add sample demonstrating how to make liteLLM calls from a workflow

4 participants