February 14, 2026Biswanath Giri

How to Be Ready for Gen AI Projects: A Complete Guide

Gen AIAIProject StructureBest PracticesLLM
How to Be Ready for Gen AI Projects: A Complete Guide

How to Be Ready for Gen AI Projects: A Complete Guide

Building production-ready Generative AI applications is fundamentally different from traditional software development. You're not just writing codeβ€”you're orchestrating LLMs, managing prompts, handling rate limits, implementing caching strategies, and ensuring your application is both cost-effective and reliable.

In this comprehensive guide, I'll walk you through everything you need to know to structure, build, and deploy Gen AI projects that scale.


🎯 The Gen AI Project Mindset

Before diving into code, understand these key differences:

Traditional Software vs Gen AI Projects

Aspect Traditional Gen AI
Logic Deterministic Probabilistic
Testing Unit tests, assertions Evaluation metrics, human review
Debugging Stack traces Prompt engineering, model behavior
Performance CPU/Memory Tokens, latency, cost
Versioning Code versions Prompt versions + model versions

Key Challenges You'll Face

  1. Non-deterministic outputs - Same input β‰  same output
  2. Token costs - Every API call costs money
  3. Rate limits - Can't scale infinitely
  4. Prompt engineering - The "code" is now natural language
  5. Model updates - Providers update models, breaking your app

πŸ“ The Ideal Gen AI Project Structure

Here's a battle-tested project structure that separates concerns and scales:

generative_ai_project/
β”œβ”€β”€ config/                          # Configuration management
β”‚   β”œβ”€β”€ __init__.py
β”‚   β”œβ”€β”€ model_config.yaml           # Model settings (temperature, max_tokens)
β”‚   β”œβ”€β”€ prompt_templates.yaml       # Centralized prompt storage
β”‚   └── logging_config.yaml         # Logging configuration
β”‚
β”œβ”€β”€ src/                             # Core source code
β”‚   β”œβ”€β”€ llm/                         # LLM client abstractions
β”‚   β”‚   β”œβ”€β”€ __init__.py
β”‚   β”‚   β”œβ”€β”€ base.py                 # Base LLM interface
β”‚   β”‚   β”œβ”€β”€ claude_client.py        # Anthropic Claude
β”‚   β”‚   β”œβ”€β”€ gpt_client.py           # OpenAI GPT
β”‚   β”‚   └── utils.py                # Token counting, etc.
β”‚   β”‚
β”‚   β”œβ”€β”€ prompt_engineering/          # Prompt management
β”‚   β”‚   β”œβ”€β”€ __init__.py
β”‚   β”‚   β”œβ”€β”€ templates.py            # Prompt templates
β”‚   β”‚   β”œβ”€β”€ few_shot.py             # Few-shot examples
β”‚   β”‚   └── chainer.py              # Prompt chaining
β”‚   β”‚
β”‚   β”œβ”€β”€ utils/                       # Utility functions
β”‚   β”‚   β”œβ”€β”€ __init__.py
β”‚   β”‚   β”œβ”€β”€ rate_limiter.py         # API rate limiting
β”‚   β”‚   β”œβ”€β”€ token_counter.py        # Token usage tracking
β”‚   β”‚   β”œβ”€β”€ cache.py                # Response caching
β”‚   β”‚   └── logger.py               # Structured logging
β”‚   β”‚
β”‚   └── handlers/                    # Business logic
β”‚       β”œβ”€β”€ __init__.py
β”‚       └── error_handler.py        # Error handling
β”‚
β”œβ”€β”€ data/                            # Data storage
β”‚   β”œβ”€β”€ cache/                       # Cached responses
β”‚   β”œβ”€β”€ prompts/                     # Prompt versions
β”‚   β”œβ”€β”€ outputs/                     # Generated outputs
β”‚   └── embeddings/                  # Vector embeddings
β”‚
β”œβ”€β”€ examples/                        # Usage examples
β”‚   β”œβ”€β”€ basic_completion.py
β”‚   β”œβ”€β”€ chat_session.py
β”‚   └── chain_prompts.py
β”‚
β”œβ”€β”€ notebooks/                       # Experimentation
β”‚   β”œβ”€β”€ prompt_testing.ipynb
β”‚   β”œβ”€β”€ response_analysis.ipynb
β”‚   └── model_experimentation.ipynb
β”‚
β”œβ”€β”€ tests/                           # Testing
β”‚   β”œβ”€β”€ test_llm_clients.py
β”‚   β”œβ”€β”€ test_prompts.py
β”‚   └── test_utils.py
β”‚
β”œβ”€β”€ requirements.txt                 # Python dependencies
β”œβ”€β”€ setup.py                         # Package setup
β”œβ”€β”€ README.md                        # Documentation
β”œβ”€β”€ .env.example                     # Environment variables template
└── Dockerfile                       # Containerization

πŸ”§ Core Components Explained

1. Configuration Management (config/)

Why separate configs? Because prompts, model settings, and logging configs change frequently. Keep them in YAML for easy editing without code changes.

config/model_config.yaml

models:
  gpt-4:
    provider: openai
    temperature: 0.7
    max_tokens: 2000
    top_p: 1.0
    frequency_penalty: 0.0
    presence_penalty: 0.0
  
  claude-3-sonnet:
    provider: anthropic
    temperature: 0.7
    max_tokens: 4096
    top_p: 1.0
  
  gemini-2.0-flash:
    provider: google
    temperature: 0.7
    max_output_tokens: 8192
    top_p: 0.95

default_model: gemini-2.0-flash

config/prompt_templates.yaml

templates:
  summarization:
    system: "You are an expert at creating concise, accurate summaries."
    user: |
      Summarize the following text in {num_sentences} sentences:
      
      {text}
      
      Summary:
  
  code_review:
    system: "You are a senior software engineer reviewing code."
    user: |
      Review this code and provide feedback on:
      1. Code quality
      2. Potential bugs
      3. Performance issues
      4. Best practices
      
      ```{language}
      {code}
      ```
  
  data_extraction:
    system: "Extract structured data from unstructured text."
    user: |
      Extract the following information from the text:
      {fields}
      
      Text: {text}
      
      Return as JSON.

2. LLM Client Abstraction (src/llm/)

Why abstract? So you can switch between OpenAI, Anthropic, Google, or local models without changing your application code.

src/llm/base.py

from abc import ABC, abstractmethod
from typing import Dict, List, Optional, AsyncIterator

class BaseLLMClient(ABC):
    """Base interface for all LLM clients"""
    
    @abstractmethod
    async def complete(
        self,
        prompt: str,
        temperature: float = 0.7,
        max_tokens: int = 1000,
        **kwargs
    ) -> str:
        """Generate a completion"""
        pass
    
    @abstractmethod
    async def chat(
        self,
        messages: List[Dict[str, str]],
        temperature: float = 0.7,
        max_tokens: int = 1000,
        **kwargs
    ) -> str:
        """Chat completion"""
        pass
    
    @abstractmethod
    async def stream(
        self,
        prompt: str,
        **kwargs
    ) -> AsyncIterator[str]:
        """Stream completion tokens"""
        pass
    
    @abstractmethod
    def count_tokens(self, text: str) -> int:
        """Count tokens in text"""
        pass

src/llm/gpt_client.py

import openai
from typing import Dict, List, AsyncIterator
from .base import BaseLLMClient

class GPTClient(BaseLLMClient):
    def __init__(self, api_key: str, model: str = "gpt-4"):
        self.client = openai.AsyncOpenAI(api_key=api_key)
        self.model = model
    
    async def complete(
        self,
        prompt: str,
        temperature: float = 0.7,
        max_tokens: int = 1000,
        **kwargs
    ) -> str:
        response = await self.client.chat.completions.create(
            model=self.model,
            messages=[{"role": "user", "content": prompt}],
            temperature=temperature,
            max_tokens=max_tokens,
            **kwargs
        )
        return response.choices[0].message.content
    
    async def chat(
        self,
        messages: List[Dict[str, str]],
        temperature: float = 0.7,
        max_tokens: int = 1000,
        **kwargs
    ) -> str:
        response = await self.client.chat.completions.create(
            model=self.model,
            messages=messages,
            temperature=temperature,
            max_tokens=max_tokens,
            **kwargs
        )
        return response.choices[0].message.content
    
    async def stream(self, prompt: str, **kwargs) -> AsyncIterator[str]:
        stream = await self.client.chat.completions.create(
            model=self.model,
            messages=[{"role": "user", "content": prompt}],
            stream=True,
            **kwargs
        )
        
        async for chunk in stream:
            if chunk.choices[0].delta.content:
                yield chunk.choices[0].delta.content
    
    def count_tokens(self, text: str) -> int:
        import tiktoken
        encoding = tiktoken.encoding_for_model(self.model)
        return len(encoding.encode(text))

3. Prompt Engineering (src/prompt_engineering/)

Why centralize prompts? Because prompts are your "code" in Gen AI. Version them, test them, and iterate on them systematically.

src/prompt_engineering/templates.py

import yaml
from pathlib import Path
from typing import Dict, Any

class PromptTemplate:
    def __init__(self, config_path: str = "config/prompt_templates.yaml"):
        with open(config_path, 'r') as f:
            self.templates = yaml.safe_load(f)['templates']
    
    def get(self, template_name: str, **kwargs) -> Dict[str, str]:
        """Get a prompt template and fill in variables"""
        if template_name not in self.templates:
            raise ValueError(f"Template {template_name} not found")
        
        template = self.templates[template_name]
        
        return {
            "system": template.get("system", ""),
            "user": template["user"].format(**kwargs)
        }
    
    def render(self, template_name: str, **kwargs) -> str:
        """Render a template as a single string"""
        template = self.get(template_name, **kwargs)
        return f"{template['system']}\n\n{template['user']}"

# Usage
prompt_manager = PromptTemplate()

# Get summarization prompt
prompt = prompt_manager.get(
    "summarization",
    num_sentences=3,
    text="Long article text here..."
)

src/prompt_engineering/chainer.py

from typing import List, Dict, Any
from ..llm.base import BaseLLMClient

class PromptChain:
    """Chain multiple prompts together"""
    
    def __init__(self, llm_client: BaseLLMClient):
        self.llm = llm_client
        self.steps = []
    
    def add_step(self, template: str, extract_fn=None):
        """Add a step to the chain"""
        self.steps.append({
            "template": template,
            "extract": extract_fn or (lambda x: x)
        })
        return self
    
    async def execute(self, initial_input: str) -> Dict[str, Any]:
        """Execute the chain"""
        results = {"input": initial_input}
        current_input = initial_input
        
        for i, step in enumerate(self.steps):
            # Render template with previous results
            prompt = step["template"].format(**results)
            
            # Get LLM response
            response = await self.llm.complete(prompt)
            
            # Extract relevant data
            extracted = step["extract"](response)
            
            # Store result
            results[f"step_{i}"] = extracted
            current_input = extracted
        
        return results

# Usage: Multi-step analysis
chain = PromptChain(llm_client)

chain.add_step(
    "Summarize this article:\n{input}",
    extract_fn=lambda x: x.strip()
).add_step(
    "Extract key insights from this summary:\n{step_0}",
    extract_fn=lambda x: x.split("\n")
).add_step(
    "Generate action items based on these insights:\n{step_1}"
)

results = await chain.execute("Long article text...")

4. Utilities (src/utils/)

src/utils/rate_limiter.py

import asyncio
import time
from collections import deque
from typing import Optional

class RateLimiter:
    """Token bucket rate limiter for API calls"""
    
    def __init__(
        self,
        requests_per_minute: int = 60,
        tokens_per_minute: int = 90000
    ):
        self.rpm_limit = requests_per_minute
        self.tpm_limit = tokens_per_minute
        
        self.request_times = deque()
        self.token_counts = deque()
    
    async def acquire(self, tokens: int = 1):
        """Wait until we can make a request"""
        now = time.time()
        
        # Remove old entries (older than 1 minute)
        while self.request_times and now - self.request_times[0] > 60:
            self.request_times.popleft()
            self.token_counts.popleft()
        
        # Check if we're at limit
        if len(self.request_times) >= self.rpm_limit:
            wait_time = 60 - (now - self.request_times[0])
            await asyncio.sleep(wait_time)
            return await self.acquire(tokens)
        
        # Check token limit
        total_tokens = sum(self.token_counts)
        if total_tokens + tokens > self.tpm_limit:
            wait_time = 60 - (now - self.request_times[0])
            await asyncio.sleep(wait_time)
            return await self.acquire(tokens)
        
        # Record this request
        self.request_times.append(now)
        self.token_counts.append(tokens)

# Usage
rate_limiter = RateLimiter(requests_per_minute=60, tokens_per_minute=90000)

async def make_llm_call(prompt: str):
    token_count = llm.count_tokens(prompt)
    await rate_limiter.acquire(token_count)
    return await llm.complete(prompt)

src/utils/cache.py

import hashlib
import json
import os
from pathlib import Path
from typing import Optional, Any

class ResponseCache:
    """Cache LLM responses to save costs"""
    
    def __init__(self, cache_dir: str = "data/cache"):
        self.cache_dir = Path(cache_dir)
        self.cache_dir.mkdir(parents=True, exist_ok=True)
    
    def _get_cache_key(self, prompt: str, model: str, **kwargs) -> str:
        """Generate cache key from prompt and params"""
        cache_data = {
            "prompt": prompt,
            "model": model,
            **kwargs
        }
        cache_str = json.dumps(cache_data, sort_keys=True)
        return hashlib.sha256(cache_str.encode()).hexdigest()
    
    def get(self, prompt: str, model: str, **kwargs) -> Optional[str]:
        """Get cached response"""
        key = self._get_cache_key(prompt, model, **kwargs)
        cache_file = self.cache_dir / f"{key}.json"
        
        if cache_file.exists():
            with open(cache_file, 'r') as f:
                data = json.load(f)
                return data['response']
        
        return None
    
    def set(self, prompt: str, model: str, response: str, **kwargs):
        """Cache a response"""
        key = self._get_cache_key(prompt, model, **kwargs)
        cache_file = self.cache_dir / f"{key}.json"
        
        with open(cache_file, 'w') as f:
            json.dump({
                "prompt": prompt,
                "model": model,
                "response": response,
                "params": kwargs
            }, f, indent=2)

# Usage
cache = ResponseCache()

async def cached_llm_call(prompt: str, model: str = "gpt-4"):
    # Check cache first
    cached = cache.get(prompt, model)
    if cached:
        print("Cache hit!")
        return cached
    
    # Make API call
    response = await llm.complete(prompt)
    
    # Cache response
    cache.set(prompt, model, response)
    
    return response

🎯 Best Practices for Gen AI Projects

1. Use YAML for Configuration

# config/model_config.yaml
models:
  production:
    name: gpt-4
    temperature: 0.3  # Lower for consistency
    max_tokens: 1000
  
  development:
    name: gpt-3.5-turbo
    temperature: 0.7
    max_tokens: 500
  
  experimentation:
    name: gpt-4
    temperature: 1.0  # Higher for creativity
    max_tokens: 2000

2. Implement Proper Error Handling

import asyncio
from typing import Optional

async def robust_llm_call(
    prompt: str,
    max_retries: int = 3,
    backoff_factor: float = 2.0
) -> Optional[str]:
    """LLM call with exponential backoff retry"""
    
    for attempt in range(max_retries):
        try:
            response = await llm.complete(prompt)
            return response
        
        except openai.RateLimitError:
            if attempt == max_retries - 1:
                raise
            
            wait_time = backoff_factor ** attempt
            print(f"Rate limited. Waiting {wait_time}s...")
            await asyncio.sleep(wait_time)
        
        except openai.APIError as e:
            print(f"API error: {e}")
            if attempt == max_retries - 1:
                raise
            
            await asyncio.sleep(1)
        
        except Exception as e:
            print(f"Unexpected error: {e}")
            raise
    
    return None

3. Use Rate Limiting for APIs

from src.utils.rate_limiter import RateLimiter

# OpenAI limits: 60 RPM, 90K TPM for GPT-4
rate_limiter = RateLimiter(
    requests_per_minute=60,
    tokens_per_minute=90000
)

async def safe_llm_call(prompt: str):
    token_count = llm.count_tokens(prompt)
    await rate_limiter.acquire(token_count)
    return await llm.complete(prompt)

4. Separate Model Clients

# src/llm/client_factory.py
from .gpt_client import GPTClient
from .claude_client import ClaudeClient
from .gemini_client import GeminiClient

class LLMClientFactory:
    @staticmethod
    def create(provider: str, **kwargs):
        if provider == "openai":
            return GPTClient(**kwargs)
        elif provider == "anthropic":
            return ClaudeClient(**kwargs)
        elif provider == "google":
            return GeminiClient(**kwargs)
        else:
            raise ValueError(f"Unknown provider: {provider}")

# Usage
llm = LLMClientFactory.create("google", api_key=os.getenv("GOOGLE_API_KEY"))

5. Cache Results Appropriately

from src.utils.cache import ResponseCache

cache = ResponseCache()

async def get_summary(text: str):
    # Check cache
    cached = cache.get(text, model="gpt-4", task="summarization")
    if cached:
        return cached
    
    # Generate
    response = await llm.complete(f"Summarize: {text}")
    
    # Cache
    cache.set(text, model="gpt-4", response=response, task="summarization")
    
    return response

6. Maintain Documentation

# README.md

## Setup

1. Clone repository
2. Install dependencies: `pip install -r requirements.txt`
3. Copy `.env.example` to `.env` and fill in API keys
4. Run examples: `python examples/basic_completion.py`

## Configuration

Edit `config/model_config.yaml` to change model settings.
Edit `config/prompt_templates.yaml` to modify prompts.

## Project Structure

- `config/` - Configuration files
- `src/llm/` - LLM client implementations
- `src/prompt_engineering/` - Prompt management
- `src/utils/` - Utility functions
- `examples/` - Usage examples
- `notebooks/` - Experimentation notebooks

7. Use Notebooks for Testing

# notebooks/prompt_testing.ipynb

# Cell 1: Setup
from src.llm.gpt_client import GPTClient
from src.prompt_engineering.templates import PromptTemplate

llm = GPTClient(api_key=os.getenv("OPENAI_API_KEY"))
prompts = PromptTemplate()

# Cell 2: Test different temperatures
temperatures = [0.0, 0.3, 0.7, 1.0]
results = {}

for temp in temperatures:
    response = await llm.complete(
        "Write a creative story about AI",
        temperature=temp
    )
    results[temp] = response

# Cell 3: Compare results
for temp, response in results.items():
    print(f"\n=== Temperature: {temp} ===")
    print(response[:200])

πŸš€ Getting Started Checklist

Phase 1: Setup (Day 1)

  • Clone repository structure
  • Install dependencies (requirements.txt)
  • Set up environment variables (.env)
  • Configure model settings (config/model_config.yaml)
  • Test basic LLM connection

Phase 2: Core Implementation (Week 1)

  • Implement LLM client abstraction
  • Create prompt template system
  • Add rate limiting
  • Implement caching
  • Set up logging

Phase 3: Prompt Engineering (Week 2)

  • Define prompt templates
  • Create few-shot examples
  • Test prompt variations
  • Implement prompt chaining
  • Version prompts

Phase 4: Testing & Optimization (Week 3)

  • Write unit tests
  • Create evaluation metrics
  • Optimize token usage
  • Implement error handling
  • Add monitoring

Phase 5: Production Ready (Week 4)

  • Containerize with Docker
  • Set up CI/CD
  • Add comprehensive documentation
  • Implement cost tracking
  • Deploy to production

πŸ“Š Development Tips

1. Follow Modular Design

βœ… Good: Separate concerns
src/llm/gpt_client.py
src/llm/claude_client.py
src/prompt_engineering/templates.py

❌ Bad: Everything in one file
main.py (2000 lines)

2. Write Component Tests

# tests/test_llm_clients.py
import pytest
from src.llm.gpt_client import GPTClient

@pytest.mark.asyncio
async def test_gpt_completion():
    client = GPTClient(api_key="test-key")
    response = await client.complete("Hello")
    assert isinstance(response, str)
    assert len(response) > 0

3. Use Version Control

git add config/prompt_templates.yaml
git commit -m "feat: add summarization prompt v2"
git tag prompt-v2.0

4. Keep Docs Updated

## Changelog

### v1.2.0 (2026-02-14)
- Added Claude 3.5 Sonnet support
- Improved rate limiting
- Updated summarization prompt

### v1.1.0 (2026-02-01)
- Added response caching
- Implemented prompt chaining

5. Monitor API Usage

# src/utils/usage_tracker.py
class UsageTracker:
    def __init__(self):
        self.total_tokens = 0
        self.total_cost = 0.0
    
    def track(self, tokens: int, model: str):
        self.total_tokens += tokens
        
        # GPT-4 pricing: $0.03/1K input, $0.06/1K output
        if model == "gpt-4":
            self.total_cost += (tokens / 1000) * 0.045  # Average
        
        print(f"Total tokens: {self.total_tokens}")
        print(f"Estimated cost: ${self.total_cost:.2f}")

πŸŽ“ Core Files You Need

1. requirements.txt

openai>=1.0.0
anthropic>=0.18.0
google-generativeai>=0.3.0
pyyaml>=6.0
tiktoken>=0.5.0
python-dotenv>=1.0.0
aiohttp>=3.9.0
pytest>=7.4.0
pytest-asyncio>=0.21.0

2. README.md

# Generative AI Project

A structured template for building production-ready Gen AI applications.

## Quick Start

1. Install: `pip install -r requirements.txt`
2. Configure: Copy `.env.example` to `.env`
3. Run: `python examples/basic_completion.py`

## Documentation

- [Project Structure](docs/structure.md)
- [Prompt Engineering Guide](docs/prompts.md)
- [API Reference](docs/api.md)

3. Dockerfile

FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["python", "main.py"]

🎯 Conclusion

Building production-ready Gen AI projects requires:

  1. βœ… Structured project organization - Separate configs, code, and data
  2. βœ… LLM client abstraction - Switch providers easily
  3. βœ… Prompt management - Version and test prompts systematically
  4. βœ… Rate limiting - Respect API limits
  5. βœ… Caching - Save costs on repeated queries
  6. βœ… Error handling - Retry with exponential backoff
  7. βœ… Documentation - Keep README and docs updated
  8. βœ… Testing - Use notebooks for experimentation, tests for validation

Next Steps

  1. Clone the structure - Start with the folder layout above
  2. Install dependencies - Set up your environment
  3. Configure models - Add your API keys
  4. Test basic calls - Verify everything works
  5. Build iteratively - Start simple, add complexity

Resources


Ready to build your Gen AI project? Start with this structure, adapt it to your needs, and iterate. The key is to start simple and add complexity as you learn.

What will you build? πŸš€

BG

Written by Biswanath Giri

Exploring the frontiers of Agentic AI, Cloud Architecture, and the future of technology.