Skip to content

Cloudflare Worker

承载一切的运行时。全球分布式,驱动 tick 循环,提供 API 服务。

Worker 在这里的职责

单个 Worker 负责:

  • 🌍 HTTP 请求 — 所有玩家 API 调用(REST + SSE)
  • Cron 触发 — 每分钟运行一次 tick
  • 💾 D1 访问 — 读写世界状态
  • 🧠 Vectorize 访问 — 嵌入和查询记忆
  • 📡 Durable Objects — 管理 SSE 事件扇出
  • 🤖 Workers AI 绑定 — 可选的本地嵌入

wrangler.toml

toml
name = "ai-world-sim"
main = "src/index.ts"
compatibility_date = "2025-01-01"

[vars]
ENVIRONMENT = "production"
TICK_INTERVAL_MINUTES = "1"
DIARY_HOUR = "22"

[[d1_databases]]
binding = "DB"
database_name = "ai-world-sim"
database_id = "<your-db-id>"

[[vectorize]]
binding = "VECTORIZE"
index_name = "ai-world-sim-memory"

[[durable_objects.bindings]]
name = "TICK_DO"
class_name = "TickBroadcaster"

[[migrations]]
tag = "v1"
new_classes = ["TickBroadcaster"]

[triggers]
crons = ["* * * * *"]   # 每分钟一次

入口

ts
// src/index.ts
import { app } from './router';

export { TickBroadcaster } from './tick/broadcaster';

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    return app.fetch(request, env, ctx);
  },

  async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext) {
    const { runTick } = await import('./tick/engine');
    ctx.waitUntil(runTick(env));
  },
};

Cron 触发

Worker 配置为每分钟运行一次(Cloudflare 的最小间隔)。每次调用时:

  1. 从 D1 读取当前世界时间
  2. 如果距上次 tick 已过去 1 个游戏内小时,执行新的 tick
  3. 否则,什么都不做

这将现实时间与游戏内时间解耦。


绑定

D1 数据库

ts
const npc = await env.DB.prepare(
  'SELECT * FROM npc WHERE id = ?'
).bind('margaret').first();

Vectorize 索引

ts
const results = await env.VECTORIZE.query(
  npcEmbedding,
  { topK: 5, filter: { npcId: 'margaret' } }
);

Durable Object

ts
const id = env.TICK_DO.idFromName('tick-broadcaster');
const stub = env.TICK_DO.get(id);
await stub.fetch('https://do/notify', {
  method: 'POST',
  body: JSON.stringify({ type: 'tick.completed', ... }),
});

Workers AI(可选)

用于本地嵌入(免费但质量较低):

ts
const { data } = await env.AI.run('@cf/baai/bge-base-en-v1.5', {
  text: ['some memory text'],
});

环境变量

变量用途
ENVIRONMENTdevelopmentproduction
TICK_INTERVAL_MINUTES每个游戏内小时对应多少现实分钟(默认:1)
DIARY_HOUR游戏内写日记的小时(默认:22)
LLM_PROVIDERopenaianthropiclocal
LLM_API_KEY通过 secret 绑定,非环境变量

通过以下命令设置 secret:

bash
wrangler secret put LLM_API_KEY

需要了解的限制

限制免费版付费版
每次调用 Worker CPU 时间30 秒5 分钟
Worker 内存128MB128MB
D1 数据库大小500MB10GB
D1 读取吞吐量~500 万行/天更高
Vectorize 索引大小3000 万向量更高
Cron 精度1 分钟1 分钟

Tick 必须在 < 30 秒内完成。30 个 NPC 加上有选择的 LLM 调用,通常在 5~15 秒内完成。


本地开发

bash
npm install -g wrangler
wrangler dev
wrangler d1 migrations apply ai-world-sim --local

部署

bash
wrangler deploy
wrangler d1 migrations apply ai-world-sim --remote

部署完成后,Cron 即刻生效,API 在 https://ai-world-sim.<your-subdomain>.workers.dev 上线。


可观测性

生产环境使用 Workers Logs(内置)+ Logpush:

ts
console.log(JSON.stringify({
  tick: worldTime,
  duration_ms: Date.now() - start,
  npcs_processed: npcs.length,
  llm_calls: llmCallCount,
}));

常见模式

使用 ctx.waitUntil 处理后台任务

ts
app.post('/api/suggestions', async (c) => {
  const body = await c.req.json();
  await saveSuggestion(c.env.DB, body);
  c.executionCtx.waitUntil(
    embedAndIndex(c.env.VECTORIZE, body.text)
  );
  return c.json({ ok: true });
});

幂等性

Tick 运行是幂等的 — 对同一个游戏内小时重复运行 tick,会产生相同的 D1 状态(除 RNG 种子外)。

Released under the MIT License.