Skip to content

Commit f6cd2bc

Browse files
Asaif AliAsaif Ali
authored andcommitted
Conversion Updates
1 parent 50cc66c commit f6cd2bc

9 files changed

Lines changed: 319 additions & 139 deletions

File tree

agent_service/agent_orchestrator.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,8 @@ def _run_streaming():
268268
store_events=not lightweight,
269269
)
270270
final_output = None
271+
completion_output = None
272+
workflow_error = None
271273
# Only these outer workflow names may update the seven-stage UI.
272274
# Nested scanner/planner events must not be mistaken for later
273275
# stages, otherwise the progress bar and chips diverge.
@@ -296,9 +298,24 @@ def _run_streaming():
296298
continue
297299
plan[idx]["status"] = "complete"
298300
publish_progress("workflow", int(((idx + 1) / len(plan)) * 100), f"Completed: {step_name or plan[idx]['name']}", plan=plan)
301+
elif "workflowcompleted" in event_name or "workflow_completed" in event_name:
302+
completion_output = event
303+
final_output = event
304+
elif "workflowerror" in event_name or "workflow_error" in event_name:
305+
workflow_error = event
299306
elif hasattr(event, "content") and getattr(event, "content", None):
300307
final_output = event
301-
return getattr(self.workflow, "run_response", None) or final_output
308+
if workflow_error is not None and completion_output is None:
309+
error_message = (
310+
getattr(workflow_error, "content", None)
311+
or getattr(workflow_error, "error", None)
312+
or "Agno workflow execution failed"
313+
)
314+
raise RuntimeError(str(error_message))
315+
# Do not read self.workflow.run_response here: the module-level
316+
# workflow object can serve concurrent requests and run_response is
317+
# mutable shared state. The completed event is request-local.
318+
return completion_output or final_output
302319

303320
result = await asyncio.to_thread(_run_streaming)
304321
for item in plan:

agent_service/app/application/agents/conversion/conversion_tools.py

Lines changed: 106 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@
2020
from app.application.agents.conversion.conversion_agent import conversion_agent
2121
from app.infrastructure.utils.Agent_helpers.conversion_helper import _conversion_event_helper
2222
from app.infrastructure.utils.language_adapters import adapter_for_file, get_adapter, ExecutionContract
23+
from app.infrastructure.utils.conversion_recipe_engine import (
24+
RecipeContext,
25+
build_conversion_rules,
26+
sanitize_generated_code,
27+
validate_target_fragment,
28+
format_validation_feedback,
29+
)
2330
from app.domain.interfaces.i_folder_structure_goals_repository import (
2431
IFolderStructureGoalsRepository,
2532
)
@@ -272,141 +279,135 @@ def _normalize_target_file_path(target_file: str, source_file_path: str, target_
272279

273280

274281
def generate_new_code(step_input: StepInput) -> dict:
275-
"""Fetches meta data for a given symbol by filtering KB"""
276-
# print(step_input, "+++")
277-
prev_output = step_input.get_step_content("get_source_code")
278-
source_code = prev_output.get("raw_code")
279-
source_file_path = prev_output.get("file_path")
280-
symbol_id = prev_output.get("symbol_id")
281-
symbol_hash = prev_output.get("symbol_hash")
282-
workflow_input = step_input.input
282+
"""Generate one planned target symbol with deterministic guardrails."""
283+
prev_output = step_input.get_step_content("get_source_code") or {}
284+
source_code = prev_output.get("raw_code") or ""
285+
source_file_path = prev_output.get("file_path") or ""
286+
symbol_id = prev_output.get("symbol_id") or ""
287+
symbol_hash = prev_output.get("symbol_hash") or ""
288+
workflow_input = step_input.input or {}
283289
runtime_tech = _get_runtime_tech_context()
284-
target_file_path = _normalize_target_file_path(
285-
workflow_input.get("target_file"),
286-
source_file_path,
287-
runtime_tech.get("language", ""),
288-
)
289-
target_symbol_name = workflow_input.get("target_symbol_name")
290-
dependencies_output = step_input.get_step_content("get_dependencies")
291-
file_deps = dependencies_output.get("file_dependencies")
292-
deps = dependencies_output.get("dependent_code_snippets")
293-
file_deps_content = file_deps if isinstance(file_deps, str) else "\n".join(file_deps)
294-
deps_content = "\n".join(deps)
295-
tech_summary = (
296-
f"Target Language : {runtime_tech['language']}\n"
297-
f"Framework : {runtime_tech['framework'] or 'None'}\n"
298-
f"Architecture : {runtime_tech['architecture'] or 'Unknown'}\n"
290+
target_language = runtime_tech.get("language", "") or ""
291+
target_file_path = _normalize_target_file_path(workflow_input.get("target_file"), source_file_path, target_language)
292+
target_symbol_name = workflow_input.get("target_symbol_name") or "migrated_symbol"
293+
294+
dependencies_output = step_input.get_step_content("get_dependencies") or {}
295+
file_deps = dependencies_output.get("file_dependencies") or []
296+
deps = dependencies_output.get("dependent_code_snippets") or []
297+
file_deps_content = file_deps if isinstance(file_deps, str) else "\n".join(str(x) for x in file_deps)
298+
deps_content = deps if isinstance(deps, str) else "\n".join(str(x) for x in deps)
299+
tech_summary = (f"Target Language : {target_language}\n" f"Framework : {runtime_tech.get('framework') or 'None'}\n" f"Architecture : {runtime_tech.get('architecture') or 'Unknown'}\n")
300+
301+
recipe_context = RecipeContext(
302+
source_language=(Path(str(source_file_path)).suffix.lstrip(".") or "unknown"),
303+
target_language=target_language,
304+
target_framework=runtime_tech.get("framework", "") or "",
305+
target_architecture=runtime_tech.get("architecture", "") or "",
299306
)
307+
conversion_rules = build_conversion_rules(recipe_context)
300308

301-
# Resolve executable behavior through the language adapter registry. The
302-
# conversion engine never branches on source/target language itself.
303309
execution_contract = None
304310
entrypoint_context = ""
305311
try:
306312
source_adapter = adapter_for_file(Path(str(source_file_path)))
307313
if source_adapter is not None:
308-
execution_contract = source_adapter.detect_execution_contract(
309-
Path(str(source_file_path)), target_symbol_name
310-
)
314+
execution_contract = source_adapter.detect_execution_contract(Path(str(source_file_path)), target_symbol_name)
311315
if execution_contract and execution_contract.executable:
312-
target_adapter = get_adapter(runtime_tech.get("language", ""))
313-
target_name = target_adapter.display_name if target_adapter else runtime_tech.get("language", "target")
316+
target_adapter = get_adapter(target_language)
317+
target_name = target_adapter.display_name if target_adapter else (target_language or "target")
314318
entrypoint_context = (
315-
"SOURCE EXECUTION CONTRACT: the source module executes the translated "
316-
f"symbol '{execution_contract.entry_symbol}'. Preserve that runtime behavior "
317-
f"using the idiomatic executable entry-point convention of the target language ({target_name}). "
318-
"Do not add an entry point when the source is clearly a reusable library/module."
319+
"SOURCE EXECUTION CONTRACT: preserve execution of "
320+
f"'{execution_contract.entry_symbol}' using the idiomatic entry-point convention "
321+
f"of {target_name}. Do not add an entry point for a reusable library/module."
319322
)
320323
except Exception:
321-
execution_contract = None
322-
entrypoint_context = ""
324+
logger.exception("Failed to determine source execution contract for %s", source_file_path)
323325

324326
try:
325327
user = current_user.get()
326328
migration_name = migration_name_ctx.get(None)
327-
if migration_name not in _conversion_step_start_sent:
328-
_conversion_step_start_sent.add(migration_name)
329-
_conversion_event_helper.send_step_start(
330-
AgentEventMessages.MIGRATION_PROGRESS_STEP_ID,
331-
AgentEventMessages.MIGRATION_PROGRESS_STEP_NAME,
332-
user,
333-
MigrationEvent.MIGRATION_PROGRESS,
334-
)
335-
_conversion_event_helper.send_step_description(
336-
AgentEventMessages.MIGRATION_PROGRESS_STEP_ID,
337-
AgentEventMessages.MIGRATION_PROGRESS_STEP_NAME,
338-
user,
339-
MigrationEvent.MIGRATION_PROGRESS,
340-
)
341-
_conversion_event_helper.send_step_log(
342-
AgentEventMessages.MIGRATION_PROGRESS_STEP_ID,
343-
f"Generating converted code for: {target_symbol_name}{target_file_path}",
344-
user,
345-
MigrationEvent.MIGRATION_PROGRESS,
346-
)
329+
step_key = f"{getattr(user, 'id', 'unknown')}:{migration_name}"
330+
if step_key not in _conversion_step_start_sent:
331+
_conversion_step_start_sent.add(step_key)
332+
_conversion_event_helper.send_step_start(AgentEventMessages.MIGRATION_PROGRESS_STEP_ID, AgentEventMessages.MIGRATION_PROGRESS_STEP_NAME, user, MigrationEvent.MIGRATION_PROGRESS)
333+
_conversion_event_helper.send_step_description(AgentEventMessages.MIGRATION_PROGRESS_STEP_ID, AgentEventMessages.MIGRATION_PROGRESS_STEP_NAME, user, MigrationEvent.MIGRATION_PROGRESS)
334+
_conversion_event_helper.send_step_log(AgentEventMessages.MIGRATION_PROGRESS_STEP_ID, f"Generating converted code for: {target_symbol_name}{target_file_path}", user, MigrationEvent.MIGRATION_PROGRESS)
347335
except Exception:
348336
pass
349337

350-
resp = conversion_agent.run(input=f"""
351-
Convert the given source symbol into equivalent target language code while making
352-
sure any dependencies are resolved/imported correctly and any similar
353-
code snippets (if given) are used as coding/formatting guidelines.
354-
SOURCE CODE: {source_code}
355-
SYMBOL ID : {symbol_id}
356-
SYMBOL HASH : {symbol_hash}
357-
SOURCE SYMBOL FILE PATH : {source_file_path}
358-
TARGET TECH SUMMARY: {tech_summary}
359-
EXISTING DEPENDENCIES TO USE: {deps_content}
360-
EXISTING SIMILAR CODE SNIPPETS: []
361-
PRE-APPROVED LIBRARIES (from dependency file — use these for imports, do not invent others): {file_deps_content}
362-
{entrypoint_context}
363-
When writing the target code, make sure to correctly use the exisisting dependencies alongwith their correct import paths/naming conventions.
364-
Return just the final raw code, no extra explanations & no extra formatting such as ```python, ```, etc.
365-
"""
366-
)
338+
prompt = f"""
339+
Convert the given source symbol into equivalent target-language code while resolving dependencies and preserving externally observable behavior.
367340
368-
raw_response = (resp.content or "").strip()
369-
target_code = _extract_clean_code(raw_response)
341+
{conversion_rules}
342+
343+
SOURCE CODE:
344+
{source_code}
345+
346+
SYMBOL ID: {symbol_id}
347+
SYMBOL HASH: {symbol_hash}
348+
SOURCE SYMBOL FILE PATH: {source_file_path}
349+
TARGET FILE PATH: {target_file_path}
350+
TARGET SYMBOL NAME: {target_symbol_name}
351+
352+
DEPENDENT CONVERTED CODE:
353+
{deps_content or 'None'}
354+
355+
PRE-APPROVED LIBRARIES (use these for imports; do not invent replacements):
356+
{file_deps_content or 'None'}
357+
358+
{entrypoint_context}
359+
360+
Return ONLY the final raw target code. Do not include markdown fences, explanations, placeholders, TODOs, or pseudocode.
361+
"""
362+
resp = conversion_agent.run(input=prompt)
363+
raw_response = (getattr(resp, "content", "") or "").strip()
364+
target_code = sanitize_generated_code(_extract_clean_code(raw_response))
370365
lowered = target_code.strip().lower()
371-
if (
372-
not target_code.strip()
373-
or lowered in {"unknown model error", "model error", "rate limit error", "rate limited"}
374-
or "rate limit reached" in lowered
375-
or "too many requests" in lowered
376-
):
377-
raise RuntimeError(
378-
"LLM code generation failed before producing source code. "
379-
"The LLM gateway may be rate-limited; retry after the gateway cooldown."
380-
)
381-
if target_code.startswith("```"):
382-
target_code = target_code.split("\n", 1)[1] if "\n" in target_code else ""
383-
if target_code.endswith("```"):
384-
target_code = target_code.rsplit("\n", 1)[0] if "\n" in target_code else target_code[:-3]
366+
if (not target_code.strip() or lowered in {"unknown model error", "model error", "rate limit error", "rate limited"} or "rate limit reached" in lowered or "too many requests" in lowered):
367+
raise RuntimeError("LLM code generation failed before producing source code. The LLM gateway may be rate-limited; retry after the gateway cooldown.")
385368

386-
target_adapter = get_adapter(runtime_tech.get("language", ""))
387-
if target_adapter is not None and execution_contract is not None:
388-
target_code = target_adapter.ensure_entrypoint(
389-
target_code, execution_contract, target_symbol_name
390-
)
369+
fragment_validation = validate_target_fragment(target_code, target_language)
370+
repair_attempts = max(0, int(os.getenv("CONVERSION_FRAGMENT_REPAIR_ATTEMPTS", "1")))
371+
for _ in range(repair_attempts):
372+
if fragment_validation.valid or not fragment_validation.available:
373+
break
374+
repair_prompt = f"""
375+
Repair ONLY the generated target-code fragment below. Do not translate it again from scratch. Preserve its intended behavior and public interface.
376+
Return only corrected raw target code.
391377
378+
{conversion_rules}
379+
380+
VALIDATION FEEDBACK:
381+
{format_validation_feedback(fragment_validation)}
382+
383+
SOURCE CODE:
384+
{source_code}
385+
386+
CURRENT TARGET CODE:
387+
{target_code}
388+
389+
PRE-APPROVED LIBRARIES:
390+
{file_deps_content or 'None'}
391+
"""
392+
repair_resp = conversion_agent.run(input=repair_prompt)
393+
target_code = sanitize_generated_code(_extract_clean_code((getattr(repair_resp, "content", "") or "").strip()))
394+
track_tokens(repair_resp, source="conversion:fragment_repair")
395+
fragment_validation = validate_target_fragment(target_code, target_language)
396+
if not fragment_validation.valid and fragment_validation.available:
397+
raise RuntimeError("Generated target-code fragment failed syntax validation after " + f"{repair_attempts} repair attempt(s): {format_validation_feedback(fragment_validation)}")
398+
399+
target_adapter = get_adapter(target_language)
400+
if target_adapter is not None and execution_contract is not None:
401+
target_code = target_adapter.ensure_entrypoint(target_code, execution_contract, target_symbol_name)
392402
track_tokens(resp, source="conversion:symbol_convert")
393403

394404
try:
395405
user = current_user.get()
396-
_conversion_event_helper.send_step_result(
397-
AgentEventMessages.MIGRATION_PROGRESS_STEP_ID,
398-
AgentEventMessages.MIGRATION_PROGRESS_STEP_NAME,
399-
f"Code generated for {target_symbol_name}",
400-
user,
401-
MigrationEvent.MIGRATION_PROGRESS,
402-
)
406+
_conversion_event_helper.send_step_result(AgentEventMessages.MIGRATION_PROGRESS_STEP_ID, AgentEventMessages.MIGRATION_PROGRESS_STEP_NAME, f"Code generated for {target_symbol_name}", user, MigrationEvent.MIGRATION_PROGRESS)
403407
except Exception:
404408
pass
405409
_send_plan_step_progress(4, workflow_input.get("plan_id", ""))
406-
return StepOutput(content={
407-
"target_code": target_code,
408-
"symbol_hash": symbol_hash,
409-
})
410+
return StepOutput(content={"target_code": target_code, "symbol_hash": symbol_hash, "fragment_validation": {"valid": fragment_validation.valid, "available": fragment_validation.available, "validator": fragment_validation.validator, "diagnostics": list(fragment_validation.diagnostics)}})
410411

411412
def save_code_to_kb(step_input: StepInput) -> StepOutput:
412413
"""Save converted code to file and KB, track part-wise completion."""

0 commit comments

Comments
 (0)