TechWayFit
● Complete Learning Series Python & AI Engineering for .NET Developers

Functions & Modules for AI Applications

Tech Buddy June 12, 2026 3 min read
Functions & Modules for AI Applications

In C#, code is always inside a class. A standalone function doesn't exist — you'd write a static method on a utility class, or a record for data. Python takes the opposite approach: functions are first-class citizens that live freely in modules. This difference has a significant impact on how you structure AI applications, where a lot of your code is transformation logic that doesn't naturally belong to an object.

This lesson covers how to define functions, organize them into modules, and structure a Python AI project in a way that's readable, testable, and easy to extend.

Defining Functions in Python

Python functions use the def keyword. Unlike C# methods, they don't require a class wrapper, an access modifier, or a return type declaration (though you should add type hints — covered in Lesson 1.1).

C# Example
public static string BuildPrompt(
                          string userInput,
                          string systemContext)
                          {
                          return $"Context: {systemContext}\n" +
                                 $"User: {userInput}";
                          }
                      
                      // Must live inside a class:
                      // public static class PromptUtils { ... }
Python Example
def build_prompt(
                          user_input: str,
                          system_context: str ) -> str:
                          return (
                              f"Context: {system_context}\n"
                              f"User: {user_input}"
                          )
                      
                      # No class required — just call build_prompt()

Default Arguments and Keyword Arguments

Python functions can have default parameter values, and callers can pass arguments by name (keyword arguments). This is especially useful for AI functions that have many optional configuration parameters.

def call_llm(
                          prompt: str,
                          model: str = "claude-3-5-sonnet-20241022",
                          max_tokens: int = 1024,
                          temperature: float = 0.7,
                          system: str = "You are a helpful assistant.") -> str:
                          """
                          Call an LLM with configurable parameters.
                          Docstrings are the Python equivalent of XML doc comments.
                          """
                          # implementation here...
                          pass
                          # Calling with keyword args — self-documenting and order-independent
                          response = call_llm(
                          prompt="Explain embeddings in one paragraph",
                          max_tokens=256,
                          temperature=0.2,
                          )
                      
                      # Only override what you need
                      quick_response = call_llm("What is RAG?")  # uses all defaults

Return Multiple Values

Python functions can return multiple values as a tuple — no wrapper class needed:

def analyze_response(text: str) -> tuple[str, float, int]:
                          """Returns (cleaned_text, confidence_score, token_count)."""
                          cleaned = text.strip()
                          confidence = 0.95  # hypothetical scoring
                          tokens = len(cleaned.split())
                          return cleaned, confidence, tokens  # Python auto-wraps in tuple
                      
                      # Unpack at the call site
                      text, score, count = analyze_response(raw_llm_output)
                      print(f"Got {count} tokens with {score:.0%} confidence")

Modules: Python's Namespace System

A Python module is simply a .py file. Every file you create is automatically a module. A package is a directory containing an __init__.py file, which makes it importable as a unit.

This maps roughly to C# namespaces, except the file structure IS the namespace — no separate namespace declarations needed.

C# Namespaces
// File: src/Prompts/PromptBuilder.cs
                      namespace MyAiApp.Prompts
                      {
                          public static class PromptBuilder
                          {
                              public static string Build(string input)
                                  => $"User: {input}";
                          }
                      }
                      
                      // Usage:
                      using MyAiApp.Prompts;
                      var p = PromptBuilder.Build("hi");
Python Modules
# File: src/prompts/builder.py
                      # (directory must have __init__.py)
                      
                      def build(input: str) -> str:
                          return f"User: {input}"
                      
                      
                      # Usage in another file:
                      from src.prompts.builder import build
                      # or
                      from src.prompts import builder
                      result = builder.build("hi")

The __init__.py File

An __init__.py file marks a directory as a Python package and can control what's exported when someone imports the package:

# src/prompts/__init__.py
                      
                      # Re-export commonly used functions so callers can do:
                      # from src.prompts import build_chat_prompt
                      from .builder import build_chat_prompt
                      from .templates import SYSTEM_PROMPTS
                      
                      __all__ = ["build_chat_prompt", "SYSTEM_PROMPTS"]  # explicit public API

Practical AI Module Structure

Here's how you'd organize a real prompt utility module for an AI application. This is the kind of code you'd write in your first week of an AI engineering project:

# src/prompts/builder.py
                      
                      from __future__ import annotations
                      from typing import Optional
                      
                      
                      # Module-level constant (like a C# static readonly field)
                      DEFAULT_SYSTEM_PROMPT = """You are an expert AI assistant specializing in
                      code analysis. Be precise, concise, and cite specific line numbers
                      when referencing code."""
                      
                      
                      def build_chat_prompt(
                          user_message: str,
                          context: Optional[str] = None,
                          system_override: Optional[str] = None,
                      ) -> dict[str, str | list]:
                          """
                          Build a structured prompt dict for the Anthropic Messages API.
                      
                          Args:
                              user_message: The user's request.
                              context: Optional background context to prepend.
                              system_override: Replace the default system prompt.
                      
                          Returns:
                              A dict with 'system' and 'messages' keys ready for the API.
                          """
                          system = system_override or DEFAULT_SYSTEM_PROMPT
                      
                          # Build the user content
                          if context:
                              user_content = f"\n{context}\n\n\n{user_message}"
                          else:
                              user_content = user_message
                      
                          return {
                              "system": system,
                              "messages": [
                                  {"role": "user", "content": user_content}
                              ],
                          }
                      
                      
                      def build_few_shot_prompt(
                          user_message: str,
                          examples: list[tuple[str, str]],
                      ) -> list[dict]:
                          """
                          Build a messages list with few-shot examples.
                      
                          Args:
                              user_message: The actual request.
                              examples: List of (user_input, assistant_output) tuples.
                          """
                          messages = []
                      
                          for user_ex, assistant_ex in examples:
                              messages.append({"role": "user", "content": user_ex})
                              messages.append({"role": "assistant", "content": assistant_ex})
                      
                          messages.append({"role": "user", "content": user_message})
                          return messages

Using the Module

# main.py (or any other file)
                      import anthropic
                      from src.prompts.builder import build_chat_prompt, build_few_shot_prompt
                      
                      client = anthropic.Anthropic()
                      
                      # Build a structured prompt
                      prompt_data = build_chat_prompt(
                          user_message="Review this Python function for potential bugs.",
                          context="This code runs in a production AI pipeline with 10k req/min.",
                      )
                      
                      response = client.messages.create(
                          model="claude-3-5-sonnet-20241022",
                          max_tokens=1024,
                          system=prompt_data["system"],
                          messages=prompt_data["messages"],
                      )
                      
                      print(response.content[0].text)

Import Patterns and Best Practices

Python has several import styles. Knowing which to use keeps your code readable and avoids namespace pollution — a common source of subtle bugs in AI codebases with large dependency trees.

# 1. Absolute import (preferred — explicit and unambiguous)
                      from src.prompts.builder import build_chat_prompt
                      
                      # 2. Relative import (use inside a package)
                      from .builder import build_chat_prompt     # same directory
                      from ..models import ChatRequest           # parent directory
                      
                      # 3. Import module, not function (good when using multiple items)
                      from src import prompts
                      prompts.build_chat_prompt(...)
                      
                      # 4. Wildcard import — AVOID in production AI code
                      from src.prompts.builder import *  # opaque, pollutes namespace
                      
                      # 5. Aliased import — good for long names or conventions
                      import anthropic as ant
                      import numpy as np  # community convention

*args and **kwargs: Flexible Function Signatures

AI wrapper functions often need to pass arbitrary arguments through to underlying SDKs. Python's *args and **kwargs handle this elegantly — similar to C#'s params keyword, but more powerful.

def build_prompt(task: str, language: str) -> str:
                      	return f"Write {language} code for: {task}"
                      	
                      	def call_llm(prompt: str, model: str = "gpt-4.1-mini") -> str:
                          	return f"Calling model={model} with prompt='{prompt}'"
                          	
                      	def total_tokens(*token_counts: int) -> int: # C# Equivalent: int total_tokens(params int[] token_counts)
                      	"""*args example: accepts any number of positional values."""
                      	return sum(token_counts)
                          	
                      	def log_details(**metadata) -> None: # C# Equivalent: void log_details(params Dictionary metadata)
                      	"""**kwargs example: accepts any number of named values."""
                      	print("Inference metadata:")
                      	for key, value in metadata.items():
                      		print(f"- {key}: {value}")
                      		
                      	

Module-Level Code and the if __name__ == "__main__" Guard

Python executes module-level code when the file is imported. This is different from C#, where only explicitly called code runs. The if __name__ == "__main__" guard ensures code only runs when the file is executed directly — not when it's imported as a module.

# src/prompts/builder.py
                      
                      def build_chat_prompt(user_message: str) -> dict:
                          # ... implementation ...
                          pass
                      
                      
                      # This block runs ONLY when you do: python builder.py
                      # It does NOT run when another file does: import builder
                      if __name__ == "__main__":
                          # Quick smoke test / development runner
                          test_prompt = build_chat_prompt("Test message")
                          print(test_prompt)
                          print("Module works correctly!")

Key Takeaways

  • Python functions are first-class and don't need a class — use standalone functions for pure transformation logic
  • A Python file is a module; a directory with __init__.py is a package — your folder structure IS your namespace
  • Use keyword arguments and default values to build self-documenting AI function signatures
  • *args / **kwargs let you write flexible wrapper functions that pass through SDK parameters without knowing them upfront
  • Use __all__ in __init__.py to define a clean public API for each package
  • The if __name__ == "__main__" guard is essential for modules that double as standalone scripts

Comments

Loading comments…

Leave a comment