快速开始

现在怎么用

用默认数据库、FederationAdmin 和 User City 跑通一条完整调用链。

如果你想先按仓库里的现成产品体验,入口是:

  • templates/localfed
  • cli/city

跑通后,你会得到这个调用链:

City Client
    ↓ FederationAdmin 创建 Bureau / 请求 Federation 签发 user_token
Federation
    ↓ User City 携带 user_token 调用 service
AIChannel → AIService → handler

1. 启动 City

import {
  Federation,
  AIService,
  AIChannel,
  type AIChannelStreamInput,
  type LanguageModelV3StreamResult,
} from "@downcity/federation";
import { Database } from "@downcity/database-sqlite";

const database = new Database({ filename: "./data.sqlite" });
const base = new Federation({ database });

// 注册一个 echo 模型
class EchoChannel extends AIChannel {
  constructor() {
    super({ id: "echo" });
  }

  protected async stream(
    input: AIChannelStreamInput,
  ): Promise<LanguageModelV3StreamResult> {
    const input_text = input.call.prompt
      .flatMap((message) => Array.isArray(message.content) ? message.content : [])
      .filter((part) => part.type === "text")
      .map((part) => part.text)
      .join("\n");
    return {
      stream: new ReadableStream({
        start(controller) {
          controller.enqueue({ type: "stream-start", warnings: [] });
          controller.enqueue({ type: "text-start", id: "echo_text" });
          controller.enqueue({ type: "text-delta", id: "echo_text", delta: `Echo: ${input_text}` });
          controller.enqueue({ type: "text-end", id: "echo_text" });
          controller.enqueue({
            type: "finish",
            finishReason: { unified: "stop", raw: "stop" },
            usage: {
              inputTokens: { total: 0, noCache: 0, cacheRead: 0, cacheWrite: 0 },
              outputTokens: { total: 0, text: 0, reasoning: 0 },
            },
          });
          controller.close();
        },
      }),
    };
  }
}

const echo = new EchoChannel();

const ai = new AIService();
ai.use(echo.model({ id: "local-echo", upstream_model: "local-echo", name: "Local Echo" }));
base.use(ai);

import { serve } from "@hono/node-server";

await base.health();
serve({ fetch: (request) => base.fetch(request), port: 43127, hostname: "127.0.0.1" });

2. 创建 Bureau 并请求签发 token

import { FederationAdmin } from "@downcity/federation/legacy";

const admin = new FederationAdmin({
  base_url: "http://127.0.0.1:43127",
  credential: administrator_session_token,
});

const token = await admin.service("accounts").action("tokens/issue").invoke({
  bureau_id: "demo",
  user_id: "user_123",
  metadata: { plan: "pro" },
  ttl: "7d",
});

3. 产品侧调用

import { City } from "@downcity/federation/legacy";

const city = new City({
  federation_url: "http://127.0.0.1:43127",
  user_token: token.user_token,
});

const catalog = await city.ai.catalog();
const model = catalog.get("local-echo");
if (!model) throw new Error("model not found");

// SDK 通路
const result = await city.ai.text({
  model,
  prompt: "你好 Downcity",
});

4. 你真正复用的是什么

多个产品接入后,复用的是这些东西:

  • .env 中的 provider key
  • user_token 校验
  • AIService 的模型路由
  • hook 里的套餐、限额、日志和扣费

下一步: