Einsatzbereiter Code

Multi-Agenten-Rechercheteam mit deinem Code verbinden

Code zum Kopieren und Einfügen, der den Agenten „Multi-Agenten-Rechercheteam“ mit deinem GitHub-Repository oder einem Ordner auf deinem Computer verbindet. Bevor etwas das KI-Modell erreicht, ersetzt ein Datenschutzfilter Passwörter, Schlüssel, E-Mail-Adressen, Telefonnummern und andere private Angaben durch Platzhalter. In der Antwort werden die echten Werte dann auf deiner Seite wieder eingesetzt.

Liest: notes/, papers/, research/, README.md
Läuft jeden Montag (oder manuell) und speichert einen Bericht zur menschlichen Prüfung
Jedes OpenAI-kompatible Modell

1 Dateien hinzufügen

Erstelle einen Ordner agent/ in deinem Repository und speichere privacy-filter.ts und agent-github.ts darin.

2 Deinen KI-Schlüssel hinzufügen

In GitHub: Settings → Secrets and variables → Actions → AI_API_KEY hinzufügen. Lege AI_BASE_URL und AI_MODEL fest, um einen beliebigen OpenAI-kompatiblen Anbieter zu nutzen.

3 GitHub Action aktivieren

Speichere den Workflow als .github/workflows/multi-agent-research-team.yml. Läuft jeden Montag (oder manuell) und speichert einen Bericht zur menschlichen Prüfung.

agent/agent-github.ts

Liest dein GitHub-Repository, verbirgt private Daten, fragt das Modell und speichert oder postet die Antwort.

/**
 * Multi-agent research team: 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": "Multi-agent research team",
  "system": "You are a multi-agent research team. Your job: summarise and cross-reference the research notes and papers 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": "Summarise the key findings, where sources disagree, and the open questions.",
  "include": [
    "notes/",
    "papers/",
    "research/",
    "README.md"
  ],
  "baseUrl": "https://api.openai.com/v1",
  "model": "gpt-5",
  "keyFallback": "",
  "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);
});

Sieh, was der Agent „Multi-Agenten-Rechercheteam“ sehen würde

Füge Code, eine Konfigurationsdatei oder eine Kundennachricht ein. Links siehst du deine Daten, rechts nur das, was das KI-Modell sehen würde.

Läuft in deinem Browser, nichts wird hochgeladen
Was die KI sieht
// 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 private Einträge ausgeblendet:
1 × Link mit Passwort
2 × API-Schlüssel
1 × E-Mail-Adresse
1 × Telefonnummer

Code für andere KI-Agenten