> ## Documentation Index
> Fetch the complete documentation index at: https://docs.poly.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Tutorial: Organizing your code

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

export const LessonMeta = ({level, difficulty, time}) => {
  const levelConfig = {
    1: {
      badge: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
      label: 'Level 1'
    },
    2: {
      badge: 'bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-200',
      label: 'Level 2'
    },
    3: {
      badge: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200',
      label: 'Level 3'
    }
  };
  const difficultyConfig = {
    Beginner: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
    Intermediate: 'bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-200',
    Advanced: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'
  };
  const lvl = levelConfig[level] || levelConfig[1];
  const diffColor = difficultyConfig[difficulty] || difficultyConfig['Beginner'];
  return <div className="flex flex-wrap items-center gap-2 my-4 not-prose">
      <span className={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold ${lvl.badge}`}>
        {lvl.label}
      </span>
      <span className={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold ${diffColor}`}>
        {difficulty}
      </span>
      {time && <span className="inline-flex items-center gap-1 text-xs text-gray-500 dark:text-gray-400">
          <svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
            <path strokeLinecap="round" strokeLinejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" />
          </svg>
          {time}
        </span>}
    </div>;
};

export const ProgressTracker = ({lessonNum, totalLessons, level}) => {
  const [checked, setChecked] = useState(false);
  return <div onClick={() => setChecked(prev => !prev)} className={checked ? 'flex items-center gap-3 p-4 rounded-lg border-2 border-green-600 bg-green-50 dark:bg-green-950 cursor-pointer select-none transition-all' : 'flex items-center gap-3 p-4 rounded-lg border-2 border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 cursor-pointer select-none transition-all'}>
      <div className={checked ? 'w-5 h-5 rounded border-2 border-green-600 bg-green-600 flex items-center justify-center shrink-0 transition-all' : 'w-5 h-5 rounded border-2 border-gray-400 dark:border-gray-500 bg-white dark:bg-gray-800 flex items-center justify-center shrink-0 transition-all'}>
        {checked ? <svg width="10" height="8" viewBox="0 0 10 8" fill="none">
            <path d="M1 4L3.5 6.5L9 1" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
          </svg> : null}
      </div>
      <div>
        <div className={checked ? 'font-semibold text-sm text-green-700 dark:text-green-300' : 'font-semibold text-sm text-gray-700 dark:text-gray-200'}>
          {checked ? 'Lesson complete' : 'Mark lesson complete'}
        </div>
        {lessonNum && totalLessons ? <div className="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
            {level ? level + ' - ' : ''}Lesson {lessonNum} of {totalLessons}
          </div> : null}
      </div>
    </div>;
};

export const FillBlank = ({prompt, answer, hint, explanation}) => {
  const [value, setValue] = useState('');
  const [submitted, setSubmitted] = useState(false);
  const normalize = s => s.trim().toLowerCase().replace(/[^a-z0-9_]/g, '');
  const answers = Array.isArray(answer) ? answer : [answer];
  const isCorrect = answers.some(a => normalize(value) === normalize(a));
  const handleSubmit = e => {
    e.preventDefault();
    if (value.trim()) setSubmitted(true);
  };
  const handleReset = () => {
    setValue('');
    setSubmitted(false);
  };
  return <div className="my-6">
      <p className="mt-0 mb-3 text-sm font-semibold leading-relaxed text-gray-900 dark:text-gray-100">
        {prompt}
      </p>
      <form onSubmit={handleSubmit} className="flex flex-col gap-2.5">
        <div className="flex gap-2">
          <input type="text" value={value} onChange={e => {
    setValue(e.target.value);
    setSubmitted(false);
  }} placeholder={hint || "Type your answer…"} className="flex-1 rounded-xl border py-2.5 px-4 text-sm font-mono border-gray-200 bg-white text-gray-900 placeholder-gray-400 outline-none focus:border-gray-400 focus:ring-2 focus:ring-gray-200 transition-all duration-150 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-600 dark:focus:border-gray-500 dark:focus:ring-gray-700" />
          <button type="submit" className="rounded-xl border py-2.5 px-5 text-sm font-medium transition-all duration-150 border-gray-800 bg-gray-800 text-white hover:bg-gray-700 hover:border-gray-700 dark:border-gray-200 dark:bg-gray-200 dark:text-gray-900 dark:hover:bg-white">
            Check
          </button>
        </div>
        {submitted ? <div className={`py-3 pl-4 pr-3.5 rounded-r-xl text-sm leading-relaxed border-l-4 ${isCorrect ? 'border-green-500 bg-green-50 dark:bg-green-900 dark:border-green-500' : 'border-red-500 bg-red-50 dark:bg-red-900 dark:border-red-500'}`}>
            {isCorrect ? <>
                <span className={`font-semibold !text-green-800 dark:!text-green-200`}>Correct.</span>{' '}
                <span className="!text-gray-700 dark:!text-gray-300">{explanation}</span>
              </> : <>
                <span className="font-semibold !text-red-800 dark:!text-red-200">Not quite.</span>{' '}
                <span className="!text-gray-700 dark:!text-gray-300">The answer is <code className="!text-gray-800 dark:!text-gray-200">{answers[0]}</code>. {explanation}</span>
              </>}
          </div> : null}
      </form>
      {submitted ? <button type="button" onClick={handleReset} className="mt-2 text-xs text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 underline underline-offset-2 cursor-pointer transition-colors duration-150">
          Try again
        </button> : null}
    </div>;
};

export const Quiz = ({questions = []}) => {
  const [selected, setSelected] = useState({});
  const [resetCount, setResetCount] = useState(0);
  const letters = ['A', 'B', 'C', 'D'];
  const handleSelect = (qIdx, optIdx) => {
    if (selected[qIdx] !== undefined) return;
    setSelected(prev => ({
      ...prev,
      [qIdx]: optIdx
    }));
  };
  const handleReset = () => {
    setSelected({});
    setResetCount(c => c + 1);
  };
  if (!questions?.length) return null;
  const getOptionClasses = ({hasAnswered, isThisCorrect, isThisSelected}) => {
    if (!hasAnswered) {
      return {
        btn: 'flex w-full items-center gap-3 py-2.5 px-4 rounded-xl text-sm leading-normal transition-all duration-150 text-left border cursor-pointer border-gray-200 bg-white text-gray-700 hover:border-gray-300 hover:bg-gray-50 hover:shadow-sm dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200 dark:hover:border-gray-500 dark:hover:bg-gray-700',
        badge: 'w-6 h-6 rounded-full text-xs font-bold flex items-center justify-center shrink-0 leading-none transition-all duration-150 bg-gray-100 text-gray-500 dark:bg-gray-700 dark:text-gray-300',
        icon: null
      };
    }
    if (isThisCorrect) {
      return {
        btn: 'flex w-full items-center gap-3 py-2.5 px-4 rounded-xl text-sm leading-normal transition-all duration-150 text-left border cursor-default border-green-400 bg-green-50 text-green-900 font-medium dark:border-green-500 dark:bg-green-950 dark:text-green-100',
        badge: 'w-6 h-6 rounded-full text-xs font-bold flex items-center justify-center shrink-0 leading-none transition-all duration-150 bg-green-500 text-white dark:bg-green-500',
        icon: <svg className="shrink-0 w-4 h-4 text-green-500 dark:text-green-400 ml-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
            <path strokeLinecap="round" strokeLinejoin="round" d="M4.5 12.75l6 6 9-13.5" />
          </svg>
      };
    }
    if (isThisSelected) {
      return {
        btn: 'flex w-full items-center gap-3 py-2.5 px-4 rounded-xl text-sm leading-normal transition-all duration-150 text-left border cursor-default border-red-400 bg-red-50 text-red-900 dark:border-red-500 dark:bg-red-950 dark:text-red-100',
        badge: 'w-6 h-6 rounded-full text-xs font-bold flex items-center justify-center shrink-0 leading-none transition-all duration-150 bg-red-500 text-white dark:bg-red-500',
        icon: <svg className="shrink-0 w-4 h-4 text-red-400 dark:text-red-400 ml-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
            <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
          </svg>
      };
    }
    return {
      btn: 'flex w-full items-center gap-3 py-2.5 px-4 rounded-xl text-sm leading-normal transition-all duration-150 text-left border cursor-default border-gray-100 bg-white text-gray-400 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-500',
      badge: 'w-6 h-6 rounded-full text-xs font-bold flex items-center justify-center shrink-0 leading-none transition-all duration-150 bg-gray-100 text-gray-500 dark:bg-gray-700 dark:text-gray-500',
      icon: null
    };
  };
  return <div key={resetCount} className="my-6">
      {questions.map((q, qIdx) => {
    const answer = selected[qIdx];
    const hasAnswered = answer !== undefined;
    const isCorrect = answer === q.correct;
    return <div key={String(qIdx)} className="mb-8">
            <p className="flex items-start gap-2.5 font-semibold text-sm mb-3 mt-0 leading-relaxed text-gray-900 dark:text-gray-100">
              <span className="inline-flex items-center justify-center w-5 h-5 rounded-full bg-gray-800 dark:bg-gray-200 text-white dark:text-gray-900 text-xs font-bold shrink-0 mt-px leading-none">
                {qIdx + 1}
              </span>
              {q.q}
            </p>

            <div className="flex flex-col gap-2">
              {q.options.map((opt, i) => {
      const isThisCorrect = i === q.correct;
      const isThisSelected = i === answer;
      const {btn, badge, icon} = getOptionClasses({
        hasAnswered,
        isThisCorrect,
        isThisSelected
      });
      return <button key={String(i)} type="button" onClick={() => handleSelect(qIdx, i)} className={btn}>
                    <span className={badge}>{letters[i]}</span>
                    <span className="flex-1">{opt}</span>
                    {icon}
                  </button>;
    })}
            </div>

            {hasAnswered ? <div className={`mt-3 py-3 pl-4 pr-3.5 rounded-r-xl text-sm leading-relaxed border-l-4 ${isCorrect ? 'border-green-500 bg-green-50 dark:bg-green-950 dark:border-green-500' : 'border-red-500 bg-red-50 dark:bg-red-950 dark:border-red-500'}`}>
                <span className={`font-semibold ${isCorrect ? '!text-green-800 dark:!text-green-200' : '!text-red-800 dark:!text-red-200'}`}>
                  {isCorrect ? 'Correct.' : 'Not quite.'}
                </span>{' '}
                <span className="!text-gray-700 dark:!text-gray-300">{q.explanation}</span>
              </div> : null}
          </div>;
  })}

      <button type="button" onClick={handleReset} className="mt-1 text-xs text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 underline underline-offset-2 cursor-pointer transition-colors duration-150">
        Reset quiz
      </button>
    </div>;
};

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

<LessonMeta level={3} difficulty="Advanced" time="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](/learn/guides/advanced/using-tools). Some examples reference [flows](/learn/guides/expert/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](/tools/introduction)** 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 theme={"theme":{"light":"github-light","dark":"github-dark"}}
# 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 theme={"theme":{"light":"github-light","dark":"github-dark"}}
# 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 theme={"theme":{"light":"github-light","dark":"github-dark"}}
order = getOrder(conv, tracking_number)
```

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

The platform-supported method for calling main functions:

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
# 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`.

<Quiz
  questions={[
{
q: "You have a utility function `is_valid_email` defined inside `validationUtils.py` (not the main function). How do you use it in another file?",
options: [
  "conv.functions.validationUtils.is_valid_email()",
  "from functions.validationUtils import is_valid_email",
  "flow.functions.is_valid_email()",
  "import validationUtils; validationUtils.is_valid_email()",
],
correct: 1,
explanation: "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.",
}
]}
/>

<FillBlank prompt="To import a helper function called is_valid_email from validationUtils.py, write: from functions.________ import is_valid_email" answer={["validationUtils"]} hint="What is the file name (without .py)?" explanation="Standard Python import syntax: from functions.fileName import functionName. This works for any function in any file, including helpers that are not the main function." />

## 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.
</Warning>

## Practical example: order tracking

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

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
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.
</Warning>

## 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

<CardGroup cols={2}>
  <Card title="Group related helpers" icon="folder">
    Don't create one function entry per helper. Group formatting utils, validation utils, and API utils into their own files.
  </Card>

  <Card title="Define once, use everywhere" icon="recycle">
    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.
  </Card>

  <Card title="Use conv.functions where possible" icon="check">
    For main functions, prefer the platform-supported `conv.functions` method. Reserve Python imports for utility helpers.
  </Card>

  <Card title="Keep functions focused" icon="crosshairs">
    Each main function should do one thing. Complex logic should be broken into helpers that are composed together.
  </Card>
</CardGroup>

## 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

<Steps>
  <Step title="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

    <Accordion title="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.
    </Accordion>

    <Accordion title="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 theme={"theme":{"light":"github-light","dark":"github-dark"}}
      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}"
      ```
    </Accordion>
  </Step>
</Steps>

<Quiz
  questions={[
{
q: "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?",
options: [
  "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",
],
correct: 1,
explanation: "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.",
}
]}
/>

<CardGroup cols={2}>
  <Card title="← Back to PolyAcademy" icon="arrow-left" href="/learn/guides/introduction">
    Level overview
  </Card>

  <Card title="Next: Flow fundamentals →" icon="arrow-right" href="/learn/guides/expert/flow-fundamentals">
    Lesson 2 of 5
  </Card>
</CardGroup>

<ProgressTracker lessonKey="l3-1-code-org" lessonNum={1} totalLessons={5} level="Level 3" />
