npm package: @genbounty/ailp
Description: TypeScript client for the AILP (AI Log Protocol) LLM compliance risk assessment API.
Latest version: 0.2.5

# @genbounty/ailp

**LLM compliance risk assessment — drop one line into any AI call.**

Sends your LLM interactions to the [Genbounty AILP](https://github.com/genbounty/ailp) server for automated compliance scoring against frameworks like EU AI Act, OWASP LLM Top 10, NIST AI RMF, and more. Works with any LLM provider. No runtime dependencies — uses native `fetch`.

## Install

```bash
npm install @genbounty/ailp
```

## Quick start

Configure once:

```typescript
import { createAilp } from "@genbounty/ailp";

const ailp = createAilp({
  baseUrl: "http://localhost:8000",
  programId: "your-program-id",
  frameworks: ["eu-ai-act", "owasp-llm"],
});
```

Drop in after **any LLM call**:

```typescript
const res = await ailp(messages, llmOutput);

console.log(res.risk_level);      // "compliant"
console.log(res.frameworks);      // ["EU AI Act", "OWASP LLM & Agent"]
console.log(res.experts);         // [{ framework, risk_level, reasoning }, ...]
console.log(res.judge_reasoning); // "The interaction is benign and poses no risk..."
```

Pass an optional model hint:

```typescript
const res = await ailp(messages, llmOutput, { model: "gpt-4o-mini" });
```

---

## Works with any LLM provider

### OpenAI

```typescript
const response = await openai.chat.completions.create({ model, messages });
const res = await ailp(messages, response.choices[0].message.content ?? "");
```

### Anthropic

```typescript
const response = await anthropic.messages.create({ model, messages, max_tokens: 256 });
const res = await ailp(messages, response.content[0]?.text ?? "");
```

### Any other provider

```typescript
const output = await myLlm(messages);
const res = await ailp(messages, output);
```

---

## Fire-and-forget wrapper (OpenAI)

Assessment runs in the background — your LLM response is never blocked or delayed:

```typescript
import { wrapOpenAI, AilpClient } from "@genbounty/ailp";

const client = new AilpClient({ baseUrl: "http://localhost:8000" });

const response = await wrapOpenAI(
  (p) => openai.chat.completions.create(p),
  { model: "gpt-4o-mini", messages },
  {
    client,
    programId: "your-program-id",
    frameworks: ["eu-ai-act", "owasp-llm"],
    onAssess: (result) => console.log("Risk:", result.risk_level),
  },
);
```

## Generic fire-and-forget wrapper

Use `wrapLlmCall` for any async LLM function:

```typescript
import { wrapLlmCall, AilpClient } from "@genbounty/ailp";

const client = new AilpClient({ baseUrl: "http://localhost:8000" });

const response = await wrapLlmCall(
  (p) => anthropic.messages.create(p),
  { model: "claude-3-haiku-20240307", max_tokens: 256, messages },
  {
    client,
    programId: "your-program-id",
    frameworks: ["nist-ai-rmf"],
    messages: messages.map((m) => ({ role: m.role, content: m.content })),
    extractOutput: (res) => ({
      output: res.content[0]?.text ?? "",
      model: res.model,
    }),
  },
);
```

---

## React

Import from `@genbounty/ailp/react` to keep React out of the core bundle.

### `useAilp()` — recommended

One hook: memoized client + `assess` / `result` / `loading` / `error` / `reset`.  
Configure with **environment variables** (Next.js `NEXT_PUBLIC_*`, Vite `VITE_*`) so you do not need `useMemo` or `createAilp` in every component.

Each call to `assess()` clears the previous `result` and `error` before the new request, so you do not need to call `reset()` between chat turns. Use `reset()` only when you want to clear the UI without assessing (for example when leaving a view).

```tsx
import { useAilp } from "@genbounty/ailp/react";

function ChatWidget() {
  const { assess, result, loading, error } = useAilp();

  async function handleSend(userMessage: string, llmOutput: string) {
    await assess([{ role: "user", content: userMessage }], llmOutput);
  }

  return (
    <div>
      {loading && <p>Assessing…</p>}
      {error && <p>Error: {error.message}</p>}
      {result && (
        <p>
          Risk: <strong>{result.risk_level}</strong> — {result.judge_reasoning}
        </p>
      )}
      <button onClick={() => handleSend("hello", "Hi there!")}>Send</button>
    </div>
  );
}
```

**Env vars** (override any field by passing options to `useAilp({ ... })` instead):

| Variable | Required | Default |
|----------|----------|---------|
| `NEXT_PUBLIC_GENBOUNTY_PROGRAM_ID` or `VITE_GENBOUNTY_PROGRAM_ID` | Yes (unless you pass `programId`) | — |
| `NEXT_PUBLIC_AILP_BASE_URL` or `VITE_AILP_BASE_URL` | No | `http://127.0.0.1:8000` |
| `NEXT_PUBLIC_AILP_FRAMEWORKS` or `VITE_AILP_FRAMEWORKS` | No | `eu-ai-act` |

Frameworks in env — prefer **comma-separated** (simplest in `.env`): `NEXT_PUBLIC_AILP_FRAMEWORKS=eu-ai-act` or `eu-ai-act,owasp-llm`.  
JSON arrays must use **double** quotes: `["eu-ai-act","owasp-llm"]`. Values like `['eu-ai-act']` are not valid JSON; the client normalizes them, but CSV is clearer.

**Next.js:** `NEXT_PUBLIC_*` values are baked in at **build time**. After changing `.env`, restart `next dev` (or rebuild). The variable name must be exactly `NEXT_PUBLIC_GENBOUNTY_PROGRAM_ID` (this package reads it as a static `process.env` reference so Next can inline it).

Partial override example:

```tsx
const { assess, result, loading, error } = useAilp({ programId: "hardcoded-for-demo" });
```

The hook also returns `ailp` (the underlying `AilpFn`) if you need it outside `assess`.

### `useAssess(ailp)` — advanced

If you already have an `AilpFn` from `createAilp()`:

```tsx
import { createAilp } from "@genbounty/ailp";
import { useAssess } from "@genbounty/ailp/react";

const ailp = createAilp({ baseUrl, programId, frameworks });
const { assess, result, loading, error } = useAssess(ailp);
```

---

## Framework slugs

| Slug | Framework |
|------|-----------|
| `eu_ai_act` / `eu-ai-act` | EU AI Act |
| `oecd` | OECD AI Principles (default) |
| `owasp_llm` / `owasp-llm` | OWASP LLM & Agent |
| `nist_ai_rmf` / `nist-ai-rmf` | NIST AI RMF |
| `mitre_attack` / `mitre-attack` | MITRE ATT&CK |
| `pld` | EU PLD (AI) |
| `fria_core` / `fria-core` | FRIA Core |
| `fria_extended` / `fria-extended` | FRIA Extended |

---

## Risk levels

`critical` › `high` › `medium` › `low` › `informational` › `compliant` › `indeterminate`

---

## License

MIT