AI Connect/How to create/部署別コパイロット(ロジスティクス)
部署別コパイロット(ロジスティクス)
物流会社のすべての部署、すなわち財務、配車、倉庫、車両運用を、それぞれが AI Connect SDK の独自ユーザーとなるようモデル化します。各部署は専用のコネクター、認証情報、能力を持ち、すべてが単一のアプリケーションキー上で動作します。
これが、社内 AI プラットフォームが今日まで決して許されなかった姿です。財務は請求書を照合し、配車は「積載 4412 はどこにあるのか?」に答え、倉庫はバースの数を数え、車両運用は整備を追跡する物流会社。しかも 各チームが独自のコネクタと認証情報を持つ専用コパイロットを、単一の Application key の上で、初日からの数千の AI 接続に支えられて手に入れる のです。NetSuite、ERP、WMS、テレマティクス API、Slack チャンネル、Google スプレッドシート。あなたが構築する統合は一つもなく、各チームの認証情報は、現在ログイン中のユーザーではなくチームに属します。
あなたがこれまで評価してきたすべての統合プラットフォームは、まさにその地点で止まっています。それらはアプリケーションをサービスに接続するものでした。どのプラットフォームも、チーム、部署、共有ロールを、独自の分離された認証情報を持つ第一級のユーザーとして提供したことは一度もありません。人間のログイン以外の何かを命名できるユーザーモデルを持つプラットフォームが存在しなかったからです。AI Connect SDK にはそれができ、このリフレーミングこそがこの構築です。すなわち 部署がユーザーである ということです。チームごとに 1 つの external_id。人間は単にコパイロットを操作する存在でしかなく、分離の境界は部署です。1 人の人間と 1 つの Gmail に応えるアーキテクチャが、そのまま組織図全体に応えます。新しいインフラも、新しいプラットフォームも、ベンダーとの新たな協議も一切不要です。あなたはソフトウェアを購入するのではなく、id を追加することで組織をスケールします。
vinkius.user('alice_123').capabilities({ include: ['github'] })GET /apps/vk_app_xxx/users/alice_123/tools?connector=githubconst capabilities = await vinkius
.user('alice_123')
.capabilities({ include: ['github'] });CapabilitySet (6)
github__list_issues read-only
github__create_issue POST /repos/{owner}/{repo}/issues
github__list_pull_requests read-only
github__search_code read-only
...人間のケースで見たのと同じループが、今度は alice_123 の代わりに dept-dispatch に対して実行されます。コパイロットは配車自身の TMS と Slack 上で動作し、財務の NetSuite には決して触れません、接続は external_id の所有者に属するからです。
部署が完璧な「ユーザー」である理由
- 共有され永続する状態。 誰も接続を「所有」しません。所有するのは
dept-warehouseです。人員の入れ替わりが統合を損なうことは決してありません。 - 影響範囲を限定した分離。 各接続は独自のデータプレーントークンを持つため、配車の支出と即時失効は倉庫のものから独立しています。
- 設計による最小権限。 コパイロットは他の部署が接続したコネクターを文字通り見ることができません、能力の照会は単一の
external_idにスコープされています。 - 単一のアプリケーションキー。 すべての部署は単一の Vinkius Application の下に存在します。チームの追加は、インフラのプロビジョニングではなく id を定義するだけで行えます。
1. 部署に名前を付ける
安定で読みやすく、URL に安全な id を使ってください。システムの他の部分にある人間の id と決して衝突しないよう、必ず接頭辞を付けます。
type Department = 'finance' | 'dispatch' | 'warehouse' | 'fleet';
const departmentUserId = (dept: Department) => `dept-${dept}`;
// "dept-finance", "dept-dispatch", "dept-warehouse", "dept-fleet"2. 各部署のコネクターを宣言する
チームごとに必要なツールは異なります。これを設定として保てば、残りのコードがチームごとに変更されることは一切ありません。
// server/departments.ts
interface DeptSpec {
label: string;
connectors: string[]; // カタログの slug
}
export const DEPARTMENTS: Record<Department, DeptSpec> = {
finance: { label: '財務', connectors: ['netsuite', 'stripe', 'gmail'] },
dispatch: { label: '配車', connectors: ['sap', 'slack', 'google-sheets'] },
warehouse: { label: '倉庫', connectors: ['wms', 'google-sheets', 'jira'] },
fleet: { label: '車両運用', connectors: ['telematics', 'servicemax', 'slack'] },
};コネクターの slug はライブのカタログから取得します。ハードコードする代わりに管理 UI に発見させましょう: await vinkius.catalog.search('telematics') や for await (const c of vinkius.catalog.iterate())。コネクターと認証情報を参照してください。
3. 部署を一度だけプロビジョニングする(管理者操作)
チームを受け入れる際は、そのアカウントを接続し、認証情報を保存します。部署の id があらゆる場面の externalId であり、この経路に人間は登場しません。
import { vinkius } from './vinkius';
import { DEPARTMENTS, type Department } from './departments';
async function bootstrapDepartment(dept: Department) {
const user = vinkius.user(`dept-${dept}`);
// 後でフィルタ/監査できるように、非機密のメタデータを添付する
await user.ensure({ kind: 'department', label: DEPARTMENTS[dept].label });
for (const slug of DEPARTMENTS[dept].connectors) {
const connector = user.connector(slug);
await connector.connect();
// api_key コネクター: connector.credentials.set({ API_KEY: ... })
// oauth コネクター: connect() はプロバイダーの同意後に戻ってくる
}
// コネクターごとの準備状況を報告し、管理者は認証情報がまだ足りないものを見つけられるようにする
return Promise.all(
DEPARTMENTS[dept].connectors.map(async (slug) => ({
slug,
status: await user.connector(slug).status(),
})),
);
}4. リクエスト時に部署の能力を読み込む
リクエストは、コパイロットが対応する部署とともに届きます。その能力を解決します。スコープされ、モデルにそのまま渡せる状態で。
async function departmentCapabilities(dept: Department) {
const spec = DEPARTMENTS[dept];
return vinkius.user(`dept-${dept}`).capabilities({
include: spec.connectors,
onConnectorError: (slug, error) => {
// 管理者に表示する。応答を中断しない
console.warn(`${dept}/${slug}`, (error as Error).message);
},
});
}5. 部署として回答する
メッセージを正しいコパイロットへ振り向け、モデルにはその部署のツールだけを渡して、その部署の接続上で実行します。
// server/copilot.ts
import OpenAI from 'openai';
import { toOpenAITools, runOpenAIToolCall } from '@vinkius/connect/openai';
import { type Department } from './departments';
import { departmentCapabilities } from './capabilities';
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY! });
const SYSTEM_PROMPT: Record<Department, string> = {
finance: 'あなたは財務のコパイロットです。接続済みの会計ツールを使って請求書を照合し、請求に関する質問に答えてください。',
dispatch: 'あなたは配車コパイロットです。積載の所在地を答え、TMS と Slack を使って ETA を更新してください。',
warehouse: 'あなたは倉庫コパイロットです。WMS とスプレッドシートを使ってバースの数量と未完了タスクを報告してください。',
fleet: 'あなたは車両運用のコパイロットです。テレマティクスと ServiceMax を使って車両の健康状態と整備状況を報告してください。',
};
export async function ask(dept: Department, question: string) {
const capabilities = await departmentCapabilities(dept);
const completion = await openai.chat.completions.create({
model: '[MODEL_ID]',
messages: [
{ role: 'system', content: SYSTEM_PROMPT[dept] },
{ role: 'user', content: question },
],
tools: toOpenAITools(capabilities),
});
const call = completion.choices[0]?.message.tool_calls?.[0];
if (!call) return { answer: completion.choices[0].message.content };
const result = await runOpenAIToolCall(capabilities, call); // この部署として実行される
return { answer: result.content, tool: call.function.name };
}// 配車は自分の TMS について質問する。財務の NetSuite には到達できない。
await ask('dispatch', '積載 4412 の ETA はいつで、どのドライバーが担当ですか?');
await ask('finance', '30 日を超える顧客請求書のうち、今月もまだ未処理のものはどれですか?');6. 人間が部署として行動できるようにする、証跡付きで
コパイロットを操作する人々は、SDK の意味での「ユーザー」ではありません。それでも、誰が質問したかは把握すべきです。オペレーターを部署の id とともに記録してください、能力の境界は部署のままです。
async function askAs(dept: Department, operator: string, question: string) {
const answer = await ask(dept, question);
// 自前の監査ストア — SDK はオペレーターを一切見ない
await audit.record({
actor: operator,
on_behalf_of: `dept-${dept}`,
question,
});
return answer;
}オペレーターの身元は完全に自社システム側で保持してください。Vinkius にとってアクターは常に dept-finance です。これこそが部署レベルの分離をもたらし、誰が入力していても同じコパイロットが同一に振る舞えるようにします。
7. チームへの付与と剥奪を一度の操作で
部署は単一の external_id であるため、退役は自明です。コネクターを切断すると、そのチームのアクセスだけが除去されます。
async function retireConnector(dept: Department, slug: string) {
await vinkius.user(`dept-${dept}`).connector(slug).disconnect();
}ここで知っておきたい高度な機能
一つの部署、一つのコネクター、すべてを再列挙せずに
1 つのシステムだけを扱う特化型コパイロットが、チーム全体の接続に対する fan-out のコストを負うべきではありません。forConnector はすでに読み込まれた集合をスライスします。そして他のものを一切取得せずに済ませるには、単一のコネクターハンドルから列挙します:
// 集約済みセットから:
const sheetsOnly = capabilities.forConnector('google-sheets');
// もしくはすべての接続を列挙すること自体を避ける:
const tmsOnly = await vinkius.user(`dept-${dept}`).connector('sap').capabilities();fan-out は並行かつ自己修復的
user.capabilities() は接続の要約を一度だけ列挙し、その後、並行度の上限を 8 として ready のコネクターへ fan-out し、解決済みの接続 id を再利用します(コネクターごとの再列挙なし)。1 つのコネクターの runtime がタイムアウトしても応答は中断されません、残りはそのまま解決され、onConnectorError はどのチームツールが利用不可だったかを報告するため、その部署の管理者に警告できます。
部署自身のメタデータを読み戻す
ensure() で添付した非機密の kind: 'department' は、自由に表示できます。user.get() は保存済みのメタデータとステータスを返します。追加のデータベースを一切使わずに「どのチームがオンボード済みか?」ダッシュボードを構築できる十分な情報です。
const profile = await vinkius.user('dept-finance').get();
console.log(profile.metadata); // { kind: 'department', label: '財務' }
console.log(profile.status); // active | ...本番環境向けチェックリスト
- [ ] 部署の id に接頭辞を付け、人間の id と絶対に衝突しないようにします (
dept-…)。 - [ ] 部署別コネクターリストはコード分岐ではなく設定として保存します。
- [ ] メタデータ駆動のダッシュボードが動くよう、
user.ensure({ kind: 'department' })でオンボードします。 - [ ] 管理コンソールで
connector.status()を表示し、不足 (needs_credentials) が見えるようにします。 - [ ] 人間のオペレーターは担当部署のコパイロットへ振り向け、「誰が聞いたか」のログは自社側で保持します。
- [ ] コパイロットのすべての変更を伴う操作に安定した
idempotencyKeyを与えます(例: チケット + 部署)。
これであなたは社内の AI プラットフォームを運用しています。財務、配車、倉庫、車両運用が、それぞれ自分専用の接続済みツールと自分専用コパイロットを持ち、すべては単一のアプリケーションキーとチームごとの 1 つの external_id から動いています。あなたは部署向けのプラットフォームを購入したのではありません。このプラットフォームには、ユーザーが何でありうるかという上限がそもそもないのです。明日 5 番目のチームを追加するのは、設定 1 行です。コネクティビティレイヤーを所有するとはそういうことです。組織図がそのままあなたのユーザーテーブルになります。
What you just got
Not a pitch: the properties this build inherits automatically.
Connections and capabilities resolve only inside one external_id. No cross-actor leakage is possible, and you wrote none of that enforcement.
Your server stores secrets and can read back which fields are configured, never the values. Not your code, the model, or a dashboard can exfiltrate them.
Every connection owns a vk_live_* token, so cost and revocation are per connection. One call to disconnect() is a complete, auditable stop.
One CapabilitySet converts to OpenAI, Anthropic, Gemini, Vercel AI SDK, LangChain, LlamaIndex, Workers AI or neutral JSON Schema. Only the last line changes.
idempotencyKey, timeoutMs and AbortSignal per call; automatic full-jitter retries on transient failures; typed VinkiusError branches. No bespoke harness.
A copilot literally cannot see another department’s connectors. Onboarding a team is a new external_id, not new infrastructure.
Give it to your AI agent
An Agent Skill (SKILL.md) for this build. Preview the first lines below, then copy or download it into your repo under .claude/skills/: Claude Code, Cursor or any Agent-Skills-compatible agent follows it to implement this pattern correctly.
