- Getting Started
- Development Environment Setup
- Azure Resources Provisioning
- Backend Implementation
- Frontend Implementation
- Deployment
- Testing Strategy
- Best Practices
- Azure Subscription with appropriate permissions
- Node.js 20+ and npm/yarn
- Python 3.11+
- Docker Desktop
- Git
- VS Code or preferred IDE
- Azure CLI
- Azure Functions Core Tools
az-ai-agentic-retrieval/
├── backend/
│ ├── api/ # Node.js Express API
│ │ ├── src/
│ │ │ ├── controllers/
│ │ │ ├── middleware/
│ │ │ ├── routes/
│ │ │ ├── services/
│ │ │ └── utils/
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── processing/ # Python processing service
│ │ ├── src/
│ │ │ ├── agents/
│ │ │ ├── processors/
│ │ │ ├── extractors/
│ │ │ └── utils/
│ │ ├── requirements.txt
│ │ └── pyproject.toml
│ └── functions/ # Azure Functions
│ ├── DocumentProcessor/
│ ├── QueryHandler/
│ ├── EmbeddingGenerator/
│ └── host.json
├── frontend/
│ ├── src/
│ │ ├── components/
│ │ ├── pages/
│ │ ├── store/
│ │ ├── services/
│ │ └── utils/
│ ├── package.json
│ └── tsconfig.json
├── infrastructure/ # IaC templates
│ ├── bicep/
│ ├── terraform/
│ └── scripts/
├── tests/
│ ├── unit/
│ ├── integration/
│ └── e2e/
├── docs/
├── .github/
│ └── workflows/
└── docker-compose.yml
git clone <repository-url>
cd az-ai-agentic-retrieval
# Install backend dependencies
cd backend/api
npm install
cd ../processing
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
pip install -r requirements.txt
cd ../functions
npm install
# Install frontend dependencies
cd ../../frontend
npm installCreate .env files for each service:
backend/api/.env
# Server Configuration
NODE_ENV=development
PORT=3000
API_VERSION=v1
# Azure Configuration
AZURE_TENANT_ID=your-tenant-id
AZURE_CLIENT_ID=your-client-id
AZURE_CLIENT_SECRET=your-client-secret
AZURE_SUBSCRIPTION_ID=your-subscription-id
# Azure OpenAI
AZURE_OPENAI_ENDPOINT=https://your-openai.openai.azure.com/
AZURE_OPENAI_API_KEY=your-api-key
AZURE_OPENAI_DEPLOYMENT_NAME=gpt-4
AZURE_OPENAI_EMBEDDING_DEPLOYMENT=text-embedding-ada-002
# Azure Cognitive Services
AZURE_COGNITIVE_SERVICES_ENDPOINT=https://your-cognitive.cognitiveservices.azure.com/
AZURE_COGNITIVE_SERVICES_KEY=your-key
# Azure Storage
AZURE_STORAGE_CONNECTION_STRING=your-connection-string
AZURE_STORAGE_CONTAINER_NAME=documents
# Azure Cosmos DB
COSMOS_DB_ENDPOINT=https://your-cosmos.documents.azure.com:443/
COSMOS_DB_KEY=your-key
COSMOS_DB_DATABASE_NAME=multilingual-kb
COSMOS_DB_CONTAINER_NAME=documents
# Azure AI Search
AZURE_SEARCH_ENDPOINT=https://your-search.search.windows.net
AZURE_SEARCH_API_KEY=your-api-key
AZURE_SEARCH_INDEX_NAME=documents-index
# Redis Cache
REDIS_CONNECTION_STRING=your-redis-connection-string
# PostgreSQL with pgvector
POSTGRES_HOST=your-postgres.postgres.database.azure.com
POSTGRES_PORT=5432
POSTGRES_DB=vectordb
POSTGRES_USER=your-username
POSTGRES_PASSWORD=your-password
# JWT Configuration
JWT_SECRET=your-jwt-secret
JWT_EXPIRATION=1h
# Rate Limiting
RATE_LIMIT_WINDOW_MS=3600000
RATE_LIMIT_MAX_REQUESTS=1000backend/processing/.env
PYTHON_ENV=development
# Azure Configuration
AZURE_OPENAI_ENDPOINT=https://your-openai.openai.azure.com/
AZURE_OPENAI_API_KEY=your-api-key
# Processing Configuration
MAX_CHUNK_SIZE=1000
CHUNK_OVERLAP=200
OCR_LANGUAGE=eng+kor
# LangChain Configuration
LANGCHAIN_TRACING_V2=true
LANGCHAIN_API_KEY=your-langchain-keyfrontend/.env
REACT_APP_API_BASE_URL=http://localhost:3000/api/v1
REACT_APP_WS_URL=ws://localhost:3000
REACT_APP_AZURE_AD_CLIENT_ID=your-client-id
REACT_APP_AZURE_AD_AUTHORITY=https://login.microsoftonline.com/your-tenant-iddocker-compose.yml
version: '3.8'
services:
api:
build: ./backend/api
ports:
- "3000:3000"
environment:
NODE_ENV: development
volumes:
- ./backend/api:/app
- /app/node_modules
depends_on:
- postgres
- redis
processing:
build: ./backend/processing
ports:
- "8000:8000"
environment:
PYTHON_ENV: development
volumes:
- ./backend/processing:/app
frontend:
build: ./frontend
ports:
- "3001:3000"
environment:
NODE_ENV: development
volumes:
- ./frontend:/app
- /app/node_modules
postgres:
image: pgvector/pgvector:pg16
ports:
- "5432:5432"
environment:
POSTGRES_DB: vectordb
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
volumes:
- postgres-data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis-data:/data
azurite:
image: mcr.microsoft.com/azure-storage/azurite
ports:
- "10000:10000"
- "10001:10001"
- "10002:10002"
volumes:
- azurite-data:/data
volumes:
postgres-data:
redis-data:
azurite-data:# Start all services
docker-compose up -d
# Or start individually
cd backend/api && npm run dev
cd backend/processing && uvicorn main:app --reload
cd frontend && npm startinfrastructure/bicep/main.bicep
targetScope = 'subscription'
param location string = 'eastus'
param environment string = 'dev'
param projectName string = 'multilingual-kb'
// Resource names
var resourceGroupName = '${projectName}-${environment}-rg'
var storageAccountName = '${projectName}${environment}st'
var cosmosDbAccountName = '${projectName}-${environment}-cosmos'
var searchServiceName = '${projectName}-${environment}-search'
var openAiName = '${projectName}-${environment}-openai'
var appServicePlanName = '${projectName}-${environment}-asp'
var webAppName = '${projectName}-${environment}-app'
var functionAppName = '${projectName}-${environment}-func'
// Resource Group
resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = {
name: resourceGroupName
location: location
}
// Deploy resources
module storage './modules/storage.bicep' = {
scope: rg
name: 'storage-deployment'
params: {
storageAccountName: storageAccountName
location: location
}
}
module cosmos './modules/cosmos.bicep' = {
scope: rg
name: 'cosmos-deployment'
params: {
accountName: cosmosDbAccountName
location: location
}
}
module search './modules/search.bicep' = {
scope: rg
name: 'search-deployment'
params: {
searchServiceName: searchServiceName
location: location
}
}
module openai './modules/openai.bicep' = {
scope: rg
name: 'openai-deployment'
params: {
openAiName: openAiName
location: location
}
}
module webapp './modules/webapp.bicep' = {
scope: rg
name: 'webapp-deployment'
params: {
appServicePlanName: appServicePlanName
webAppName: webAppName
location: location
}
}
module functions './modules/functions.bicep' = {
scope: rg
name: 'functions-deployment'
params: {
functionAppName: functionAppName
storageAccountName: storageAccountName
location: location
}
}Deploy:
az login
az account set --subscription <subscription-id>
az deployment sub create \
--location eastus \
--template-file infrastructure/bicep/main.bicep \
--parameters environment=devinfrastructure/terraform/main.tf
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.0"
}
}
}
provider "azurerm" {
features {}
}
variable "environment" {
default = "dev"
}
variable "location" {
default = "eastus"
}
variable "project_name" {
default = "multilingual-kb"
}
resource "azurerm_resource_group" "main" {
name = "${var.project_name}-${var.environment}-rg"
location = var.location
}
module "storage" {
source = "./modules/storage"
resource_group_name = azurerm_resource_group.main.name
location = var.location
environment = var.environment
}
module "cosmos" {
source = "./modules/cosmos"
resource_group_name = azurerm_resource_group.main.name
location = var.location
environment = var.environment
}
# Additional modules...Deploy:
cd infrastructure/terraform
terraform init
terraform plan
terraform applybackend/api/src/server.ts
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import compression from 'compression';
import { errors } from 'celebrate';
import routes from './routes';
import { errorHandler } from './middleware/errorHandler';
import { requestLogger } from './middleware/requestLogger';
import { rateLimiter } from './middleware/rateLimiter';
import { authenticateJWT } from './middleware/auth';
const app = express();
// Middleware
app.use(helmet());
app.use(cors());
app.use(compression());
app.use(express.json({ limit: '50mb' }));
app.use(express.urlencoded({ extended: true, limit: '50mb' }));
app.use(requestLogger);
app.use(rateLimiter);
// Health check
app.get('/health', (req, res) => {
res.json({ status: 'healthy' });
});
// API routes
app.use('/api/v1', authenticateJWT, routes);
// Error handling
app.use(errors());
app.use(errorHandler);
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});backend/api/src/services/queryService.ts
import { OpenAIClient } from '@azure/openai';
import { SearchClient } from '@azure/search-documents';
import { AgentOrchestrator } from './agentOrchestrator';
export class QueryService {
private openAIClient: OpenAIClient;
private searchClient: SearchClient;
private orchestrator: AgentOrchestrator;
constructor() {
this.openAIClient = new OpenAIClient(
process.env.AZURE_OPENAI_ENDPOINT!,
process.env.AZURE_OPENAI_API_KEY!
);
this.orchestrator = new AgentOrchestrator();
}
async processQuery(query: string, options: any) {
// Step 1: Analyze query
const analysis = await this.orchestrator.analyzeQuery(query);
// Step 2: Retrieve relevant documents
const retrievedDocs = await this.orchestrator.retrieveDocuments(
analysis,
options
);
// Step 3: Build context
const context = await this.orchestrator.buildContext(retrievedDocs);
// Step 4: Generate response
const response = await this.orchestrator.generateResponse(
query,
context,
options
);
// Step 5: Validate and format
const validated = await this.orchestrator.validateResponse(response);
return validated;
}
}backend/processing/src/main.py
from fastapi import FastAPI, UploadFile, File
from processors.document_processor import DocumentProcessor
from extractors.text_extractor import TextExtractor
from extractors.image_extractor import ImageExtractor
from agents.embeddings_agent import EmbeddingsAgent
app = FastAPI()
document_processor = DocumentProcessor()
text_extractor = TextExtractor()
image_extractor = ImageExtractor()
embeddings_agent = EmbeddingsAgent()
@app.post("/api/process/document")
async def process_document(file: UploadFile = File(...)):
# Process document based on type
content = await document_processor.process(file)
# Extract text and images
text_content = await text_extractor.extract(content)
images = await image_extractor.extract(content)
# Generate embeddings
embeddings = await embeddings_agent.generate(text_content)
return {
"document_id": content.id,
"text_chunks": text_content,
"embeddings": embeddings,
"images": images
}
@app.post("/api/embeddings/generate")
async def generate_embeddings(text: str):
embeddings = await embeddings_agent.generate([text])
return {"embeddings": embeddings}backend/processing/src/agents/query_analyzer_agent.py
from langchain.chat_models import AzureChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain.schema import HumanMessage, SystemMessage
class QueryAnalyzerAgent:
def __init__(self):
self.llm = AzureChatOpenAI(
deployment_name=os.getenv("AZURE_OPENAI_DEPLOYMENT_NAME"),
temperature=0.0
)
async def analyze(self, query: str) -> dict:
"""Analyze query to extract intent, keywords, and context."""
prompt = ChatPromptTemplate.from_messages([
SystemMessage(content="""You are a query analysis agent.
Analyze the user's query and extract:
1. Primary intent
2. Keywords and entities
3. Query type (factual, analytical, comparative, etc.)
4. Language
5. Expected response format
"""),
HumanMessage(content=query)
])
response = await self.llm.apredict_messages(
prompt.format_messages()
)
return self._parse_analysis(response.content)
def _parse_analysis(self, content: str) -> dict:
# Parse LLM response into structured format
return {
"intent": "...",
"keywords": [],
"query_type": "...",
"language": "...",
"expected_format": "..."
}backend/functions/DocumentProcessor/index.ts
import { AzureFunction, Context } from "@azure/functions";
import { BlobServiceClient } from "@azure/storage-blob";
import axios from "axios";
const documentProcessor: AzureFunction = async function (
context: Context,
myBlob: any
): Promise<void> {
context.log("Processing document:", context.bindingData.name);
const blobName = context.bindingData.name;
const documentId = generateDocumentId();
// Send to Python processing service
const response = await axios.post(
`${process.env.PROCESSING_SERVICE_URL}/api/process/document`,
{
blobName: blobName,
documentId: documentId,
}
);
// Store results in Cosmos DB
context.bindings.outputDocument = {
id: documentId,
blobName: blobName,
status: "processed",
...response.data,
processedAt: new Date().toISOString(),
};
context.log("Document processed successfully");
};
export default documentProcessor;frontend/src/App.tsx
import React from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { Provider } from 'react-redux';
import { MsalProvider } from '@azure/msal-react';
import { store } from './store';
import { msalInstance } from './auth/msalConfig';
import ChatInterface from './pages/ChatInterface';
import DocumentManager from './pages/DocumentManager';
import Analytics from './pages/Analytics';
function App() {
return (
<Provider store={store}>
<MsalProvider instance={msalInstance}>
<BrowserRouter>
<Routes>
<Route path="/" element={<ChatInterface />} />
<Route path="/documents" element={<DocumentManager />} />
<Route path="/analytics" element={<Analytics />} />
</Routes>
</BrowserRouter>
</MsalProvider>
</Provider>
);
}
export default App;frontend/src/components/ChatInterface.tsx
import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { sendMessage } from '../store/chatSlice';
import MessageList from './MessageList';
import MessageInput from './MessageInput';
import DocumentUpload from './DocumentUpload';
const ChatInterface: React.FC = () => {
const dispatch = useDispatch();
const messages = useSelector((state) => state.chat.messages);
const [language, setLanguage] = useState('en');
const handleSendMessage = (message: string) => {
dispatch(sendMessage({ message, language }));
};
return (
<div className="chat-container">
<div className="chat-header">
<h1>Multilingual Knowledge Base</h1>
<LanguageSelector
value={language}
onChange={setLanguage}
/>
</div>
<MessageList messages={messages} />
<DocumentUpload />
<MessageInput onSend={handleSendMessage} />
</div>
);
};
export default ChatInterface;backend/api/tests/unit/queryService.test.ts
import { QueryService } from '../../src/services/queryService';
describe('QueryService', () => {
let queryService: QueryService;
beforeEach(() => {
queryService = new QueryService();
});
test('should process query successfully', async () => {
const query = 'What are the main topics?';
const result = await queryService.processQuery(query, {});
expect(result).toHaveProperty('response');
expect(result).toHaveProperty('sources');
});
});tests/integration/document-upload.test.ts
import request from 'supertest';
import app from '../../backend/api/src/server';
describe('Document Upload Integration', () => {
test('should upload and process document', async () => {
const response = await request(app)
.post('/api/v1/documents/upload')
.attach('file', './test-files/sample.pdf')
.field('title', 'Test Document')
.expect(202);
expect(response.body).toHaveProperty('documentId');
expect(response.body.status).toBe('processing');
});
});tests/e2e/chat-flow.spec.ts
import { test, expect } from '@playwright/test';
test('complete chat flow', async ({ page }) => {
await page.goto('http://localhost:3001');
// Upload document
await page.setInputFiles('input[type="file"]', './test-files/sample.pdf');
await page.click('button:has-text("Upload")');
// Wait for processing
await page.waitForSelector('.document-status:has-text("Completed")');
// Send query
await page.fill('textarea[name="message"]', 'What is this document about?');
await page.click('button:has-text("Send")');
// Verify response
await page.waitForSelector('.message.assistant');
const response = await page.textContent('.message.assistant');
expect(response).toBeTruthy();
});.github/workflows/deploy.yml
name: Deploy to Azure
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '20'
- name: Setup Python
uses: actions/setup-python@v2
with:
python-version: '3.11'
- name: Build API
run: |
cd backend/api
npm install
npm run build
- name: Build Frontend
run: |
cd frontend
npm install
npm run build
- name: Login to Azure
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Deploy to Azure App Service
uses: azure/webapps-deploy@v2
with:
app-name: ${{ secrets.AZURE_WEBAPP_NAME }}
package: ./backend/api/dist
- name: Deploy Functions
run: |
cd backend/functions
func azure functionapp publish ${{ secrets.AZURE_FUNCTION_APP_NAME }}- Use try-catch blocks consistently
- Log errors with context
- Return user-friendly error messages
- Implement retry logic for transient failures
- Implement caching at multiple levels
- Use connection pooling
- Optimize database queries
- Implement lazy loading
- Validate all inputs
- Use parameterized queries
- Implement rate limiting
- Store secrets in Azure Key Vault
- Use managed identities
- Implement comprehensive logging
- Set up alerts for critical errors
- Monitor performance metrics
- Track usage analytics
- Use TypeScript for type safety
- Follow consistent coding standards
- Write comprehensive tests
- Conduct code reviews
- Use linting and formatting tools
- Set up development environment
- Provision Azure resources
- Implement core backend services
- Develop frontend components
- Integrate Azure AI services
- Implement testing
- Set up CI/CD pipeline
- Deploy to Azure
- Monitor and optimize
For questions or issues, refer to the Architecture Documentation or API Specification.