尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

火山引擎+Supabase+IGA Pages:AI Agent应用全栈托管部署实战

火山引擎+Supabase+IGA Pages:AI Agent应用全栈托管部署实战 1. 从零到一为什么选择火山引擎SupabaseIGA Pages这套组合最近在折腾AI Agent应用从原型验证到最终上线最头疼的往往不是模型调优而是那一堆繁琐的部署和运维工作。服务器要买、数据库要配、前端要部署、域名要解析、HTTPS要配置……一套流程下来精力被消耗大半真正花在Agent逻辑上的时间反而没多少。我一直在寻找一种能让我“一句话部署”的解决方案。这里的“一句话”不是魔法而是一种极致的抽象开发者只需要关心核心的AI Agent业务逻辑而将服务器、数据库、身份认证、对象存储、前端托管等所有基础设施的复杂度全部交给可靠、免运维的云服务去处理。经过多次实践和对比我最终锁定了“火山引擎 Supabase IGA Pages”这套技术栈。它完美契合了AI Agent应用快速迭代、全栈托管、成本可控的需求。简单拆解一下这三个核心组件各自扮演的角色火山引擎这里主要指的是其云服务器ECS和对象存储TOS服务。ECS为我们运行AI Agent的后端核心逻辑比如基于LangChain、LlamaIndex的链或智能体提供了稳定、可弹性伸缩的计算环境。而TOS则用于存储Agent可能需要的知识库文件、用户上传的文档、生成的图片或音频等非结构化数据。火山引擎的稳定性和在国内的访问速度是重要考量。Supabase这是一个开源的Firebase替代品但它远不止于此。对于AI Agent应用Supabase提供了几个开箱即用的杀手级功能1)PostgreSQL数据库用于存储用户会话、Agent执行历史、结构化知识等。2)实时订阅可以实现Agent执行状态、结果的实时推送到前端对于构建交互式应用至关重要。3)身份认证内置了邮箱/密码、OAuth如GitHub, Google等全套Auth系统省去自己搭建用户体系的麻烦。4)边缘函数虽然本篇以ECS为主但Supabase Edge Functions可以作为轻量级、事件驱动的后端逻辑补充。IGA Pages这是一个静态网站托管服务。我们的AI Agent前端比如用Vue、React或Next.js构建的交互界面可以打包成静态文件直接部署在IGA Pages上。它自动提供全球CDN加速、HTTPS证书并且通常与Git仓库集成实现提交代码自动部署。这意味着前端发布就像推送代码一样简单。这套组合的精髓在于“关注点分离”和“全栈托管”。你的核心AI逻辑跑在火山引擎的云服务器上数据和服务由Supabase管理用户界面由IGA Pages全球分发。每一层都是专业、免运维的你只需要用代码将它们“粘合”起来。接下来我就手把手带你走通从环境准备到一键部署的完整流程。2. 环境准备与项目骨架搭建在开始写第一行Agent代码之前我们需要把“舞台”搭好。这个阶段的目标是创建好所有必要的云资源并在本地初始化一个结构清晰的项目。2.1 云资源创建与配置首先我们需要在三个平台上完成初始设置。1. 火山引擎控制台登录火山引擎控制台进入云服务器ECS产品页面。创建ECS实例选择离你的目标用户近的地域如华北2-北京。对于AI Agent初期选择通用计算型如ecs.g1.large通常够用具体配置需根据你的模型负载调整。关键点在于镜像选择强烈推荐选择预装了Docker的公共镜像如Ubuntu 20.04 with Docker这能极大简化后续环境部署。安全组需要放行你后端服务的端口例如我们后续用到的FastAPI服务默认在8000端口。创建存储桶TOS进入对象存储TOS控制台创建一个新的存储桶Bucket。记住其名称和地域。在权限管理ACL中建议先设置为“私有读写”后续通过预签名URL或服务端代理方式访问确保数据安全。记录下Endpoint访问域名。2. Supabase项目创建访问Supabase官网并注册登录。新建项目点击“New Project”输入项目名称设置数据库密码务必保存好。选择离你ECS实例近的地域例如AWS ap-northeast-1 对应东京以减少网络延迟。免费计划对于初期项目完全足够。获取连接信息项目创建完成后进入项目设置Settings - API。这里你会找到几个关键信息Project URL你的Supabase项目地址格式如https://xxxxx.supabase.co。anon/public key用于前端或公开客户端安全调用Supabase API的密钥。service_role key超级密钥仅用于后端或可信环境拥有最高权限切勿泄露。Database Connection String数据库直接连接字符串格式为postgresql://postgres:[YOUR-PASSWORD]db.xxxxx.supabase.co:5432/postgres。3. IGA Pages服务准备以Vercel为例其他如Netlify、Cloudflare Pages同理。关联Git仓库在Vercel控制台点击“Add New” - “Project”导入你的GitHub/GitLab仓库。这要求你的前端代码已经存放在Git仓库中。环境变量配置在项目设置的“Environment Variables”中添加前端需要使用的环境变量例如VITE_SUPABASE_URL和VITE_SUPABASE_ANON_KEY值就是从Supabase控制台获取的那两个。这样前端构建时就能安全地注入这些配置。2.2 本地项目初始化与结构设计在本地开发环境我们创建一个标准的全栈项目目录。清晰的目录结构是后续高效开发和部署的基础。my-ai-agent/ ├── backend/ # AI Agent后端核心 (运行于火山引擎ECS) │ ├── app/ │ │ ├── main.py # FastAPI应用主入口 │ │ ├── agents/ # 具体的Agent逻辑模块 │ │ ├── core/ # 核心配置、工具类 │ │ └── services/ # 业务服务层如调用Supabase、TOS │ ├── requirements.txt # Python依赖 │ ├── Dockerfile # Docker镜像构建文件 │ └── docker-compose.yml # 可选本地开发环境编排 ├── frontend/ # 前端应用 (部署于IGA Pages) │ ├── src/ │ │ ├── main.jsx # 应用入口 │ │ ├── App.jsx # 主组件 │ │ ├── lib/ │ │ │ └── supabase.js # Supabase客户端初始化 │ │ └── components/ # React/Vue组件 │ ├── package.json │ ├── vite.config.js # 或 next.config.js │ └── .env.local # 本地环境变量.gitignore └── scripts/ # 部署脚本 └── deploy.sh # 一键部署脚本后端核心依赖 (backend/requirements.txt) 示例fastapi0.104.1 uvicorn[standard]0.24.0 supabase2.3.1 langchain0.0.340 openai1.3.0 # 或其他LLM SDK psycopg2-binary2.9.9 # PostgreSQL驱动 python-multipart0.0.6 # 文件上传 boto31.34.0 # 用于访问火山引擎TOS (AWS S3兼容接口)前端环境变量 (frontend/.env.local) 示例VITE_SUPABASE_URLhttps://xxxxx.supabase.co VITE_SUPABASE_ANON_KEYyour-anon-key-here这个结构将前后端完全分离通过API进行通信符合现代Web应用的最佳实践也为独立部署奠定了基础。3. AI Agent后端核心FastAPI与Supabase深度集成后端是整个AI Agent的大脑它负责接收前端请求编排LLM调用、工具使用Tool Calling、记忆Memory以及和数据库的交互。我们使用FastAPI作为Web框架因为它异步性能好、自动生成API文档非常适合AI应用。3.1 构建FastAPI应用与Supabase客户端首先在后端项目中建立与Supabase的连接。我们创建一个配置文件和一个服务类来集中管理。backend/app/core/config.pyfrom pydantic_settings import BaseSettings import os class Settings(BaseSettings): # Supabase 配置 supabase_url: str os.getenv(SUPABASE_URL) supabase_key: str os.getenv(SUPABASE_SERVICE_KEY) # 使用service_role key # 数据库连接字符串 (可选用于直接SQL操作) database_url: str fpostgresql://postgres:{os.getenv(DB_PASSWORD)}{os.getenv(DB_HOST)}:5432/postgres # 火山引擎TOS配置 (S3兼容) tos_access_key: str os.getenv(TOS_ACCESS_KEY) tos_secret_key: str os.getenv(TOS_SECRET_KEY) tos_endpoint: str os.getenv(TOS_ENDPOINT) tos_bucket_name: str os.getenv(TOS_BUCKET_NAME) # LLM配置 (例如OpenAI) openai_api_key: str os.getenv(OPENAI_API_KEY) class Config: env_file .env settings Settings()backend/app/services/supabase_client.pyfrom supabase import create_client, Client from app.core.config import settings import logging logger logging.getLogger(__name__) class SupabaseService: _client: Client None classmethod def get_client(cls) - Client: if cls._client is None: try: cls._client create_client(settings.supabase_url, settings.supabase_key) logger.info(Supabase client initialized successfully.) except Exception as e: logger.error(fFailed to initialize Supabase client: {e}) raise return cls._client classmethod async def save_agent_session(cls, session_data: dict): 保存Agent会话历史到sessions表 client cls.get_client() response client.table(sessions).insert(session_data).execute() return response.data classmethod async def get_user_conversations(cls, user_id: str, limit: int 10): 获取用户最近的对话历史 client cls.get_client() response client.table(sessions)\ .select(*)\ .eq(user_id, user_id)\ .order(created_at, descTrue)\ .limit(limit)\ .execute() return response.data这里的关键是使用SUPABASE_SERVICE_KEY而非ANON_KEY。因为后端服务需要较高的数据库操作权限如向任何表插入数据service_role key是必需的。切记这个密钥绝不能暴露给前端。3.2 实现一个具备记忆与工具调用能力的Agent端点接下来我们实现一个真正的AI Agent端点。这个Agent将能够进行多轮对话记忆并且可以调用一个“获取天气”的模拟工具。首先在Supabase中创建两张表通过SQL Editor执行-- 会话表记录每次对话的元信息 CREATE TABLE sessions ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, user_id TEXT NOT NULL, title TEXT, -- 自动生成的会话标题 created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE(utc::text, NOW()) NOT NULL, updated_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE(utc::text, NOW()) NOT NULL ); -- 消息表记录会话中的每条消息 CREATE TABLE messages ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, session_id UUID REFERENCES sessions(id) ON DELETE CASCADE, role TEXT NOT NULL CHECK (role IN (user, assistant, system, tool)), content TEXT, tool_calls JSONB, -- 存储Agent提出的工具调用请求 tool_call_id TEXT, -- 对应tool_calls中的id created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE(utc::text, NOW()) NOT NULL ); -- 为常用查询创建索引 CREATE INDEX idx_messages_session_id ON messages(session_id); CREATE INDEX idx_sessions_user_id ON sessions(user_id);然后实现Agent路由 (backend/app/main.py)from fastapi import FastAPI, HTTPException, Depends from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel from typing import List, Optional import logging from app.services.supabase_client import SupabaseService from app.services.agent_orchestrator import AgentOrchestrator app FastAPI(titleAI Agent Backend) # 配置CORS允许前端域名访问 app.add_middleware( CORSMiddleware, allow_origins[https://your-iga-pages-domain.vercel.app], # 替换为你的前端域名 allow_credentialsTrue, allow_methods[*], allow_headers[*], ) # 请求/响应模型 class Message(BaseModel): role: str content: str class ChatRequest(BaseModel): session_id: Optional[str] None # 为空则创建新会话 message: str user_id: str # 从前端认证token中解析而来 class ChatResponse(BaseModel): session_id: str reply: str tool_used: Optional[str] None app.post(/chat, response_modelChatResponse) async def chat_with_agent(request: ChatRequest): 核心聊天端点。 1. 根据session_id获取或创建会话。 2. 从数据库加载该会话的历史消息作为记忆。 3. 将用户新消息和记忆交给AgentOrchestrator处理。 4. Agent可能返回纯文本也可能请求调用工具。 5. 执行工具将结果返回给Agent获取最终回复。 6. 将用户消息、工具调用、工具结果、助手回复完整保存到数据库。 try: # 初始化编排器内部包含LangChain Agent或自定义逻辑 orchestrator AgentOrchestrator(user_idrequest.user_id) # 处理会话 if not request.session_id: # 创建新会话并可能用第一条消息生成一个标题 session_data {user_id: request.user_id} new_session await SupabaseService.save_agent_session(session_data) session_id new_session[0][id] # 可选异步生成会话标题 # asyncio.create_task(generate_session_title(session_id, request.message)) else: session_id request.session_id # 获取历史消息最近10轮作为上下文 history await SupabaseService.get_messages_by_session(session_id, limit20) # 将历史消息转换为LangChain或其他框架所需的Memory格式 # 调用Agent编排器处理当前轮次 agent_response await orchestrator.process( session_idsession_id, user_inputrequest.message, chat_historyhistory ) # 保存交互记录到Supabase await SupabaseService.save_message({ session_id: session_id, role: user, content: request.message }) if agent_response.tool_calls: for tool_call in agent_response.tool_calls: await SupabaseService.save_message({ session_id: session_id, role: assistant, tool_calls: tool_call.dict(), content: None }) # ... 保存工具执行结果和助手最终回复 return ChatResponse( session_idsession_id, replyagent_response.final_output, tool_usedagent_response.tool_name ) except Exception as e: logging.error(fError in chat endpoint: {e}) raise HTTPException(status_code500, detailInternal Server Error)backend/app/services/agent_orchestrator.py简化示例from langchain.agents import AgentExecutor, create_openai_tools_agent from langchain_openai import ChatOpenAI from langchain.memory import ConversationBufferMemory from langchain.tools import tool from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder import os class AgentOrchestrator: def __init__(self, user_id: str): self.llm ChatOpenAI(modelgpt-4-turbo-preview, temperature0, api_keyos.getenv(OPENAI_API_KEY)) self.memory ConversationBufferMemory(memory_keychat_history, return_messagesTrue) self.tools [self.get_weather_tool] # 注册工具 self.agent self._create_agent() tool def get_weather_tool(city: str) - str: 获取指定城市的当前天气。这是一个模拟工具。 # 实际项目中这里会调用真实的天气API weather_data { 北京: 晴15°C, 上海: 多云18°C, 深圳: 阵雨22°C } return weather_data.get(city, f未找到{city}的天气信息。) def _create_agent(self): prompt ChatPromptTemplate.from_messages([ (system, 你是一个乐于助人的AI助手。你可以使用工具来获取信息。), MessagesPlaceholder(variable_namechat_history), (human, {input}), MessagesPlaceholder(variable_nameagent_scratchpad), ]) agent create_openai_tools_agent(self.llm, self.tools, prompt) return AgentExecutor(agentagent, toolsself.tools, memoryself.memory, verboseTrue) async def process(self, session_id: str, user_input: str, chat_history: list): # 将数据库中的历史记录加载到LangChain Memory中 for msg in chat_history: if msg[role] user: self.memory.chat_memory.add_user_message(msg[content]) elif msg[role] assistant: self.memory.chat_memory.add_ai_message(msg[content]) # 执行Agent response await self.agent.ainvoke({input: user_input}) return AgentResponse( final_outputresponse[output], tool_callsresponse.get(intermediate_steps, []), tool_nameresponse.get(tool, None) )这个架构实现了带有持久化记忆存储在Supabase和工具调用能力的Agent。每次对话的完整轨迹都被记录下来便于后续分析、调试或实现更复杂的“反思”与“规划”能力。4. 前端交互React与Supabase实时通信前端的目标是提供一个美观、响应式的界面让用户能与AI Agent自然对话并实时看到对话流和Agent的思考过程如工具调用。4.1 初始化Supabase客户端并实现实时订阅在前端项目中我们首先初始化Supabase客户端并利用其强大的实时Realtime功能来同步对话更新。frontend/src/lib/supabase.jsimport { createClient } from supabase/supabase-js const supabaseUrl import.meta.env.VITE_SUPABASE_URL const supabaseAnonKey import.meta.env.VITE_SUPABASE_ANON_KEY if (!supabaseUrl || !supabaseAnonKey) { throw new Error(Missing Supabase environment variables) } export const supabase createClient(supabaseUrl, supabaseAnonKey, { realtime: { params: { eventsPerSecond: 10, // 控制事件频率 }, }, })在聊天组件中订阅消息变更import { useEffect, useState } from react import { supabase } from ../lib/supabase function ChatRoom({ sessionId }) { const [messages, setMessages] useState([]) useEffect(() { // 1. 首次加载时获取历史消息 const fetchMessages async () { const { data, error } await supabase .from(messages) .select(*) .eq(session_id, sessionId) .order(created_at, { ascending: true }) if (!error data) setMessages(data) } fetchMessages() // 2. 订阅该会话的新消息实时推送 const channel supabase .channel(room:${sessionId}) .on( postgres_changes, { event: INSERT, schema: public, table: messages, filter: session_ideq.${sessionId}, }, (payload) { // 当数据库中有新消息插入时实时更新UI setMessages((prev) [...prev, payload.new]) } ) .subscribe() // 清理订阅 return () { supabase.removeChannel(channel) } }, [sessionId]) // ... 渲染消息列表 }通过实时订阅当后端Agent将新的消息用户输入、工具调用、助手回复插入Supabase数据库时所有打开了该会话的前端页面都会立即收到更新无需轮询。这为构建协同编辑、客服看板等场景提供了可能。4.2 构建聊天UI与调用后端API接下来构建主要的聊天界面并实现与后端FastAPI服务的通信。frontend/src/components/ChatInterface.jsximport { useState, useRef, useEffect } from react import { supabase } from ../lib/supabase import ./ChatInterface.css export default function ChatInterface({ user }) { const [input, setInput] useState() const [isLoading, setIsLoading] useState(false) const [currentSessionId, setCurrentSessionId] useState(null) const messagesEndRef useRef(null) // 发送消息 const handleSend async () { if (!input.trim() || isLoading) return const userMessage input setInput() setIsLoading(true) try { // 调用后端 /chat 接口 const response await fetch(${import.meta.env.VITE_BACKEND_URL}/chat, { method: POST, headers: { Content-Type: application/json }, // 假设用户身份已通过Supabase Auth获取 body: JSON.stringify({ session_id: currentSessionId, message: userMessage, user_id: user.id }), }) if (!response.ok) throw new Error(Network response was not ok) const data await response.json() // 如果这是新会话设置sessionId if (!currentSessionId data.session_id) { setCurrentSessionId(data.session_id) } // 注意助手回复会通过Supabase实时订阅自动添加到messages状态中 // 这里无需手动更新UI } catch (error) { console.error(Error sending message:, error) // 可以在这里添加一个错误提示到消息列表 const errorMessage { id: Date.now(), role: system, content: 发送失败: ${error.message}, created_at: new Date().toISOString() } // 手动添加错误消息到状态如果不想依赖实时订阅 } finally { setIsLoading(false) } } // 滚动到底部 useEffect(() { messagesEndRef.current?.scrollIntoView({ behavior: smooth }) }, [messages]) // 依赖messages状态当消息更新时滚动 return ( div classNamechat-container div classNamemessages-panel {messages.map((msg) ( div key{msg.id} className{message ${msg.role}} div classNameavatar{msg.role user ? : }/div div classNamecontent {msg.role tool ? ( div classNametool-call small调用了工具: {msg.tool_name}/small pre{JSON.stringify(msg.content, null, 2)}/pre /div ) : ( p{msg.content}/p )} /div /div ))} {isLoading ( div classNamemessage assistant div classNameavatar/div div classNamecontent div classNametyping-indicator span/spanspan/spanspan/span /div /div /div )} div ref{messagesEndRef} / /div div classNameinput-area textarea value{input} onChange{(e) setInput(e.target.value)} onKeyDown{(e) e.key Enter !e.shiftKey handleSend()} placeholder输入你的问题... disabled{isLoading} rows3 / button onClick{handleSend} disabled{isLoading || !input.trim()} {isLoading ? 思考中... : 发送} /button /div /div ) }这个前端组件完成了消息发送、加载状态显示、消息列表渲染区分用户、助手、工具消息和自动滚动的核心循环。它与后端的REST API交互发起对话并依靠Supabase的实时功能来接收更新实现了流畅的聊天体验。5. 一键部署Docker化与自动化脚本将所有组件部署上线的最后一步是将后端服务容器化并推送到火山引擎ECS同时将前端部署到IGA Pages。我们的目标是实现“一句话”或“一个脚本”完成全部操作。5.1 编写Dockerfile与docker-compose.yml首先为后端服务创建Dockerfile确保环境一致性。backend/Dockerfile# 使用官方Python镜像 FROM python:3.11-slim # 设置工作目录 WORKDIR /app # 设置环境变量防止Python输出缓冲使日志实时显示 ENV PYTHONUNBUFFERED1 # 安装系统依赖例如PostgreSQL客户端库、构建工具 RUN apt-get update apt-get install -y \ gcc \ postgresql-client \ rm -rf /var/lib/apt/lists/* # 复制依赖文件并安装 COPY requirements.txt . RUN pip install --no-cache-dir --upgrade pip \ pip install --no-cache-dir -r requirements.txt # 复制应用代码 COPY . . # 暴露端口与FastAPI应用内一致 EXPOSE 8000 # 启动命令使用uvicorn作为ASGI服务器 CMD [uvicorn, app.main:app, --host, 0.0.0.0, --port, 8000, --reload]注意生产环境应使用--workers指定多进程并配合gunicorn等WSGI服务器。这里使用--reload仅适用于开发或调试。backend/docker-compose.yml(用于本地开发与测试)version: 3.8 services: backend: build: . ports: - 8000:8000 environment: - SUPABASE_URL${SUPABASE_URL} - SUPABASE_SERVICE_KEY${SUPABASE_SERVICE_KEY} - OPENAI_API_KEY${OPENAI_API_KEY} - TOS_ACCESS_KEY${TOS_ACCESS_KEY} - TOS_SECRET_KEY${TOS_SECRET_KEY} - TOS_ENDPOINT${TOS_ENDPOINT} - TOS_BUCKET_NAME${TOS_BUCKET_NAME} volumes: - ./app:/app/app # 挂载代码目录实现代码热重载 # 如果需要本地数据库可以添加一个PostgreSQL服务 # depends_on: # - db # db: # image: postgres:15 # environment: # POSTGRES_PASSWORD: example # volumes: # - postgres_data:/var/lib/postgresql/data # volumes: # postgres_data:5.2 编写自动化部署脚本“一句话上线”的灵魂在于自动化脚本。我们编写一个Shell脚本将构建、推送、部署的步骤串联起来。scripts/deploy.sh#!/bin/bash set -e # 遇到错误即停止 echo 开始 AI Agent 全栈部署... # 1. 定义变量 BACKEND_DIR./backend FRONTEND_DIR./frontend ECR_REGISTRYyour-volcano-engine-container-registry-address # 替换为你的火山引擎容器镜像仓库地址 ECS_INSTANCE_IPyour-ecs-public-ip # 替换为你的ECS公网IP ECS_SSH_USERroot # 或你的用户名 IMAGE_TAGlatest IMAGE_NAMEmy-ai-agent-backend # 2. 构建前端静态文件 echo 构建前端应用... cd $FRONTEND_DIR npm install npm run build # 假设package.json中build命令是 vite build 或 next build echo ✅ 前端构建完成。 # 3. 部署前端到IGA Pages (以Vercel CLI为例) echo 部署前端到 Vercel... # 确保已安装Vercel CLI并登录: npm i -g vercel vercel login vercel --prod --confirm # 自动检测项目并部署 FRONTEND_URL$(vercel ls | grep my-ai-agent-frontend | head -1 | awk {print $2}) echo ✅ 前端已部署至: $FRONTEND_URL # 4. 构建并推送后端Docker镜像 echo 构建后端Docker镜像... cd ../$BACKEND_DIR docker build -t $IMAGE_NAME:$IMAGE_TAG . # 登录火山引擎容器镜像仓库 (假设使用标准Docker Registry) # 请先在火山引擎控制台创建镜像仓库并获取登录命令 # docker login --usernameyour-username $ECR_REGISTRY docker tag $IMAGE_NAME:$IMAGE_TAG $ECR_REGISTRY/$IMAGE_NAME:$IMAGE_TAG docker push $ECR_REGISTRY/$IMAGE_NAME:$IMAGE_TAG echo ✅ 后端镜像已推送至容器仓库。 # 5. 远程部署到火山引擎ECS echo ️ 部署到火山引擎ECS... # 通过SSH连接到ECS实例执行部署命令 ssh $ECS_SSH_USER$ECS_INSTANCE_IP EOF set -e echo 1. 拉取最新镜像... docker pull $ECR_REGISTRY/$IMAGE_NAME:$IMAGE_TAG echo 2. 停止并移除旧容器... docker stop $IMAGE_NAME || true docker rm $IMAGE_NAME || true echo 3. 运行新容器... docker run -d \\ --name $IMAGE_NAME \\ --restart unless-stopped \\ -p 8000:8000 \\ -e SUPABASE_URL$SUPABASE_URL \\ -e SUPABASE_SERVICE_KEY$SUPABASE_SERVICE_KEY \\ -e OPENAI_API_KEY$OPENAI_API_KEY \\ -e TOS_ACCESS_KEY$TOS_ACCESS_KEY \\ -e TOS_SECRET_KEY$TOS_SECRET_KEY \\ -e TOS_ENDPOINT$TOS_ENDPOINT \\ -e TOS_BUCKET_NAME$TOS_BUCKET_NAME \\ $ECR_REGISTRY/$IMAGE_NAME:$IMAGE_TAG echo 4. 检查容器状态... sleep 5 docker ps | grep $IMAGE_NAME echo ✅ 后端服务部署完成。 EOF # 6. 更新前端环境变量指向新的后端地址 echo 更新前端环境变量后端API地址... # 这里需要根据你ECS实例的网络配置来设置。 # 如果你的ECS有公网IP和域名可以这样设置 BACKEND_API_URLhttp://$ECS_INSTANCE_IP:8000 # 或你的域名 # 使用Vercel CLI更新环境变量 cd ../$FRONTEND_DIR vercel env add VITE_BACKEND_URL production $BACKEND_API_URL echo 全栈部署完成 echo 前端访问: $FRONTEND_URL echo 后端API: $BACKEND_API_URL echo 请确保ECS安全组已放行8000端口。这个脚本涵盖了从本地构建到云端部署的全流程。在实际使用前你需要替换脚本中的占位符容器仓库地址、ECS IP等。确保本地已安装Docker、Vercel CLI并完成相应的登录认证。为ECS实例配置SSH密钥对以便无密码登录。在ECS安全组中开放8000端口或你自定义的端口。执行bash scripts/deploy.sh理论上就可以完成从代码到服务的全自动上线。这就是“一句话上线”的终极形态——将复杂性封装在一个脚本中。6. 实测踩坑与性能优化要点将应用部署上线并运行一段时间后我遇到了几个典型问题这里分享出来希望能帮你避开这些坑。6.1 数据库连接池与长连接管理在初期后端服务运行一段时间后偶尔会出现数据库连接超时或耗尽的问题。这是因为Supabase PostgreSQL数据库对连接数有限制免费版约20个并发连接而FastAPI在默认情况下每个请求可能会创建新的数据库连接。解决方案使用连接池并在应用生命周期内管理连接。我们在FastAPI的启动和关闭事件中管理一个全局的数据库连接池如asyncpg池或SQLAlchemy的引擎。# backend/app/core/database.py from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker from app.core.config import settings engine create_async_engine( settings.database_url, echoFalse, # 生产环境设为False pool_size5, # 连接池大小根据Supabase限制和你的并发量调整 max_overflow10, pool_pre_pingTrue, # 每次从池中取连接前先ping一下防止连接失效 pool_recycle300, # 连接回收时间秒 ) AsyncSessionLocal async_sessionmaker(engine, class_AsyncSession, expire_on_commitFalse) # 在FastAPI的依赖注入中使用 async def get_db(): async with AsyncSessionLocal() as session: try: yield session finally: await session.close() # 在main.py中注册事件 app.on_event(startup) async def startup_event(): # 可以在这里进行一些初始化连接池会自动创建 pass app.on_event(shutdown) async def shutdown_event(): await engine.dispose()这样确保了数据库连接被高效复用不会超出限制。6.2 Agent超时与异步任务队列AI Agent的推理尤其是涉及复杂链式调用或大模型响应慢时很容易超过HTTP请求的典型超时时间如30秒。让用户前端长时间等待一个HTTP响应是不现实的。解决方案采用异步任务模式。快速响应后端/chat接口收到请求后立即返回一个task_id或session_id告知请求已接受。后台处理将实际的Agent处理逻辑放入一个后台任务队列如Celery、RQ或更简单的asyncio.create_task配合内存队列但后者可靠性低。状态推送后台任务处理过程中将状态更新开始处理、调用工具、生成结果通过WebSocket或Supabase的Realtime功能推送到前端。前端轮询/订阅前端根据task_id轮询一个状态接口或直接订阅Supabase中该任务记录的变化来获取最终结果和中间过程。# 伪代码示例FastAPI 后台任务 from fastapi import BackgroundTasks import asyncio app.post(/chat/async) async def chat_async(request: ChatRequest, background_tasks: BackgroundTasks): task_id str(uuid.uuid4()) # 将任务放入后台 background_tasks.add_task(run_agent_task, task_id, request) return {task_id: task_id, status: accepted} async def run_agent_task(task_id: str, request: ChatRequest): # 1. 在Supabase中创建一条任务记录状态为processing # 2. 执行耗时的Agent逻辑 # 3. 每一步更新任务记录如写入工具调用结果 # 4. 最终完成时更新状态为completed并写入最终回复 # 前端通过订阅tasks表或轮询/task/{task_id}来获取进度。6.3 前端环境变量与安全在frontend/.env.local中配置的Supabase密钥是ANON_KEY它是公开的。但后端API地址VITE_BACKEND_URL在开发和生产环境可能不同。如果直接写死在代码里每次部署都需要修改。解决方案利用IGA Pages如Vercel的环境变量配置。在Vercel项目设置的Environment Variables中分别配置Production、Preview、Development环境下的VITE_BACKEND_URL。在代码中通过import.meta.env.VITE_BACKEND_URL访问。在部署脚本中可以通过Vercel CLI (vercel env add) 动态更新环境变量如脚本第6步所示。安全提醒永远不要在前端环境变量或代码中放入敏感信息如SUPABASE_SERVICE_KEY、数据库密码、第三方API密钥等。这些必须仅在后端环境ECS的环境变量或密钥管理服务中设置。6.4 成本监控与优化这套架构虽然便捷但涉及多项云服务需要关注成本尤其是火山引擎ECS按量计费实例在不使用时可以关机节省费用。对于流量波动大的应用可以考虑配置弹性伸缩AS。Supabase免费计划有使用限制数据库空间、带宽、函数调用次数。务必在控制台设置用量警报并优化查询避免全表扫描。对于messages这类增长快的表考虑归档旧数据或使用分区。LLM API调用如OpenAI这是AI应用的主要成本之一。实施对话长度限制、缓存常见回答、对用户进行分级限流等都是有效的控制手段。部署完成后定期查看各云服务商的控制台账单和用量图表建立成本意识是项目健康运营的关键。
返回列表