Refactor DeerFlow to use Gateway's LangGraph-compatible API

- Updated documentation and comments to reflect the transition from LangGraph Server to Gateway.
- Changed default URLs in ChannelManager and tests to point to Gateway.
- Removed references to LangGraph Server in deployment scripts and configurations.
- Updated Nginx configuration to route API traffic to Gateway.
- Adjusted frontend configurations to utilize Gateway's API.
- Removed LangGraph service from Docker Compose files, consolidating services under Gateway.
- Added regression tests to ensure Gateway integration works as expected.

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
JeffJiang
2026-04-26 20:38:34 +08:00
parent 653b7ae17a
commit 7bf618de67
20 changed files with 177 additions and 474 deletions
+15 -42
View File
@@ -3,52 +3,38 @@
# deploy.sh - Build, start, or stop DeerFlow production services
#
# Commands:
# deploy.sh [--MODE] — build + start (default: --standard)
# deploy.sh — build + start
# deploy.sh build — build all images (mode-agnostic)
# deploy.sh start [--MODE] — start from pre-built images (default: --standard)
# deploy.sh start — start from pre-built images
# deploy.sh down — stop and remove containers
#
# Runtime modes:
# --standard (default) All services including LangGraph server.
# --gateway No LangGraph container; nginx routes /api/langgraph/*
# to the Gateway compat API instead.
#
# Sandbox mode (local / aio / provisioner) is auto-detected from config.yaml.
#
# Examples:
# deploy.sh # build + start in standard mode
# deploy.sh --gateway # build + start in gateway mode
# deploy.sh # build + start
# deploy.sh build # build all images
# deploy.sh start --gateway # start pre-built images in gateway mode
# deploy.sh start # start pre-built images
# deploy.sh down # stop and remove containers
#
# Must be run from the repo root directory.
set -e
RUNTIME_MODE="standard"
case "${1:-}" in
build|start|down)
CMD="$1"
if [ -n "${2:-}" ]; then
case "$2" in
--standard) RUNTIME_MODE="standard" ;;
--gateway) RUNTIME_MODE="gateway" ;;
*) echo "Unknown mode: $2"; echo "Usage: deploy.sh [build|start|down] [--standard|--gateway]"; exit 1 ;;
esac
echo "Unknown argument: $2"
echo "Usage: deploy.sh [build|start|down]"
exit 1
fi
;;
--standard|--gateway)
CMD=""
RUNTIME_MODE="${1#--}"
;;
"")
CMD=""
;;
*)
echo "Unknown argument: $1"
echo "Usage: deploy.sh [build|start|down] [--standard|--gateway]"
echo "Usage: deploy.sh [build|start|down]"
exit 1
;;
esac
@@ -212,7 +198,7 @@ if [ "$CMD" = "build" ]; then
echo " ✓ Images built successfully"
echo "=========================================="
echo ""
echo " Next: deploy.sh start [--gateway]"
echo " Next: deploy.sh start"
echo ""
exit 0
fi
@@ -225,23 +211,14 @@ echo "=========================================="
echo ""
# ── Detect runtime configuration ────────────────────────────────────────────
# Only needed for start / up — determines which containers to launch.
# Only needed for start / up — determines whether provisioner is launched.
sandbox_mode="$(detect_sandbox_mode)"
echo -e "${BLUE}Sandbox mode: $sandbox_mode${NC}"
echo -e "${BLUE}Runtime mode: $RUNTIME_MODE${NC}"
echo -e "${BLUE}Runtime: Gateway embedded agent runtime${NC}"
case "$RUNTIME_MODE" in
gateway)
export LANGGRAPH_UPSTREAM=gateway:8001
export LANGGRAPH_REWRITE=/api/
services="frontend gateway nginx"
;;
standard)
services="frontend gateway langgraph nginx"
;;
esac
services="frontend gateway nginx"
if [ "$sandbox_mode" = "provisioner" ]; then
services="$services provisioner"
@@ -282,17 +259,13 @@ fi
echo ""
echo "=========================================="
echo " DeerFlow is running! ($RUNTIME_MODE mode)"
echo " DeerFlow is running!"
echo "=========================================="
echo ""
echo " 🌐 Application: http://localhost:${PORT:-2026}"
echo " 📡 API Gateway: http://localhost:${PORT:-2026}/api/*"
if [ "$RUNTIME_MODE" = "gateway" ]; then
echo " 🤖 Runtime: Gateway embedded"
echo " API: /api/langgraph/* → Gateway (compat)"
else
echo " 🤖 LangGraph: http://localhost:${PORT:-2026}/api/langgraph/*"
fi
echo " 🤖 Runtime: Gateway embedded"
echo " API: /api/langgraph/* → Gateway"
echo ""
echo " Manage:"
echo " make down — stop and remove containers"
+11 -34
View File
@@ -148,18 +148,15 @@ init() {
}
# Start Docker development environment
# Usage: start [--gateway]
start() {
local sandbox_mode
local services
local gateway_mode=false
# Check for --gateway flag
for arg in "$@"; do
if [ "$arg" = "--gateway" ]; then
gateway_mode=true
fi
done
if [ "$#" -gt 0 ]; then
echo -e "${YELLOW}Unknown option for start: $1${NC}"
echo "Usage: $0 start"
exit 1
fi
echo "=========================================="
echo " Starting DeerFlow Docker Development"
@@ -168,21 +165,12 @@ start() {
sandbox_mode="$(detect_sandbox_mode)"
if $gateway_mode; then
services="frontend gateway nginx"
if [ "$sandbox_mode" = "provisioner" ]; then
services="frontend gateway provisioner nginx"
fi
else
services="frontend gateway langgraph nginx"
if [ "$sandbox_mode" = "provisioner" ]; then
services="frontend gateway langgraph provisioner nginx"
fi
services="frontend gateway nginx"
if [ "$sandbox_mode" = "provisioner" ]; then
services="frontend gateway provisioner nginx"
fi
if $gateway_mode; then
echo -e "${BLUE}Runtime: Gateway mode (experimental) — no LangGraph container${NC}"
fi
echo -e "${BLUE}Runtime: Gateway embedded agent runtime${NC}"
echo -e "${BLUE}Detected sandbox mode: $sandbox_mode${NC}"
if [ "$sandbox_mode" = "provisioner" ]; then
echo -e "${BLUE}Provisioner enabled (Kubernetes mode).${NC}"
@@ -232,12 +220,6 @@ start() {
fi
fi
# Set nginx routing for gateway mode (envsubst in nginx container)
if $gateway_mode; then
export LANGGRAPH_UPSTREAM=gateway:8001
export LANGGRAPH_REWRITE=/api/
fi
echo "Building and starting containers..."
cd "$DOCKER_DIR" && $COMPOSE_CMD up --build -d --remove-orphans $services
echo ""
@@ -247,12 +229,8 @@ start() {
echo ""
echo " 🌐 Application: http://localhost:2026"
echo " 📡 API Gateway: http://localhost:2026/api/*"
if $gateway_mode; then
echo " 🤖 Runtime: Gateway embedded"
echo " API: /api/langgraph/* → Gateway (compat)"
else
echo " 🤖 LangGraph: http://localhost:2026/api/langgraph/*"
fi
echo " 🤖 Runtime: Gateway embedded"
echo " API: /api/langgraph/* → Gateway"
echo ""
echo " 📋 View logs: make docker-logs"
echo " 🛑 Stop: make docker-stop"
@@ -332,7 +310,6 @@ help() {
echo "Commands:"
echo " init - Pull the sandbox image (speeds up first Pod startup)"
echo " start - Start Docker services (auto-detects sandbox mode from config.yaml)"
echo " start --gateway - Start without LangGraph container (Gateway mode, experimental)"
echo " restart - Restart all running Docker services"
echo " logs [option] - View Docker development logs"
echo " --frontend View frontend logs only"
+17 -87
View File
@@ -3,13 +3,11 @@
# serve.sh — Unified DeerFlow service launcher
#
# Usage:
# ./scripts/serve.sh [--dev|--prod] [--gateway] [--daemon] [--stop|--restart]
# ./scripts/serve.sh [--dev|--prod] [--daemon] [--stop|--restart]
#
# Modes:
# --dev Development mode with hot-reload (default)
# --prod Production mode, pre-built frontend, no hot-reload
# --gateway Gateway mode (experimental): skip LangGraph server,
# agent runtime embedded in Gateway API
# --daemon Run all services in background (nohup), exit after startup
#
# Actions:
@@ -18,13 +16,11 @@
# --restart Stop all services, then start with the given mode flags
#
# Examples:
# ./scripts/serve.sh --dev # Standard dev (4 processes)
# ./scripts/serve.sh --dev --gateway # Gateway dev (3 processes)
# ./scripts/serve.sh --prod --gateway # Gateway prod (3 processes)
# ./scripts/serve.sh --dev --daemon # Standard dev, background
# ./scripts/serve.sh --dev --gateway --daemon # Gateway dev, background
# ./scripts/serve.sh --dev # Gateway dev, hot reload
# ./scripts/serve.sh --prod # Gateway prod
# ./scripts/serve.sh --dev --daemon # Gateway dev, background
# ./scripts/serve.sh --stop # Stop all services
# ./scripts/serve.sh --restart --dev --gateway # Restart in gateway mode
# ./scripts/serve.sh --restart --dev # Restart dev services
#
# Must be run from the repo root directory.
@@ -44,7 +40,6 @@ fi
# ── Argument parsing ─────────────────────────────────────────────────────────
DEV_MODE=true
GATEWAY_MODE=false
DAEMON_MODE=false
SKIP_INSTALL=false
ACTION="start" # start | stop | restart
@@ -53,14 +48,13 @@ for arg in "$@"; do
case "$arg" in
--dev) DEV_MODE=true ;;
--prod) DEV_MODE=false ;;
--gateway) GATEWAY_MODE=true ;;
--daemon) DAEMON_MODE=true ;;
--skip-install) SKIP_INSTALL=true ;;
--stop) ACTION="stop" ;;
--restart) ACTION="restart" ;;
*)
echo "Unknown argument: $arg"
echo "Usage: $0 [--dev|--prod] [--gateway] [--daemon] [--skip-install] [--stop|--restart]"
echo "Usage: $0 [--dev|--prod] [--daemon] [--skip-install] [--stop|--restart]"
exit 1
;;
esac
@@ -79,7 +73,6 @@ _kill_port() {
stop_all() {
echo "Stopping all services..."
pkill -f "langgraph dev" 2>/dev/null || true
pkill -f "uvicorn app.gateway.app:app" 2>/dev/null || true
pkill -f "next dev" 2>/dev/null || true
pkill -f "next start" 2>/dev/null || true
@@ -88,7 +81,6 @@ stop_all() {
sleep 1
pkill -9 nginx 2>/dev/null || true
# Force-kill any survivors still holding the service ports
_kill_port 2024
_kill_port 8001
_kill_port 3000
./scripts/cleanup-containers.sh deer-flow-sandbox 2>/dev/null || true
@@ -109,21 +101,11 @@ if [ "$ACTION" = "restart" ]; then
ALREADY_STOPPED=true
fi
# ── Derive runtime flags ────────────────────────────────────────────────────
if $GATEWAY_MODE; then
export SKIP_LANGGRAPH_SERVER=1
fi
# Mode label for banner
if $DEV_MODE && $GATEWAY_MODE; then
MODE_LABEL="DEV + GATEWAY (experimental)"
elif $DEV_MODE; then
MODE_LABEL="DEV (hot-reload enabled)"
elif $GATEWAY_MODE; then
MODE_LABEL="PROD + GATEWAY (experimental)"
if $DEV_MODE; then
MODE_LABEL="DEV (Gateway runtime, hot-reload enabled)"
else
MODE_LABEL="PROD (optimized)"
MODE_LABEL="PROD (Gateway runtime, optimized)"
fi
if $DAEMON_MODE; then
@@ -145,8 +127,7 @@ else
FRONTEND_CMD="env BETTER_AUTH_SECRET=$($PYTHON_BIN -c 'import secrets; print(secrets.token_hex(16))') pnpm run preview"
fi
# Extra flags for uvicorn/langgraph
LANGGRAPH_EXTRA_FLAGS="--no-reload"
# Extra flags for uvicorn
if $DEV_MODE && ! $DAEMON_MODE; then
GATEWAY_EXTRA_FLAGS="--reload --reload-include='*.yaml' --reload-include='.env' --reload-exclude='*.pyc' --reload-exclude='__pycache__' --reload-exclude='sandbox/' --reload-exclude='.deer-flow/'"
else
@@ -185,32 +166,6 @@ else
echo "⏩ Skipping dependency install (--skip-install)"
fi
# ── Sync frontend .env.local ─────────────────────────────────────────────────
# Next.js .env.local takes precedence over process env vars.
# The script manages the NEXT_PUBLIC_LANGGRAPH_BASE_URL line to ensure
# the frontend routes match the active backend mode.
FRONTEND_ENV_LOCAL="$REPO_ROOT/frontend/.env.local"
ENV_KEY="NEXT_PUBLIC_LANGGRAPH_BASE_URL"
sync_frontend_env() {
if $GATEWAY_MODE; then
# Point frontend to Gateway's compat API
if [ -f "$FRONTEND_ENV_LOCAL" ] && grep -q "^${ENV_KEY}=" "$FRONTEND_ENV_LOCAL"; then
sed -i.bak "s|^${ENV_KEY}=.*|${ENV_KEY}=/api/langgraph-compat|" "$FRONTEND_ENV_LOCAL" && rm -f "${FRONTEND_ENV_LOCAL}.bak"
else
echo "${ENV_KEY}=/api/langgraph-compat" >> "$FRONTEND_ENV_LOCAL"
fi
else
# Remove override — frontend falls back to /api/langgraph (standard)
if [ -f "$FRONTEND_ENV_LOCAL" ] && grep -q "^${ENV_KEY}=" "$FRONTEND_ENV_LOCAL"; then
sed -i.bak "/^${ENV_KEY}=/d" "$FRONTEND_ENV_LOCAL" && rm -f "${FRONTEND_ENV_LOCAL}.bak"
fi
fi
}
sync_frontend_env
# ── Banner ───────────────────────────────────────────────────────────────────
echo ""
@@ -221,10 +176,7 @@ echo ""
echo " Mode: $MODE_LABEL"
echo ""
echo " Services:"
if ! $GATEWAY_MODE; then
echo " LangGraph → localhost:2024 (agent runtime)"
fi
echo " Gateway → localhost:8001 (REST API$(if $GATEWAY_MODE; then echo " + agent runtime"; fi))"
echo " Gateway → localhost:8001 (REST API + agent runtime)"
echo " Frontend → localhost:3000 (Next.js)"
echo " Nginx → localhost:2026 (reverse proxy)"
echo ""
@@ -268,34 +220,17 @@ run_service() {
mkdir -p logs
mkdir -p temp/client_body_temp temp/proxy_temp temp/fastcgi_temp temp/uwsgi_temp temp/scgi_temp
# 1. LangGraph (skip in gateway mode)
if ! $GATEWAY_MODE; then
CONFIG_LOG_LEVEL=$(grep -m1 '^log_level:' config.yaml 2>/dev/null | awk '{print $2}' | tr -d ' ')
LANGGRAPH_LOG_LEVEL="${LANGGRAPH_LOG_LEVEL:-${CONFIG_LOG_LEVEL:-info}}"
LANGGRAPH_JOBS_PER_WORKER="${LANGGRAPH_JOBS_PER_WORKER:-10}"
LANGGRAPH_ALLOW_BLOCKING="${LANGGRAPH_ALLOW_BLOCKING:-0}"
LANGGRAPH_ALLOW_BLOCKING_FLAG=""
if [ "$LANGGRAPH_ALLOW_BLOCKING" = "1" ]; then
LANGGRAPH_ALLOW_BLOCKING_FLAG="--allow-blocking"
fi
run_service "LangGraph" \
"cd backend && NO_COLOR=1 CLICOLOR=0 CLICOLOR_FORCE=0 PY_COLORS=0 TERM=dumb uv run langgraph dev --no-browser $LANGGRAPH_ALLOW_BLOCKING_FLAG --n-jobs-per-worker $LANGGRAPH_JOBS_PER_WORKER --server-log-level $LANGGRAPH_LOG_LEVEL $LANGGRAPH_EXTRA_FLAGS 2>&1 | LC_ALL=C LC_CTYPE=C LANG=C perl -pe 's/\e\[[0-9;]*[[:alpha:]]//g' > ../logs/langgraph.log" \
2024 60
else
echo "⏩ Skipping LangGraph (Gateway mode — runtime embedded in Gateway)"
fi
# 2. Gateway API
# 1. Gateway API
run_service "Gateway" \
"cd backend && PYTHONPATH=. uv run uvicorn app.gateway.app:app --host 0.0.0.0 --port 8001 $GATEWAY_EXTRA_FLAGS > ../logs/gateway.log 2>&1" \
8001 30
# 3. Frontend
# 2. Frontend
run_service "Frontend" \
"cd frontend && $FRONTEND_CMD > ../logs/frontend.log 2>&1" \
3000 120
# 4. Nginx
# 3. Nginx
run_service "Nginx" \
"nginx -g 'daemon off;' -c '$REPO_ROOT/docker/nginx/nginx.local.conf' -p '$REPO_ROOT' > logs/nginx.log 2>&1" \
2026 10
@@ -309,16 +244,11 @@ echo "=========================================="
echo ""
echo " 🌐 http://localhost:2026"
echo ""
if $GATEWAY_MODE; then
echo " Routing: Frontend → Nginx → Gateway (embedded runtime)"
echo " API: /api/langgraph-compat/* → Gateway agent runtime"
else
echo " Routing: Frontend → Nginx → LangGraph + Gateway"
echo " API: /api/langgraph/* → LangGraph server (2024)"
fi
echo " Routing: Frontend → Nginx → Gateway"
echo " API: /api/langgraph/* → Gateway agent runtime"
echo " /api/* → Gateway REST API (8001)"
echo ""
echo " 📋 Logs: logs/{langgraph,gateway,frontend,nginx}.log"
echo " 📋 Logs: logs/{gateway,frontend,nginx}.log"
echo ""
if $DAEMON_MODE; then