Skip to content

Commit 4edea33

Browse files
Merge branch 'main' of github.com:codeflash-ai/codeflash into feat/markdown-read-writable-context
2 parents 8a09c35 + ae3c7ca commit 4edea33

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+736
-1169
lines changed

.github/workflows/deploy-docs-to-azure.yaml

Lines changed: 0 additions & 31 deletions
This file was deleted.

codeflash/api/aiservice.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,79 @@ def optimize_python_code_refinement(self, request: list[AIServiceRefinerRequest]
278278
console.rule()
279279
return []
280280

281+
def get_new_explanation( # noqa: D417
282+
self,
283+
source_code: str,
284+
optimized_code: str,
285+
dependency_code: str,
286+
trace_id: str,
287+
original_line_profiler_results: str,
288+
optimized_line_profiler_results: str,
289+
original_code_runtime: str,
290+
optimized_code_runtime: str,
291+
speedup: str,
292+
annotated_tests: str,
293+
optimization_id: str,
294+
original_explanation: str,
295+
) -> str:
296+
"""Optimize the given python code for performance by making a request to the Django endpoint.
297+
298+
Parameters
299+
----------
300+
- source_code (str): The python code to optimize.
301+
- optimized_code (str): The python code generated by the AI service.
302+
- dependency_code (str): The dependency code used as read-only context for the optimization
303+
- original_line_profiler_results: str - line profiler results for the baseline code
304+
- optimized_line_profiler_results: str - line profiler results for the optimized code
305+
- original_code_runtime: str - runtime for the baseline code
306+
- optimized_code_runtime: str - runtime for the optimized code
307+
- speedup: str - speedup of the optimized code
308+
- annotated_tests: str - test functions annotated with runtime
309+
- optimization_id: str - unique id of opt candidate
310+
- original_explanation: str - original_explanation generated for the opt candidate
311+
312+
Returns
313+
-------
314+
- List[OptimizationCandidate]: A list of Optimization Candidates.
315+
316+
"""
317+
payload = {
318+
"trace_id": trace_id,
319+
"source_code": source_code,
320+
"optimized_code": optimized_code,
321+
"original_line_profiler_results": original_line_profiler_results,
322+
"optimized_line_profiler_results": optimized_line_profiler_results,
323+
"original_code_runtime": original_code_runtime,
324+
"optimized_code_runtime": optimized_code_runtime,
325+
"speedup": speedup,
326+
"annotated_tests": annotated_tests,
327+
"optimization_id": optimization_id,
328+
"original_explanation": original_explanation,
329+
"dependency_code": dependency_code,
330+
}
331+
logger.info("Generating explanation")
332+
console.rule()
333+
try:
334+
response = self.make_ai_service_request("/explain", payload=payload, timeout=60)
335+
except requests.exceptions.RequestException as e:
336+
logger.exception(f"Error generating explanations: {e}")
337+
ph("cli-optimize-error-caught", {"error": str(e)})
338+
return ""
339+
340+
if response.status_code == 200:
341+
explanation: str = response.json()["explanation"]
342+
logger.debug(f"New Explanation: {explanation}")
343+
console.rule()
344+
return explanation
345+
try:
346+
error = response.json()["error"]
347+
except Exception:
348+
error = response.text
349+
logger.error(f"Error generating optimized candidates: {response.status_code} - {error}")
350+
ph("cli-optimize-error-response", {"response_status_code": response.status_code, "error": error})
351+
console.rule()
352+
return ""
353+
281354
def log_results( # noqa: D417
282355
self,
283356
function_trace_id: str,

codeflash/api/cfapi.py

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@
1414

1515
from codeflash.cli_cmds.console import console, logger
1616
from codeflash.code_utils.env_utils import ensure_codeflash_api_key, get_codeflash_api_key, get_pr_number
17-
from codeflash.code_utils.git_utils import get_repo_owner_and_name
17+
from codeflash.code_utils.git_utils import get_current_branch, get_repo_owner_and_name, git_root_dir
18+
from codeflash.github.PrComment import FileDiffContent, PrComment
1819
from codeflash.version import __version__
1920

2021
if TYPE_CHECKING:
2122
from requests import Response
2223

23-
from codeflash.github.PrComment import FileDiffContent, PrComment
24+
from codeflash.result.explanation import Explanation
25+
2426
from packaging import version
2527

2628
if os.environ.get("CODEFLASH_CFAPI_SERVER", default="prod").lower() == "local":
@@ -182,6 +184,59 @@ def create_pr(
182184
return make_cfapi_request(endpoint="/create-pr", method="POST", payload=payload)
183185

184186

187+
def create_staging(
188+
original_code: dict[Path, str],
189+
new_code: dict[Path, str],
190+
explanation: Explanation,
191+
existing_tests_source: str,
192+
generated_original_test_source: str,
193+
function_trace_id: str,
194+
coverage_message: str,
195+
) -> Response:
196+
"""Create a staging pull request, targeting the specified branch. (usually 'staging').
197+
198+
:param original_code: A mapping of file paths to original source code.
199+
:param new_code: A mapping of file paths to optimized source code.
200+
:param explanation: An Explanation object with optimization details.
201+
:param existing_tests_source: Existing test code.
202+
:param generated_original_test_source: Generated tests for the original function.
203+
:param function_trace_id: Unique identifier for this optimization trace.
204+
:param coverage_message: Coverage report or summary.
205+
:return: The response object from the backend.
206+
"""
207+
relative_path = explanation.file_path.relative_to(git_root_dir()).as_posix()
208+
209+
build_file_changes = {
210+
Path(p).relative_to(git_root_dir()).as_posix(): FileDiffContent(
211+
oldContent=original_code[p], newContent=new_code[p]
212+
)
213+
for p in original_code
214+
}
215+
216+
payload = {
217+
"baseBranch": get_current_branch(),
218+
"diffContents": build_file_changes,
219+
"prCommentFields": PrComment(
220+
optimization_explanation=explanation.explanation_message(),
221+
best_runtime=explanation.best_runtime_ns,
222+
original_runtime=explanation.original_runtime_ns,
223+
function_name=explanation.function_name,
224+
relative_file_path=relative_path,
225+
speedup_x=explanation.speedup_x,
226+
speedup_pct=explanation.speedup_pct,
227+
winning_behavior_test_results=explanation.winning_behavior_test_results,
228+
winning_benchmarking_test_results=explanation.winning_benchmarking_test_results,
229+
benchmark_details=explanation.benchmark_details,
230+
).to_json(),
231+
"existingTests": existing_tests_source,
232+
"generatedTests": generated_original_test_source,
233+
"traceId": function_trace_id,
234+
"coverage_message": coverage_message,
235+
}
236+
237+
return make_cfapi_request(endpoint="/create-staging", method="POST", payload=payload)
238+
239+
185240
def is_github_app_installed_on_repo(owner: str, repo: str, *, suppress_errors: bool = False) -> bool:
186241
"""Check if the Codeflash GitHub App is installed on the specified repository.
187242

codeflash/cli_cmds/cli.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ def parse_args() -> Namespace:
7777
parser.add_argument(
7878
"--no-pr", action="store_true", help="Do not create a PR for the optimization, only update the code locally."
7979
)
80+
parser.add_argument("--staging-review", action="store_true", help="Upload optimizations to staging for review")
8081
parser.add_argument(
8182
"--verify-setup",
8283
action="store_true",

codeflash/optimization/function_optimizer.py

Lines changed: 116 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from rich.tree import Tree
2020

2121
from codeflash.api.aiservice import AiServiceClient, AIServiceRefinerRequest, LocalAiServiceClient
22-
from codeflash.api.cfapi import add_code_context_hash, mark_optimization_success
22+
from codeflash.api.cfapi import add_code_context_hash, create_staging, mark_optimization_success
2323
from codeflash.benchmarking.utils import process_benchmark_data
2424
from codeflash.cli_cmds.console import code_print, console, logger, progress_bar
2525
from codeflash.code_utils import env_utils
@@ -1104,70 +1104,124 @@ def find_and_process_best_optimization(
11041104
original_code_combined[explanation.file_path] = self.function_to_optimize_source_code
11051105
new_code_combined = new_helper_code.copy()
11061106
new_code_combined[explanation.file_path] = new_code
1107-
if not self.args.no_pr:
1108-
coverage_message = (
1109-
original_code_baseline.coverage_results.build_message()
1110-
if original_code_baseline.coverage_results
1111-
else "Coverage data not available"
1112-
)
1113-
generated_tests = remove_functions_from_generated_tests(
1114-
generated_tests=generated_tests, test_functions_to_remove=test_functions_to_remove
1115-
)
1116-
original_runtime_by_test = (
1117-
original_code_baseline.benchmarking_test_results.usable_runtime_data_by_test_case()
1118-
)
1119-
optimized_runtime_by_test = (
1120-
best_optimization.winning_benchmarking_test_results.usable_runtime_data_by_test_case()
1121-
)
1122-
qualified_name = self.function_to_optimize.qualified_name_with_modules_from_root(self.project_root)
1123-
# Add runtime comments to generated tests before creating the PR
1124-
generated_tests = add_runtime_comments_to_generated_tests(
1125-
generated_tests, original_runtime_by_test, optimized_runtime_by_test
1126-
)
1127-
generated_tests_str = "\n\n".join(
1128-
[test.generated_original_test_source for test in generated_tests.generated_tests]
1129-
)
1130-
existing_tests = existing_tests_source_for(
1131-
qualified_name,
1132-
function_to_all_tests,
1133-
test_cfg=self.test_cfg,
1134-
original_runtimes_all=original_runtime_by_test,
1135-
optimized_runtimes_all=optimized_runtime_by_test,
1136-
)
1137-
if concolic_test_str:
1138-
generated_tests_str += "\n\n" + concolic_test_str
1139-
1140-
check_create_pr(
1141-
original_code=original_code_combined,
1142-
new_code=new_code_combined,
1143-
explanation=explanation,
1144-
existing_tests_source=existing_tests,
1145-
generated_original_test_source=generated_tests_str,
1146-
function_trace_id=self.function_trace_id[:-4] + exp_type
1147-
if self.experiment_id
1148-
else self.function_trace_id,
1149-
coverage_message=coverage_message,
1150-
git_remote=self.args.git_remote,
1151-
)
1152-
if (
1153-
self.args.all
1154-
or env_utils.get_pr_number()
1155-
or self.args.replay_test
1156-
or (self.args.file and not self.args.function)
1157-
):
1158-
self.write_code_and_helpers(
1159-
self.function_to_optimize_source_code,
1160-
original_helper_code,
1161-
self.function_to_optimize.file_path,
1162-
)
1163-
else:
1164-
# Mark optimization success since no PR will be created
1165-
mark_optimization_success(
1166-
trace_id=self.function_trace_id, is_optimization_found=best_optimization is not None
1167-
)
1107+
self.process_review(
1108+
original_code_baseline,
1109+
best_optimization,
1110+
generated_tests,
1111+
test_functions_to_remove,
1112+
concolic_test_str,
1113+
original_code_combined,
1114+
new_code_combined,
1115+
explanation,
1116+
function_to_all_tests,
1117+
exp_type,
1118+
original_helper_code,
1119+
code_context,
1120+
)
11681121
self.log_successful_optimization(explanation, generated_tests, exp_type)
11691122
return best_optimization
11701123

1124+
def process_review(
1125+
self,
1126+
original_code_baseline: OriginalCodeBaseline,
1127+
best_optimization: BestOptimization,
1128+
generated_tests: GeneratedTestsList,
1129+
test_functions_to_remove: list[str],
1130+
concolic_test_str: str | None,
1131+
original_code_combined: dict[Path, str],
1132+
new_code_combined: dict[Path, str],
1133+
explanation: Explanation,
1134+
function_to_all_tests: dict[str, set[FunctionCalledInTest]],
1135+
exp_type: str,
1136+
original_helper_code: dict[Path, str],
1137+
code_context: CodeOptimizationContext,
1138+
) -> None:
1139+
coverage_message = (
1140+
original_code_baseline.coverage_results.build_message()
1141+
if original_code_baseline.coverage_results
1142+
else "Coverage data not available"
1143+
)
1144+
1145+
generated_tests = remove_functions_from_generated_tests(
1146+
generated_tests=generated_tests, test_functions_to_remove=test_functions_to_remove
1147+
)
1148+
1149+
original_runtime_by_test = original_code_baseline.benchmarking_test_results.usable_runtime_data_by_test_case()
1150+
optimized_runtime_by_test = (
1151+
best_optimization.winning_benchmarking_test_results.usable_runtime_data_by_test_case()
1152+
)
1153+
1154+
generated_tests = add_runtime_comments_to_generated_tests(
1155+
generated_tests, original_runtime_by_test, optimized_runtime_by_test
1156+
)
1157+
1158+
generated_tests_str = "\n#------------------------------------------------\n".join(
1159+
[test.generated_original_test_source for test in generated_tests.generated_tests]
1160+
)
1161+
if concolic_test_str:
1162+
generated_tests_str += "\n#------------------------------------------------\n" + concolic_test_str
1163+
1164+
existing_tests = existing_tests_source_for(
1165+
self.function_to_optimize.qualified_name_with_modules_from_root(self.project_root),
1166+
function_to_all_tests,
1167+
test_cfg=self.test_cfg,
1168+
original_runtimes_all=original_runtime_by_test,
1169+
optimized_runtimes_all=optimized_runtime_by_test,
1170+
)
1171+
new_explanation_raw_str = self.aiservice_client.get_new_explanation(
1172+
source_code=code_context.read_writable_code,
1173+
dependency_code=code_context.read_only_context_code,
1174+
trace_id=self.function_trace_id[:-4] + exp_type if self.experiment_id else self.function_trace_id,
1175+
optimized_code=best_optimization.candidate.source_code,
1176+
original_line_profiler_results=original_code_baseline.line_profile_results["str_out"],
1177+
optimized_line_profiler_results=best_optimization.line_profiler_test_results["str_out"],
1178+
original_code_runtime=humanize_runtime(original_code_baseline.runtime),
1179+
optimized_code_runtime=humanize_runtime(best_optimization.runtime),
1180+
speedup=f"{int(performance_gain(original_runtime_ns=original_code_baseline.runtime, optimized_runtime_ns=best_optimization.runtime) * 100)}%",
1181+
annotated_tests=generated_tests_str,
1182+
optimization_id=best_optimization.candidate.optimization_id,
1183+
original_explanation=best_optimization.candidate.explanation,
1184+
)
1185+
new_explanation = Explanation(
1186+
raw_explanation_message=new_explanation_raw_str or explanation.raw_explanation_message,
1187+
winning_behavior_test_results=explanation.winning_behavior_test_results,
1188+
winning_benchmarking_test_results=explanation.winning_benchmarking_test_results,
1189+
original_runtime_ns=explanation.original_runtime_ns,
1190+
best_runtime_ns=explanation.best_runtime_ns,
1191+
function_name=explanation.function_name,
1192+
file_path=explanation.file_path,
1193+
benchmark_details=explanation.benchmark_details,
1194+
)
1195+
data = {
1196+
"original_code": original_code_combined,
1197+
"new_code": new_code_combined,
1198+
"explanation": new_explanation,
1199+
"existing_tests_source": existing_tests,
1200+
"generated_original_test_source": generated_tests_str,
1201+
"function_trace_id": self.function_trace_id[:-4] + exp_type
1202+
if self.experiment_id
1203+
else self.function_trace_id,
1204+
"coverage_message": coverage_message,
1205+
}
1206+
1207+
if not self.args.no_pr and not self.args.staging_review:
1208+
data["git_remote"] = self.args.git_remote
1209+
check_create_pr(**data)
1210+
elif self.args.staging_review:
1211+
create_staging(**data)
1212+
else:
1213+
# Mark optimization success since no PR will be created
1214+
mark_optimization_success(
1215+
trace_id=self.function_trace_id, is_optimization_found=best_optimization is not None
1216+
)
1217+
1218+
if ((not self.args.no_pr) or not self.args.staging_review) and (
1219+
self.args.all or env_utils.get_pr_number() or (self.args.file and not self.args.function)
1220+
):
1221+
self.write_code_and_helpers(
1222+
self.function_to_optimize_source_code, original_helper_code, self.function_to_optimize.file_path
1223+
)
1224+
11711225
def establish_original_code_baseline(
11721226
self,
11731227
code_context: CodeOptimizationContext,

codeflash/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
# These version placeholders will be replaced by uv-dynamic-versioning during build.
2-
__version__ = "0.16.0"
2+
__version__ = "0.16.1"

0 commit comments

Comments
 (0)