Skip to content
Merged
Show file tree
Hide file tree
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
11 changes: 8 additions & 3 deletions lib/crewai/tests/llms/anthropic/test_anthropic.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,19 @@ def test_anthropic_completion_is_used_when_claude_provider():



def test_anthropic_completion_module_is_imported():
def test_anthropic_completion_module_is_imported(monkeypatch):
"""
Test that the completion module is properly imported when using Anthropic provider
"""
module_name = "crewai.llms.providers.anthropic.completion"

if module_name in sys.modules:
del sys.modules[module_name]
import crewai.llms.providers.anthropic as anthropic_package

if hasattr(anthropic_package, "completion"):
monkeypatch.setattr(
anthropic_package, "completion", anthropic_package.completion
)
monkeypatch.delitem(sys.modules, module_name, raising=False)

LLM(model="anthropic/claude-3-5-sonnet-20241022")

Expand Down
30 changes: 16 additions & 14 deletions lib/devtools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,26 +26,28 @@ devtools release 1.10.3a1 # pre-release
devtools release 1.10.3 --no-edit # skip editing release notes
devtools release 1.10.3 --dry-run # preview without changes
devtools release 1.10.3 --skip-enterprise # skip enterprise release phase
devtools release 1.10.3 --skip-litellm-catalog-update # skip the catalog PR
```

**Flow:**

1. Bumps `__version__` and dependency pins across all `lib/` packages
2. Runs `uv sync`
3. Creates version bump PR against main, polls until merged
4. Generates release notes (OpenAI) from commits since last release
5. Updates Edge changelogs (`docs/edge/{en,pt-BR,ko,ar}/changelog.mdx`)
6. Downloads the LiteLLM model catalog into `lib/crewai/src/crewai/llms/litellm_catalog/model_prices_and_context_window.json`, freezes `docs/edge/` into `docs/v<version>/`, registers the version in `docs.json`, and points the canonical `/<lang>/:slug*` redirects at the new default
7. Opens a `[docs-freeze]` PR against main, polls until merged
8. Tags main and creates GitHub release
9. Triggers PyPI publish workflow
10. Updates `crewAIInc/crew_deployment_test` to the exact CrewAI version,
1. Downloads the LiteLLM model catalog into `lib/crewai/src/crewai/llms/litellm_catalog/model_prices_and_context_window.json` and opens a `chore: update litellm model catalog` PR against main, polling until merged (skipped when the catalog is unchanged)
2. Bumps `__version__` and dependency pins across all `lib/` packages
3. Runs `uv sync`
4. Creates version bump PR against main, polls until merged
5. Generates release notes (OpenAI) from commits since last release
6. Updates Edge changelogs (`docs/edge/{en,pt-BR,ko,ar}/changelog.mdx`)
7. Freezes `docs/edge/` into `docs/v<version>/`, registers the version in `docs.json`, and points the canonical `/<lang>/:slug*` redirects at the new default
8. Opens a `[docs-freeze]` PR against main, polls until merged
9. Tags main and creates GitHub release
10. Triggers PyPI publish workflow
11. Updates `crewAIInc/crew_deployment_test` to the exact CrewAI version,
creates a bump PR, and waits for it to merge
11. Updates `crewAIInc/flow_deployment_test` to the exact CrewAI version,
12. Updates `crewAIInc/flow_deployment_test` to the exact CrewAI version,
creates a bump PR, and waits for it to merge
12. Clones enterprise repo, bumps versions and `crewai[tools]` dep, runs `uv sync`
13. Creates enterprise bump PR, polls until merged
14. Tags and creates GitHub release on enterprise repo
13. Clones enterprise repo, bumps versions and `crewai[tools]` dep, runs `uv sync`
14. Creates enterprise bump PR, polls until merged
15. Tags and creates GitHub release on enterprise repo

> The `docs-snapshots` CI guard rejects writes under `docs/v*/` and deletions/renames in `docs/images/` unless the PR title starts with `[docs-freeze]`. The release CLI sets that prefix automatically; manual edits to a frozen snapshot need the same prefix to land.
>
Expand Down
120 changes: 97 additions & 23 deletions lib/devtools/src/crewai_devtools/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -1088,6 +1088,59 @@ def _generate_release_notes(
return release_notes, openai_client, is_prerelease


def _update_catalog_and_create_pr(cwd: Path, version: str, dry_run: bool) -> str | None:
"""Refresh the LiteLLM catalog and open a standalone PR for it.
Runs before the version bump so the catalog lands on main ahead of the
release commits. The catalog is downloaded before any branch is created, so
when it matches the checked-in file no branch or PR is created.
Returns:
The catalog branch name if a PR was created, None otherwise.
"""
if dry_run:
console.print(
f"[dim][DRY RUN][/dim] Would download the LiteLLM catalog into {CATALOG_RELATIVE}"
)
console.print(
f"[dim][DRY RUN][/dim] Would open PR titled "
f"'chore: update litellm model catalog for v{version}' if it changed, "
"and wait for merge"
)
return None

run_command(["git", "checkout", "main"])
run_command(["git", "pull"])

catalog_path = update_catalog(cwd)
if not run_command(["git", "status", "--porcelain", "--", str(catalog_path)]):
console.print("[green]✓[/green] LiteLLM catalog is already up to date")
return None
Comment thread
cursor[bot] marked this conversation as resolved.
Resolved
console.print(
f"[green]✓[/green] Updated LiteLLM catalog {catalog_path.relative_to(cwd)}"
)

catalog_branch = f"chore/update-litellm-catalog-v{version}"
try:
create_or_reset_branch(catalog_branch)
except BaseException:
run_command(["git", "checkout", "--", str(catalog_path)])
raise

pr_title = f"chore: update litellm model catalog for v{version}"
run_command(["git", "add", str(catalog_path)])
run_command(["git", "commit", "-m", pr_title])
run_command(["git", "push", "-u", "origin", catalog_branch])
console.print(f"[green]✓[/green] Pushed branch {catalog_branch}")

pr_url = run_command(
["gh", "pr", "create", "--base", "main", "--title", pr_title, "--body", ""]
)
console.print("[green]✓[/green] Created catalog PR")
console.print(f"[cyan]PR URL:[/cyan] {pr_url}")
return catalog_branch


def _update_docs_and_create_pr(
cwd: Path,
version: str,
Expand All @@ -1098,18 +1151,17 @@ def _update_docs_and_create_pr(
) -> str | None:
"""Update Edge changelogs, freeze a snapshot, and open the docs PR.
Runs after the version-bump PR has merged. Refreshes the checked-in
LiteLLM catalog, then for a stable release freezes ``docs/edge/`` into
``docs/v<version>/`` (after the Edge changelogs have been updated so the
snapshot contains the new entry), updates ``docs/docs.json`` to register
the new version and canonical-URL redirects, and opens a ``[docs-freeze]``
PR. The ``docs-snapshots`` CI guard recognises that title prefix and
allows the snapshot directory to land. The catalog file is included in
that same PR.
Runs after the version-bump PR has merged. For a stable release, freezes
``docs/edge/`` into ``docs/v<version>/`` (after the Edge changelogs have
been updated so the snapshot contains the new entry), updates
``docs/docs.json`` to register the new version and canonical-URL
redirects, and opens a ``[docs-freeze]`` PR. The ``docs-snapshots`` CI
guard recognises that title prefix and allows the snapshot directory to
land.
For a pre-release, the catalog is still refreshed and the Edge changelogs
are updated. Pre-releases don't get a frozen snapshot — they ride Edge —
and the PR title omits the ``[docs-freeze]`` prefix.
For a pre-release, only the Edge changelogs are updated. Pre-releases
don't get a frozen snapshot — they ride Edge — and the PR title omits the
``[docs-freeze]`` prefix.
Returns:
The docs branch name if a PR was created, None otherwise.
Expand All @@ -1120,9 +1172,6 @@ def _update_docs_and_create_pr(
changelog_langs: list[ChangelogLang] = ["en", "pt-BR", "ko", "ar"]

if dry_run:
console.print(
f"[dim][DRY RUN][/dim] Would download the LiteLLM catalog into {CATALOG_RELATIVE}"
)
for lang in changelog_langs:
cl_path = edge_root / lang / "changelog.mdx"
translated = " (translated)" if lang != "en" else ""
Expand All @@ -1149,14 +1198,6 @@ def _update_docs_and_create_pr(

docs_paths_staged: list[str] = []

# The bump PR has already merged. Refresh the catalog before the docs
# snapshot so this PR carries the model data that ships with the release.
catalog_path = update_catalog(cwd)
console.print(
f"[green]✓[/green] Updated LiteLLM catalog {catalog_path.relative_to(cwd)}"
)
docs_paths_staged.append(str(catalog_path))

# Step 1: update Edge changelogs first so the snapshot we freeze afterwards
# contains the new release's entry.
for lang in changelog_langs:
Expand Down Expand Up @@ -2313,16 +2354,23 @@ def tag(dry_run: bool, no_edit: bool) -> None:
is_flag=True,
help="Skip phases 1 & 2, run only the enterprise release phase",
)
@click.option(
"--skip-litellm-catalog-update",
is_flag=True,
help="Skip the LiteLLM catalog update phase",
)
def release(
version: str,
dry_run: bool,
no_edit: bool,
skip_enterprise: bool,
skip_to_enterprise: bool,
skip_litellm_catalog_update: bool,
) -> None:
"""Full release: bump versions, tag, and publish a GitHub release.
Combines bump and tag into a single workflow. Creates a version bump PR,
Combines bump and tag into a single workflow. Opens a PR refreshing the
LiteLLM model catalog (skipped when unchanged), then creates a version bump PR,
waits for it to be merged, then generates release notes, updates docs,
creates the tag, and publishes a GitHub release. Then bumps versions and
releases the enterprise repo.
Expand All @@ -2333,12 +2381,15 @@ def release(
no_edit: Skip editing release notes.
skip_enterprise: Skip the enterprise release phase.
skip_to_enterprise: Skip phases 1 & 2, run only the enterprise release phase.
skip_litellm_catalog_update: Skip the LiteLLM catalog update phase.
"""
flags: list[str] = []
if no_edit:
flags.append("--no-edit")
if skip_enterprise:
flags.append("--skip-enterprise")
if skip_litellm_catalog_update:
flags.append("--skip-litellm-catalog-update")
flag_suffix = (" " + " ".join(flags)) if flags else ""
enterprise_hint = (
""
Expand Down Expand Up @@ -2405,6 +2456,29 @@ def release(
for pkg in packages:
console.print(f" - {pkg.name}")

if skip_litellm_catalog_update:
console.print(
"\n[dim]Skipping Phase 0: LiteLLM catalog update "
"(--skip-litellm-catalog-update)[/dim]"
)
else:
console.print("\n[bold cyan]Phase 0: Updating the LiteLLM catalog[/bold cyan]")

try:
catalog_branch = _update_catalog_and_create_pr(cwd, version, dry_run)
if catalog_branch:
_poll_pr_until_merged(catalog_branch, "catalog PR")
run_command(["git", "checkout", "main"])
run_command(["git", "pull"])
console.print("[green]✓[/green] main branch updated with the catalog")
except BaseException as e:
Comment thread
Vidit-Ostwal marked this conversation as resolved.
Resolved
_print_release_error(e)
_resume_hint(
f"Phase 0 failed. Fix the issue, then re-run:\n\n"
f" devtools release {version}{flag_suffix}"
)
sys.exit(1)

console.print(f"\n[bold cyan]Phase 1: Bumping versions to {version}[/bold cyan]")

try:
Expand Down
4 changes: 2 additions & 2 deletions lib/devtools/src/crewai_devtools/litellm_catalog_update.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# ruff: noqa: T201
"""Refresh the checked-in LiteLLM model catalog.

``devtools release`` calls :func:`update_catalog` after the version-bump PR
merges, while it is preparing the docs PR. The standalone script
``devtools release`` calls :func:`update_catalog` first, in its own PR, before
the version-bump PR. The standalone script
``scripts/update_litellm_catalog.py`` is the manual wrapper.
"""

Expand Down
72 changes: 72 additions & 0 deletions lib/devtools/tests/test_release_catalog_phase.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""Phase 0 of ``devtools release`` opens a catalog PR only when the catalog changed."""

from pathlib import Path

from crewai_devtools import cli
import pytest


@pytest.fixture
def calls(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> list[list[str]]:
"""Record git/gh commands; ``git status --porcelain`` output is set per test."""
recorded: list[list[str]] = []
monkeypatch.setattr(cli, "update_catalog", lambda root: root / cli.CATALOG_RELATIVE)
monkeypatch.setattr(
cli,
"create_or_reset_branch",
lambda branch: recorded.append(["branch", branch]),
)
return recorded


def _fake_run(recorded: list[list[str]], status: str):
def run(cmd: list[str], cwd: Path | None = None) -> str:
recorded.append(cmd)
if cmd[:3] == ["git", "status", "--porcelain"]:
return status
if cmd[:3] == ["gh", "pr", "create"]:
return "https://github.com/o/r/pull/1"
return ""

return run


def test_unchanged_catalog_creates_no_branch_or_pr(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path, calls: list[list[str]]
) -> None:
monkeypatch.setattr(cli, "run_command", _fake_run(calls, status=""))

assert cli._update_catalog_and_create_pr(tmp_path, "1.2.3", dry_run=False) is None

assert not any(c[0] == "branch" for c in calls)
assert not any(c[:2] == ["gh", "pr"] for c in calls)


def test_changed_catalog_opens_a_pr_from_its_own_branch(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path, calls: list[list[str]]
) -> None:
monkeypatch.setattr(cli, "run_command", _fake_run(calls, status=" M catalog.json"))

branch = cli._update_catalog_and_create_pr(tmp_path, "1.2.3", dry_run=False)

assert branch == "chore/update-litellm-catalog-v1.2.3"
assert ["branch", branch] in calls
pr = next(c for c in calls if c[:3] == ["gh", "pr", "create"])
assert "chore: update litellm model catalog for v1.2.3" in pr


def test_declining_the_branch_reset_restores_the_catalog(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path, calls: list[list[str]]
) -> None:
monkeypatch.setattr(cli, "run_command", _fake_run(calls, status=" M catalog.json"))

def decline(branch: str) -> None:
raise SystemExit(1)

monkeypatch.setattr(cli, "create_or_reset_branch", decline)

with pytest.raises(SystemExit):
cli._update_catalog_and_create_pr(tmp_path, "1.2.3", dry_run=False)

assert any(c[:3] == ["git", "checkout", "--"] for c in calls)
assert not any(c[:2] == ["gh", "pr"] for c in calls)
3 changes: 2 additions & 1 deletion scripts/update_litellm_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"""Refresh the checked-in LiteLLM model catalog.

Manual wrapper around :func:`crewai_devtools.litellm_catalog_update.update_catalog`.
``devtools release`` calls that function after the version-bump PR merges.
``devtools release`` calls that function first, in its own PR, before the
version-bump PR.

Usage::

Expand Down
Loading