hashbrown

Getting Started

Guide

  1. 1. Basics of AI
  2. 2. System Instructions
  3. 3. Skillet Schema
  4. 4. Streaming
  5. 5. Tool Calling
  6. 6. Structured Output
  7. 7. Generative UI
  8. 8. JavaScript Runtime

Recipes

  1. Natural Language Forms
  2. UI Chatbot with Tools
  3. Predictive Suggestions
  4. Remote MCP

Platforms

AI Basics: Roles, Turns & Completions

Understanding how Hashbrown (and most LLM APIs) model a conversation is the first step toward building anything useful. This page introduces the core vocabulary: messages, roles, the assistant turn, and a completion.


1. What is a message?

A message is a single unit of conversation exchanged between the user and the assistant. In TypeScript terms:

User Message

A user message is the simplest message in a conversation exchange. It has the role of user and includes either the string content or a JSON object, depending on how you format your messages.

interface UserMessage {
  role: 'user';
  content: string | JsonValue;
}

Error Message

If generating a completion fails, the error message is exposed using the role error where the content is the error message:

interface ErrorMessage {
  role: 'error';
  content: string;
}

Assistant Message

An assistant message is generated by the large-language model, and includes at least one of the following:

  • content if the assistant has generated a response
  • toolCalls if the assistant wants to call a tool before generating a response

Occasionally, an assistant message may contain both content and toolCalls, though generally it will generate toolCalls in a loop until it is prepared to generate a message with content.

Hashbrown strongly types tool calls for you. Assistant messages are modeled with the following types:

type ToolCall<ListOfTools extends AnyTool> =
  | {
      role: 'tool';
      status: 'done';
      name: Name;
      args: Args;
      result: PromiseSettledResult<Result>;
      toolCallId: string;
    }
  | {
      role: 'tool';
      status: 'pending';
      name: Name;
      args: Args;
      toolCallId: string;
      progress?: number;
    };

interface AssistantMessage<Output, ListOfTools> {
  role: 'assistant';
  content?: Output;
  toolCalls: ToolCall<ListOfTools>[];
}

2. Message roles

Role Who sends it Typical purpose
user Human (or your UI on their behalf) Ask a question, issue a command
assistant The LLM Answer, ask a clarifying question, or call a tool
error Your code or Hashbrown Error generated as a result of some failure

Examples

{ role: 'user', content: 'Turn on the living-room lights' }

// Assistant decides it needs additional data
{ role: 'assistant', toolCalls: [{ name: 'getLights', args: { room: 'living' }, status: 'pending' }] }

// Assistant can now finish its turn:
{ role: 'assistant', content: 'Lights on! Anything else I can help with?' }

3. The assistant turn

A single user message may trigger a chain of assistant actions until it produces a final answer. We call that chain a turn.

User ► Assistant(tool call) ► Tool ► Assistant(tool call) ► Tool ... ► Assistant(final)

The turn ends when the assistant sends a regular content message (without toolCalls). Hashbrown takes care of wiring these messages together; you read them as a single, ordered array.


4. What is a completion?

A completion is the assistant’s entire response payload for a given prompt. In Hashbrown you encounter two flavours:

a) Single-turn completion

Use the function from @hashbrownai/angular when you just need “input in, output out”.

import { Component } from '@angular/core';
import { completionResource } from '@hashbrownai/angular';

@Component({
  selector: 'app-weather',
  template: `
    <p>{{ completion.isReceiving() ? '...' : completion.value() }}</p>
  `,
})
export class WeatherComponent {
  input = signal('Weather in tokyo tomorrow?');
  completion = completionResource({
    model: 'gpt-4.1',
    input: this.input,
    system: 'You are a terse weather bot.',
    tools: [getWeatherTool],
  });
}

Hashbrown manages the request/stream for you and returns the assistant’s completed text (or structured JSON, when you use a schema).

b) Multi-turn chat completion

When you want stateful back-and-forth, use the chatResource function from @hashbrownai/angular.

import { Component } from '@angular/core';
import { chat } from '@hashbrownai/angular';

@Component({
  selector: 'app-chat-example',
  template: `
    @for (message of chat.messages(); track $index) {
      <p>{{ m.role }}: {{ m.content ?? '[tool]' }}</p>
    }
    <button (click)="sayHi()">Say hi</button>

    @if (chat.isReceiving()) {
      <p>Assistant is typing...</p>
    }
  `,
})
export class ChatExampleComponent {
  chat = chatResource({
    model: 'gpt-4.1',
    system: 'You are a helpful assistant.',
  });

  sayHi() {
    this.sendMessage({ role: 'user', content: 'Hi!' });
  }
}

messages already contains all roles, so you can render or inspect the conversation however you like.


5. Error messages

If generating an assistant's turn fails, Hashbrown converts the failure into a message:

{ role: 'error', content: '500: Internal Server Error' }

You can call the retry() callback function returned from Hashbrown's functions to reattempt generating the completion.


6. Quick cheat-sheet

Message  = { role, content | toolCalls }
Roles    = user | assistant | error
Turn     = everything the assistant does until it emits normal content
Completion (single) = output from useCompletion()
Completion (chat)   = latest assistant message(s) inside useChat() state

Next steps

Write system instructions

Learn to set instructions for large-language models when generating completions

AI Basics: Roles, Turns & Completions 1. What is a message? User Message Error Message Assistant Message 2. Message roles Examples 3. The assistant turn 4. What is a completion? a) Single-turn completion b) Multi-turn chat completion 5. Error messages 6. Quick cheat-sheet Next steps