大多数基因是纯函数——接收输入、计算、返回输出。但真实世界的 Agent 需要与外部服务通信:LLM 提供商、天气 API、数据库、搜索引擎。Rotifer 通过 Hybrid 保真度来解决这个问题:为基因提供受控的网络访问,经由 Network Gateway——一个带有域名白名单、速率限制、超时强制执行和响应大小上限的沙箱化 fetch 代理。
本教程将从零开始构建一个生产级的 Hybrid 基因。
何时使用 Hybrid
| 保真度 | 网络访问 | 适用场景 |
|---|---|---|
| Wrapped | 无 | 纯逻辑——文本转换、数学计算、格式化 |
| Hybrid | Gateway 控制 | 外部 API 调用——LLM、天气、搜索、数据库 |
| Native | 无 | CPU 密集计算——编译后的 WASM、加密、解析 |
当基因需要访问外部世界时,选择 Hybrid。Network Gateway 确保它只能访问你明确允许的域名,并以你定义的速率运行。
第一步:初始化基因
rotifer init weather-gene --fidelity Hybrid这会创建一个基因目录,其中的 phenotype.json 已预配置为 Hybrid 保真度:
{ "name": "weather-gene", "domain": "utility", "description": "", "inputSchema": { "type": "object", "properties": { "prompt": { "type": "string" } }, "required": [] }, "outputSchema": { "type": "object", "properties": { "result": { "type": "string" } } }, "dependencies": [], "version": "0.1.0", "fidelity": "Hybrid", "transparency": "Open", "network": { "allowedDomains": [], "maxTimeoutMs": 30000, "maxResponseBytes": 1048576, "maxRequestsPerMin": 10 }}network 块是 Hybrid 与其他保真度级别的核心区别。每个字段都至关重要——运行时会强制执行所有限制。
第二步:配置网络访问
编辑 phenotype.json,声明基因需要访问的域名:
{ "network": { "allowedDomains": ["wttr.in"], "maxTimeoutMs": 10000, "maxResponseBytes": 524288, "maxRequestsPerMin": 5 }}域名规则:
- 精确匹配 —
"wttr.in"只允许wttr.in,不包括子域名。 - 通配符 —
"*.supabase.co"匹配abc123.supabase.co和supabase.co本身。 - 禁止地址 —
localhost、127.0.0.1、192.168.*、10.*等私有/回环地址会被发布管道拒绝。本地测试可以通过,但发布时会被阻止。
保持域名列表最小化。你添加的每个域名都是基因暴露的攻击面。如果基因只调用一个 API,就只列一个域名。
调优限制参数:
| 字段 | 默认值 | 建议 |
|---|---|---|
maxTimeoutMs | 30000 | 设为预期 API 延迟的 2–3 倍。快速 API 用 10s,LLM 用 30s。 |
maxResponseBytes | 1048576 (1 MiB) | 大多数 JSON API 足够。大型载荷可增加,简单端点可减少。 |
maxRequestsPerMin | 10 | 匹配上游速率限制。不要设得比提供商允许的更高。 |
第三步:编写 Express 函数
在基因目录中创建 express.ts。Hybrid 基因接收一个带有 gatewayFetch 函数的 ctx 对象——这是你唯一的网络接口。全局 fetch 在基因沙箱内不可用。
import type { GatewayFetchOptions, GatewayResponse } from "@rotifer/core";
interface WeatherInput { city: string; format?: "brief" | "detailed";}
interface WeatherOutput { result: string; city: string; source: string;}
export default async function express( input: WeatherInput, ctx: { gatewayFetch: (url: string, options?: GatewayFetchOptions) => Promise<GatewayResponse> }): Promise<WeatherOutput> { const city = encodeURIComponent(input.city); const formatParam = input.format === "detailed" ? "" : "?format=3"; const url = `https://wttr.in/${city}${formatParam}`;
const response = await ctx.gatewayFetch(url, { method: "GET", headers: { "Accept": "text/plain" }, });
if (response.status !== 200) { throw new Error(`Weather API returned ${response.status}: ${response.body}`); }
return { result: response.body.trim(), city: input.city, source: "wttr.in", };}要点:
ctx.gatewayFetch,而非fetch— 这是经过 Gateway 代理的版本,遵守你的域名白名单、速率限制和超时配置。- 在调用前验证输入 — 对用户提供的字符串进行编码,验证格式。
- 返回结构化输出 — 基因的消费者(Agent 或其他基因)期望类型化数据,而非原始 API 响应。
第四步:处理 Gateway 错误
Network Gateway 会抛出带有特定错误码的 NetworkGatewayError。生产级基因必须优雅地处理每种错误:
import type { GatewayFetchOptions, GatewayResponse } from "@rotifer/core";
interface WeatherInput { city: string;}
interface WeatherOutput { result: string; city: string; source: string; degraded: boolean;}
export default async function express( input: WeatherInput, ctx: { gatewayFetch: (url: string, options?: GatewayFetchOptions) => Promise<GatewayResponse> }): Promise<WeatherOutput> { const city = encodeURIComponent(input.city);
try { const response = await ctx.gatewayFetch(`https://wttr.in/${city}?format=3`, { method: "GET", headers: { "Accept": "text/plain" }, });
return { result: response.body.trim(), city: input.city, source: "wttr.in", degraded: response.truncated, }; } catch (err: any) { switch (err.code) { case "DOMAIN_BLOCKED": throw new Error(`配置错误:wttr.in 不在 allowedDomains 中`);
case "RATE_LIMITED": return { result: "天气服务暂时不可用(速率限制)。请稍后重试。", city: input.city, source: "fallback", degraded: true, };
case "TIMEOUT": return { result: "天气服务未在时限内响应。请稍后重试。", city: input.city, source: "fallback", degraded: true, };
case "RESPONSE_TOO_LARGE": return { result: "天气响应超出大小限制。请尝试简要格式。", city: input.city, source: "fallback", degraded: true, };
case "NETWORK_ERROR": return { result: `网络错误:${err.message}`, city: input.city, source: "fallback", degraded: true, };
default: throw err; } }}模式是:对配置错误抛出异常(DOMAIN_BLOCKED 意味着 phenotype 配置有误),对瞬态错误优雅降级(速率限制、超时、网络故障)。degraded 标志让下游消费者知道结果可能不完整。
第五步:测试与编译
运行基因的测试套件:
rotifer test weather-gene预期输出:
✓ weather-gene/express — city:"London" → result contains "London" ✓ weather-gene/express — city:"東京" → result contains "東京" ✓ weather-gene/error — invalid city → graceful fallback
3 passed | 0 failed Next: rotifer compile weather-gene然后编译为 IR:
rotifer compile weather-gene Compiling weather-gene v0.1.0 Fidelity: Hybrid Network: wttr.in (1 domain) Timeout: 10000ms | Max body: 512 KiB | Rate: 5/min Output: genes/weather-gene/weather-gene.wasm (42 KiB)
✓ Compiled successfully编译器将网络配置嵌入 WASM 自定义节(custom sections)。运行时在加载时读取——基因无法绕过其声明的网络策略。
第六步:发布并在 Agent 中运行
发布到 Rotifer Cloud 注册表:
rotifer publish weather-gene在 Agent 中使用:
rotifer agent create my-weather-agent --genes weather-generotifer agent run my-weather-agent --input '{"city": "Berlin"}' Agent: my-weather-agent Gene: weather-gene (Hybrid, gateway: wttr.in)
Result: Berlin: ⛅ +12°C运行时自动检测 Hybrid 保真度,从编译后的 IR 中读取网络配置,构造一个带有声明限制的 NetworkGateway 实例,并将 gatewayFetch 注入基因的执行上下文。基因永远不会接触原始 fetch。
最佳实践
最小域名面积。 只列出基因实际调用的域名。一个基因、一个 API、一个域名是理想状态。如果需要多个 API,考虑拆分为多个基因在 Agent 中组合。
使用环境变量存储 API 密钥。 永远不要在基因源码中硬编码凭证。使用 ctx.env 获取 Agent 运营者在部署时提供的 API 密钥。基因声明需要什么;运营者提供值。
合理的限制参数。 将 maxTimeoutMs 设为预期延迟的 2–3 倍,而非最大值 30s。将 maxResponseBytes 设为你预期的实际载荷大小,而非默认的 1 MiB。紧凑的限制能尽早发现回退问题。
LLM 提供商无关性。 如果基因封装了 LLM,将端点 URL 和模型名称作为输入参数。不要硬编码 api.openai.com——让运营者选择提供商。声明 "allowedDomains": ["*.openai.com", "*.anthropic.com", "*.mistral.ai"] 或将域名作为运行时参数接受。
测试正常路径和错误路径。 每个 gatewayFetch 调用都有五种失败方式。你的测试套件至少应覆盖:成功响应、速率限制响应和超时。在测试中直接使用 NetworkGateway 类来模拟每种场景。
深入阅读: 查看完整的 Hybrid Gene 开发指南,了解 Network Gateway 参考与 RAG 流水线示例。