- SDK
- First Message
SDK
First Message
Send your first message in one call — no assistant or thread setup needed.
Overview
Call send_message / sendMessage with just a string. A thread and assistant are created automatically. The response includes thread_id and assistant_id so you can continue the conversation or reuse the assistant.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
content | string | — | Text content of the message |
thread_id | uuid | — | Omit to auto-create a new thread; pass to continue an existing conversation |
assistant_id | uuid | — | Pin the new thread to an existing assistant (shares its memory, documents, and config) |
system_prompt | string | — | Instructions for this turn. Falls back to the assistant’s stored description if omitted |
stream | boolean | false | Stream response as SSE chunks |
llm_provider | string | "openai" | LLM provider (openai, anthropic, google, xai, openrouter, etc.) |
model_name | string | "gpt-4o" | Model name |
memory | string | "off" | Memory Lite: "Auto", "Readonly", or "off" |
memory_pro | string | — | Memory Pro (higher accuracy): "Auto" or "Readonly" |
memory_response_citation | boolean | false | Include memory citations in the assistant reply |
web_search | string | "off" | "Auto" to enable web search |
tools | array | — | Tool definitions for this turn (OpenAI-style) |
json_output | boolean | false | Request JSON object output from the model |
thinking | object | null | Enable reasoning. See Thinking guide |
Non-streaming
Python
JavaScript
TypeScript
import asyncio
from backboard import BackboardClient
async def main():
client = BackboardClient(api_key="YOUR_API_KEY")
# Send a message — a thread is auto-created behind the scenes
response = await client.send_message(
"Hello! Tell me a fun fact about space.",
llm_provider="openrouter",
model_name="moonshotai/kimi-k2.6",
)
# The response contains the assistant's reply plus IDs to continue later
print(f"Reply: {response.content}")
print(f"Thread ID: {response.thread_id}")
print(f"Assistant ID: {response.assistant_id}")
if __name__ == "__main__":
asyncio.run(main())
Streaming
Set stream=true and iterate over SSE chunks as they arrive.
SSE event types
| Event type | When it fires | Key fields |
|---|---|---|
run_started | The run has begun | run_id, message_id, provider, model_name |
message_start | The assistant message is about to stream | message_id, run_id |
content_streaming | Each piece of the assistant’s text | content |
reasoning_streaming | Each piece of reasoning (when thinking is enabled) | content |
reasoning_ended | Reasoning phase complete, content about to begin | — |
tool_call_start | A tool call’s name is known, arguments still streaming | run_id, tool_call_id, name |
tool_call_ready | A tool call’s arguments have finished streaming | run_id, tool_call |
tool_submit_required | Model is requesting one or more tool calls | tool_calls, run_id, model_provider, model_name, usage fields |
run_ended | The run finished successfully | thread_id, assistant_id, status, usage fields |
run_failed | The run failed | run_id, error, error_type |
error | The request could not be processed | error |
Handle run_failed and error explicitly — a stream that ends without run_ended has failed, and a client that only waits for run_ended will hang.
The SDKs yield every chunk through to you, including event types they do not model explicitly. This table covers the events most clients need; others exist. Switch on chunk["type"] and ignore anything you do not recognize — new event types are additive.
Usage fields on run_ended and tool_submit_required are input_tokens, output_tokens, total_tokens, cached_input_tokens, cache_write_input_tokens, and cost_usd. See Messages for details, and Tool Calls for the early tool-call events.
Example: basic streaming
Python
JavaScript
TypeScript
import asyncio
from backboard import BackboardClient
async def main():
client = BackboardClient(api_key="YOUR_API_KEY")
full_reply = ""
async for chunk in await client.send_message(
"Tell me a fun fact about space.",
stream=True,
):
event_type = chunk.get("type")
if event_type == "content_streaming":
# Print each token as it arrives
piece = chunk.get("content", "")
full_reply += piece
print(piece, end="", flush=True)
elif event_type == "run_ended":
# Final metadata is available here
print(f"\n\nTokens used: {chunk.get('total_tokens')}")
print(f"\nFull reply: {full_reply}")
if __name__ == "__main__":
asyncio.run(main())
Example: streaming with thinking (reasoning)
When thinking is enabled, the stream emits reasoning tokens before content tokens:
async for chunk in await client.send_message(
"What is the derivative of x^3 * sin(x)?",
llm_provider="openai",
model_name="o3",
thinking={"effort": "high"},
stream=True,
):
event_type = chunk.get("type")
if event_type == "reasoning_streaming":
# The model's internal reasoning — show it or hide it
print(chunk.get("content", ""), end="", flush=True)
elif event_type == "reasoning_ended":
print("\n--- reasoning done, answer starting ---")
elif event_type == "content_streaming":
print(chunk.get("content", ""), end="", flush=True)
Continuing a conversation
Pass thread_id from the first response to continue on the same thread:
Python
JavaScript
TypeScript
# First message — thread auto-created
first_reply = await client.send_message("What is quantum computing?")
# Follow-up on the same thread — the model sees prior history
follow_up = await client.send_message(
"Can you explain that more simply?",
thread_id=first_reply.thread_id,
)
print(follow_up.content)
Using an assistant
Pass assistant_id to pin the conversation to a specific assistant. New threads created under that assistant share its memory, documents, and default configuration:
Python
JavaScript
TypeScript
# Tell the assistant something — memory saves it
first = await client.send_message(
"My name is Sarah and I prefer dark mode.",
assistant_id="your-assistant-id",
memory="Auto",
)
# New thread, same assistant — memory carries over
second = await client.send_message(
"What do you remember about me?",
assistant_id="your-assistant-id",
memory="Auto",
)
print(second.content) # "You told me your name is Sarah and you prefer dark mode."
Per-turn system prompt
Pass system_prompt to give the model instructions for this turn:
reply = await client.send_message(
"What should I eat today?",
system_prompt="You are a nutritionist who gives concise meal suggestions.",
)
print(reply.content)
If you omit system_prompt, the assistant’s stored description (set via create_assistant or update_assistant) is used as fallback.
Switching models mid-conversation
You can use a different model for each message within the same thread:
# Turn 1 with OpenAI
first = await client.send_message(
"What is quantum computing?",
llm_provider="openai",
model_name="gpt-4o",
)
# Turn 2 with Anthropic — same thread, different model
second = await client.send_message(
"Can you explain that more simply?",
thread_id=first.thread_id,
llm_provider="anthropic",
model_name="claude-sonnet-4-20250514",
)
Thinking (reasoning)
Enable the model to reason step-by-step before answering:
response = await client.send_message(
"What is the derivative of x^3 * sin(x)?",
llm_provider="openai",
model_name="o3",
thinking={"effort": "high"},
)
print("Reasoning:", response.reasoning)
print("Answer:", response.content)
Different providers use different fields (e.g. effort for OpenAI, budget_tokens for Anthropic). See the full Thinking guide for every provider.
Response fields (non-streaming)
| Field | Description |
|---|---|
thread_id | Thread UUID (auto-created or existing) |
assistant_id | Assistant UUID used |
content | The assistant’s reply |
reasoning | The model’s reasoning trace (when thinking is enabled) |
message_id | Unique message identifier |
status | COMPLETED, REQUIRES_ACTION (tool calls pending), FAILED |
tool_calls | Array of tool call objects (when REQUIRES_ACTION) |
model_provider / model_name | Model used |
input_tokens / output_tokens / total_tokens | Token usage |
memory_operation_id | Poll this to check memory save status |
retrieved_memories | Memories used as context |
retrieved_files / retrieved_files_count | Documents used as context |
context_usage | Context window utilization (percent, used_tokens, context_limit) |
Legacy approach: The older add_message / addMessage methods (which require a pre-created assistant and thread) are still fully supported. See Create & update assistants for that flow.