Skip to content

labeebkm/multi-tenant-ai-chatbot

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GreenTiq AI Platform Backend

Production-ready multi-tenant AI chatbot backend built with FastAPI and Amazon Bedrock.

Project Structure

greentiq-backend/
|-- app/
|   |-- main.py                  # FastAPI app entry point
|   |-- config.py                # Settings loaded from environment variables
|   |-- core/
|   |   |-- dependencies.py      # Dependency injection for singleton services
|   |   |-- exceptions.py        # Custom HTTP exceptions
|   |   `-- logging.py           # Structured JSON logging
|   |-- models/
|   |   `-- schemas.py           # Pydantic request and response models
|   |-- routes/
|   |   |-- chat.py              # POST /chat
|   |   |-- ingest.py            # POST /ingest, POST /ingest/file
|   |   `-- tenant.py            # Tenant management endpoints
|   `-- services/
|       |-- agent_service.py         # Agent persona prompt selection
|       |-- bedrock_service.py       # Bedrock runtime and KB API wrapper
|       |-- conversation_service.py  # Session history storage
|       |-- rag_service.py           # Retrieval-augmented generation pipeline
|       |-- s3_service.py            # Tenant-isolated S3 uploads
|       |-- tenant_service.py        # Tenant lookup and caching
|       `-- usage_service.py         # Token usage tracking
|-- tests/
|-- .env.example
|-- .gitignore
|-- requirements.txt
`-- README.md

Prerequisites

Before running the backend, set up the following AWS resources.

1. Enable Bedrock model access

Enable these models in Bedrock model access:

  • Anthropic Claude 3 Sonnet
  • Amazon Titan Text Embeddings V2

2. Create an S3 bucket

Example:

Bucket name: greentiq-dev-bucket
Region: us-east-1
Block all public access: ON

All tenant documents are stored under tenant-specific prefixes such as acme-corp/docs/.

3. Create a Bedrock Knowledge Base per tenant

Example:

Name: greentiq-kb-acme-corp
Embedding model: Amazon Titan Text Embeddings V2
Data source: s3://greentiq-dev-bucket/acme-corp/docs/
Vector store: OpenSearch Serverless collection

Save the Knowledge Base ID and Data Source ID for tenant registration.

4. Create DynamoDB tables

The backend can run locally without these tables, but persistence is limited.

Table 1: greentiq-tenants-dev
  Partition key: tenant_id (String)

Table 2: greentiq-usage-dev
  Partition key: tenant_id (String)
  Sort key: request_id (String)

Table 3: greentiq-conversations-dev
  Partition key: tenant_id (String)
  Sort key: session_id (String)

Notes:

  • Without the tenant table, registered tenants only live in memory for the current process.
  • Without the usage table, /tenants/{tenant_id}/usage cannot persist usage data.
  • Without the conversations table, chat memory falls back to in-process memory and is lost on restart.

Local Setup

# 1. Clone the project
git clone https://github.com/your-org/greentiq-backend.git
cd greentiq-backend

# 2. Create and activate a virtual environment
python -m venv venv

# Windows
venv\Scripts\activate

# Mac/Linux
source venv/bin/activate

# 3. Install dependencies
pip install -r requirements.txt

# 4. Configure environment variables
cp .env.example .env

# 5. Configure AWS CLI credentials if needed
aws configure

# 6. Run the API
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000

Server URL: http://localhost:8000

OpenAPI docs: http://localhost:8000/docs

Step-by-Step Usage

Step 1 - Register a tenant

curl -X POST http://localhost:8000/tenants/register \
  -H "Content-Type: application/json" \
  -d '{
    "tenant_id": "acme-corp",
    "knowledge_base_id": "YOUR_KB_ID",
    "data_source_id": "YOUR_DS_ID",
    "custom_system_prompt": null
  }'

Example response:

{
  "tenant_id": "acme-corp",
  "knowledge_base_id": "XYZABC123",
  "data_source_id": "DS456DEF",
  "s3_prefix": "acme-corp/docs/",
  "custom_system_prompt": null,
  "created_at": "2026-03-27T10:00:00+00:00"
}

Step 2 - Ingest knowledge

Ingest raw text

curl -X POST http://localhost:8000/ingest \
  -H "Content-Type: application/json" \
  -d '{
    "tenant_id": "acme-corp",
    "source_type": "text",
    "source_value": "Our return policy allows returns within 30 days of purchase."
  }'

Ingest a website

curl -X POST http://localhost:8000/ingest \
  -H "Content-Type: application/json" \
  -d '{
    "tenant_id": "acme-corp",
    "source_type": "website",
    "source_value": "https://www.acmecorp.com/support"
  }'

Website ingestion notes:

  • Only valid http:// and https:// URLs are accepted.
  • If the site cannot be fetched, the API returns 502 instead of ingesting an error string.
  • If no readable text can be extracted, the API returns 422.

Upload a file

curl -X POST http://localhost:8000/ingest/file \
  -F "tenant_id=acme-corp" \
  -F "file=@/path/to/product-manual.pdf"

Accepted file types:

  • PDF
  • TXT
  • DOC
  • DOCX
  • HTML
  • Markdown

File upload limits:

  • Maximum size: 50 MB
  • Unsupported file types return 415

Example response:

{
  "tenant_id": "acme-corp",
  "job_id": "ingest-job-abc123",
  "status": "accepted",
  "message": "Content uploaded to S3. Knowledge base sync started in background. Use GET /ingest/status to check progress.",
  "s3_key": "acme-corp/docs/text_20260327_abc12345.txt"
}

Check ingestion status

curl "http://localhost:8000/ingest/status?tenant_id=acme-corp&job_id=ingest-job-abc123"

Example response:

{
  "tenant_id": "acme-corp",
  "job_id": "ingest-job-abc123",
  "status": "COMPLETE",
  "complete": true
}

Wait for "complete": true before sending chat requests.

Step 3 - Chat

Support agent

curl -X POST http://localhost:8000/chat \
  -H "Content-Type: application/json" \
  -d '{
    "tenant_id": "acme-corp",
    "agent_type": "support",
    "message": "What is your return policy?"
  }'

Sales agent

curl -X POST http://localhost:8000/chat \
  -H "Content-Type: application/json" \
  -d '{
    "tenant_id": "acme-corp",
    "agent_type": "sales",
    "message": "Why should I choose your product?"
  }'

Conversation memory

# First message - omit session_id to start a new session
curl -X POST http://localhost:8000/chat \
  -H "Content-Type: application/json" \
  -d '{
    "tenant_id": "acme-corp",
    "agent_type": "support",
    "message": "What plans do you offer?"
  }'

# Reuse the returned session_id for follow-up messages
curl -X POST http://localhost:8000/chat \
  -H "Content-Type: application/json" \
  -d '{
    "tenant_id": "acme-corp",
    "agent_type": "support",
    "message": "Tell me more about the second one",
    "session_id": "SESSION_ID_FROM_PREVIOUS_RESPONSE"
  }'

Conversation behavior:

  • If session_id is omitted, the backend creates one automatically.
  • Conversation history is persisted in DynamoDB when greentiq-conversations-dev exists.
  • Without the conversation table, memory still works within the current process.

Example response:

{
  "tenant_id": "acme-corp",
  "agent_type": "support",
  "response": "Our return policy allows returns within 30 days of purchase.",
  "session_id": "sess_acme-corp_f3a91c2d4e5f",
  "tokens_used": {
    "input_tokens": 420,
    "output_tokens": 85,
    "total_tokens": 505
  },
  "source_chunks_used": 3,
  "fallback_used": false
}

If fallback_used is true, the model did not find enough relevant KB context above the configured relevance threshold.

Step 4 - Custom agent prompt

curl -X PUT http://localhost:8000/tenants/acme-corp/prompt \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "You are Aria, Acme Corp'\''s friendly AI assistant. Always be warm, professional, and concise."
  }'

Then send chat requests with "agent_type": "custom".

Prompt update notes:

  • Prompt changes update the in-memory tenant cache immediately.
  • If DynamoDB is unavailable in local development, the custom prompt still works for the current process.

Step 5 - View usage

curl http://localhost:8000/tenants/acme-corp/usage

Example response:

{
  "tenant_id": "acme-corp",
  "total_requests": 47,
  "total_input_tokens": 19740,
  "total_output_tokens": 3995,
  "total_tokens": 23735
}

Running Tests

# Run all tests
pytest tests/ -v

# Quieter output
pytest tests/ -q

API Reference Summary

Method Endpoint Description
GET /health Health check
POST /tenants/register Register a new tenant
GET /tenants/{tenant_id} Get tenant config
PUT /tenants/{tenant_id}/prompt Update custom agent prompt
GET /tenants/{tenant_id}/usage Get token usage stats
POST /chat Send a chat message
POST /ingest Ingest text or website content
POST /ingest/file Upload a tenant document
GET /ingest/status Check ingestion job status
GET /ingest/files List ingested tenant files

Environment Variables Reference

Variable Default Description
AWS_REGION us-east-1 AWS region
S3_BUCKET_NAME greentiq-dev-bucket Shared S3 bucket for tenant documents
BEDROCK_MODEL_ID anthropic.claude-3-sonnet-20240229-v1:0 Default Bedrock chat model
BEDROCK_EMBED_MODEL_ID amazon.titan-embed-text-v2:0 Embedding model for KBs
BEDROCK_MAX_TOKENS 1024 Max output tokens per response
BEDROCK_TOP_K_RESULTS 5 Number of KB chunks to retrieve
BEDROCK_RELEVANCE_THRESHOLD 0.4 Minimum retrieval score to use a chunk
DYNAMODB_TENANT_TABLE greentiq-tenants-dev Tenant config table
DYNAMODB_USAGE_TABLE greentiq-usage-dev Usage tracking table
DYNAMODB_CONVERSATION_TABLE greentiq-conversations-dev Conversation memory table
DEBUG false Enables verbose error details

DEBUG also accepts common deployment values such as dev, debug, release, and production.

Common Issues and Fixes

"Tenant not found"

Register the tenant first with POST /tenants/register.

"Bedrock error: AccessDeniedException"

Your AWS principal does not have sufficient Bedrock permissions.

fallback_used: true on every response

The KB may still be syncing, or the relevance threshold is too high. Check /ingest/status and consider lowering BEDROCK_RELEVANCE_THRESHOLD.

"Unsupported file type"

Use one of the accepted file formats for /ingest/file: PDF, TXT, DOC, DOCX, HTML, or Markdown.

"Failed to fetch website content"

Confirm the URL is reachable over HTTP or HTTPS from the backend environment.

Conversation memory disappears after restart

Create the greentiq-conversations-dev table so session history persists outside process memory.

Usage totals are always zero in dev

Create the usage table, or confirm your AWS credentials allow DynamoDB access.

To launch backend

uvicorn app.main:app --reload

About

Multi-tenant chatbot platform where each business gets their own private AI assistant trained on their documents. Built on FastAPI and AWS Bedrock with RAG, agent personas, and per-tenant token tracking.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages