Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
d34cbbf
Create codeflash_wrap_decorator.py
KRRT7 Sep 26, 2025
fa705e1
first pass
KRRT7 Sep 26, 2025
3e96474
Add async function support to AI service and optimization pipeline
KRRT7 Sep 26, 2025
759fdb1
Complete async test instrumentation and utilities implementation
KRRT7 Sep 26, 2025
669e22a
Complete async throughput measurement support
KRRT7 Sep 26, 2025
05e5a94
formatting
KRRT7 Sep 26, 2025
4325416
resolve paths
KRRT7 Sep 26, 2025
736faa1
add E2E gha & pytest-asyncio
KRRT7 Sep 26, 2025
2d1696a
restore from other branch
KRRT7 Sep 26, 2025
2200b21
bring over changes from other branch
KRRT7 Sep 26, 2025
5f7e11d
Add missing async throughput parameters to AI service explanation
KRRT7 Sep 26, 2025
11ede6e
Update code_utils.py
KRRT7 Sep 26, 2025
a65f026
restore fields
KRRT7 Sep 26, 2025
1fe7bd1
update type
KRRT7 Sep 26, 2025
6bd39f7
fix cov method
KRRT7 Sep 26, 2025
c6c9d95
few missing things
KRRT7 Sep 26, 2025
08a3412
some other tests
KRRT7 Sep 26, 2025
ce1445c
Update function_optimizer.py
KRRT7 Sep 26, 2025
58f088c
throughput
KRRT7 Sep 26, 2025
3f1f8d3
sync with main
KRRT7 Sep 27, 2025
4b37f27
linting
KRRT7 Sep 27, 2025
6795a5c
pass mypy
KRRT7 Sep 27, 2025
02f96e7
mypy
KRRT7 Sep 27, 2025
2c97b92
formatting
KRRT7 Sep 27, 2025
87ddb98
Merge branch 'main' into clean-async-branch
KRRT7 Sep 27, 2025
b9c4781
Optimize remove_functions_from_generated_tests
codeflash-ai[bot] Sep 27, 2025
62bea9e
gate async behind --async
KRRT7 Sep 27, 2025
278822b
pass --async to E2E
KRRT7 Sep 27, 2025
16ce6ff
formatter
KRRT7 Sep 27, 2025
6116fc6
Merge branch 'main' into clean-async-branch
KRRT7 Sep 27, 2025
7a6a432
mypy fix
KRRT7 Sep 27, 2025
e27c133
Merge pull request #779 from codeflash-ai/codeflash/optimize-pr769-20…
KRRT7 Sep 27, 2025
585249f
Optimize AsyncCallInstrumenter.visit_AsyncFunctionDef
codeflash-ai[bot] Sep 27, 2025
be2ffea
Merge pull request #780 from codeflash-ai/codeflash/optimize-pr769-20…
KRRT7 Sep 27, 2025
2c8d2ba
make asyncio optional
KRRT7 Sep 28, 2025
2022e85
Update cli.py
KRRT7 Sep 28, 2025
b992f71
loosen gain % for E2E
KRRT7 Sep 29, 2025
782dba1
update this one too
KRRT7 Sep 29, 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
69 changes: 69 additions & 0 deletions .github/workflows/e2e-async.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: E2E - Async

on:
pull_request:
paths:
- '**' # Trigger for all paths

workflow_dispatch:

jobs:
async-optimization:
# Dynamically determine if environment is needed only when workflow files change and contributor is external
environment: ${{ (github.event_name == 'workflow_dispatch' || (contains(toJSON(github.event.pull_request.files.*.filename), '.github/workflows/') && github.event.pull_request.user.login != 'misrasaurabh1' && github.event.pull_request.user.login != 'KRRT7')) && 'external-trusted-contributors' || '' }}

runs-on: ubuntu-latest
env:
CODEFLASH_AIS_SERVER: prod
POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }}
CODEFLASH_API_KEY: ${{ secrets.CODEFLASH_API_KEY }}
COLUMNS: 110
MAX_RETRIES: 3
RETRY_DELAY: 5
EXPECTED_IMPROVEMENT_PCT: 10
CODEFLASH_END_TO_END: 1
steps:
- name: 🛎️ Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}

- name: Validate PR
run: |
# Check for any workflow changes
if git diff --name-only "${{ github.event.pull_request.base.sha }}" "${{ github.event.pull_request.head.sha }}" | grep -q "^.github/workflows/"; then
echo "⚠️ Workflow changes detected."

# Get the PR author
AUTHOR="${{ github.event.pull_request.user.login }}"
echo "PR Author: $AUTHOR"

# Allowlist check
if [[ "$AUTHOR" == "misrasaurabh1" || "$AUTHOR" == "KRRT7" ]]; then
echo "✅ Authorized user ($AUTHOR). Proceeding."
elif [[ "${{ github.event.pull_request.state }}" == "open" ]]; then
echo "✅ PR triggered by 'pull_request_target' and is open. Assuming protection rules are in place. Proceeding."
else
echo "⛔ Unauthorized user ($AUTHOR) attempting to modify workflows. Exiting."
exit 1
fi
else
echo "✅ No workflow file changes detected. Proceeding."
fi

- name: Set up Python 3.11 for CLI
uses: astral-sh/setup-uv@v5
with:
python-version: 3.11.6

- name: Install dependencies (CLI)
run: |
uv sync

- name: Run Codeflash to optimize async code
id: optimize_async_code
run: |
uv run python tests/scripts/end_to_end_test_async.py
2 changes: 1 addition & 1 deletion .github/workflows/e2e-bubblesort-pytest-nogit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
COLUMNS: 110
MAX_RETRIES: 3
RETRY_DELAY: 5
EXPECTED_IMPROVEMENT_PCT: 300
EXPECTED_IMPROVEMENT_PCT: 70
CODEFLASH_END_TO_END: 1
steps:
- name: 🛎️ Checkout
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/e2e-bubblesort-unittest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
COLUMNS: 110
MAX_RETRIES: 3
RETRY_DELAY: 5
EXPECTED_IMPROVEMENT_PCT: 300
EXPECTED_IMPROVEMENT_PCT: 40
CODEFLASH_END_TO_END: 1
steps:
- name: 🛎️ Checkout
Expand Down
43 changes: 43 additions & 0 deletions code_to_optimize/async_bubble_sort.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import asyncio
from typing import List, Union


async def async_sorter(lst: List[Union[int, float]]) -> List[Union[int, float]]:
"""
Async bubble sort implementation for testing.
"""
print("codeflash stdout: Async sorting list")

await asyncio.sleep(0.01)

n = len(lst)
for i in range(n):
for j in range(0, n - i - 1):
if lst[j] > lst[j + 1]:
lst[j], lst[j + 1] = lst[j + 1], lst[j]

result = lst.copy()
print(f"result: {result}")
return result


class AsyncBubbleSorter:
"""Class with async sorting method for testing."""

async def sorter(self, lst: List[Union[int, float]]) -> List[Union[int, float]]:
"""
Async bubble sort implementation within a class.
"""
print("codeflash stdout: AsyncBubbleSorter.sorter() called")

# Add some async delay
await asyncio.sleep(0.005)

n = len(lst)
for i in range(n):
for j in range(0, n - i - 1):
if lst[j] > lst[j + 1]:
lst[j], lst[j + 1] = lst[j + 1], lst[j]

result = lst.copy()
return result
16 changes: 16 additions & 0 deletions code_to_optimize/code_directories/async_e2e/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import time
import asyncio


async def retry_with_backoff(func, max_retries=3):
if max_retries < 1:
raise ValueError("max_retries must be at least 1")
last_exception = None
for attempt in range(max_retries):
try:
return await func()
except Exception as e:
last_exception = e
if attempt < max_retries - 1:
time.sleep(0.0001 * attempt)
raise last_exception
6 changes: 6 additions & 0 deletions code_to_optimize/code_directories/async_e2e/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[tool.codeflash]
disable-telemetry = true
formatter-cmds = ["ruff check --exit-zero --fix $file", "ruff format $file"]
module-root = "."
test-framework = "pytest"
tests-root = "tests"
Empty file.
13 changes: 13 additions & 0 deletions codeflash/api/aiservice.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ def optimize_python_code( # noqa: D417
trace_id: str,
num_candidates: int = 10,
experiment_metadata: ExperimentMetadata | None = None,
*,
is_async: bool = False,
) -> list[OptimizedCandidate]:
"""Optimize the given python code for performance by making a request to the Django endpoint.

Expand Down Expand Up @@ -133,6 +135,7 @@ def optimize_python_code( # noqa: D417
"repo_owner": git_repo_owner,
"repo_name": git_repo_name,
"n_candidates": N_CANDIDATES_EFFECTIVE,
"is_async": is_async,
}

logger.info("!lsp|Generating optimized candidates…")
Expand Down Expand Up @@ -299,6 +302,9 @@ def get_new_explanation( # noqa: D417
annotated_tests: str,
optimization_id: str,
original_explanation: str,
original_throughput: str | None = None,
optimized_throughput: str | None = None,
throughput_improvement: str | None = None,
) -> str:
"""Optimize the given python code for performance by making a request to the Django endpoint.

Expand All @@ -315,6 +321,9 @@ def get_new_explanation( # noqa: D417
- annotated_tests: str - test functions annotated with runtime
- optimization_id: str - unique id of opt candidate
- original_explanation: str - original_explanation generated for the opt candidate
- original_throughput: str | None - throughput for the baseline code (operations per second)
- optimized_throughput: str | None - throughput for the optimized code (operations per second)
- throughput_improvement: str | None - throughput improvement percentage

Returns
-------
Expand All @@ -334,6 +343,9 @@ def get_new_explanation( # noqa: D417
"optimization_id": optimization_id,
"original_explanation": original_explanation,
"dependency_code": dependency_code,
"original_throughput": original_throughput,
"optimized_throughput": optimized_throughput,
"throughput_improvement": throughput_improvement,
}
logger.info("loading|Generating explanation")
console.rule()
Expand Down Expand Up @@ -488,6 +500,7 @@ def generate_regression_tests( # noqa: D417
"test_index": test_index,
"python_version": platform.python_version(),
"codeflash_version": codeflash_version,
"is_async": function_to_optimize.is_async,
}
try:
response = self.make_ai_service_request("/testgen", payload=payload, timeout=600)
Expand Down
15 changes: 15 additions & 0 deletions codeflash/cli_cmds/cli.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import importlib.util
import logging
import sys
from argparse import SUPPRESS, ArgumentParser, Namespace
Expand Down Expand Up @@ -96,6 +97,12 @@ def parse_args() -> Namespace:
)
parser.add_argument("--no-draft", default=False, action="store_true", help="Skip optimization for draft PRs")
parser.add_argument("--worktree", default=False, action="store_true", help="Use worktree for optimization")
parser.add_argument(
"--async",
default=False,
action="store_true",
help="Enable optimization of async functions. By default, async functions are excluded from optimization.",
)

args, unknown_args = parser.parse_known_args()
sys.argv[:] = [sys.argv[0], *unknown_args]
Expand Down Expand Up @@ -139,6 +146,14 @@ def process_and_validate_cmd_args(args: Namespace) -> Namespace:
if env_utils.is_ci():
args.no_pr = True

if getattr(args, "async", False) and importlib.util.find_spec("pytest_asyncio") is None:
logger.warning(
"Warning: The --async flag requires pytest-asyncio to be installed.\n"
"Please install it using:\n"
' pip install "codeflash[asyncio]"'
)
raise SystemExit(1)

return args


Expand Down
10 changes: 1 addition & 9 deletions codeflash/code_utils/code_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ def get_qualified_name(module_name: str, full_qualified_name: str) -> str:

def module_name_from_file_path(file_path: Path, project_root_path: Path, *, traverse_up: bool = False) -> str:
try:
relative_path = file_path.relative_to(project_root_path)
relative_path = file_path.resolve().relative_to(project_root_path.resolve())
return relative_path.with_suffix("").as_posix().replace("/", ".")
except ValueError:
if traverse_up:
Expand Down Expand Up @@ -268,14 +268,6 @@ def validate_python_code(code: str) -> str:
return code


def has_any_async_functions(code: str) -> bool:
try:
module = ast.parse(code)
except SyntaxError:
return False
return any(isinstance(node, ast.AsyncFunctionDef) for node in ast.walk(module))


def cleanup_paths(paths: list[Path]) -> None:
for path in paths:
if path and path.exists():
Expand Down
Loading
Loading