hashbrown

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

RoleWho sends itTypical purpose
userHuman (or your UI on their behalf)Ask a question, issue a command
assistantThe LLMAnswer, ask a clarifying question, or call a tool
errorYour code or HashbrownError 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 completionResource 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({
    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({
    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

AI Basics: Roles, Turns & Completions1. What is a message?User MessageError MessageAssistant Message2. Message rolesExamples3. The assistant turn4. What is a completion?a) Single-turn completionb) Multi-turn chat completion5. Error messages6. Quick cheat-sheetNext steps