> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://poly-ai.ferndocs.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://poly-ai.ferndocs.com/_mcp/server.

# Tutorial: Organizing your code

> PolyAcademy Level 3 – Structure functions, create utility files, and use imports to keep code maintainable.

**Level 3 – Lesson 1 of 5** – Structure your Agent Studio code so it stays manageable as your project grows.

> **Lesson**
>
> Level 3 · Advanced · 20 min

Organize Python functions into utility files, use standard imports for helpers, and use `conv.functions` for main functions – this keeps complex projects readable and maintainable.

This lesson assumes familiarity with Python and [functions](/academy/poly-academy/poly-academy-level-2/using-tools). Some examples reference [flows](/academy/poly-academy/poly-academy-level-3/flow-fundamentals), which are covered in the next lesson — you can follow along without understanding flows yet, or read Lesson 2 first if you prefer.

## How Agent Studio organizes code

Each entry on the **[Tools](/agent-studio/tools/overview)** page is a Python file, not just a single function. This means you can define multiple functions in one file – utility helpers, validation logic, formatters – alongside the main function that Agent Studio creates.

```
functions/
├── getOrder.py              # Main function + any helpers
├── formattingUtils.py       # Collection of utility functions
├── startOrderTrackingFlow.py
└── orderTrackingFlow/       # Flow functions (one folder per flow)
    ├── saveTrackingNumber.py
    └── trackingNumberConfirmed.py
```

**Key rules:**

* Each file has one **main function** with the same name as the file – this is the one Agent Studio validates and constrains (it must accept `conv` as the first argument)
* You can define additional functions above the main function – these are helpers that don't need to follow Agent Studio's signature rules
* Flow functions live in a subfolder named after the flow

## Utility files

You don't need 50 separate function entries for 50 helpers. Group related utilities in a single file:

```python
# formattingUtils.py

import re

def get_digits_only(text: str) -> str:
    """Extract only digits from text."""
    return re.sub(r'\D', '', text)

def is_valid_tracking_number(number: str) -> bool:
    """Validate format: exactly 3 letters followed by 5 digits."""
    return bool(re.match(r'^[A-Za-z]{3}\d{5}$', number.strip()))

def tracking_number_to_words(number: str) -> str:
    """Convert tracking number to TTS-friendly format."""
    letters = number[:3]
    digits = number[3:]
    digit_words = {
        '0': 'zero', '1': 'one', '2': 'two', '3': 'three',
        '4': 'four', '5': 'five', '6': 'six', '7': 'seven',
        '8': 'eight', '9': 'nine'
    }
    letter_part = ' '.join(letters.upper())
    first_two = ' '.join(digit_words[d] for d in digits[:2])
    last_three = ' '.join(digit_words[d] for d in digits[2:])
    return f"{letter_part} - {first_two} - {last_three}"

# Main function (required by Agent Studio, but not LLM-callable)
def formatting_utils(conv):
    pass
```

The main function at the bottom is required by Agent Studio but can be a no-op. Set the LLM description to indicate this file is a utility collection, not an LLM-callable function.

## Importing functions

There are two ways to use code from other files.

### Method 1: Python imports

Standard Python import syntax works for any function in any file:

```python
# Import a specific utility
from functions.formattingUtils import is_valid_tracking_number

# Import a main function from another file
from functions.getOrder import getOrder

# Import a flow function
from functions.orderTrackingFlow.trackingNumberConfirmed import tracking_number_confirmed
```

When using Python imports with main functions, you must pass `conv` (and `flow` for flow functions) as arguments:

```python
order = getOrder(conv, tracking_number)
```

### Method 2: `conv.functions` and `flow.functions`

The platform-supported method for calling main functions:

```python
# Call a global function
order = conv.functions.getOrder(tracking_number)

# Call a flow function
flow.functions.trackingNumberConfirmed()
```

**Advantages:**

* Autocomplete and type hints in the editor
* No need to pass `conv` or `flow` – they're handled automatically
* Officially supported by the platform

**Limitation:** You can only reference main functions this way. You cannot access utility helpers (like `is_valid_tracking_number`) through `conv.functions`.

#### Check your understanding

#### You have a utility function \`is_valid_email\` defined inside \`validationUtils.py\` (not the main function). How do you use it in another file?

* conv.functions.validationUtils.is_valid_email()
* from functions.validationUtils import is_valid_email
* flow\.functions.is_valid_email()
* import validationUtils; validationUtils.is_valid_email()

**Answer:** from functions.validationUtils import is_valid_email

conv.functions only works for main functions. To access a helper defined above the main function, use standard Python import syntax: from functions.fileName import functionName.

> **Try it**
>
> To import a helper function called is_valid_email from validationUtils.py, write: from functions.\_\_\_\_\_\_\_\_ import is_valid_email
>
> **Answer:** `validationUtils`

## When to use which method

| Scenario                                      | Method                                      |
| --------------------------------------------- | ------------------------------------------- |
| Calling a main function from another file     | `conv.functions.functionName()`             |
| Calling a utility/helper from a utils file    | `from functions.fileName import helperName` |
| Calling a flow function from outside the flow | `flow.functions.functionName()`             |
| Need autocomplete and type hints              | `conv.functions` / `flow.functions`         |

> **Warning**
>
> Python imports in Agent Studio may show squiggly underlines in the editor ("import could not be resolved"). These are false positives – the imports work at runtime.

## Practical example: order tracking

Here's how a real flow function uses both import methods:

```python
from functions.formattingUtils import is_valid_tracking_number

def save_tracking_number(conv, flow, tracking_number: str) -> str:
    # Use imported utility for validation
    if not is_valid_tracking_number(tracking_number):
        return "That doesn't look right. The tracking number should be 3 letters followed by 5 digits. Ask the user to try again."

    # Use conv.functions for the main getOrder function
    order = conv.functions.getOrder(tracking_number)

    if order is None:
        return f"No order found for {tracking_number}. Ask the user to double-check."

    conv.state["order"] = order
    conv.state["tracking_number"] = tracking_number
    flow.goto_step("report_status")
    return f"Order found: {order}"
```

## Function types and their roles

As your project grows, distinguishing between different function types becomes critical. Here's a reference for how to think about each type:

| Function type                               | LLM-callable? | Description                                                                                                              |
| ------------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------ |
| **Flow functions**                          | Yes           | Local to a single flow. Keep them thin – they orchestrate step logic and delegate complex work to global functions.      |
| **Global functions (KB / general-purpose)** | Yes           | Reusable across flows and KB topics. Includes flow start functions, transfer calls, and other shared callable functions. |
| **API handlers**                            | No            | Handle outbound calls to external services. Never call directly from prompts.                                            |
| **Services**                                | No            | Business rules and orchestration between APIs, models, and utilities.                                                    |
| **Utility files**                           | No            | Simple, deterministic helpers (string formatting, date conversion). No side effects.                                     |
| **Constants**                               | No            | Shared constants, enums, and configuration values. No logic.                                                             |

> **Warning**
>
> Never import flow functions into other flow functions or global functions. This creates "invisible dependencies" that are extremely difficult to debug – if a flow function is no longer referenced in any prompt, it disappears from the UI but may still be imported elsewhere.

## Separation of concerns

Follow a **"thin flows, fat services"** approach:

* Keep LLM-facing flow functions focused on: collecting user input, writing state, and transitioning steps
* Put deterministic logic (validation, comparisons, branching rules, API formatting) in helper modules
* Do not import LLM-facing functions into unrelated helpers as a way to reuse business logic – reuse helper functions instead

## Best practices

#### Group related helpers

Don't create one function entry per helper. Group formatting utils, validation utils, and API utils into their own files.

#### Define once, use everywhere

If a regex pattern or validation rule is needed in multiple places, define it in a utility file and import it. Update once, fix everywhere.

#### Use conv.functions where possible

For main functions, prefer the platform-supported `conv.functions` method. Reserve Python imports for utility helpers.

#### Keep functions focused

Each main function should do one thing. Complex logic should be broken into helpers that are composed together.

## Common pitfalls

| Pitfall                                   | Solution                                                                                                             |
| ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| Business logic embedded in flow functions | Move to global services or utils. Keep flow functions focused on orchestration.                                      |
| "Invisible functions" that can't be found | Use clear naming. Centralise reusable logic in global functions. Never import logic from flow functions into others. |
| Duplicated logic across functions         | Centralise repeated patterns into a single global function or util. Update in one place.                             |
| Bloated global function files             | Simulate folders by namespacing files by concern (e.g., `tracking_utils`, `tracking_api`, `tracking_service`).       |
| Circular dependencies                     | Follow the function hierarchy: flow functions → global functions → services → utils. Never import upward.            |

## Try it yourself

#### Challenge: Refactor a monolithic function

You have a single function that validates a tracking number, calls an API, formats the result for TTS, and returns it. It's 80 lines long.

Plan how you would split this into:

1. A validation utility
2. An API wrapper
3. A TTS formatting utility
4. A main function that composes them

#### Hint

Create a `trackingUtils.py` with the validation and formatting helpers. Create a `getOrder.py` for the API call. The main flow function imports from both and orchestrates the logic.

#### Example structure

**trackingUtils.py:**

* `is_valid_tracking_number(number)` – regex validation
* `tracking_number_to_words(number)` – TTS formatting
* `tracking_utils(conv)` – no-op main function

**getOrder.py:**

* `getOrder(conv, tracking_number)` – API call, returns order dict or None

**Flow function (saveTrackingNumber):**

```python
from functions.trackingUtils import is_valid_tracking_number
from functions.trackingUtils import tracking_number_to_words

def save_tracking_number(conv, flow, tracking_number: str) -> str:
    if not is_valid_tracking_number(tracking_number):
        return "Invalid format. Ask the user to try again."

    order = conv.functions.getOrder(tracking_number)
    if order is None:
        return "Order not found."

    readable = tracking_number_to_words(tracking_number)
    conv.state["order"] = order
    flow.goto_step("confirm")
    return f"Found order for {readable}: {order}"
```

#### Check your understanding

#### You need to update a tracking number format from '3 letters + 5 digits' to '4 letters + 5 digits'. The validation is used in 5 different flow functions. What's the best approach?

* Update the regex in all 5 flow functions
* Update it in the one utility file where the validation function is defined
* Create a new validation function and gradually migrate
* Use conv.functions to centralize the validation

**Answer:** Update it in the one utility file where the validation function is defined

If validation is defined once in a utility file and imported everywhere, you update it in one place. This is the core benefit of modular code – single source of truth.

#### [← Back to PolyAcademy](/academy/poly-academy/home)

Level overview

#### [Next: Flow fundamentals →](/academy/poly-academy/poly-academy-level-3/flow-fundamentals)

Lesson 2 of 5