Skip to main content
Level 3 — Lesson 1 of 5 — Structure your Agent Studio code so it stays manageable as your project grows.
How Agent Studio’s file system works, how to share code between functions, and when to use each approach.
This lesson assumes familiarity with Python and functions. Some examples reference flows, 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 organises code

Each entry on the Functions 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:
# 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:
# 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:
order = getOrder(conv, tracking_number)

Method 2: conv.functions and flow.functions

The platform-supported method for calling main functions:
# 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.

When to use which method

ScenarioMethod
Calling a main function from another fileconv.functions.functionName()
Calling a utility/helper from a utils filefrom functions.fileName import helperName
Calling a flow function from outside the flowflow.functions.functionName()
Need autocomplete and type hintsconv.functions / flow.functions
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:
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 typeLLM-callable?Description
Flow functionsYesLocal to a single flow. Keep them thin — they orchestrate step logic and delegate complex work to global functions.
Global functions (KB / general-purpose)YesReusable across flows and KB topics. Includes flow start functions, transfer calls, and other shared callable functions.
API handlersNoHandle outbound calls to external services. Never call directly from prompts.
ServicesNoBusiness rules and orchestration between APIs, models, and utilities.
Utility filesNoSimple, deterministic helpers (string formatting, date conversion). No side effects.
ConstantsNoShared constants, enums, and configuration values. No logic.
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

PitfallSolution
Business logic embedded in flow functionsMove to global services or utils. Keep flow functions focused on orchestration.
”Invisible functions” that can’t be foundUse clear naming. Centralise reusable logic in global functions. Never import logic from flow functions into others.
Duplicated logic across functionsCentralise repeated patterns into a single global function or util. Update in one place.
Bloated global function filesSimulate folders by namespacing files by concern (e.g., tracking_utils, tracking_api, tracking_service).
Circular dependenciesFollow the function hierarchy: flow functions → global functions → services → utils. Never import upward.

Try it yourself

1

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
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.
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):
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}"

← Back to PolyAcademy

Level overview

Next: Flow fundamentals →

Lesson 2 of 5
Last modified on March 31, 2026