first commit
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
# Secrets
|
||||||
|
compose/.env
|
||||||
|
|
||||||
|
# Claude Code
|
||||||
|
.claude/
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
.DS_Store
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
# pm-aigateway
|
||||||
|
|
||||||
|
Self-hosted AI gateway powered by [agentgateway](https://agentgateway.dev) — a unified, OpenAI-compatible proxy for LLM providers, MCP servers, and A2A agents.
|
||||||
|
|
||||||
|
## Project goal
|
||||||
|
|
||||||
|
Provide a single API endpoint that routes LLM requests to multiple providers (OpenAI, Anthropic, etc.) with centralized auth and observability. Stateless to start; extend to MCP and A2A as needed.
|
||||||
|
|
||||||
|
## Deployment targets
|
||||||
|
|
||||||
|
| Target | Status |
|
||||||
|
|--------|--------|
|
||||||
|
| Docker Compose | **current** — single stateless container |
|
||||||
|
| Kubernetes | planned |
|
||||||
|
|
||||||
|
## Stack
|
||||||
|
|
||||||
|
- **agentgateway** — the sole service; stateless, single binary in a container
|
||||||
|
|
||||||
|
No database or cache needed for the current scope.
|
||||||
|
|
||||||
|
## Repository layout
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── CLAUDE.md
|
||||||
|
├── compose/
|
||||||
|
│ ├── docker-compose.yml
|
||||||
|
│ └── .env.example
|
||||||
|
└── config/
|
||||||
|
└── config.yaml # model list and routing rules
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key configuration
|
||||||
|
|
||||||
|
All provider API keys are supplied via environment variables (never committed).
|
||||||
|
Copy `compose/.env.example` → `compose/.env` and fill in values.
|
||||||
|
|
||||||
|
Required env vars:
|
||||||
|
- `OPENAI_API_KEY`
|
||||||
|
- `ANTHROPIC_API_KEY`
|
||||||
|
|
||||||
|
agentgateway reads `config/config.yaml` on startup. Restart the container to pick up changes.
|
||||||
|
|
||||||
|
## Common commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start
|
||||||
|
docker compose -f compose/docker-compose.yml --env-file compose/.env up -d
|
||||||
|
|
||||||
|
# Tail logs
|
||||||
|
docker compose -f compose/docker-compose.yml logs -f
|
||||||
|
|
||||||
|
# Restart after config change
|
||||||
|
docker compose -f compose/docker-compose.yml restart
|
||||||
|
|
||||||
|
# Stop
|
||||||
|
docker compose -f compose/docker-compose.yml down
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ports
|
||||||
|
|
||||||
|
| Endpoint | Port |
|
||||||
|
|----------|------|
|
||||||
|
| OpenAI-compatible API | 4000 |
|
||||||
|
| agentgateway UI | 15000 |
|
||||||
|
|
||||||
|
## Test
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s http://localhost:4000/v1/chat/completions \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"model": "gpt-4o-mini",
|
||||||
|
"messages": [{"role": "user", "content": "Hello"}]
|
||||||
|
}' | jq .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Coding conventions
|
||||||
|
|
||||||
|
- Secrets in `compose/.env` only — gitignored. `compose/.env.example` is committed.
|
||||||
|
- Pin the agentgateway image tag; avoid `latest` in production.
|
||||||
|
- Keep `config/config.yaml` as the single source of truth for model/routing config.
|
||||||
|
|
||||||
|
## Future extensions
|
||||||
|
|
||||||
|
- **MCP gateway**: add `mcp.servers` block to `config.yaml` to expose tools
|
||||||
|
- **A2A gateway**: add `a2a` block for agent-to-agent routing
|
||||||
|
- **Persistence**: add Postgres if spend tracking or virtual keys are needed
|
||||||
|
- **Kubernetes**: translate compose to k8s manifests under `k8s/`
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# Container runtime: override with `make <target> COMMAND=podman`
|
||||||
|
COMMAND := docker
|
||||||
|
|
||||||
|
COMPOSE_FILE := compose/docker-compose.yml
|
||||||
|
ENV_FILE := compose/.env
|
||||||
|
API_URL := http://localhost:4000
|
||||||
|
|
||||||
|
# ─── Docker Compose ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
docker-up: ## Start the stack (detached)
|
||||||
|
$(COMMAND) compose -f $(COMPOSE_FILE) --env-file $(ENV_FILE) up -d
|
||||||
|
|
||||||
|
docker-down: ## Stop and remove containers
|
||||||
|
$(COMMAND) compose -f $(COMPOSE_FILE) --env-file $(ENV_FILE) down
|
||||||
|
|
||||||
|
docker-restart: docker-down docker-up ## Full restart (re-reads env vars and config)
|
||||||
|
|
||||||
|
docker-logs: ## Tail logs
|
||||||
|
$(COMMAND) compose -f $(COMPOSE_FILE) logs -f
|
||||||
|
|
||||||
|
docker-ps: ## Show container status
|
||||||
|
$(COMMAND) compose -f $(COMPOSE_FILE) ps
|
||||||
|
|
||||||
|
docker-test: ## Send a test request to each configured model
|
||||||
|
@echo "→ claude-sonnet-4-6 (Anthropic)"
|
||||||
|
@curl -sf $(API_URL)/v1/chat/completions \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"model":"claude-sonnet-4-6","messages":[{"role":"user","content":"ping"}]}' \
|
||||||
|
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d['choices'][0]['message']['content'])"
|
||||||
|
@echo "→ or-gpt-5.5 (OpenRouter)"
|
||||||
|
@curl -sf $(API_URL)/v1/chat/completions \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"model":"or-gpt-5.5","messages":[{"role":"user","content":"ping"}]}' \
|
||||||
|
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d['choices'][0]['message']['content'])"
|
||||||
|
|
||||||
|
docker-ui: ## Open the agentgateway UI in the browser
|
||||||
|
open http://localhost:15000/ui
|
||||||
|
|
||||||
|
# ─── Help ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
.PHONY: docker-up docker-down docker-restart docker-logs docker-ps docker-test docker-ui help
|
||||||
|
|
||||||
|
help: ## Show this help
|
||||||
|
@grep -E '^[a-zA-Z_-]+:.*##' $(MAKEFILE_LIST) \
|
||||||
|
| awk 'BEGIN {FS = ":.*##"}; {printf " %-20s %s\n", $$1, $$2}'
|
||||||
|
|
||||||
|
.DEFAULT_GOAL := help
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
OPENAI_API_KEY=sk-...
|
||||||
|
ANTHROPIC_API_KEY=sk-ant-...
|
||||||
|
OPENROUTER_API_KEY=sk-or-...
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
services:
|
||||||
|
agentgateway:
|
||||||
|
image: ghcr.io/agentgateway/agentgateway:v1.1.0
|
||||||
|
ports:
|
||||||
|
- "4000:4000" # OpenAI-compatible API
|
||||||
|
- "15000:15000" # UI
|
||||||
|
volumes:
|
||||||
|
- ../config:/etc/agentgateway:ro
|
||||||
|
command: ["-f", "/etc/agentgateway/config-binds.yaml"]
|
||||||
|
environment:
|
||||||
|
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
|
||||||
|
- OPENROUTER_API_KEY=${OPENROUTER_API_KEY}
|
||||||
|
restart: unless-stopped
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# yaml-language-server: $schema=https://agentgateway.dev/schema/config
|
||||||
|
# Format binds — workaround pour le bug UI "e.binds.forEach"
|
||||||
|
# Note: OpenRouter (hostOverride) incompatible avec le format binds, voir config.yaml
|
||||||
|
|
||||||
|
config:
|
||||||
|
adminAddr: 0.0.0.0:15000
|
||||||
|
|
||||||
|
binds:
|
||||||
|
- port: 4000
|
||||||
|
listeners:
|
||||||
|
- routes:
|
||||||
|
- backends:
|
||||||
|
- ai:
|
||||||
|
name: claude-sonnet-4-6
|
||||||
|
provider:
|
||||||
|
anthropic:
|
||||||
|
model: claude-sonnet-4-6
|
||||||
|
policies:
|
||||||
|
backendAuth:
|
||||||
|
key: "$ANTHROPIC_API_KEY"
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# yaml-language-server: $schema=https://agentgateway.dev/schema/config
|
||||||
|
|
||||||
|
config:
|
||||||
|
adminAddr: 0.0.0.0:15000
|
||||||
|
|
||||||
|
llm:
|
||||||
|
models:
|
||||||
|
- name: claude-sonnet-4-6
|
||||||
|
provider: anthropic
|
||||||
|
params:
|
||||||
|
model: claude-sonnet-4-6
|
||||||
|
apiKey: "$ANTHROPIC_API_KEY"
|
||||||
|
|
||||||
|
- name: or-gpt-5.5
|
||||||
|
provider: openAI
|
||||||
|
params:
|
||||||
|
model: openai/gpt-5.5
|
||||||
|
hostOverride: "openrouter.ai:443"
|
||||||
|
pathOverride: "/api/v1/chat/completions"
|
||||||
|
apiKey: "$OPENROUTER_API_KEY"
|
||||||
|
backendTLS: {}
|
||||||
Reference in New Issue
Block a user