import ast import asyncio import json import os import sys from dataclasses import dataclass from io import StringIO from dotenv import load_dotenv from typing_extensions import Never from agent_framework import Agent, Executor, WorkflowBuilder, WorkflowContext, handler from agent_framework.openai import OpenAIChatClient load_dotenv() _MATH_EXAMPLES = [ { "question": "What is 37593 * 67?", "code": '{\n "code": "print(37593 * 67)"\n}', }, { "question": "What is the value of x in the equation 2x + 3 = 11?", "code": '{\n "code": "print((11-3)/2)"\n}', }, { "question": "How many of the integers between 0 and 99 inclusive are divisible by 8?", "code": '{\n "code": "count = 0\\nfor i in range(100):\\n ' 'if i % 8 == 0:\\n count += 1\\nprint(count)"\n}', }, ] _SYSTEM_PROMPT = ( "I want you to act as a Math expert specializing in Algebra, Geometry, and Calculus. " "Given the question, develop python code to model the user's question.\n" "The python code will print the result at the end.\n" "Please generate executable python code, your reply will be in JSON format, something like:\n" '{\n "code": "print(1+1)"\n}' ) _USER_TEMPLATE = """\ This a set of examples including question and the final answer: {examples} Now come to the real task, make sure return a valid json. The json should \ contain a key named "code" and the value is the python code. For example: {{ "code": "print(1+1)" }} QUESTION: {question} CODE:""" def _format_examples() -> str: parts = [] for ex in _MATH_EXAMPLES: parts.append(f"QUESTION: {ex['question']}\nCODE:\n{ex['code']}\n") return "\n".join(parts) def _infinite_loop_check(code_snippet): tree = ast.parse(code_snippet) for node in ast.walk(tree): if isinstance(node, ast.While): if not node.orelse: return True return False def _syntax_error_check(code_snippet): try: ast.parse(code_snippet) except SyntaxError: return True return False def _error_fix(code_snippet): tree = ast.parse(code_snippet) for node in ast.walk(tree): if isinstance(node, ast.While): if not node.orelse: node.orelse = [ast.Pass()] return ast.unparse(tree) @dataclass class MathResult: code: str answer: str class CodeGenExecutor(Executor): def __init__(self, **kwargs): super().__init__(**kwargs) client = OpenAIChatClient( azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"], model=os.environ["AZURE_OPENAI_DEPLOYMENT"], api_key=os.environ["AZURE_OPENAI_API_KEY"], ) self._agent = Agent( client=client, name="MathCodeGen", instructions=_SYSTEM_PROMPT, ) @handler async def generate(self, question: str, ctx: WorkflowContext[str]) -> None: user_msg = _USER_TEMPLATE.format( examples=_format_examples(), question=question ) response = await self._agent.run(user_msg) await ctx.send_message(response.text) class CodeRefineExecutor(Executor): @handler async def refine(self, original_code: str, ctx: WorkflowContext[str]) -> None: try: code = json.loads(original_code)["code"] fixed = code if _infinite_loop_check(code): fixed = _error_fix(code) if _syntax_error_check(fixed): fixed = _error_fix(fixed) await ctx.send_message(fixed) except json.JSONDecodeError: await ctx.send_message("JSONDecodeError") except Exception as e: await ctx.send_message("Unknown Error:" + str(e)) class CodeExecutionExecutor(Executor): @handler async def run_code(self, code_snippet: str, ctx: WorkflowContext[Never, MathResult]) -> None: if code_snippet == "JSONDecodeError" or code_snippet.startswith("Unknown Error:"): await ctx.yield_output(MathResult(code=code_snippet, answer=code_snippet)) return old_stdout = sys.stdout redirected_output = sys.stdout = StringIO() try: exec(code_snippet.lstrip()) # noqa: S102 except Exception as e: sys.stdout = old_stdout await ctx.yield_output(MathResult(code=code_snippet, answer=str(e))) return sys.stdout = old_stdout answer = redirected_output.getvalue().strip() await ctx.yield_output(MathResult(code=code_snippet, answer=answer)) def create_workflow(): """Create a fresh workflow instance. MAF workflows do not support concurrent execution, so each concurrent caller needs its own workflow instance. """ _code_gen = CodeGenExecutor(id="code_gen") _code_refine = CodeRefineExecutor(id="code_refine") _code_exec = CodeExecutionExecutor(id="final_code_execution") return ( WorkflowBuilder(name="MathsToCodeWorkflow", start_executor=_code_gen) .add_edge(_code_gen, _code_refine) .add_edge(_code_refine, _code_exec) .build() ) async def main(): workflow = create_workflow() result = await workflow.run( "If a rectangle has a length of 10 and width of 5, what is the area?" ) output = result.get_outputs()[0] print(f"Code: {output.code}") print(f"Answer: {output.answer}") if __name__ == "__main__": asyncio.run(main())