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 responsetoolCalls
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 @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