← 返回博客

构建生产级 Hybrid Gene

高级教程:创建通过 Network Gateway 调用外部 API 的基因,包含域名白名单、速率限制与优雅错误处理。

大多数基因是纯函数——接收输入、计算、返回输出。但真实世界的 Agent 需要与外部服务通信:LLM 提供商、天气 API、数据库、搜索引擎。Rotifer 通过 Hybrid 保真度来解决这个问题:为基因提供受控的网络访问,经由 Network Gateway——一个带有域名白名单、速率限制、超时强制执行和响应大小上限的沙箱化 fetch 代理。

本教程将从零开始构建一个生产级的 Hybrid 基因。

何时使用 Hybrid

保真度网络访问适用场景
Wrapped纯逻辑——文本转换、数学计算、格式化
HybridGateway 控制外部 API 调用——LLM、天气、搜索、数据库
NativeCPU 密集计算——编译后的 WASM、加密、解析

当基因需要访问外部世界时,选择 Hybrid。Network Gateway 确保它只能访问你明确允许的域名,并以你定义的速率运行。

第一步:初始化基因

Terminal window
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
}
}

域名规则:

保持域名列表最小化。你添加的每个域名都是基因暴露的攻击面。如果基因只调用一个 API,就只列一个域名。

调优限制参数:

字段默认值建议
maxTimeoutMs30000设为预期 API 延迟的 2–3 倍。快速 API 用 10s,LLM 用 30s。
maxResponseBytes1048576 (1 MiB)大多数 JSON API 足够。大型载荷可增加,简单端点可减少。
maxRequestsPerMin10匹配上游速率限制。不要设得比提供商允许的更高。

第三步:编写 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",
};
}

要点:

第四步:处理 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 标志让下游消费者知道结果可能不完整。

第五步:测试与编译

运行基因的测试套件:

Terminal window
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:

Terminal window
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 注册表:

Terminal window
rotifer publish weather-gene

在 Agent 中使用:

Terminal window
rotifer agent create my-weather-agent --genes weather-gene
rotifer 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 流水线示例。