Skip to main content
Record user feedback against a generation or session β€” the substrate for evals and fine-tuning datasets. @bryel/feedback runs server-side with zero dependencies.
bun add @bryel/feedback

Record feedback

Call recordFeedback with the target you’re scoring and the kind of signal you captured.
import { recordFeedback, thumbsUp } from "@bryel/feedback";

await recordFeedback({
  apiKey: process.env.BRYEL_KEY!,
  target: { type: "message", id: messageId }, // or { type: "session", id: sessionId }
  kind: "thumb",      // thumb | score | comment | correction | label
  score: 1,           // πŸ‘=1 / πŸ‘Ž=0, or a scalar
  userId,             // β†’ annotator
  source: "end_user", // end_user | labeler | model_judge | code
});

// shorthands
await thumbsUp({ apiKey, target: { type: "message", id: messageId } });
See the feedback API reference for every field and shorthand.

The messageId handshake

Feedback arrives after the answer, so the client needs a handle that also exists on the trace. Mint a messageId per turn, pass it into telemetry metadata (bryel stamps it as bryel.interaction.id), and return it to the client.
1

Mint, stamp, and return on the server

const messageId = crypto.randomUUID();
streamText({ /* … */ experimental_telemetry: { isEnabled: true, metadata: { sessionId, userId, messageId } } });
return result.toTextStreamResponse({ headers: { "x-bryel-message-id": messageId } });
2

Hold the id on the client

The browser keeps messageId (from the header) and POSTs a πŸ‘/πŸ‘Ž to your backend.
3

Proxy to bryel from your backend

await recordFeedback({ apiKey: process.env.BRYEL_KEY!, target: { type: "message", id: messageId }, kind: "thumb", score: 1, userId, source: "end_user" });
Keep your API key server-side. Proxy browser clicks through your backend β€” never ship BRYEL_KEY to the client.
To capture the messageId upstream in the first place, see the Vercel instrumentation guide.