Repository ini adalah AI Service Backend untuk aplikasi BeinBout. Fungsinya: menjalankan analisis psikologis berbasis LLM + Tool Calling + RAG untuk:
- Initial Persona
- Daily Journal Reflection
- Weekly Checkup Analysis
- CFS250D6Y309 - Muhamad Nadira Fabyansyah (Project Manager & AI Engineer)
- CFS134D6Y504 - Agung Arya Dwipa Laksana (Back-end Developer)
- CFS134D6Y415 - Kaka Kendra Nugraha (Front-end Developer)
- CFS296D6Y591 - Muhammad Irfan Daffa' Ardianto (Front-end Developer)
- CFS134D6Y584 - Denisyal Hendra Putra (Front-end Developer)
- Structured LLM Output (Pydantic): response AI diparse ke schema ketat.
- Mandatory RAG Tool Calling: model diarahkan selalu memanggil
retrieve_informationsebelum final answer. - Vector Retrieval: embedding via Azure OpenAI + similarity search pgvector HNSW.
- Secured API Call: header
x-ai-api-callwajib untuk route/api/*. - Health Endpoint:
GET /healthuntuk readiness check.
| Layer | Stack |
|---|---|
| Runtime | Python 3.12 |
| API Framework | FastAPI |
| Data Validation | Pydantic v2 |
| ORM | SQLModel + SQLAlchemy |
| Vector Store | PostgreSQL + pgvector |
| LLM/Embedding | Azure OpenAI |
| Logging | Loguru |
| Container | Docker |
flowchart TD
A[Client / Backend Core] -->|POST /api/* + x-ai-api-call| B[FastAPI Router]
B --> C[chat_agent]
C -->|Tool Call| D[retrieve_information]
D --> E[embedding input]
E --> F[(Azure OpenAI Embedding)]
D --> G[(PostgreSQL + pgvector)]
C --> H[(Azure OpenAI Chat)]
H --> C
C --> I[Parsed Structured Output]
I --> A
AI FastAPI - Production/
├── main.py
├── requirements.txt
├── Dockerfile
├── api/
│ ├── deps.py
│ └── route/
│ ├── initial_persona.py
│ ├── daily_journal.py
│ └── weekly_checkup.py
├── core/
│ ├── config.py
│ ├── database.py
│ └── ai/
│ ├── llm_agent.py
│ └── embed.py
├── models/
│ └── rag_db.py
├── schemas/
│ ├── payload_initial_persona.py
│ ├── payload_daily_journal.py
│ ├── payload_weekly_checkup.py
│ ├── llm_initial_persona.py
│ ├── llm_daily_journal.py
│ ├── llm_weekly_checkup.py
│ └── llm_tool_calling.py
└── utils/
├── rag_search.py
└── ai/
├── create_tool.py
├── execute_tool.py
├── tool.py
└── tools/
└── retrieve_information.py
Copy dari .env.example:
# Project Setting
BEINBOUT_AI_CALL_KEY=""
SERVICE_MODE="development" # development | production
# PostgreSQL PGVector
DATABASE_URL=""
# Azure Foundry AI Service
AZURE_AI_KEY_CREDENTIALS=""
AZURE_AI_ENDPOINT=""
AZURE_AI_API_VERSION=""
AZURE_AI_EMBEDDING_MODEL_NAME=""
AZURE_AI_EMBEDDING_DEPLOYMENT=""
AZURE_AI_LLM_MODEL_NAME=""
AZURE_AI_LLM_DEPLOYMENT=""Di implementasi saat ini, model yang dipakai oleh service adalah
AZURE_AI_LLM_MODEL_NAMEdan embedding model adalahAZURE_AI_EMBEDDING_MODEL_NAME.
- Python 3.12+
- PostgreSQL + extension
vector - Azure OpenAI credentials
git clone https://github.com/BeinBout/Project-Capstone-AI.git
cd "Project-Capstone-AI"
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtuvicorn main:app --reload --host 0.0.0.0 --port 8000Endpoints:
- API Base:
http://localhost:8000 - Swagger:
http://localhost:8000/docs - ReDoc:
http://localhost:8000/redoc - Health:
http://localhost:8000/health
docker build -t beinbout-ai-service .
docker run --rm -p 8000:8000 --env-file .env beinbout-ai-serviceSemua endpoint /api/* wajib mengirim header:
x-ai-api-call: <BEINBOUT_AI_CALL_KEY>Jika invalid → 403 Request Forbidden.
Detail schema berikut diringkas dari Pydantic model pada folder
schemas/.
Request JSON Schema (ringkas):
{
"type": "object",
"required": ["user_context", "answers", "total_score", "dominant_categories"],
"properties": {
"user_context": {
"type": "object",
"required": ["umur", "berat_badan", "tinggi_badan", "bmi_calc"],
"properties": {
"umur": { "type": "integer", "minimum": 0, "maximum": 120 },
"berat_badan": { "type": "integer", "exclusiveMinimum": 0 },
"tinggi_badan": { "type": "integer", "exclusiveMinimum": 0 },
"bmi_calc": { "type": "number", "exclusiveMinimum": 0 }
}
},
"answers": {
"type": "array",
"items": {
"type": "object",
"required": ["category", "question_text", "selected_option", "emotion_tag", "score_value"],
"properties": {
"category": { "type": "string" },
"question_text": { "type": "string" },
"selected_option": { "type": "string" },
"emotion_tag": { "type": "string" },
"score_value": { "type": "integer" }
}
}
},
"total_score": { "type": "integer" },
"dominant_categories": { "type": "array", "items": { "type": "string" } }
}
}Response shape (ringkas):
ai_summary: stringai_insights: { risk_level, risk_score, dominant_stressor[], personality_summary, recommendations[], progress_status, weekly_insight, ai_low_confidence }
Request JSON Schema (ringkas):
{
"type": "object",
"required": ["user_context", "current_persona", "journal"],
"properties": {
"user_context": {
"type": "object",
"required": ["umur", "berat_badan", "tinggi_badan"],
"properties": {
"umur": { "type": "integer" },
"berat_badan": { "type": "integer" },
"tinggi_badan": { "type": "integer" }
}
},
"current_persona": {
"type": "object",
"required": ["risk_level", "risk_score", "dominant_stressor"],
"properties": {
"risk_level": { "type": "string" },
"risk_score": { "type": "integer" },
"dominant_stressor": { "type": "array", "items": { "type": "string" } }
}
},
"recent_trend": {
"type": ["object", "null"],
"properties": {
"last_3_days_avg_mood": { "type": "number" },
"last_3_days_avg_sleep": { "type": "number" },
"consecutive_negative_days": { "type": "integer" }
}
},
"journal": {
"type": "object",
"required": ["mood", "mood_intensity", "sleep_duration_hours", "sleep_quality", "content"],
"properties": {
"mood": { "type": "string" },
"mood_intensity": { "type": "integer" },
"sleep_duration_hours": { "type": "number" },
"sleep_quality": { "type": "string" },
"content": { "type": "string" }
}
}
}
}Response shape (ringkas):
ai_reflection: stringai_tags: string[] (max 3)ai_sentiment_score: number (-1.0 s/d 1.0)ai_anomaly_detected: booleanai_anomaly_type: "sleep_deficit" | "mood_drop" | "stress_spike" | nullai_low_confidence: boolean
Request JSON Schema (ringkas):
{
"type": "object",
"required": ["user_context", "current_persona", "weekly_metrics", "checkup_answers", "dominant_categories"],
"properties": {
"user_context": {
"type": "object",
"required": ["umur", "berat_badan", "tinggi_badan"],
"properties": {
"umur": { "type": "integer" },
"berat_badan": { "type": "integer" },
"tinggi_badan": { "type": "integer" }
}
},
"current_persona": {
"type": "object",
"required": ["risk_level", "risk_score", "dominant_stressor", "personality_summary"],
"properties": {
"risk_level": { "type": "string" },
"risk_score": { "type": "integer" },
"dominant_stressor": { "type": "array", "items": { "type": "string" } },
"personality_summary": { "type": "string" }
}
},
"weekly_metrics": {
"type": "object",
"required": ["avg_mood_intensity", "avg_sleep_hours", "dominant_mood", "negative_sentiment_ratio", "journal_entries_count", "anomaly_count"],
"properties": {
"avg_mood_intensity": { "type": "number" },
"avg_sleep_hours": { "type": "number" },
"dominant_mood": { "type": "string" },
"negative_sentiment_ratio": { "type": "number" },
"journal_entries_count": { "type": "integer" },
"anomaly_count": { "type": "integer" }
}
},
"checkup_answers": {
"type": "array",
"items": {
"type": "object",
"required": ["category", "question_text", "selected_option", "emotion_tag", "score_value"],
"properties": {
"category": { "type": "string" },
"question_text": { "type": "string" },
"selected_option": { "type": "string" },
"emotion_tag": { "type": "string" },
"score_value": { "type": "integer" }
}
}
},
"dominant_categories": { "type": "array", "items": { "type": "string" } }
}
}Response shape (ringkas):
ai_summary: stringai_insights: { risk_level, risk_score, dominant_stressor[], personality_summary, recommendations[], progress_status, weekly_insight, ai_low_confidence }
| Column | Type | Constraint | Keterangan |
|---|---|---|---|
id |
integer | PK, auto increment | ID dokumen |
content |
text | NOT NULL | Teks chunk knowledge base |
embedding |
vector(1536) | NOT NULL | Embedding vector |
extra_info |
jsonb | default {} |
Metadata dokumen |
source_type |
varchar(50) | NOT NULL | Sumber dataset |
created_at |
timestamptz | default now() |
Timestamp insert |
- Extension:
CREATE EXTENSION IF NOT EXISTS vector - Index:
CREATE INDEX IF NOT EXISTS ix_rag_db_embedding_hnsw
ON rag_db USING hnsw (embedding vector_ip_ops)- Logging level:
development→DEBUG- selain itu →
WARNING
- CORS saat ini masih
allow_origins=["*"].
- Restrict CORS origin ke domain FE resmi.
- Simpan secrets di secret manager (bukan plain
.envdi server). - Tambahkan rate limit + WAF/API gateway policy.
- Observability: metrics, tracing, error alert.
- Buat pipeline ingestion RAG yang repeatable (chunking, embed, upsert, dedup).
README ini fokus ke AI Service (bukan Core Backend utama). Jadi dokumentasi endpoint dan schema di sini khusus untuk pipeline analitik AI BeinBout.