INIT(app): initial setup
- Initialize project structure - Add base application files
This commit is contained in:
58
.gitignore
vendored
Normal file
58
.gitignore
vendored
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
ENV/
|
||||||
|
.venv
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
.pytest_cache/
|
||||||
|
.coverage
|
||||||
|
htmlcov/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
*.egg-info/
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
*.env
|
||||||
|
|
||||||
|
# Chainlit
|
||||||
|
.chainlit/
|
||||||
|
.files/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
logs/
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
docker-compose.override.yml
|
||||||
|
|
||||||
|
# Kubernetes secrets (local only)
|
||||||
|
*-secret.yaml
|
||||||
|
!deploy/vault/*.yaml
|
||||||
|
|
||||||
|
# Node modules (if any frontend added)
|
||||||
|
node_modules/
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
tmp/
|
||||||
|
temp/
|
||||||
|
*.tmp
|
||||||
|
|
||||||
261
README.md
Normal file
261
README.md
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
# MAS (Multi-Agent System)
|
||||||
|
|
||||||
|
MAS is a unified UI and orchestration layer for multiple AI agents (similar to ChatGPT, Claude, Gemini), running on your own Kubernetes cluster.
|
||||||
|
|
||||||
|
## 🎯 Architecture
|
||||||
|
|
||||||
|
### Agents
|
||||||
|
- **Claude Code (Orchestrator)**: overall coordinator & DevOps expert
|
||||||
|
- **Qwen Backend**: backend engineer (FastAPI, Node.js)
|
||||||
|
- **Qwen Frontend**: frontend engineer (Next.js, React)
|
||||||
|
- **Qwen SRE**: monitoring & reliability engineer
|
||||||
|
|
||||||
|
### Tech stack
|
||||||
|
- **Backend**: LangGraph + LangChain + FastAPI
|
||||||
|
- **UI**: Chainlit (chat-style UI)
|
||||||
|
- **Database**: PostgreSQL (CNPG)
|
||||||
|
- **Cache**: Redis
|
||||||
|
- **LLMs**: Claude API + **Groq Llama 3.x** (OpenAI-compatible API)
|
||||||
|
- **Deploy**: Kubernetes + ArgoCD
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Local development
|
||||||
|
|
||||||
|
### 1. Run with Docker Compose
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd deploy/docker
|
||||||
|
|
||||||
|
# Copy or create .env and fill in your API keys
|
||||||
|
# (ANTHROPIC_API_KEY, GROQ_API_KEY, etc.)
|
||||||
|
|
||||||
|
# Start the full stack
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
# Tail logs
|
||||||
|
docker compose logs -f mas
|
||||||
|
```
|
||||||
|
|
||||||
|
Open: `http://localhost:8000`
|
||||||
|
|
||||||
|
### 2. Run backend directly (Python)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd services/backend
|
||||||
|
|
||||||
|
# Create venv
|
||||||
|
python -m venv venv
|
||||||
|
source venv/bin/activate # Windows: venv\Scripts\activate
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
cp .env.example .env
|
||||||
|
# Edit .env and set your API keys
|
||||||
|
|
||||||
|
# Run Chainlit app
|
||||||
|
chainlit run chainlit_app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ☸️ Kubernetes deployment
|
||||||
|
|
||||||
|
### 1. Create namespace and secrets
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl create namespace mas
|
||||||
|
|
||||||
|
kubectl create secret generic mas-api-keys \
|
||||||
|
--from-literal=anthropic-api-key=YOUR_CLAUDE_KEY \
|
||||||
|
--from-literal=openai-api-key=YOUR_OPENAI_KEY \
|
||||||
|
--from-literal=google-api-key=YOUR_GEMINI_KEY \
|
||||||
|
-n mas
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Deploy via ArgoCD
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create ArgoCD Application
|
||||||
|
kubectl apply -f deploy/argocd/mas.yaml
|
||||||
|
|
||||||
|
# Sync and check status
|
||||||
|
argocd app sync mas
|
||||||
|
argocd app get mas
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Deploy from your server (example)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# SSH into your k3s master
|
||||||
|
ssh oracle-master
|
||||||
|
|
||||||
|
# Apply ArgoCD Application
|
||||||
|
sudo kubectl apply -f /path/to/deploy/argocd/mas.yaml
|
||||||
|
|
||||||
|
# Check status
|
||||||
|
sudo kubectl get pods -n mas
|
||||||
|
sudo kubectl logs -f deployment/mas -n mas
|
||||||
|
```
|
||||||
|
|
||||||
|
Ingress example (if configured): `https://mas.mayne.vcn`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 UI customization
|
||||||
|
|
||||||
|
### Chainlit theme & behavior
|
||||||
|
|
||||||
|
You can customize the UI via `services/backend/.chainlit`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[UI]
|
||||||
|
name = "MAS"
|
||||||
|
show_readme_as_default = true
|
||||||
|
default_collapse_content = true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Agent prompts
|
||||||
|
|
||||||
|
System prompts for each agent live in `services/backend/agents.py`.
|
||||||
|
You can tune:
|
||||||
|
- how the **Orchestrator** routes tasks
|
||||||
|
- coding style of backend/frontend agents
|
||||||
|
- SRE troubleshooting behavior
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Observability
|
||||||
|
|
||||||
|
### Prometheus ServiceMonitor (example)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: monitoring.coreos.com/v1
|
||||||
|
kind: ServiceMonitor
|
||||||
|
metadata:
|
||||||
|
name: mas
|
||||||
|
namespace: mas
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: mas
|
||||||
|
endpoints:
|
||||||
|
- port: http
|
||||||
|
path: /metrics
|
||||||
|
```
|
||||||
|
|
||||||
|
### Grafana dashboards
|
||||||
|
|
||||||
|
Recommended panels:
|
||||||
|
- LangGraph workflow metrics
|
||||||
|
- Per-agent latency & error rate
|
||||||
|
- Token usage and cost estimates
|
||||||
|
- Backend API latency & 5xx rate
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Advanced features
|
||||||
|
|
||||||
|
### 1. MCP (Model Context Protocol) with Claude
|
||||||
|
|
||||||
|
Using Claude Code as Orchestrator, MAS can access:
|
||||||
|
- Filesystem (read/write project files)
|
||||||
|
- Git (status, commit, push, PR)
|
||||||
|
- SSH (run remote commands on your servers)
|
||||||
|
- PostgreSQL (schema inspection, migrations, queries)
|
||||||
|
- Kubernetes (kubectl via MCP tool)
|
||||||
|
|
||||||
|
This allows fully automated workflows like:
|
||||||
|
- “Create a new service, add deployment manifests, and deploy to k3s.”
|
||||||
|
- “Debug failing pods and propose a fix, then open a PR.”
|
||||||
|
|
||||||
|
### 2. Multi-agent collaboration (LangGraph)
|
||||||
|
|
||||||
|
Typical workflow:
|
||||||
|
|
||||||
|
```text
|
||||||
|
User request
|
||||||
|
↓
|
||||||
|
Claude Orchestrator
|
||||||
|
↓ decides which agent(s) to call
|
||||||
|
Backend Dev → Frontend Dev → SRE
|
||||||
|
↓
|
||||||
|
Claude Orchestrator (review & summary)
|
||||||
|
↓
|
||||||
|
Final answer to user
|
||||||
|
```
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
- Full‑stack feature (API + UI + monitoring)
|
||||||
|
- Infra rollout (Harbor, Tekton, CNPG, MetalLB) with validation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Usage examples
|
||||||
|
|
||||||
|
### Backend API request
|
||||||
|
|
||||||
|
```text
|
||||||
|
User: "Create a signup API with FastAPI.
|
||||||
|
Use PostgreSQL and JWT tokens."
|
||||||
|
|
||||||
|
🎼 Orchestrator:
|
||||||
|
→ routes to Qwen Backend
|
||||||
|
|
||||||
|
⚙️ Qwen Backend:
|
||||||
|
→ generates FastAPI router, Pydantic models, DB schema, JWT logic
|
||||||
|
|
||||||
|
🎼 Orchestrator:
|
||||||
|
→ reviews, suggests improvements, and outputs final code snippet & file layout
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend component request
|
||||||
|
|
||||||
|
```text
|
||||||
|
User: "Build a responsive dashboard chart component using Recharts."
|
||||||
|
|
||||||
|
🎼 Orchestrator:
|
||||||
|
→ routes to Qwen Frontend
|
||||||
|
|
||||||
|
🎨 Qwen Frontend:
|
||||||
|
→ generates a Next.js/React component with TypeScript and responsive styles
|
||||||
|
|
||||||
|
🎼 Orchestrator:
|
||||||
|
→ explains how to integrate it into your existing app
|
||||||
|
```
|
||||||
|
|
||||||
|
### Infra / SRE request
|
||||||
|
|
||||||
|
```text
|
||||||
|
User: "Prometheus is firing high memory alerts for the PostgreSQL pod.
|
||||||
|
Help me stabilize it."
|
||||||
|
|
||||||
|
🎼 Orchestrator:
|
||||||
|
→ routes to Qwen SRE
|
||||||
|
|
||||||
|
📊 Qwen SRE:
|
||||||
|
→ analyzes metrics & logs (conceptually),
|
||||||
|
proposes tuning (Postgres config, indexes, pooler),
|
||||||
|
and suggests alert threshold adjustments.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🤝 Contributing
|
||||||
|
|
||||||
|
Contributions are welcome:
|
||||||
|
- New agents (e.g., data engineer, security engineer)
|
||||||
|
- New tools (Harbor, Tekton, CNPG, MetalLB integrations)
|
||||||
|
- Better prompts and workflows
|
||||||
|
- Docs and examples
|
||||||
|
|
||||||
|
Feel free to open issues or PRs in your Git repository.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|
||||||
21
deploy/argocd/mas.yaml
Normal file
21
deploy/argocd/mas.yaml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: mas
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
source:
|
||||||
|
repoURL: https://gitea0213.kro.kr/bluemayne/mas.git
|
||||||
|
targetRevision: HEAD
|
||||||
|
path: deploy/k8s
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: mas
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
|
|
||||||
24
deploy/docker/Dockerfile
Normal file
24
deploy/docker/Dockerfile
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 시스템 의존성 설치
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
build-essential \
|
||||||
|
curl \
|
||||||
|
git \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Python 의존성 설치
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# 애플리케이션 코드 복사
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Chainlit 포트
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
# Chainlit 실행
|
||||||
|
CMD ["chainlit", "run", "chainlit_app.py", "--host", "0.0.0.0", "--port", "8000"]
|
||||||
|
|
||||||
73
deploy/docker/docker-compose.yml
Normal file
73
deploy/docker/docker-compose.yml
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
mas:
|
||||||
|
build: ../../services/backend
|
||||||
|
container_name: mas
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
environment:
|
||||||
|
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
|
||||||
|
# Groq API (OpenAI-compatible)
|
||||||
|
- GROQ_API_KEY=${GROQ_API_KEY}
|
||||||
|
- GROQ_API_BASE=${GROQ_API_BASE:-https://api.groq.com/openai/v1}
|
||||||
|
# (optional) keep other providers
|
||||||
|
- OPENAI_API_KEY=${OPENAI_API_KEY}
|
||||||
|
- GOOGLE_API_KEY=${GOOGLE_API_KEY}
|
||||||
|
- DATABASE_URL=postgresql+asyncpg://mas:mas@postgres:5432/mas
|
||||||
|
- REDIS_URL=redis://redis:6379/0
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
- postgres
|
||||||
|
- ollama
|
||||||
|
volumes:
|
||||||
|
- ../../services/backend:/app
|
||||||
|
networks:
|
||||||
|
- mas-network
|
||||||
|
|
||||||
|
# Ollama (로컬 Qwen 모델)
|
||||||
|
ollama:
|
||||||
|
image: ollama/ollama:latest
|
||||||
|
container_name: mas-ollama
|
||||||
|
ports:
|
||||||
|
- "11434:11434"
|
||||||
|
volumes:
|
||||||
|
- ollama-data:/root/.ollama
|
||||||
|
networks:
|
||||||
|
- mas-network
|
||||||
|
|
||||||
|
# PostgreSQL
|
||||||
|
postgres:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
container_name: mas-postgres
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: mas
|
||||||
|
POSTGRES_USER: mas
|
||||||
|
POSTGRES_PASSWORD: mas
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
volumes:
|
||||||
|
- postgres-data:/var/lib/postgresql/data
|
||||||
|
networks:
|
||||||
|
- mas-network
|
||||||
|
|
||||||
|
# Redis
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
container_name: mas-redis
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
volumes:
|
||||||
|
- redis-data:/data
|
||||||
|
networks:
|
||||||
|
- mas-network
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
ollama-data:
|
||||||
|
postgres-data:
|
||||||
|
redis-data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
mas-network:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
73
deploy/k8s/deployment.yaml
Normal file
73
deploy/k8s/deployment.yaml
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: mas
|
||||||
|
namespace: mas
|
||||||
|
labels:
|
||||||
|
app: mas
|
||||||
|
spec:
|
||||||
|
replicas: 2
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: mas
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: mas
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: mas
|
||||||
|
image: harbor.mayne.vcn/mas/platform:latest
|
||||||
|
ports:
|
||||||
|
- containerPort: 8000
|
||||||
|
name: http
|
||||||
|
env:
|
||||||
|
- name: ANTHROPIC_API_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: mas-api-keys
|
||||||
|
key: anthropic-api-key
|
||||||
|
- name: OPENAI_API_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: mas-api-keys
|
||||||
|
key: openai-api-key
|
||||||
|
- name: GOOGLE_API_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: mas-api-keys
|
||||||
|
key: google-api-key
|
||||||
|
- name: GROQ_API_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: mas-api-keys
|
||||||
|
key: groq-api-key
|
||||||
|
- name: GROQ_API_BASE
|
||||||
|
value: "https://api.groq.com/openai/v1"
|
||||||
|
- name: DATABASE_URL
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: mas-postgres
|
||||||
|
key: database-url
|
||||||
|
- name: REDIS_URL
|
||||||
|
value: "redis://redis:6379/0"
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "512Mi"
|
||||||
|
cpu: "500m"
|
||||||
|
limits:
|
||||||
|
memory: "2Gi"
|
||||||
|
cpu: "2000m"
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 8000
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 8000
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 5
|
||||||
|
|
||||||
29
deploy/k8s/ingress.yaml
Normal file
29
deploy/k8s/ingress.yaml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: mas
|
||||||
|
namespace: mas
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
|
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||||
|
nginx.ingress.kubernetes.io/websocket-services: "mas"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
|
||||||
|
spec:
|
||||||
|
ingressClassName: nginx
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- mas.mayne.vcn
|
||||||
|
secretName: mas-tls
|
||||||
|
rules:
|
||||||
|
- host: mas.mayne.vcn
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: mas
|
||||||
|
port:
|
||||||
|
number: 8000
|
||||||
|
|
||||||
13
deploy/k8s/kustomization.yaml
Normal file
13
deploy/k8s/kustomization.yaml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
|
||||||
|
namespace: mas
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- namespace.yaml
|
||||||
|
- ../vault/mas-api-keys.yaml
|
||||||
|
- ../vault/mas-postgres.yaml
|
||||||
|
- deployment.yaml
|
||||||
|
- service.yaml
|
||||||
|
- ingress.yaml
|
||||||
|
|
||||||
7
deploy/k8s/namespace.yaml
Normal file
7
deploy/k8s/namespace.yaml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: mas
|
||||||
|
labels:
|
||||||
|
name: mas
|
||||||
|
|
||||||
17
deploy/k8s/service.yaml
Normal file
17
deploy/k8s/service.yaml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: mas
|
||||||
|
namespace: mas
|
||||||
|
labels:
|
||||||
|
app: mas
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
ports:
|
||||||
|
- port: 8000
|
||||||
|
targetPort: 8000
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
|
selector:
|
||||||
|
app: mas
|
||||||
|
|
||||||
31
deploy/vault/mas-api-keys.yaml
Normal file
31
deploy/vault/mas-api-keys.yaml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
apiVersion: external-secrets.io/v1beta1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: mas-api-keys
|
||||||
|
namespace: mas
|
||||||
|
spec:
|
||||||
|
refreshInterval: 1h
|
||||||
|
secretStoreRef:
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
name: vault-backend
|
||||||
|
target:
|
||||||
|
name: mas-api-keys
|
||||||
|
creationPolicy: Owner
|
||||||
|
data:
|
||||||
|
- secretKey: anthropic-api-key
|
||||||
|
remoteRef:
|
||||||
|
key: mas/api-keys
|
||||||
|
property: ANTHROPIC_API_KEY
|
||||||
|
- secretKey: groq-api-key
|
||||||
|
remoteRef:
|
||||||
|
key: mas/api-keys
|
||||||
|
property: GROQ_API_KEY
|
||||||
|
- secretKey: openai-api-key
|
||||||
|
remoteRef:
|
||||||
|
key: mas/api-keys
|
||||||
|
property: OPENAI_API_KEY
|
||||||
|
- secretKey: google-api-key
|
||||||
|
remoteRef:
|
||||||
|
key: mas/api-keys
|
||||||
|
property: GOOGLE_API_KEY
|
||||||
|
|
||||||
27
deploy/vault/mas-postgres.yaml
Normal file
27
deploy/vault/mas-postgres.yaml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
apiVersion: external-secrets.io/v1beta1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: mas-postgres
|
||||||
|
namespace: mas
|
||||||
|
spec:
|
||||||
|
refreshInterval: 1h
|
||||||
|
secretStoreRef:
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
name: vault-backend
|
||||||
|
target:
|
||||||
|
name: mas-postgres
|
||||||
|
creationPolicy: Owner
|
||||||
|
data:
|
||||||
|
- secretKey: database-url
|
||||||
|
remoteRef:
|
||||||
|
key: mas/postgres
|
||||||
|
property: DATABASE_URL
|
||||||
|
- secretKey: username
|
||||||
|
remoteRef:
|
||||||
|
key: mas/postgres
|
||||||
|
property: USERNAME
|
||||||
|
- secretKey: password
|
||||||
|
remoteRef:
|
||||||
|
key: mas/postgres
|
||||||
|
property: PASSWORD
|
||||||
|
|
||||||
17
services/backend/.chainlit
Normal file
17
services/backend/.chainlit
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
[project]
|
||||||
|
enable_telemetry = false
|
||||||
|
user_env = []
|
||||||
|
session_timeout = 3600
|
||||||
|
cache = false
|
||||||
|
|
||||||
|
[features]
|
||||||
|
prompt_playground = true
|
||||||
|
unsafe_allow_html = true
|
||||||
|
latex = true
|
||||||
|
|
||||||
|
[UI]
|
||||||
|
name = "MAS Platform"
|
||||||
|
default_collapse_content = true
|
||||||
|
default_expand_messages = false
|
||||||
|
hide_cot = false
|
||||||
|
|
||||||
240
services/backend/agents.py
Normal file
240
services/backend/agents.py
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
"""
|
||||||
|
MAS (Multi-Agent System) 에이전트 정의
|
||||||
|
"""
|
||||||
|
from typing import Annotated, Literal, TypedDict
|
||||||
|
from langchain_anthropic import ChatAnthropic
|
||||||
|
from langchain_openai import ChatOpenAI
|
||||||
|
from langgraph.graph import StateGraph, END
|
||||||
|
from langgraph.prebuilt import ToolNode
|
||||||
|
from langchain_core.messages import HumanMessage, SystemMessage
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
class AgentState(TypedDict):
|
||||||
|
"""에이전트 간 공유되는 상태"""
|
||||||
|
messages: list
|
||||||
|
current_agent: str
|
||||||
|
task_type: str
|
||||||
|
result: dict
|
||||||
|
|
||||||
|
|
||||||
|
# ===== 1. Claude Code - Orchestrator =====
|
||||||
|
claude_orchestrator = ChatAnthropic(
|
||||||
|
model="claude-3-5-sonnet-20241022",
|
||||||
|
api_key=os.getenv("ANTHROPIC_API_KEY"),
|
||||||
|
temperature=0
|
||||||
|
)
|
||||||
|
|
||||||
|
ORCHESTRATOR_PROMPT = """당신은 MAS의 총괄 조율자이자 DevOps 전문가입니다.
|
||||||
|
|
||||||
|
**역할**:
|
||||||
|
- 사용자 요청을 분석하여 적절한 에이전트에게 작업 할당
|
||||||
|
- Kubernetes, ArgoCD, Helm, Kustomize 관리
|
||||||
|
- CI/CD 파이프라인 구성
|
||||||
|
- 최종 코드 리뷰 및 승인
|
||||||
|
|
||||||
|
**사용 가능한 에이전트**:
|
||||||
|
1. backend_developer: FastAPI, Node.js 백엔드 개발
|
||||||
|
2. frontend_developer: Next.js, React 프론트엔드 개발
|
||||||
|
3. sre_specialist: 모니터링, 성능 최적화, 보안
|
||||||
|
|
||||||
|
요청을 분석하고 어떤 에이전트가 처리해야 할지 결정하세요.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# ===== 2. Groq #1 - Backend Developer =====
|
||||||
|
# Groq OpenAI-compatible endpoint
|
||||||
|
GROQ_API_BASE = os.getenv("GROQ_API_BASE", "https://api.groq.com/openai/v1")
|
||||||
|
GROQ_API_KEY = os.getenv("GROQ_API_KEY", "")
|
||||||
|
|
||||||
|
groq_backend = ChatOpenAI(
|
||||||
|
model=os.getenv("GROQ_BACKEND_MODEL", "llama-3.3-70b-specdec"),
|
||||||
|
base_url=GROQ_API_BASE,
|
||||||
|
api_key=GROQ_API_KEY,
|
||||||
|
temperature=0.7,
|
||||||
|
)
|
||||||
|
|
||||||
|
BACKEND_PROMPT = """당신은 백엔드 개발 전문가입니다.
|
||||||
|
|
||||||
|
**역할**:
|
||||||
|
- FastAPI, Node.js 백엔드 개발
|
||||||
|
- REST API 설계 및 구현
|
||||||
|
- 데이터베이스 쿼리 최적화
|
||||||
|
- 비즈니스 로직 구현
|
||||||
|
|
||||||
|
요청된 백엔드 작업을 수행하고 코드를 생성하세요.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# ===== 3. Groq #2 - Frontend Developer =====
|
||||||
|
groq_frontend = ChatOpenAI(
|
||||||
|
model=os.getenv("GROQ_FRONTEND_MODEL", "llama-3.1-8b-instant"),
|
||||||
|
base_url=GROQ_API_BASE,
|
||||||
|
api_key=GROQ_API_KEY,
|
||||||
|
temperature=0.7,
|
||||||
|
)
|
||||||
|
|
||||||
|
FRONTEND_PROMPT = """당신은 프론트엔드 개발 전문가입니다.
|
||||||
|
|
||||||
|
**역할**:
|
||||||
|
- Next.js, React 컴포넌트 개발
|
||||||
|
- UI/UX 구현
|
||||||
|
- 상태 관리
|
||||||
|
- 반응형 디자인
|
||||||
|
|
||||||
|
요청된 프론트엔드 작업을 수행하고 코드를 생성하세요.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# ===== 4. Groq #3 - SRE Specialist =====
|
||||||
|
groq_sre = ChatOpenAI(
|
||||||
|
model=os.getenv("GROQ_SRE_MODEL", "llama-3.1-8b-instant"),
|
||||||
|
base_url=GROQ_API_BASE,
|
||||||
|
api_key=GROQ_API_KEY,
|
||||||
|
temperature=0.3,
|
||||||
|
)
|
||||||
|
|
||||||
|
SRE_PROMPT = """당신은 SRE(Site Reliability Engineer) 전문가입니다.
|
||||||
|
|
||||||
|
**역할**:
|
||||||
|
- 시스템 모니터링 (Prometheus, Grafana, Loki)
|
||||||
|
- 로그 분석 및 알람 설정
|
||||||
|
- 성능 튜닝
|
||||||
|
- 보안 취약점 점검
|
||||||
|
|
||||||
|
요청된 SRE 작업을 수행하고 솔루션을 제시하세요.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def orchestrator_node(state: AgentState) -> AgentState:
|
||||||
|
"""Claude Code - 작업 분석 및 할당"""
|
||||||
|
messages = state["messages"]
|
||||||
|
|
||||||
|
response = claude_orchestrator.invoke([
|
||||||
|
SystemMessage(content=ORCHESTRATOR_PROMPT),
|
||||||
|
HumanMessage(content=messages[-1]["content"])
|
||||||
|
])
|
||||||
|
|
||||||
|
# 작업 타입 결정
|
||||||
|
content = response.content.lower()
|
||||||
|
if "backend" in content or "api" in content or "fastapi" in content:
|
||||||
|
next_agent = "backend_developer"
|
||||||
|
elif "frontend" in content or "ui" in content or "react" in content:
|
||||||
|
next_agent = "frontend_developer"
|
||||||
|
elif "monitoring" in content or "performance" in content or "sre" in content:
|
||||||
|
next_agent = "sre_specialist"
|
||||||
|
else:
|
||||||
|
next_agent = "orchestrator" # 자신이 직접 처리
|
||||||
|
|
||||||
|
state["messages"].append({
|
||||||
|
"role": "orchestrator",
|
||||||
|
"content": response.content
|
||||||
|
})
|
||||||
|
state["current_agent"] = next_agent
|
||||||
|
|
||||||
|
return state
|
||||||
|
|
||||||
|
|
||||||
|
def backend_node(state: AgentState) -> AgentState:
|
||||||
|
"""Groq #1 - 백엔드 개발"""
|
||||||
|
messages = state["messages"]
|
||||||
|
|
||||||
|
response = groq_backend.invoke([
|
||||||
|
SystemMessage(content=BACKEND_PROMPT),
|
||||||
|
HumanMessage(content=messages[-1]["content"])
|
||||||
|
])
|
||||||
|
|
||||||
|
state["messages"].append({
|
||||||
|
"role": "backend_developer",
|
||||||
|
"content": response.content
|
||||||
|
})
|
||||||
|
state["current_agent"] = "orchestrator" # 결과를 오케스트레이터에게 반환
|
||||||
|
|
||||||
|
return state
|
||||||
|
|
||||||
|
|
||||||
|
def frontend_node(state: AgentState) -> AgentState:
|
||||||
|
"""Groq #2 - 프론트엔드 개발"""
|
||||||
|
messages = state["messages"]
|
||||||
|
|
||||||
|
response = groq_frontend.invoke([
|
||||||
|
SystemMessage(content=FRONTEND_PROMPT),
|
||||||
|
HumanMessage(content=messages[-1]["content"])
|
||||||
|
])
|
||||||
|
|
||||||
|
state["messages"].append({
|
||||||
|
"role": "frontend_developer",
|
||||||
|
"content": response.content
|
||||||
|
})
|
||||||
|
state["current_agent"] = "orchestrator"
|
||||||
|
|
||||||
|
return state
|
||||||
|
|
||||||
|
|
||||||
|
def sre_node(state: AgentState) -> AgentState:
|
||||||
|
"""Groq #3 - SRE 작업"""
|
||||||
|
messages = state["messages"]
|
||||||
|
|
||||||
|
response = groq_sre.invoke([
|
||||||
|
SystemMessage(content=SRE_PROMPT),
|
||||||
|
HumanMessage(content=messages[-1]["content"])
|
||||||
|
])
|
||||||
|
|
||||||
|
state["messages"].append({
|
||||||
|
"role": "sre_specialist",
|
||||||
|
"content": response.content
|
||||||
|
})
|
||||||
|
state["current_agent"] = "orchestrator"
|
||||||
|
|
||||||
|
return state
|
||||||
|
|
||||||
|
|
||||||
|
def router(state: AgentState) -> Literal["backend_developer", "frontend_developer", "sre_specialist", "end"]:
|
||||||
|
"""다음 에이전트 라우팅"""
|
||||||
|
current = state.get("current_agent", "orchestrator")
|
||||||
|
|
||||||
|
if current == "backend_developer":
|
||||||
|
return "backend_developer"
|
||||||
|
elif current == "frontend_developer":
|
||||||
|
return "frontend_developer"
|
||||||
|
elif current == "sre_specialist":
|
||||||
|
return "sre_specialist"
|
||||||
|
else:
|
||||||
|
return "end"
|
||||||
|
|
||||||
|
|
||||||
|
# ===== LangGraph 워크플로우 구성 =====
|
||||||
|
def create_mas_graph():
|
||||||
|
"""MAS 워크플로우 그래프 생성"""
|
||||||
|
workflow = StateGraph(AgentState)
|
||||||
|
|
||||||
|
# 노드 추가
|
||||||
|
workflow.add_node("orchestrator", orchestrator_node)
|
||||||
|
workflow.add_node("backend_developer", backend_node)
|
||||||
|
workflow.add_node("frontend_developer", frontend_node)
|
||||||
|
workflow.add_node("sre_specialist", sre_node)
|
||||||
|
|
||||||
|
# 엣지 정의
|
||||||
|
workflow.set_entry_point("orchestrator")
|
||||||
|
workflow.add_conditional_edges(
|
||||||
|
"orchestrator",
|
||||||
|
router,
|
||||||
|
{
|
||||||
|
"backend_developer": "backend_developer",
|
||||||
|
"frontend_developer": "frontend_developer",
|
||||||
|
"sre_specialist": "sre_specialist",
|
||||||
|
"end": END
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# 각 에이전트는 작업 후 orchestrator로 복귀
|
||||||
|
workflow.add_edge("backend_developer", "orchestrator")
|
||||||
|
workflow.add_edge("frontend_developer", "orchestrator")
|
||||||
|
workflow.add_edge("sre_specialist", "orchestrator")
|
||||||
|
|
||||||
|
return workflow.compile()
|
||||||
|
|
||||||
|
|
||||||
|
# 그래프 인스턴스 생성
|
||||||
|
mas_graph = create_mas_graph()
|
||||||
|
|
||||||
85
services/backend/chainlit_app.py
Normal file
85
services/backend/chainlit_app.py
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
"""
|
||||||
|
Chainlit UI for MAS Platform
|
||||||
|
"""
|
||||||
|
import chainlit as cl
|
||||||
|
from agents import mas_graph, AgentState
|
||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
|
||||||
|
@cl.on_chat_start
|
||||||
|
async def start():
|
||||||
|
"""채팅 시작 시"""
|
||||||
|
await cl.Message(
|
||||||
|
content="🤖 **Multi-Agent System**에 오신 것을 환영합니다!\n\n"
|
||||||
|
"저는 다음 전문가 팀과 함께 작업합니다:\n\n"
|
||||||
|
"- 🎼 **Claude Code**: 총괄 조율자 & DevOps 전문가\n"
|
||||||
|
"- ⚙️ **Qwen Backend**: 백엔드 개발자\n"
|
||||||
|
"- 🎨 **Qwen Frontend**: 프론트엔드 개발자\n"
|
||||||
|
"- 📊 **Qwen SRE**: 모니터링 & 성능 전문가\n\n"
|
||||||
|
"무엇을 도와드릴까요?"
|
||||||
|
).send()
|
||||||
|
|
||||||
|
|
||||||
|
@cl.on_message
|
||||||
|
async def main(message: cl.Message):
|
||||||
|
"""메시지 수신 시"""
|
||||||
|
|
||||||
|
# 초기 상태
|
||||||
|
initial_state: AgentState = {
|
||||||
|
"messages": [{"role": "user", "content": message.content}],
|
||||||
|
"current_agent": "orchestrator",
|
||||||
|
"task_type": "",
|
||||||
|
"result": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 응답 메시지 생성
|
||||||
|
response_msg = cl.Message(content="")
|
||||||
|
await response_msg.send()
|
||||||
|
|
||||||
|
# MAS 그래프 실행
|
||||||
|
async for event in mas_graph.astream(initial_state):
|
||||||
|
for node_name, state in event.items():
|
||||||
|
if node_name != "__end__":
|
||||||
|
last_message = state["messages"][-1]
|
||||||
|
agent_name = last_message["role"]
|
||||||
|
agent_content = last_message["content"]
|
||||||
|
|
||||||
|
# 에이전트별 아이콘
|
||||||
|
agent_icons = {
|
||||||
|
"orchestrator": "🎼",
|
||||||
|
"backend_developer": "⚙️",
|
||||||
|
"frontend_developer": "🎨",
|
||||||
|
"sre_specialist": "📊"
|
||||||
|
}
|
||||||
|
|
||||||
|
icon = agent_icons.get(agent_name, "🤖")
|
||||||
|
|
||||||
|
# 스트리밍 업데이트
|
||||||
|
response_msg.content += f"\n\n{icon} **{agent_name}**:\n{agent_content}"
|
||||||
|
await response_msg.update()
|
||||||
|
|
||||||
|
# 최종 업데이트
|
||||||
|
await response_msg.update()
|
||||||
|
|
||||||
|
|
||||||
|
@cl.on_settings_update
|
||||||
|
async def setup_agent(settings):
|
||||||
|
"""설정 업데이트"""
|
||||||
|
print(f"Settings updated: {settings}")
|
||||||
|
|
||||||
|
|
||||||
|
# 사이드바 설정
|
||||||
|
@cl.author_rename
|
||||||
|
def rename(orig_author: str):
|
||||||
|
"""에이전트 이름 매핑"""
|
||||||
|
rename_dict = {
|
||||||
|
"orchestrator": "Claude Code (Orchestrator)",
|
||||||
|
"backend_developer": "Qwen Backend Dev",
|
||||||
|
"frontend_developer": "Qwen Frontend Dev",
|
||||||
|
"sre_specialist": "Qwen SRE"
|
||||||
|
}
|
||||||
|
return rename_dict.get(orig_author, orig_author)
|
||||||
|
|
||||||
30
services/backend/requirements.txt
Normal file
30
services/backend/requirements.txt
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# LangGraph & LangChain
|
||||||
|
langgraph==0.2.53
|
||||||
|
langchain==0.3.13
|
||||||
|
langchain-anthropic==0.3.0
|
||||||
|
langchain-openai==0.2.14
|
||||||
|
langchain-google-genai==2.0.8
|
||||||
|
|
||||||
|
# Chainlit (UI)
|
||||||
|
chainlit==1.3.1
|
||||||
|
|
||||||
|
# API Framework
|
||||||
|
fastapi==0.115.6
|
||||||
|
uvicorn[standard]==0.34.0
|
||||||
|
pydantic==2.10.5
|
||||||
|
pydantic-settings==2.7.0
|
||||||
|
|
||||||
|
# Database
|
||||||
|
sqlalchemy==2.0.36
|
||||||
|
asyncpg==0.30.0
|
||||||
|
psycopg2-binary==2.9.10
|
||||||
|
|
||||||
|
# MCP (Model Context Protocol)
|
||||||
|
mcp==1.1.2
|
||||||
|
|
||||||
|
# Utilities
|
||||||
|
python-dotenv==1.0.1
|
||||||
|
redis==5.2.1
|
||||||
|
aioredis==2.0.1
|
||||||
|
httpx==0.28.1
|
||||||
|
|
||||||
Reference in New Issue
Block a user