Hybrid Gene 开发指南
Hybrid Gene 将 Rotifer 的安全模型扩展到受控网络访问。与 Wrapped Gene(纯函数)和 Native Gene(仅 WASM)不同,Hybrid Gene 可以调用外部 API——但必须通过网络网关(Network Gateway),网关会强制执行域名白名单、速率限制、超时和响应大小上限。
本指南将带你完成 Hybrid Gene 的创建、测试和发布全流程。
何时使用 Hybrid
Section titled “何时使用 Hybrid”| 保真度 | 网络 | 适用场景 |
|---|---|---|
| Wrapped | 无 | 纯数据变换、文本处理 |
| Hybrid | 网关控制 | LLM API、数据库查询、Embedding |
| Native | 无 | CPU 密集型 WASM 计算 |
当你的 Gene 需要调用外部服务(OpenAI、Anthropic、Supabase 等),同时仍需协议安全保障时,使用 Hybrid。
步骤 1:初始化 Hybrid 项目
Section titled “步骤 1:初始化 Hybrid 项目”rotifer init my-hybrid-gene --fidelity Hybrid这会创建一个包含 network 配置块的 phenotype.json:
{ "name": "my-hybrid-gene", "domain": "general", "fidelity": "Hybrid", "version": "0.1.0", "inputSchema": { "type": "object" }, "outputSchema": { "type": "object" }, "network": { "allowedDomains": ["api.example.com"], "maxTimeoutMs": 30000, "maxResponseBytes": 1048576, "maxRequestsPerMin": 10 }}也可以将已有 Gene 转换为 Hybrid:
rotifer wrap my-gene --fidelity Hybrid --domain text.analyze步骤 2:配置网络访问
Section titled “步骤 2:配置网络访问”编辑 phenotype.json,声明你的 Gene 需要访问的域名:
{ "network": { "allowedDomains": ["api.openai.com", "*.supabase.co"], "maxTimeoutMs": 30000, "maxResponseBytes": 1048576, "maxRequestsPerMin": 10 }}网络配置参考
Section titled “网络配置参考”| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
allowedDomains | string[] | [] | Gene 可以访问的域名。支持精确匹配和 *.domain.com 通配符 |
maxTimeoutMs | number | 30000 | 单次请求超时(毫秒) |
maxResponseBytes | number | 1048576 | 响应体最大大小(默认 1 MiB) |
maxRequestsPerMin | number | 10 | 速率限制——60 秒滑动窗口内的最大请求数 |
- 精确匹配:
api.openai.com仅允许api.openai.com - 通配符:
*.supabase.co允许abc.supabase.co和supabase.co - 禁止的域名(发布时拒绝):
localhost、127.x.x.x、0.0.0.0、::1- 私有 IP:
10.x.x.x、172.16–31.x.x、192.168.x.x
步骤 3:编写 Express 函数
Section titled “步骤 3:编写 Express 函数”Hybrid Gene 通过第二个参数接收 gatewayFetch 函数。使用它代替全局 fetch:
export async function express( input: { query: string }, ctx?: { gatewayFetch?: typeof fetch }): Promise<{ answer: string }> { if (!ctx?.gatewayFetch) { throw new Error("gatewayFetch is required for Hybrid genes"); }
const response = await ctx.gatewayFetch("https://api.openai.com/v1/chat/completions", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${process.env.OPENAI_API_KEY}`, }, body: JSON.stringify({ model: "gpt-4o-mini", messages: [{ role: "user", content: input.query }], }), });
const data = await response.json(); return { answer: data.choices[0].message.content };}当你的 Gene 调用 gatewayFetch 时:
- URL 检查 — URL 必须是 https,主机必须是域名而不是 IP 地址,否则抛出
INVALID_REQUEST错误。再验证主机名是否在allowedDomains中,如果被拦截,立即抛出DOMAIN_BLOCKED错误。 - 速率检查 — 如果 Gene 在当前 60 秒窗口内超出
maxRequestsPerMin,抛出RATE_LIMITED错误。 - 超时 — 请求超过
maxTimeoutMs后被中止,抛出TIMEOUT错误。 - 响应上限 — 如果响应体超过
maxResponseBytes,会被截断并设置truncated: true标志。 - 重定向 — 由网关自己跟随,最多 5 次,每一跳请求前都重新做一遍 URL 检查。重定向到别的源时会丢掉你的请求头;301、302、303 会把请求改成不带请求体的 GET。超过 5 次抛出
NETWORK_ERROR。
| 错误码 | 含义 |
|---|---|
INVALID_REQUEST | 不是 https 地址,或主机是 IP 地址 |
DOMAIN_BLOCKED | URL(或某次重定向)的主机名不在 allowedDomains 中 |
RATE_LIMITED | 超出 maxRequestsPerMin |
TIMEOUT | 请求超过 maxTimeoutMs |
RESPONSE_TOO_LARGE | 响应体超过 maxResponseBytes |
NETWORK_ERROR | 其他 fetch 错误 |
步骤 4:编译
Section titled “步骤 4:编译”rotifer compile my-hybrid-gene对于 Hybrid Gene,编译器会保留 fidelity: "Hybrid" 标志,并将 network 配置嵌入 IR 自定义段。输出会显示允许的域名:
✓ Compiled to Rotifer IR Fidelity: Hybrid Network: api.openai.com, *.supabase.co IR Hash: sha256-abc123...步骤 5:发布
Section titled “步骤 5:发布”rotifer publish my-hybrid-gene发布命令在认证之前运行两项 Hybrid 专用验证:
E0055 — 缺少允许域名
Section titled “E0055 — 缺少允许域名”如果 network.allowedDomains 为空或缺失:
✗ [E0055] Hybrid gene must declare at least one allowed domain in network.allowedDomains
Fix: Add "network": { "allowedDomains": ["api.example.com"] } to phenotype.jsonE0056 — 禁止的域名
Section titled “E0056 — 禁止的域名”如果任何域名解析为 localhost 或私有 IP:
✗ [E0056] Forbidden domain in network.allowedDomains: "localhost" (localhost/private IP not allowed)
Fix: Remove private/localhost domains before publishing步骤 6:在 Agent 中运行
Section titled “步骤 6:在 Agent 中运行”创建包含 Hybrid Gene 的 Agent:
rotifer agent create my-agent --genes my-hybrid-generotifer agent run my-agent --input '{"query": "What is Rotifer?"}'Agent 运行时自动:
- 检测 Gene phenotype 中的
fidelity: "Hybrid" - 用 Gene 的
network配置创建NetworkGateway - 将
gatewayFetch注入express()调用 - 使用
--verbose时输出网关统计信息
CLI 运行时强制执行什么(v0.9.3 起)
Section titled “CLI 运行时强制执行什么(v0.9.3 起)”已编译的 Gene 经 rotifer run、rotifer agent run 或 rotifer test 在 WASM 沙箱里运行时,只拿到它 phenotype 里声明的权限,多一点都没有:
- 只有
fidelity: "Hybrid"且带network块的 Gene 能访问网络。Native、Wrapped,以及没有network块的 Hybrid Gene 都不能。 - 任何 Gene 都没有文件系统访问权限。
- 每个
rotifer.net请求在发出之前先核对:只允许 HTTPS,不允许 IP 地址;主机名必须与network.allowedDomains中的某一项完全相同(不自动包含子域名);而且必须出现在该 Gene 某个externalDependencies条目的domains里。之后再核对本次运行的请求次数上限和运行截止时间。 - 每次出网都会给本次运行的
resource_cost加一笔附加费:每次调用一个固定成本,再加上收发字节数对应的成本。所以网络用量会体现在 Arena 测得的成本里。
依赖在 phenotype 里这样声明:
"externalDependencies": [ { "apiType": "rest", "semanticTag": "llm-completion", "domains": ["api.openai.com"] }]API Key(nonstandard 预览,ADR-327;@rotifer/playground 0.31.0 或更高)。需要 Key 的依赖在 credentials 里写上它的名字,Gene 在请求头里用 ${env:名字} 引用它:
"credentials": ["OPENAI_API_KEY"]"headers": { "Authorization": "Bearer ${env:OPENAI_API_KEY}" }- CLI 按声明的名字读取同名环境变量。别的变量一概不读,也没有存 Key 的文件。
- 真实值在请求离开沙箱时才填入。Gene 看不到它,它也不会被打印或写进日志。
- 每个 Gene 只拿到自己声明的名字,组合运行时也一样。
- 声明了的变量没设置或为空时,
rotifer run在 Gene 运行之前就停下,并指出是哪个变量。只有能联网的 Gene(Hybrid 且带network块)才会被要求提供 Key;别的 Gene 拿到 Key 也发不出去。
用 Tavily 做真实网页搜索 是按这种方式写的一个完整 Gene。
被拒绝的请求会以负数错误码返回给 Gene,rotifer run 会报 Execution failed: … 并带上这个码:
| 错误码 | 含义 |
|---|---|
-2 | 没有网络权限(不是 Hybrid,或没有 network 块),或请求头用 ${env:名字} 引用了 Gene 没有在 credentials 里声明的名字 |
-3 | 主机名不在 allowedDomains 中 |
-4 | 请求超时,或本次运行已过截止时间 |
-5 | 网络错误(请求没能完成) |
-6 | 响应大于 maxResponseBytes |
-7 | 已达本次运行的请求次数上限 |
-10 | 请求格式错误、不是 HTTPS,或用了 IP 地址 |
-11 | 主机名没有在任何 externalDependencies 条目中声明 |
实战示例:RAG 管道
Section titled “实战示例:RAG 管道”Rotifer 文档助手管道展示了 4-Gene Hybrid 组合:
query-parser (Wrapped) │ ▼doc-retrieval (Hybrid) → OpenAI Embeddings + Supabase pgvector │ ▼answer-synthesizer (Hybrid) → Claude / OpenAI LLM │ ▼source-linker (Wrapped)每个 Hybrid Gene 仅声明它需要的域名:
| Gene | 允许的域名 |
|---|---|
doc-retrieval | api.openai.com, *.supabase.co |
answer-synthesizer | api.anthropic.com, api.openai.com |
最小域名暴露面
Section titled “最小域名暴露面”仅声明 Gene 实际调用的域名。allowedDomains 中的每个域名都是一个信任面——保持最小化。
"allowedDomains": ["api.openai.com"]显式处理网关错误——不要让网络故障导致 Gene 崩溃:
try { const res = await ctx.gatewayFetch(url, options); // ...} catch (err) { if (err.code === "RATE_LIMITED") { return { answer: "速率限制——请重试。", error: true }; } throw err;}使用环境变量管理 API Key
Section titled “使用环境变量管理 API Key”永远不要在 Gene 源码中硬编码 API Key。在 WASM 沙箱里,把 Key 写进 credentials,用 ${env:名字} 引用(见上文「CLI 运行时强制执行什么」)。在 Node.js 路径上,读取环境变量:
const apiKey = process.env.OPENAI_API_KEY;if (!apiKey) throw new Error("OPENAI_API_KEY not set");LLM 提供商无关性
Section titled “LLM 提供商无关性”设计 Gene 支持多个 LLM 提供商,通过环境变量切换:
const provider = process.env.ROTIFER_LLM_PROVIDER || "openai";const endpoint = provider === "claude" ? "https://api.anthropic.com/v1/messages" : "https://api.openai.com/v1/chat/completions";合理的限制参数
Section titled “合理的限制参数”根据使用场景调整 maxTimeoutMs 和 maxResponseBytes:
| 使用场景 | 超时 | 响应大小 |
|---|---|---|
| LLM 聊天补全 | 30s | 1 MiB |
| Embedding API | 10s | 256 KiB |
| 数据库查询 | 5s | 512 KiB |