Skip to content

Latest commit

 

History

History
893 lines (738 loc) · 21.4 KB

File metadata and controls

893 lines (738 loc) · 21.4 KB

Implementation Guide - Multilingual Agentic Retrieval System

Table of Contents

  1. Getting Started
  2. Development Environment Setup
  3. Azure Resources Provisioning
  4. Backend Implementation
  5. Frontend Implementation
  6. Deployment
  7. Testing Strategy
  8. Best Practices

Getting Started

Prerequisites

  • 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

Project Structure

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

Development Environment Setup

1. Clone and Initialize Repository

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 install

2. Configure Environment Variables

Create .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=1000

backend/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-key

frontend/.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-id

3. Docker Compose Setup

docker-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:

4. Start Development Environment

# 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 start

Azure Resources Provisioning

Option 1: Using Bicep

infrastructure/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=dev

Option 2: Using Terraform

infrastructure/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 apply

Backend Implementation

1. Node.js API Server

backend/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;
  }
}

2. Python Processing Service

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": "..."
        }

3. Azure Functions

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 Implementation

1. React Application Structure

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;

Testing Strategy

1. Unit Tests

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');
  });
});

2. Integration Tests

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');
  });
});

3. End-to-End Tests

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();
});

Deployment

CI/CD Pipeline

.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 }}

Best Practices

1. Error Handling

  • Use try-catch blocks consistently
  • Log errors with context
  • Return user-friendly error messages
  • Implement retry logic for transient failures

2. Performance Optimization

  • Implement caching at multiple levels
  • Use connection pooling
  • Optimize database queries
  • Implement lazy loading

3. Security

  • Validate all inputs
  • Use parameterized queries
  • Implement rate limiting
  • Store secrets in Azure Key Vault
  • Use managed identities

4. Monitoring

  • Implement comprehensive logging
  • Set up alerts for critical errors
  • Monitor performance metrics
  • Track usage analytics

5. Code Quality

  • Use TypeScript for type safety
  • Follow consistent coding standards
  • Write comprehensive tests
  • Conduct code reviews
  • Use linting and formatting tools

Next Steps

  1. Set up development environment
  2. Provision Azure resources
  3. Implement core backend services
  4. Develop frontend components
  5. Integrate Azure AI services
  6. Implement testing
  7. Set up CI/CD pipeline
  8. Deploy to Azure
  9. Monitor and optimize

For questions or issues, refer to the Architecture Documentation or API Specification.