Build a code-review assistant with the Assistants API
30 minBy the end you'll have a reusable Assistant, a Thread per PR, a streaming Run that calls your custom review_pull_request tool, and a tiny Next.js UI that pastes in a URL and watches the review stream in.
- • A Ringside API key
- • Node 20+ and Next.js 14 or 15
- • A GitHub token if you want to hit real PRs in the tool implementation
- • Familiarity with SSE + fetch streaming helps but isn't required
- • 30 minutes
Create the Assistant
// register tools once, use forever
An Assistant is a named config: model + instructions + tool schema. Create it once and reference it from every run. You can update instructions later without touching your call sites.
The tool review_pull_request takes a PR URL. Ringside will ask the model when to invoke it. You run the actual fetch in your own code.
Function tools don't execute on Ringside's side. The model decides when to call them; you receive a requires_action event; you run the tool yourself and post the result back. This keeps secrets (like GitHub tokens) on your infra.
Create a Thread for the PR
// one thread per PR
Threads hold message history. Use one thread per PR so multi-turn conversations (e.g. "also check the test coverage") pick up where the last one left off.
Stash any useful context on metadata the PR URL, the author, the repo. Ringside passes it through unchanged and it shows up in your dashboard's thread browser.
Post the PR diff as a message
// user turn = your data
Append a user message containing the diff. Keep it under ~50k tokens or the run will come back with input_too_large; for bigger PRs, summarize or split.
ordinal in the response is the message position in the thread. Useful for display and for re-fetching later.
Create a streaming Run
// stream: true
Kick off the run with stream: true. Ringside returns SSE chunks in the OpenAI Assistants event taxonomy: run lifecycle (created/in_progress/completed) interleaved with message deltas.
When the model wants to call a tool, the stream emits thread.run.requires_action and pauses. The next move is yours.
SSE runs idle for up to 10 minutes while waiting on tool output. If you miss the requires_action event, the run will eventually expire and emit run.failed with reason='expired' - safer than hanging forever.
Handle requires_action
// run the tool, submit outputs
Read the tool_calls array, execute each call in your own code (this is where you'd call the GitHub API), and POST the JSON-stringified outputs back to submit_tool_outputs.
The stream resumes immediately and the model writes its review using the tool output. Terminal: thread.run.completed.
You can loop tool calls up to 10 times per run. The 11th returns 400 max_tool_iterations_exceeded - design your tool schemas so the model reaches an answer in a few hops.
Tiny Next.js UI
// input + button + stream
Your /api/review route proxies the Assistant run and streams back plain text. The client just reads from response.body and appends to a <pre>.
30-line component, no frameworks, no chat-ui library. Paste a PR URL, hit Review, watch the assistant's analysis appear character-by-character.
You built it.
An Assistant with a custom tool, a Thread per PR, a streaming Run that calls out to your tool implementation, and a working UI. The same scaffold swaps in for any function-tool agent. Replace the tool schema and your implementation, done.