Lesson 6.6: Code Review and Improvements
Duration: 60 minutes
Learning Objectives
By the end of this lesson, you will be able to:
- Apply code review best practices to AI applications
- Write tests for AI-powered features
- Identify and fix common issues
- Plan future improvements and features
- 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
anytypes (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
-
Add Authentication
- API key validation for endpoints
- Session-based auth for web clients
- Rate limiting per user
-
Improve RAG
- Hybrid search (keyword + semantic)
- Document metadata filtering
- Citation of sources in responses
-
Enhanced Tools
- Image generation tool
- Code execution tool
- Email/notification tool
Medium-term Improvements
-
Web Interface
- React/Next.js frontend
- Chat history persistence
- File upload for RAG
-
Multi-model Support
- Model selection per request
- Fallback providers
- Cost optimization routing
-
Observability
- Detailed metrics
- Request tracing
- Usage dashboards
Long-term Vision
-
Multi-agent System
- Specialized agents for different tasks
- Agent collaboration protocols
- Dynamic agent spawning
-
Learning and Personalization
- User preference learning
- Response style adaptation
- Custom knowledge per user
-
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**