Digi-Dan is a FastAPI retrieval-augmented generation backend for a portfolio assistant. It stores profile, education, project, skill, and experience facts in Upstash Vector, retrieves the most relevant chunks for a question, and sends the grounded context to either Gemini or DeepSeek for the final answer.
The project is designed to run locally with Uvicorn and deploy as a Python/FastAPI serverless backend on Vercel. It can also be reused as a template for any personal portfolio, resume bot, documentation assistant, or small knowledge-base chatbot.
- FastAPI backend with Vercel-compatible ASGI entrypoint.
- RAG flow using Gemini embeddings and Upstash Vector.
- Generation provider switch between Gemini and DeepSeek.
- JSON, JSONL, and TXT ingestion helper for vector uploads.
- Browser origin allowlist for portfolio frontend access.
X-API-Secretbypass for local testing and server-to-server calls.- Bearer-token protected ingest endpoint.
- Per-client in-memory chat rate limiting.
- GitHub Actions workflow that uploads updated RAG data automatically.
- Source citations returned with each answer.
Portfolio frontend
-> POST /api/chat
-> FastAPI app
-> Gemini embedding model embeds the question
-> Upstash Vector returns relevant source chunks
-> Gemini or DeepSeek generates an answer from the retrieved context
-> API returns answer, provider, and source recordsImportant files:
app.py Vercel/Uvicorn ASGI entrypoint
api/endpoint/application.py FastAPI app, middleware, routes
api/core/config.py Environment variable loading
api/core/models.py Request and response models
api/core/prompts.py Default system prompt
api/query/rag.py Retrieval and prompt assembly
api/query/providers.py Gemini, DeepSeek, and embedding calls
api/tools/vector_store.py Upstash Vector query/upsert helpers
api/tools/upload_vectorstore.py CLI uploader for RAG data
daniel-zachary-rag-data.json Template knowledge base
.github/workflows/upload-rag-data.yml Automatic vector upload workflowBasic root check.
Response:
{
"name": "Digi-Dan",
"status": "ok"
}Health check for local, Vercel, or uptime monitoring.
Response:
{
"status": "ok"
}Protected by the same origin/bypass middleware as other /api/* routes.
Ask the model a question.
Request body:
{
"message": "Where did Daniel get his college education?",
"provider": "gemini",
"top_k": 4,
"namespace": "digi-dan-rag"
}Fields:
message Required question.
provider Optional. "gemini" or "deepseek". Defaults to DEFAULT_PROVIDER.
system Optional per-request system prompt override.
top_k Optional number of retrieved chunks. Defaults to RETRIEVAL_TOP_K.
namespace Optional Upstash namespace. Defaults to UPSTASH_NAMESPACE.Response:
{
"answer": "Daniel studied at President Ramon Magsaysay State University...",
"provider": "gemini",
"sources": [
{
"id": "education-college",
"score": 0.8238814,
"text": "Daniel's college education...",
"metadata": {
"section": "education",
"topic": "college"
}
}
]
}Upload documents into Upstash Vector through the API.
Headers:
Authorization: Bearer BOT_ADMIN_TOKENRequest body:
{
"namespace": "digi-dan-rag",
"documents": [
{
"id": "education-college",
"text": "Daniel studied Computer Engineering...",
"metadata": {
"section": "education"
}
}
]
}Response:
{
"upserted": 1,
"namespace": "digi-dan-rag"
}Copy .env.example to .env for local development.
cp .env.example .envOn Windows PowerShell:
Copy-Item .env.example .envVariables:
APP_NAME
Display name used by the FastAPI app and root endpoint.
CORS_ORIGINS
Comma-separated full browser origins allowed by CORS.
Example: https://your-portfolio.vercel.app,http://localhost:3000
ALLOWED_ORIGIN_DOMAINS
Comma-separated hostnames allowed by the custom origin middleware.
Do not include protocol.
Example: your-portfolio.vercel.app,yourdomain.com,localhost
API_BYPASS_SECRET
Secret sent as X-API-Secret for terminal tests or server-to-server calls.
Never expose this in browser code.
GEMINI_API_KEY
Required for embeddings and Gemini generation.
GEMINI_MODEL
Gemini generation model. Default: gemini-2.5-flash.
GEMINI_EMBEDDING_MODEL
Gemini embedding model used for retrieval and ingestion.
Default: gemini-embedding-001.
EMBEDDING_DIMENSIONS
Embedding vector size. Must match your Upstash Vector index dimension.
Default: 768.
DEEPSEEK_API_KEY
Required only when using provider "deepseek".
DEEPSEEK_BASE_URL
DeepSeek API base URL. Default: https://api.deepseek.com.
DEEPSEEK_MODEL
DeepSeek chat model. Default: deepseek-chat.
UPSTASH_VECTOR_REST_URL
Upstash Vector REST URL.
UPSTASH_VECTOR_REST_TOKEN
Upstash Vector REST token.
UPSTASH_NAMESPACE
Default namespace for retrieval and ingestion.
Example: digi-dan-rag.
BOT_ADMIN_TOKEN
Bearer token required by POST /api/ingest.
DEFAULT_PROVIDER
Default generation provider when a chat request omits provider.
Allowed values: gemini, deepseek.
RETRIEVAL_TOP_K
Default number of source chunks to retrieve.
MAX_CONTEXT_CHARS
Maximum retrieved context characters passed into the generation prompt.
CHAT_RATE_LIMIT
Maximum chat requests per client per window. Set 0 to disable.
CHAT_RATE_LIMIT_WINDOW_SECONDS
Rate limit window size in seconds.Keep .env as plain KEY=value lines only. Do not paste terminal commands into it.
Create and activate a virtual environment:
python -m venv .venvPowerShell:
.\.venv\Scripts\Activate.ps1macOS/Linux:
source .venv/bin/activateInstall dependencies:
python -m pip install -r requirements.txtRun the API:
uvicorn app:app --reload --port 8001Test health from PowerShell:
Invoke-RestMethod `
-Uri "http://127.0.0.1:8001/api/health" `
-Headers @{ "X-API-Secret" = "YOUR_API_BYPASS_SECRET" }Ask a question:
Invoke-RestMethod `
-Uri "http://127.0.0.1:8001/api/chat" `
-Method Post `
-ContentType "application/json" `
-Headers @{ "X-API-Secret" = "YOUR_API_BYPASS_SECRET" } `
-Body '{"message":"Where did Daniel get his college education?","provider":"gemini","top_k":4,"namespace":"digi-dan-rag"}'The included daniel-zachary-rag-data.json uses this structure:
{
"documents": [
{
"id": "education-college",
"text": "Daniel's college education...",
"metadata": {
"section": "education",
"topic": "college"
}
}
]
}Guidelines:
- Keep each document focused on one topic.
- Put likely user wording near the beginning of
text. - Use stable, readable IDs such as
education-collegeorproject-rice-leaf-detection. - Use metadata for filtering, debugging, and source organization.
- Add summary chunks for broad questions such as education, projects, or experience.
Upload the RAG file manually:
python -m api.tools.upload_vectorstore daniel-zachary-rag-data.json --namespace digi-dan-ragSupported upload file types:
.json Either {"documents": [...]} or a raw array of documents
.jsonl One document object per line
.txt Uploaded as one documentThe workflow in .github/workflows/upload-rag-data.yml runs when RAG data or ingestion code changes on main. It validates the JSON and uploads it to Upstash Vector.
Add these GitHub repository secrets:
GEMINI_API_KEY
UPSTASH_VECTOR_REST_URL
UPSTASH_VECTOR_REST_TOKENOptional GitHub repository variables:
UPSTASH_NAMESPACE=digi-dan-rag
GEMINI_EMBEDDING_MODEL=gemini-embedding-001
EMBEDDING_DIMENSIONS=768You can also run the workflow manually from the GitHub Actions tab.
This repo deploys to Vercel as a FastAPI backend. The root app.py exposes the ASGI app:
from api.endpoint.application import create_app
app = create_app()Vercel settings:
Framework Preset: FastAPI
Root Directory: repository root
Build Command: default/empty
Install Command: default/empty
Output Directory: default/emptyAdd the same production environment variables in Vercel Project Settings. At minimum:
GEMINI_API_KEY
UPSTASH_VECTOR_REST_URL
UPSTASH_VECTOR_REST_TOKEN
UPSTASH_NAMESPACE
API_BYPASS_SECRET
BOT_ADMIN_TOKEN
DEFAULT_PROVIDER
ALLOWED_ORIGIN_DOMAINS
CORS_ORIGINSAdd DEEPSEEK_API_KEY only if you want DeepSeek generation.
Recommended pattern: keep secrets on the portfolio server and proxy requests through your portfolio backend.
Example Next.js route:
export async function POST(req: Request) {
const body = await req.json();
const response = await fetch(`${process.env.DIGI_DAN_API_URL}/api/chat`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Secret": process.env.DIGI_DAN_API_SECRET!,
},
body: JSON.stringify({
message: body.message,
provider: body.provider ?? "gemini",
top_k: body.top_k ?? 4,
}),
});
return Response.json(await response.json(), { status: response.status });
}Portfolio environment variables:
DIGI_DAN_API_URL=https://your-digi-dan-backend.vercel.app
DIGI_DAN_API_SECRET=your API_BYPASS_SECRETBrowser code calls your portfolio route:
const response = await fetch("/api/digi-dan", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message }),
});
const data = await response.json();If your portfolio is fully static, you may call /api/chat directly from the browser, but only if your domain is configured in ALLOWED_ORIGIN_DOMAINS and CORS_ORIGINS. Do not send X-API-Secret from browser code.
To adapt this project for your own assistant:
- Fork or copy the repository.
- Rename
APP_NAME. - Replace
daniel-zachary-rag-data.jsonwith your own knowledge base. - Update
api/core/prompts.pywith your assistant personality and response rules. - Create an Upstash Vector index with the same dimension as
EMBEDDING_DIMENSIONS. - Set your local, Vercel, and GitHub Actions environment variables.
- Run the uploader once to populate the vector database.
- Connect your frontend to
POST /api/chat.
Template ideas:
Portfolio assistant
Resume chatbot
Project documentation bot
Small company FAQ bot
Course or seminar knowledge base
Personal second-brain search assistant- Do not commit
.env. - Do not expose
API_BYPASS_SECRETorBOT_ADMIN_TOKENin browser JavaScript. - Keep
/api/ingestserver-only or admin-only. - Use narrow
ALLOWED_ORIGIN_DOMAINSvalues in production. - Rotate secrets if they are ever pasted into public logs or frontend code.
Forbidden origin
The request did not come from an allowed browser origin and did not include a matching X-API-Secret. For terminal tests, send X-API-Secret. For browser tests, configure ALLOWED_ORIGIN_DOMAINS and CORS_ORIGINS.
GEMINI_API_KEY is not configured
The app needs Gemini for embeddings even when generation uses DeepSeek.
Unexpected or missing answers
Check the returned sources. If the right source is missing, improve the relevant RAG chunk wording, add a summary chunk, increase top_k, and re-upload the RAG data.
Wrong namespace
Set UPSTASH_NAMESPACE or pass "namespace" in the chat request.
Vercel unmatched function pattern
Use the FastAPI preset with the root app.py entrypoint. Do not add a stale functions pattern for app.py or api/index.py.