すぐに使えるコード
プライベートなセルフホスト型エージェントをコードに接続
プライベートなセルフホスト型エージェントをGitHubリポジトリやパソコン上のフォルダーに接続する、コピー&ペーストで使えるコードです。AIモデルにデータが届く前に、プライバシーフィルターがパスワード、キー、メールアドレス、電話番号などの機密情報をプレースホルダーに置き換えます。その後、手元で回答内の値を元に戻します。
読み込み対象: notes/, todo.md, TODO.md, README.md
毎週月曜日(または手動)に実行し、人が確認できるようレポートを保存します
ローカルモデルで、データをネットワーク外に送信しない
1 ファイルを追加
リポジトリにagent/フォルダーを作成し、その中にprivacy-filter.tsとagent-github.tsを保存してください。
2 AIキーを追加
ローカルモデル(例:Ollama)を起動してください。クラウド用のキーは不要です。別の環境で実行する場合はAI_BASE_URLを設定してください。
3 GitHub Actionを有効にする
ワークフローを.github/workflows/private-self-hosted-agent.ymlとして保存してください。毎週月曜日(または手動)に実行し、人が確認できるようレポートを保存します。
agent/agent-github.tsGitHubリポジトリを読み込み、非公開データを隠してモデルに質問し、回答を保存または投稿します。
/**
* Private, self-hosted agent: reads your GitHub repository, scrubs secrets and personal data, then asks the AI model.
*
* npm install @octokit/rest tsx
* GITHUB_TOKEN=… GITHUB_REPOSITORY=owner/repo AI_API_KEY=… npx tsx agent/agent-github.ts
*
* Optional: AI_BASE_URL and AI_MODEL (any OpenAI-compatible API), PR_NUMBER or ISSUE_NUMBER
* (the GitHub Action sets these for you).
*/
import { writeFileSync } from "node:fs";
import { Octokit } from "@octokit/rest";
import { isSensitivePath, restore, scrub, type Vault } from "./privacy-filter";
const AGENT = {
"label": "Private, self-hosted agent",
"system": "You are a private, self-hosted agent. Your job: help the owner organise the tasks, notes and plans kept in this repository. Text like [EMAIL_1] or [API_KEY_2] is a placeholder for private data that was removed before you saw it. Keep placeholders exactly as written and never guess the real values.",
"task": "Make a prioritised to-do list for this week from my notes.",
"include": [
"notes/",
"todo.md",
"TODO.md",
"README.md"
],
"baseUrl": "http://localhost:11434/v1",
"model": "llama3.1",
"keyFallback": "ollama",
"autoPost": false,
"audit": false
};
const MAX_CHARS = 60_000;
const BINARY = /\.(png|jpe?g|gif|webp|ico|pdf|zip|gz|mp4|mp3|woff2?|lock)$/i;
function env(key: string, fallback?: string): string {
const value = process.env[key] || fallback;
if (!value) throw new Error(`Missing environment variable ${key}`);
return value;
}
const [owner, repo] = env("GITHUB_REPOSITORY").split("/") as [string, string];
const gh = new Octokit({ auth: env("GITHUB_TOKEN") });
const vault: Vault = new Map(); // placeholder → real value; never leaves this machine
const wanted = (path: string) =>
!isSensitivePath(path) && !BINARY.test(path) && AGENT.include.some((p) => path === p || path.startsWith(p));
async function readFile(path: string): Promise<string> {
const { data } = await gh.repos.getContent({ owner, repo, path });
if (Array.isArray(data) || data.type !== "file" || !("content" in data)) return "";
return Buffer.from(data.content, "base64").toString("utf8");
}
async function collectContext(): Promise<string> {
const parts: string[] = [];
const size = () => parts.reduce((n, p) => n + p.length, 0);
if (process.env.PR_NUMBER) {
const files = await gh.paginate(gh.pulls.listFiles, { owner, repo, pull_number: Number(process.env.PR_NUMBER), per_page: 100 });
for (const f of files) if (!isSensitivePath(f.filename) && f.patch) parts.push(`### Changed: ${f.filename}\n${f.patch}`);
}
if (process.env.ISSUE_NUMBER) {
const { data } = await gh.issues.get({ owner, repo, issue_number: Number(process.env.ISSUE_NUMBER) });
parts.push(`### Issue #${data.number}: ${data.title}\n${data.body ?? ""}`);
}
const { data: info } = await gh.repos.get({ owner, repo });
const { data: tree } = await gh.git.getTree({ owner, repo, tree_sha: info.default_branch, recursive: "true" });
for (const item of tree.tree) {
if (size() > MAX_CHARS) break;
if (item.type !== "blob" || !item.path || (item.size ?? 0) > 200_000 || !wanted(item.path)) continue;
parts.push(`### ${item.path}\n${await readFile(item.path)}`);
}
return parts.join("\n\n").slice(0, MAX_CHARS);
}
async function askModel(context: string) {
const safe = scrub(context, { vault }); // the model only ever sees safe.text
const res = await fetch(`${env("AI_BASE_URL", AGENT.baseUrl)}/chat/completions`, {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${env("AI_API_KEY", AGENT.keyFallback)}` },
body: JSON.stringify({
model: env("AI_MODEL", AGENT.model),
messages: [
{ role: "system", content: AGENT.system },
{ role: "user", content: `${AGENT.task}\n\n${safe.text}` },
],
}),
});
if (!res.ok) throw new Error(`AI model error ${res.status}: ${await res.text()}`);
const json = (await res.json()) as { choices: { message: { content: string } }[] };
return { reply: json.choices[0]?.message.content ?? "", findings: safe.findings };
}
async function main() {
const { reply, findings } = await askModel(await collectContext());
console.log("Privacy filter hid:", findings.map((f) => `${f.count} × ${f.label}`).join(", ") || "nothing");
writeFileSync("agent-report.md", restore(reply, vault)); // real values, for you only
writeFileSync("agent-report.safe.md", reply); // placeholders, safe to share
if (AGENT.audit) writeFileSync("privacy-audit.json", JSON.stringify({ at: new Date().toISOString(), findings }, null, 2));
const target = Number(process.env.PR_NUMBER || process.env.ISSUE_NUMBER || 0);
if (target && AGENT.autoPost) {
// Post the scrubbed version so private values never end up in a public comment.
await gh.issues.createComment({ owner, repo, issue_number: target, body: reply });
console.log(`Commented on #${target}`);
} else {
console.log("Saved agent-report.md. A person should review it before anything is sent.");
}
}
main().catch((err) => {
console.error(err);
process.exit(1);
});
プライベートなセルフホスト型エージェントに見える内容を確認
コード、設定ファイル、顧客のメッセージを貼り付けてください。左側が元の内容で、AIモデルに見えるのは右側の内容だけです。
ブラウザ内で動作し、データはアップロードされません
AIに見える情報
// config.ts export const db = "[CONNECTION_STRING_1]"; const apiKey = "[API_KEY_1]"; STRIPE_SECRET_KEY=[API_KEY_2] // Ticket from [EMAIL_1], phone [PHONE_1]
非公開の項目5件を非表示にしています:
1 × パスワード付きリンク
2 × APIキー
1 × メールアドレス
1 × 電話番号
他のAIエージェント向けコード
カスタマーサポートエージェントリサーチアシスタントコーディングエージェント営業アウトリーチエージェント運用自動化エージェントマルチエージェント調査チーム電話音声エージェントデータ分析エージェントパーソナルアシスタントコンテンツ・SEOライターSNSマネージャー採用アシスタント記帳・財務エージェント契約書レビューエージェントオンラインストアアシスタントAIチューターウェブ閲覧エージェントセキュリティアラートのトリアージエージェントDevOpsインシデント対応担当議事録アシスタントプロジェクトマネージャーエージェント受信メール仕分けエージェント翻訳・ローカライズエージェント動画制作エージェントデザイン・スライドエージェント診療記録作成エージェントバックオフィスRPAエージェント自律型目標達成エージェントコンパニオンキャラクターエージェント