From Zero to AI

Lesson 6.6: Code Review and Improvements

Duration: 60 minutes

Learning Objectives

By the end of this lesson, you will be able to:

  1. Apply code review best practices to AI applications
  2. Write tests for AI-powered features
  3. Identify and fix common issues
  4. Plan future improvements and features
  5. Document your project professionally

Introduction

You have built a complete AI assistant. Before calling it done, let us review the code quality, add tests, and discuss improvements. This lesson covers the practices that separate hobby projects from professional software.


Code Review Checklist

1. Architecture Review

Your project should have clear separation:

┌─────────────────────────────────────────────────────────────────────┐
│                     ARCHITECTURE REVIEW                             │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  GOOD                              BAD                              │
│  ┌─────────┐                      ┌─────────────────────┐          │
│  │  Core   │ Single responsibility  │    God Object     │          │
│  └────┬────┘                      │  Does everything   │          │
│       │                           │  1000+ lines       │          │
│  ┌────┴────┐                      │  Mixed concerns    │          │
│  │Providers│ Clear interfaces      └─────────────────────┘          │
│  └────┬────┘                                                        │
│       │                                                             │
│  ┌────┴────┐                                                        │
│  │  Tools  │ Isolated, testable                                     │
│  └────┬────┘                                                        │
│       │                                                             │
│  ┌────┴────┐                                                        │
│  │   RAG   │ Independent module                                     │
│  └─────────┘                                                        │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Verify:

  • Each module has a single responsibility
  • Dependencies flow in one direction (no circular imports)
  • Interfaces are defined for external integrations
  • Configuration is centralized

2. Type Safety Review

Check that TypeScript is used effectively:

// GOOD: Explicit types, narrow interfaces
interface ChatRequest {
  message: string;
  sessionId?: string;
}

async function handleChat(req: ChatRequest): Promise<ChatResponse> {
  // ...
}

// BAD: Using `any`, overly broad types
async function handleChat(req: any): Promise<any> {
  // ...
}

Verify:

  • No any types (except where truly necessary)
  • Function parameters and returns are typed
  • Interfaces are used for complex objects
  • Zod schemas validate external input

3. Error Handling Review

Errors should be handled consistently:

// GOOD: Custom errors, informative messages
class ToolError extends Error {
  constructor(
    message: string,
    public toolName: string,
    public cause?: Error
  ) {
    super(message);
    this.name = 'ToolError';
  }
}

// BAD: Generic errors, no context
throw new Error('Something went wrong');

Verify:

  • Custom error classes for different error types
  • Errors include context (what failed, why)
  • Async errors are caught and handled
  • User-facing errors are safe (no stack traces in production)

4. Security Review

Security should be built-in:

// GOOD: Input validation, rate limiting
const body = RequestSchema.parse(await parseBody(req));
if (!checkRateLimit(ip)) {
  return sendError(res, 429, "Too many requests");
}

// BAD: Trust all input
const body = JSON.parse(await getBody(req));
await processRequest(body.anything);

Verify:

  • All user input is validated
  • API keys are in environment variables
  • Rate limiting is implemented
  • Error messages do not leak sensitive info

Writing Tests

Unit Tests for Tools

Create tests/tools.test.ts:

import assert from 'node:assert';
import { describe, it } from 'node:test';

import { calculatorTool } from '../src/tools/calculator.js';

describe('Calculator Tool', () => {
  it('should add numbers correctly', async () => {
    const result = await calculatorTool.execute({ expression: '2 + 2' });
    assert.ok(result.includes('4'));
  });

  it('should handle multiplication', async () => {
    const result = await calculatorTool.execute({ expression: '5 * 3' });
    assert.ok(result.includes('15'));
  });

  it('should handle complex expressions', async () => {
    const result = await calculatorTool.execute({ expression: '(10 + 5) * 2' });
    assert.ok(result.includes('30'));
  });

  it('should handle functions', async () => {
    const result = await calculatorTool.execute({ expression: 'sqrt(16)' });
    assert.ok(result.includes('4'));
  });

  it('should handle division by zero', async () => {
    const result = await calculatorTool.execute({ expression: '10 / 0' });
    assert.ok(result.toLowerCase().includes('error') || result.includes('Infinity'));
  });

  it('should handle invalid expressions', async () => {
    const result = await calculatorTool.execute({ expression: 'abc + def' });
    assert.ok(result.toLowerCase().includes('error'));
  });
});

Integration Tests for RAG

Create tests/rag.test.ts:

import { mkdir, rm, writeFile } from 'fs/promises';
import assert from 'node:assert';
import { before, describe, it } from 'node:test';

import { Retriever } from '../src/rag/retriever.js';

describe('RAG Retriever', () => {
  const testDocsPath = './test-documents';

  before(async () => {
    // Create test documents
    await mkdir(testDocsPath, { recursive: true });

    await writeFile(
      `${testDocsPath}/typescript.md`,
      `# TypeScript Guide
      
TypeScript is a typed superset of JavaScript. It adds static type checking to help catch errors early.

Key features:
- Type annotations
- Interfaces
- Generics
- Type inference`
    );

    await writeFile(
      `${testDocsPath}/javascript.md`,
      `# JavaScript Basics

JavaScript is a dynamic programming language used for web development.

Key concepts:
- Variables (let, const, var)
- Functions
- Objects and arrays
- Promises and async/await`
    );
  });

  it('should initialize without errors', async () => {
    const retriever = new Retriever({ documentsPath: testDocsPath });
    await assert.doesNotReject(retriever.initialize(testDocsPath));
  });

  it('should retrieve relevant documents for TypeScript query', async () => {
    const retriever = new Retriever({ documentsPath: testDocsPath, minScore: 0.5 });
    await retriever.initialize(testDocsPath);

    const context = await retriever.retrieve('What is TypeScript?');

    assert.ok(context.length > 0, 'Should return context');
    assert.ok(context.toLowerCase().includes('typescript'), 'Context should mention TypeScript');
  });

  it('should return empty for unrelated queries', async () => {
    const retriever = new Retriever({ documentsPath: testDocsPath, minScore: 0.9 });
    await retriever.initialize(testDocsPath);

    const context = await retriever.retrieve('How to cook pasta?');

    // Very high threshold means unlikely to match unrelated content
    assert.ok(context.length === 0 || !context.includes('pasta'));
  });

  // Cleanup
  after(async () => {
    await rm(testDocsPath, { recursive: true, force: true });
  });
});

Testing the Assistant

Create tests/assistant.test.ts:

import assert from 'node:assert';
import { describe, it, mock } from 'node:test';

import { Assistant } from '../src/core/assistant.js';

describe('Assistant', () => {
  it('should create an instance with default options', () => {
    const assistant = new Assistant();
    assert.ok(assistant);
  });

  it('should register tools', () => {
    const assistant = new Assistant();

    const mockTool = {
      name: 'test_tool',
      description: 'A test tool',
      parameters: { type: 'object' as const, properties: {} },
      execute: async () => 'test result',
    };

    assistant.registerTool(mockTool);
    // Tool registration should not throw
    assert.ok(true);
  });

  it('should clear history', () => {
    const assistant = new Assistant();
    assistant.clearHistory();

    const history = assistant.getHistory();
    // Should only have system message
    assert.ok(history.length <= 1);
  });

  it('should maintain conversation history', async () => {
    // This test would require mocking the API
    // Shown here as a pattern for how to structure it

    const assistant = new Assistant();

    // Mock the provider
    // In a real test, use dependency injection or test doubles

    // const response = await assistant.chat("Hello");
    // assert.ok(response.content);
    // assert.strictEqual(assistant.getHistory().length, 3); // system + user + assistant
  });
});

Running Tests

npm test

Common Issues and Fixes

Issue 1: Memory Leaks in Long Sessions

Problem: Conversation history grows indefinitely.

Fix: Add conversation pruning:

class ConversationManager {
  private maxMessages = 50;

  addMessage(message: Message): void {
    this.messages.push(message);

    // Keep conversation manageable
    if (this.messages.length > this.maxMessages) {
      // Keep system message + recent messages
      const systemMessage = this.messages.find((m) => m.role === 'system');
      const recentMessages = this.messages.slice(-this.maxMessages + 1);

      this.messages = systemMessage ? [systemMessage, ...recentMessages] : recentMessages;
    }
  }
}

Issue 2: API Costs Spiraling

Problem: Too many API calls, high costs.

Fix: Add caching and monitoring:

class CachedProvider implements AIProvider {
  private cache = new Map<string, { response: ProviderResponse; timestamp: number }>();
  private cacheTTL = 60000; // 1 minute

  async chat(messages: Message[], tools?: ToolDefinition[]): Promise<ProviderResponse> {
    const cacheKey = JSON.stringify({ messages, tools });
    const cached = this.cache.get(cacheKey);

    if (cached && Date.now() - cached.timestamp < this.cacheTTL) {
      return cached.response;
    }

    const response = await this.provider.chat(messages, tools);
    this.cache.set(cacheKey, { response, timestamp: Date.now() });

    return response;
  }
}

Issue 3: Slow RAG Retrieval

Problem: Vector search is slow with many documents.

Fix: Use a proper vector database in production:

// Replace in-memory store with ChromaDB or Pinecone
import { ChromaClient } from 'chromadb';

class ChromaVectorStore {
  private client: ChromaClient;
  private collection: Collection;

  async initialize(): Promise<void> {
    this.client = new ChromaClient();
    this.collection = await this.client.getOrCreateCollection({
      name: 'documents',
    });
  }

  async add(
    id: string,
    embedding: number[],
    content: string,
    metadata: Record<string, unknown>
  ): Promise<void> {
    await this.collection.add({
      ids: [id],
      embeddings: [embedding],
      documents: [content],
      metadatas: [metadata],
    });
  }

  async search(queryEmbedding: number[], topK: number): Promise<SearchResult[]> {
    const results = await this.collection.query({
      queryEmbeddings: [queryEmbedding],
      nResults: topK,
    });

    return results.ids[0].map((id, i) => ({
      id,
      content: results.documents[0][i],
      metadata: results.metadatas[0][i],
      score: 1 - (results.distances?.[0]?.[i] ?? 0),
    }));
  }
}

Issue 4: Tool Execution Timeout

Problem: Tools hang indefinitely.

Fix: Add timeouts:

async function executeWithTimeout<T>(
  fn: () => Promise<T>,
  timeoutMs: number,
  errorMessage: string
): Promise<T> {
  const timeoutPromise = new Promise<never>((_, reject) => {
    setTimeout(() => reject(new Error(errorMessage)), timeoutMs);
  });

  return Promise.race([fn(), timeoutPromise]);
}

// Usage in tool execution
const result = await executeWithTimeout(
  () => tool.execute(args),
  30000,
  `Tool ${tool.name} timed out after 30 seconds`
);

Future Improvements

Short-term Improvements

  1. Add Authentication

    • API key validation for endpoints
    • Session-based auth for web clients
    • Rate limiting per user
  2. Improve RAG

    • Hybrid search (keyword + semantic)
    • Document metadata filtering
    • Citation of sources in responses
  3. Enhanced Tools

    • Image generation tool
    • Code execution tool
    • Email/notification tool

Medium-term Improvements

  1. Web Interface

    • React/Next.js frontend
    • Chat history persistence
    • File upload for RAG
  2. Multi-model Support

    • Model selection per request
    • Fallback providers
    • Cost optimization routing
  3. Observability

    • Detailed metrics
    • Request tracing
    • Usage dashboards

Long-term Vision

  1. Multi-agent System

    • Specialized agents for different tasks
    • Agent collaboration protocols
    • Dynamic agent spawning
  2. Learning and Personalization

    • User preference learning
    • Response style adaptation
    • Custom knowledge per user
  3. Enterprise Features

    • Multi-tenant support
    • SSO integration
    • Audit logging

Documentation

README Template

Create a professional README.md:

# AI Knowledge Assistant

An intelligent assistant powered by OpenAI/Anthropic with RAG capabilities and tool integration.

## Features

- Conversational AI with memory
- Document-based knowledge retrieval (RAG)
- Built-in tools: calculator, weather, notes, web search
- Streaming responses
- Multi-provider support (OpenAI, Anthropic)

## Quick Start

### Prerequisites

- Node.js 18+
- OpenAI API key

### Installation

```bash
git clone https://github.com/yourusername/ai-assistant.git
cd ai-assistant
npm install
cp .env.example .env
# Edit .env with your API keys
```

Running

# CLI mode
npm run dev

# Server mode
npm run dev server

Configuration

Variable Description Default
OPENAI_API_KEY OpenAI API key Required
ANTHROPIC_API_KEY Anthropic API key Optional
DEFAULT_MODEL Model to use gpt-4o
DOCUMENTS_PATH Path to documents for RAG ./documents

API Endpoints

Method Path Description
GET /health Health check
POST /chat Send message
POST /chat/stream Send message (streaming)
POST /clear Clear session

Project Structure

src/
├── core/           # Core application logic
├── providers/      # AI provider integrations
├── rag/           # RAG implementation
├── tools/         # Tool implementations
└── utils/         # Utilities

Development

# Run tests
npm test

# Type checking
npm run lint

# Build
npm run build

License

MIT


---

## Congratulations

You have completed the capstone project and the entire course series! You now have:

- A working AI assistant with RAG and tools
- Production deployment knowledge
- Code quality and testing practices
- A foundation for building more complex AI applications

### What You Built

┌─────────────────────────────────────────────────────────────────────┐ │ YOUR AI KNOWLEDGE ASSISTANT │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ CAPABILITIES │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ Conversation│ │ RAG │ │ Tools │ │ │ │ Memory │ │ Knowledge │ │ Actions │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ │ INTEGRATIONS │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ OpenAI │ │ Anthropic │ │ │ │ API │ │ API │ │ │ └─────────────┘ └─────────────┘ │ │ │ │ DEPLOYMENT │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ CLI Mode │ │ HTTP Server │ │ Cloud │ │ │ │ │ │ Streaming │ │ Platforms │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘


### Skills Mastered

Throughout this course series, you learned:

1. **TypeScript** - Type-safe programming
2. **Async Programming** - Promises, async/await, streaming
3. **API Integration** - REST APIs, authentication
4. **AI Fundamentals** - LLMs, prompting, providers
5. **RAG** - Embeddings, vector search, retrieval
6. **Function Calling** - Tool integration
7. **Agents** - Planning, reasoning, multi-agent systems
8. **Production** - Deployment, monitoring, security

---

## Key Takeaways

1. **Code review** catches issues before production
2. **Tests** ensure reliability and enable refactoring
3. **Documentation** helps others (and future you) understand the code
4. **Continuous improvement** keeps the project relevant
5. **Security and monitoring** are essential for production

---

## What's Next?

Your journey does not end here. Consider:

1. **Build something real** - Apply these skills to a problem you care about
2. **Contribute to open source** - Join AI/ML projects on GitHub
3. **Stay current** - AI moves fast; follow releases and research
4. **Share knowledge** - Write about what you learned, help others
5. **Explore specializations** - Fine-tuning, multimodal AI, voice interfaces

---

## Resources

| Resource | Type | Level |
|----------|------|-------|
| [OpenAI Cookbook](https://cookbook.openai.com/) | Examples | Intermediate |
| [LangChain.js Documentation](https://docs.langchain.com/oss/javascript/langchain/overview) | Documentation | Intermediate |
| [Vercel AI SDK](https://ai-sdk.dev/docs) | Documentation | Intermediate |
| [Hugging Face](https://huggingface.co/) | Platform | All levels |
| [Papers with Code](https://paperswithcode.com/) | Research | Advanced |

---

## Final Words

You started this journey knowing nothing about programming. Now you can build AI-powered applications. That is an incredible achievement.

The AI field is evolving rapidly. What you learned here is a foundation. Keep building, keep learning, and keep pushing the boundaries of what is possible.

Good luck on your journey!

**Course Complete**