DEV Community

Cover image for Building an AI-Powered Function Orchestrator: When AI Becomes Your Code Planner
Ravgeet Dhillon
Ravgeet Dhillon

Posted on • Originally published at ravgeet.in

Building an AI-Powered Function Orchestrator: When AI Becomes Your Code Planner

As developers, we constantly face a dilemma: how do we make our code flexible enough to handle natural human requests without hardcoding every possible scenario?

While working on various automation projects, I kept running into the same pattern—users would ask for something in plain English, like "Can you calculate 2+25-4^2? or "Process these files and send me a summary", but my code was rigid, expecting specific formats and predefined workflows.

The breakthrough came when I realized: What if AI handled the planning, and I just focused on building solid, reusable functions? Instead of trying to anticipate every user request, let AI interpret the intent and dynamically orchestrate my functions.

This post walks through building a mini AI agent framework that separates what you can do (functions) from how to do it (AI planning). The result? Code that adapts to human intent rather than forcing humans to adapt to your interface.

The Problem: Traditional Code vs. Human Intent

How many times have you written code that looks like this?

def complex_math_solver(expression):
    # Parse expression
    # Apply PEMDAS rules
    # Handle edge cases
    # Return result
    pass
Enter fullscreen mode Exit fullscreen mode

The problem? You're cramming parsing logic, mathematical rules, and execution into one monolithic function. What if we could separate these concerns entirely?

The Solution: AI as a Function Orchestrator

Instead of hardcoding business logic, what if we let AI handle the planning while we focus on building atomic, reusable functions?

Here's the architecture:

Step 1: Define Atomic Functions

First, we create simple, single-purpose functions that do one thing well. Think of these as your building blocks—each function is pure, testable, and completely independent. The key is keeping them atomic so AI can combine them in any order to solve complex problems.

def sum(a, b):
    return a + b

def multiply(a, b):
    return a * b

def power(a, b):
    return a ** b

# Function registry for dynamic execution
FUNCTIONS = {
    "sum": sum,
    "multiply": multiply,
    "power": power,
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Document Your Functions (For AI)

Next, we create clear documentation for each function. This isn't just good practice — it's essential for AI to understand what each function does and how to use it. Think of this as your function's "instruction manual" that AI reads to make smart planning decisions.

DOCS = {
    "sum": {
        "description": "Add two numbers",
        "args": {
            "a": {
                "type": "number",
                "description": "a float or int number",
            },
            "b": {
                "type": "number",
                "description": "a float or int number",
            },
        },
    },
    "multiply": {
        "description": "Multiply two numbers",
        "args": {
            "a": {
                "type": "number",
                "description": "a float or int number",
            },
            "b": {
                "type": "number",
                "description": "a float or int number",
            },
        },
    },
    "power": {
        "description": "Raise a to the power of b",
        "args": {
            "a": {
                "type": "number",
                "description": "a float or int number",
            },
            "b": {
                "type": "number",
                "description": "a float or int number",
            },
        },
    },
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Let AI Generate Execution Plans

Finally, we let AI do the heavy lifting — interpreting natural language requests and creating step-by-step execution plans. The AI uses your function documentation to understand what's possible, then figures out the optimal sequence to achieve the user's goal.

def get_action_plan(user_query):
    prompt = f"""
    You are an AI planner. Convert the user's request into a step-by-step plan.

    Available functions:
    {json.dumps(DOCS, indent=2)}

    User's query: "{user_query}"

    Return a JSON plan with ordered steps.
    """

    response = openai.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
        temperature=0
    )
    return json.loads(response.choices[0].message.content)
Enter fullscreen mode Exit fullscreen mode

Real Example: "Solve 2+2*5+4^2"

When a user sends this request to the system, it gets passed to OpenAI along with the function documentation. The AI analyzes the mathematical expression, applies PEMDAS rules, and returns a structured JSON plan that breaks down the calculation into atomic steps using the available functions.

Input: Natural language request

AI Output: Structured execution plan

{
  "steps": [
    { "function": "power", "args": { "a": 4, "b": 2 } },
    { "function": "multiply", "args": { "a": 2, "b": 5 } },
    { "function": "sum", "args": { "a": 2, "b": "<result_of_step_2>" } },
    {
      "function": "sum",
      "args": { "a": "<result_of_step_3>", "b": "<result_of_step_1>" }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Execution Output:

Step 1: power(4, 2) → 16
Step 2: multiply(2, 5) → 10
Step 3: sum(2, 10) → 12
Step 4: sum(12, 16) → 28

Final Result: 28
Enter fullscreen mode Exit fullscreen mode

The Magic: Dynamic Plan Execution

This function takes the AI-generated plan and executes it step by step. The key insight is result chaining—each step can reference outputs from previous steps using placeholders like <result_of_step_1>. The system automatically resolves these references, creating a dynamic pipeline where complex calculations emerge from simple function compositions.

def execute_plan(plan):
    results = {}
    for idx, step in enumerate(plan["steps"], start=1):
        func_name = step["function"]
        args = step["args"]

        # Replace placeholders with previous results
        for k, v in args.items():
            if isinstance(v, str) and v.startswith("<result_of_step_"):
                step_idx = int(v.split("_")[-1].replace(">", ""))
                args[k] = results[step_idx]

        # Execute function dynamically
        func = FUNCTIONS[func_name]
        result = func(**args)
        results[idx] = result

        print(f"Step {idx}: {func_name}({args}) → {result}")

    return results[len(results)]
Enter fullscreen mode Exit fullscreen mode

Why This Architecture Wins

Separation of Concerns

  • You write: Pure, testable functions

  • AI handles: Complex planning and orchestration

  • System manages: Execution flow and state

Human-in-the-Loop Safety

if "error" in plan:
    print("❌ Error in plan generation:")
    print(plan["error"]["message"])
    sys.exit(1)  # Safe exit on planning failures
Enter fullscreen mode Exit fullscreen mode

For example, if a user asks "Divide 10 by 0 and add 5", the AI can detect this is mathematically impossible and return an error response like:

{
  "error": {
    "message": "Cannot divide by zero - this operation is undefined in mathematics"
  }
}
Enter fullscreen mode Exit fullscreen mode

This prevents dangerous operations from executing and provides clear feedback to users.

Infinite Extensibility

Today, it's math functions:

FUNCTIONS = {
    "sum": sum,
    "multiply": multiply,
    "divide": divide
}
Enter fullscreen mode Exit fullscreen mode

Tomorrow it could be anything:

FUNCTIONS = {
    "read_file": read_file,
    "send_email": send_email,
    "query_database": query_db,
    "fetch_weather": weather_api,
    "analyze_sentiment": sentiment,
}
Enter fullscreen mode Exit fullscreen mode

Real-World Applications

File Operations

"Take all .txt files in /docs, extract headings, and create a summary document"

Required Functions: list_files(), read_file(), extract_headings(), create_document(), write_file()

API Orchestration

"Get weather for New York, if it's raining, send a Slack message to #general"

Required Functions: fetch_weather(), check_condition(), send_slack_message()

Data Pipeline

"Load sales.csv, calculate monthly averages, generate a chart, and email it to the team"

Required Functions: load_csv(), calculate_average(), group_by_month(), generate_chart(), send_email()

MCP Server Integration

"Query our customer database, analyze sentiment of recent feedback, and create a dashboard"

Required Functions: query_database(), analyze_sentiment(), aggregate_data(), create_dashboard(), save_report()

The Bigger Picture

This isn't just a math solver — it's a mini AI agent framework. The system provides:

  1. Function Library: Atomic, reusable components

  2. AI Planner: Intelligent request interpretation

  3. Execution Engine: Safe, traceable function orchestration

  4. Human Oversight: Approval and error handling

What's Next?

  1. Add more function types (file ops, API calls)

  2. Implement approval workflows (show plan before execution)

  3. Add function validation (type checking, parameter validation)

  4. Build a web interface (make it accessible to non-developers)

  5. Integrate with MCP servers (extend to complex business logic)

The bottom line: Stop hardcoding business logic. Let AI handle the planning, you handle the implementation. The result? More flexible, maintainable, and extensible code that adapts to user intent rather than forcing users to adapt to your interface.

Top comments (0)