1. Suna 源码解读:从架构到部署的完整实践
Suna 是 Kortix AI 开源的一个通用型 Agent 平台,你可以把它理解成"能自己动手干活的 AI 助手"——不只是聊天,而是能自主规划任务、调用浏览器、执行代码、读写文件、生成报告。它适合谁?想深入理解 Agent 平台设计思路的后端开发者、需要自托管一套自动化任务系统的团队,以及打算基于成熟框架做二次开发的人。我这次把它的源码结构、核心模块和本地跑通流程完整走了一遍,重点拆解三个关键设计:Sandbox 隔离执行、Redis 流式总线、Supabase 数据中心。整篇文章会给出可复制的环境配置、依赖安装命令和启动验证步骤,让你从源码层面掌握它的运行机制,并找到二次开发的入口。需要说明的是,Suna 默认依赖若干海外中间件,国内直连体验会偏慢,所以部署时我会同步给出替换思路,把外部依赖换成自建服务,这样生产环境才跑得动。下面从源码目录开始,一层层往下拆。
1.1 源码目录结构与模块职责
先把仓库拉下来,看清楚每个目录在干什么,后面读代码才不会迷路。
git clone https://github.com/kortix-ai/suna cd suna ls -la你会看到大致这样的结构:
suna/ ├── backend/ # 后端服务(Python / FastAPI) │ ├── agents/ # Agent 主循环、Prompt、工具集 │ │ └── tools/ # 浏览器、代码、文件、API 等工具实现 │ ├── api/ # FastAPI 路由 │ └── ... ├── frontend/ # 前端应用(Next.js / React / TailwindCSS) ├── docs/ # 文档与架构图 ├── README.md └── docker-compose.yml # 一键起 Supabase + Redis + 后端 + 前端backend/agents/是整个项目的心脏。Agent 的主循环在这里:接收用户任务,调用 LLM 做规划,解析出要执行的工具,再把工具结果回灌给 LLM,循环直到任务完成。backend/agents/tools/下每个文件对应一类能力,比如浏览器自动化、代码执行、文件系统操作、API 调用。想加自定义工具,基本就是在这个目录里新增一个模块,然后注册到工具列表。
backend/api/是 FastAPI 的路由层,负责对外暴露接口。所有 Agent 执行都从/agent/execute这类端点发起,前端通过 SSE 或 WebSocket 订阅后端推送的流式结果。
frontend/是 Next.js 应用,提供聊天界面、仪表盘、文件管理、浏览器远程查看等交互。它本身不跑 Agent 逻辑,只负责展示和下发指令。
docker-compose.yml是本地跑通的关键,它把 Supabase、Redis、后端、前端编排在一起,一条命令就能起全套。
1.2 三大核心设计:Sandbox、Redis Stream、Supabase
读 Suna 源码,最值得花时间的是这三个抽象,它们决定了平台的隔离性、流式能力和数据组织方式。
Sandbox 隔离执行:每个 Agent 任务会拉起一个独立的容器,代码执行、依赖安装、浏览器操作都在沙箱内完成。这样 A 任务不会污染 B 任务,用户提交的代码也不会碰到宿主机。源码里通过 Daytona SDK 调度容器,简化流程大致是这样:
from daytona_sdk import Daytona daytona = Daytona() sandbox = daytona.create() # 起一个隔离容器 sandbox.exec("pip install pandas") # 在沙箱里装包 result = sandbox.exec("python analysis.py") # 在沙箱里执行 daytona.remove(sandbox) # 任务结束销毁生产环境里 Daytona 是商业云服务,自部署可以换成自建 Docker host 或 Kubernetes Job,核心思路是把"Agent 执行环境"和"主服务"做物理隔离。
Redis 作为流式响应缓冲:这是 Suna 最值得借鉴的设计。LLM 的流式输出不直接推给前端,而是先写进 Redis Stream,前端再从 Redis 读。写入端和读取端分离:
# 写入端(推理进程) for chunk in llm.stream(messages): redis.xadd(f"agent_run:{run_id}", {"token": chunk}) # 读取端(推送进程 / SSE 端点) last_id = request_last_id or "0" while True: entries = redis.xread({f"agent_run:{run_id}": last_id}, block=1000) for entry in entries: yield entry.token last_id = entry.id这样做的好处是推理和前端消费完全解耦,前端断线重连后能从上次的 Stream ID 续读,不会丢消息;多个客户端也能同时订阅同一个agent_run。代价是系统复杂度上升,Redis 内存要配MAXLEN裁剪,否则长文本高并发时内存压力会很大。
Supabase 作为数据中心:Supabase 在 Suna 里一身多职——GoTrue 负责邮箱和 OAuth 登录,Postgres 存用户、会话、消息、Agent run、文件元数据,Storage 存上传文档和产出物,Realtime 提供可选的实时订阅。对个人开发者很友好,一个服务搞定认证加数据库加存储。企业生产建议自建 Postgres + MinIO,避免 vendor lock-in。
1.3 整体架构与数据流
把上面三块拼起来,Suna 的架构大致是这样:
┌────────────┐ ┌─────────────┐ ┌────────────────┐ │ Frontend │◄─►│ Backend │◄─►│ LLM API │ │ (Next.js) │SSE│ (FastAPI) │ │ (OpenAI/Claude)│ └────────────┘ └─────┬───────┘ └────────────────┘ │ ┌──────────────┼────────────┬──────────────┐ ▼ ▼ ▼ ▼ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ Redis │ │Supabase │ │ Daytona │ │ Browser │ │(Stream) │ │(Data/Auth)│ (Sandbox)│ │base/CDP │ └─────────┘ └─────────┘ └─────────┘ └─────────┘一次任务的完整数据流:前端下发任务 → 后端/agent/execute接收 → Agent 主循环调 LLM 规划 → 需要执行工具时在 Daytona 沙箱里跑 → 每步结果写 Redis Stream → 前端从 Redis 读并渲染。Supabase 全程负责认证、持久化和文件存储。
理解了这条链路,后面部署时遇到报错就能快速定位是哪一环出的问题。
2. TaoToken 前置:模型接入与 API Key 准备
Suna 本身不绑定特定模型,它通过 LLM API 调用外部模型。源码里默认走 OpenAI 或 Anthropic 的接口,但你可以通过 LiteLLM 这类抽象层接国内模型。这里我用 TaoToken 作为模型接入层来演示,因为它兼容 OpenAI 协议,改 Base URL 就能用,省去改源码的麻烦。
2.1 为什么要在 Suna 里配 TaoToken
Suna 的 Agent 主循环对模型的调用量不小——一个"调研加生成报告"的任务往往要 20 到 80 次 LLM 调用,加上多次工具调用。如果模型接口不稳定或延迟高,整个任务会拖得很慢。TaoToken 提供统一的 OpenAI 兼容接口,你可以在不改 Suna 源码的前提下,把模型请求指向它,同时方便切换不同模型做对比。
具体来说,Suna 后端读环境变量里的 LLM 配置,你只要把 Base URL 和 Key 填对,Agent 就能正常跑。下面给出完整配置。
2.2 获取 API Key 与配置入口
先到 TaoToken 控制台创建 API Key。打开 https://taotoken.net/api-keys ,登录后新建一个 Key,复制保存。这个 Key 后面要填进 Suna 的.env文件。
TaoToken 的 API 地址是 https://taotoken.net/api ,兼容 OpenAI 的/v1/chat/completions路径。也就是说,Suna 里凡是填 OpenAI Base URL 的地方,换成这个地址即可。
如果你还没注册,可以先到 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 了解下,注册后在控制台就能拿到 Key。
2.3 模型选择建议
Suna 的 Agent 任务对模型的规划能力和工具调用能力要求较高。实测下来,带 function calling 能力的模型跑起来更顺,因为 Agent 需要模型输出结构化的工具调用指令。你可以在 TaoToken 的模型列表里挑一个支持工具调用的模型,把 Model ID 记下来,后面填进配置。
模型对话调试可以到 https://taotoken.net/models 直接试,确认模型能正常返回再往 Suna 里配,省得在项目里排查。
2.4 环境变量准备清单
在改 Suna 配置前,先把需要的值列清楚:
| 变量名 | 用途 | 取值来源 |
|---|---|---|
| LLM_API_KEY | 模型调用密钥 | TaoToken 控制台 |
| LLM_API_BASE | 模型接口地址 | https://taotoken.net/api |
| LLM_MODEL | 模型 ID | TaoToken 模型列表 |
| REDIS_URL | 流式总线 | 本地 docker 或自建 |
| SUPABASE_URL | 数据中心 | 本地 docker 或自建 |
| SUPABASE_KEY | Supabase 密钥 | 本地 docker 或自建 |
把这些准备好,下一步就能直接改配置文件启动。
3. 可复制配置:Suna 本地部署与 settings 片段
这一节给出完整的可复制配置。Suna 用.env管理环境变量,用docker-compose.yml编排服务。我按源码里的路径和字段名来写,你直接照着改就行。
3.1 克隆仓库与初始化环境文件
git clone https://github.com/kortix-ai/suna cd suna cp .env.example .env.env.example里列出了所有需要的变量,复制成.env后逐项填写。
3.2 填写 .env 配置
打开.env,把模型相关的字段改成 TaoToken 的值。关键片段如下:
# ---- LLM 配置 ---- LLM_API_KEY=sk-你的TaoToken密钥 LLM_API_BASE=https://taotoken.net/api LLM_MODEL=你的模型ID # ---- Redis 流式总线 ---- REDIS_URL=redis://redis:6379/0 # ---- Supabase 数据中心 ---- SUPABASE_URL=http://supabase:8000 SUPABASE_KEY=你的Supabase密钥 # ---- Sandbox 沙箱 ---- DAYTONA_API_KEY=你的Daytona密钥注意LLM_API_BASE填的是https://taotoken.net/api,不要带/v1,具体路径由 Suna 的调用代码拼接。如果你的模型需要特定路径,可以在源码里调整。
3.3 docker-compose 编排说明
Suna 的docker-compose.yml会把 Supabase、Redis、后端、前端一起起起来。核心服务定义大致如下:
services: redis: image: redis:7-alpine ports: - "6379:6379" supabase: image: supabase/postgres:15 environment: POSTGRES_PASSWORD: yourpassword ports: - "5432:5432" backend: build: ./backend env_file: .env depends_on: - redis - supabase ports: - "8000:8000" frontend: build: ./frontend env_file: .env depends_on: - backend ports: - "3000:3000"如果你要替换海外中间件,把supabase换成自建 Postgres + MinIO,把 Daytona 换成自建 Docker host,改的就是这一段。
3.4 启动全套服务
docker compose up -d docker compose psdocker compose ps会列出所有容器状态,确认 redis、supabase、backend、frontend 都是running或healthy。
3.5 后端关键配置片段
Suna 后端读环境变量后,会在启动时初始化 LLM 客户端。源码里大致是这样组织的:
import os from openai import OpenAI client = OpenAI( api_key=os.getenv("LLM_API_KEY"), base_url=os.getenv("LLM_API_BASE"), ) def call_llm(messages, tools=None): resp = client.chat.completions.create( model=os.getenv("LLM_MODEL"), messages=messages, tools=tools, stream=True, ) for chunk in resp: yield chunk这段代码说明:只要LLM_API_BASE指向 TaoToken,Suna 的模型调用就走 TaoToken,不需要改其他逻辑。
3.6 前端配置片段
前端通过环境变量拿到后端地址,配置在frontend/.env.local:
NEXT_PUBLIC_API_BASE=http://localhost:8000 NEXT_PUBLIC_SUPABASE_URL=http://localhost:8000 NEXT_PUBLIC_SUPABASE_ANON_KEY=你的匿名密钥改完重启前端容器即可生效。
4. 验证请求与成功结果
配置填好后,先别急着跑复杂任务,用最小请求验证模型链路通不通,再验证 Agent 主循环能不能跑起来。
4.1 验证模型接口连通性
先用 curl 直接打 TaoToken 的接口,确认 Key 和 Base URL 没问题:
curl https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer sk-你的TaoToken密钥" \ -H "Content-Type: application/json" \ -d '{ "model": "你的模型ID", "messages": [{"role": "user", "content": "回复 ok"}] }'如果返回里有choices字段和正常内容,说明模型链路通了。这一步能排除掉大部分 401 和模型不存在的问题。
4.2 验证后端健康状态
curl http://localhost:8000/health返回{"status":"ok"}之类的响应,说明后端起来了。
4.3 发起一次 Agent 任务
通过后端接口下发一个简单任务:
curl -X POST http://localhost:8000/agent/execute \ -H "Content-Type: application/json" \ -d '{ "task": "计算 1 到 100 的和,并输出结果", "user_id": "test-user" }'后端会返回一个run_id,你可以用它去订阅流式输出。
4.4 订阅流式结果
前端通过 SSE 订阅,你也可以用 curl 模拟:
curl -N http://localhost:8000/agent/stream?run_id=你的run_id正常的话,你会看到一段段 token 陆续返回,最后是任务完成的结果。这说明 Redis Stream 的写入和读取都通了。
4.5 成功结果长什么样
一次成功的任务,你会看到类似这样的输出流:
[plan] 我需要计算 1 到 100 的和 [tool] 调用代码执行工具 [code] sum(range(1, 101)) [result] 5050 [answer] 1 到 100 的和是 5050看到[answer]这一行,说明 Agent 主循环、工具调用、沙箱执行、流式推送整条链路都跑通了。
4.6 前端界面验证
打开浏览器访问http://localhost:3000,登录后新建一个对话,输入同样的任务。如果界面上能看到逐步输出的过程,并且最终给出答案,说明前后端联调成功。
5. 本篇常见错排查
部署 Suna 时最容易卡在几个地方,我按真实报错来对照排查。
5.1 401 Unauthorized
报错长这样:
openai.AuthenticationError: Error code: 401 - {'error': {'message': 'Invalid API key'}}原因通常是LLM_API_KEY填错,或者 Key 前后带了空格。检查.env里的值,确认没有多余引号和空格。另外确认LLM_API_BASE是https://taotoken.net/api,不要多写/v1导致路径重复。
5.2 local proxy failed / connection refused
报错:
httpx.ConnectError: [Errno 111] Connection refused这通常是 Redis 或 Supabase 没起来。先docker compose ps看容器状态,如果 redis 是exited,看日志:
docker compose logs redis常见原因是端口被占用,改docker-compose.yml里的端口映射即可。
5.3 reading choices 报错
报错:
KeyError: 'choices'这说明模型返回的结构和预期不符。可能是模型 ID 填错,或者该模型不支持 OpenAI 兼容格式。到 https://taotoken.net/models 确认模型 ID,并测试它是否返回标准choices结构。
5.4 OAuth 回调失败
报错:
OAuth callback error: redirect_uri_mismatchSupabase 的 OAuth 配置里,回调地址要和你实际访问的地址一致。本地开发时把http://localhost:3000/auth/callback加进允许列表。如果你自建了 Supabase,检查 GoTrue 的SITE_URL配置。
5.5 沙箱启动失败
报错:
DaytonaError: failed to create sandboxDaytona 是海外服务,国内访问可能超时。自部署时把它换成自建 Docker host,改backend/agents/tools/里沙箱调用的实现,把 Daytona SDK 换成dockerPython SDK 即可。
5.6 三件套检查清单
如果你用的是 Claude Code、Cline MCP 或 Codex 这类工具接入,出现认证问题时,按三件套逐项核对:
| 项目 | 检查点 |
|---|---|
| Base URL | 是否为 https://taotoken.net/api |
| API Key | 是否与控制台一致,无空格 |
| Model ID | 是否在模型列表中且支持工具调用 |
三项都对,认证类报错基本能解决。接入文档可以参考 https://taotoken.net/doc ,里面有各工具的配置示例。
5.7 流式输出中断
如果 SSE 连接频繁断开,检查 Redis Stream 是否配了MAXLEN。默认不裁剪时,长任务会把内存吃满,导致 Redis 拒绝写入。在xadd时加上MAXLEN ~ 10000控制单 stream 长度。
6. 从源码到二次开发:扩展入口与落地建议
跑通之后,下一步就是基于 Suna 做二次开发。它的扩展点比较清晰,我按优先级列几个。
6.1 新增自定义工具
backend/agents/tools/是工具目录。新增一个工具,大致是写一个类,实现execute方法,然后注册到工具列表。比如加一个内部 API 调用工具:
class InternalAPITool: name = "internal_api" description = "调用公司内部 API 获取数据" def execute(self, endpoint: str, params: dict): resp = requests.get(f"https://internal.example.com/{endpoint}", params=params) return resp.json()注册后在 Agent 主循环里就能被模型调用。
6.2 替换模型层
Suna 默认走 OpenAI 兼容接口,如果你想接多个模型做路由,可以在call_llm外面包一层 LiteLLM,根据任务类型选模型。这样简单任务用便宜模型,复杂规划用强模型,成本能降不少。
6.3 替换沙箱实现
把 Daytona 换成自建 Docker host,核心是改沙箱创建和销毁的逻辑。用dockerPython SDK 起容器,挂载工作目录,执行完销毁。这样国内访问没有额外延迟,数据也不出内网。
6.4 多租户与权限
如果面向多团队使用,Supabase 的 RLS(行级安全)可以天然支持多租户隔离。在 Postgres 表上配好策略,每个租户只能看到自己的数据。
6.5 工作流编排
Suna 偏"自由 Agent",强一致的工作流支持较弱。如果你的业务需要严格的多步编排,可以引入 Temporal 或 Prefect,把 Agent 作为其中一个节点,补足这块短板。
6.6 长期编码与 Agent 场景
如果你打算把 Suna 作为长期运行的 Agent 底座,模型调用量会持续增长。这种情况下可以关注 TaoToken 的 Coding Plan,https://taotoken.net/coding-plan ,适合需要稳定模型供给的编码和 Agent 场景。控制台在 https://taotoken.net/console ,可以管理 Key 和用量。
6.7 部署到生产前的检查
上线前把这几项过一遍:沙箱隔离是否到位、Redis 内存策略是否配好、Supabase 是否换成自建、日志审计是否开启、敏感数据是否加密。这几项做完,Suna 就能从 demo 变成能扛生产任务的系统。
整个流程走下来,Suna 的工程价值在于三个抽象:Sandbox 负责隔离执行,Redis Stream 负责解耦流式,Supabase 负责一体化数据。把这三块换成自建基础设施,再配上稳定的模型接入,就能落地成企业级 Agent 平台。