Add LiteLLM activity sample - #343
Conversation
|
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. |
| if not content: | ||
| raise ValueError("LiteLLM returned an empty response") |
There was a problem hiding this comment.
Does this happen often? I would opt to remove this and make the example less defensive.
| if not isinstance(response, ModelResponse): | ||
| raise TypeError("Expected a non-streaming LiteLLM response") |
There was a problem hiding this comment.
Optional, but it would be great to see a streaming example as well, where we demonstrate how to integrate LiteLLM with Temporal Workflow Streams.
There was a problem hiding this comment.
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_activitysample (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
litellmdependency 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. |
| 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" |
There was a problem hiding this comment.
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"), |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
Does this work?
| async def test_call_litellm(monkeypatch: Any) -> None: | |
| async def test_call_litellm(monkeypatch: pytest.MonkeyPatch) -> None: |
| # 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?" |
There was a problem hiding this comment.
| # 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?" |
What changed
litellm_activitysample with serializable Workflow inputs.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
pytest tests/litellm_activity(2 passed)cc @brianstrauch