# Create a chat completion
Source: https://docs.inceptionlabs.ai/api-reference/chat/create-a-chat-completion
https://api.inceptionlabs.ai/openapi.json post /v1/chat/completions
Generate a chat completion from a sequence of messages. Returns a `ChatCompletion` object, or a server-sent events stream of `ChatCompletionChunk` deltas when `stream=true`. Supports tool calling, structured output via `response_format`, and reasoning controls.
# Create a code edit completion
Source: https://docs.inceptionlabs.ai/api-reference/edit/create-a-code-edit-completion
https://api.inceptionlabs.ai/openapi.json post /v1/edit/completions
Generate an edit to a code region given the surrounding context. The request must contain a single user message whose content includes the required edit prompt tags (e.g. `<|code_to_edit|>`, `<|cursor|>`, `<|current_file_content|>`). Returns an `EditCompletion` object. Streaming and tool calling are not supported on this endpoint.
# Create a fill-in-the-middle completion
Source: https://docs.inceptionlabs.ai/api-reference/fim/create-a-fill-in-the-middle-completion
https://api.inceptionlabs.ai/openapi.json post /v1/fim/completions
Generate a code completion given a `prompt` (prefix) and optional `suffix`. Designed for IDE-style inline completion. Returns a `FimCompletion` object, or a server-sent events stream of `FimCompletionChunk` deltas when `stream=true`. Tool calling and function calling are not supported.
# List available models
Source: https://docs.inceptionlabs.ai/api-reference/models/list-available-models
https://api.inceptionlabs.ai/openapi.json get /v1/models
List the models currently available through the API.
# List chat models
Source: https://docs.inceptionlabs.ai/api-reference/models/list-chat-models
https://api.inceptionlabs.ai/openapi.json get /v1/chat/completions/models
List models available for chat completions.
# List Edit models
Source: https://docs.inceptionlabs.ai/api-reference/models/list-edit-models
https://api.inceptionlabs.ai/openapi.json get /v1/edit/completions/models
List models available for edit completions.
# List FIM models
Source: https://docs.inceptionlabs.ai/api-reference/models/list-fim-models
https://api.inceptionlabs.ai/openapi.json get /v1/fim/completions/models
List models available for fill-in-the-middle completions.
# Autocomplete (FIM)
Source: https://docs.inceptionlabs.ai/capabilities/fim
Generate inline code autocomplete suggestions with the Inception fill-in-the-middle (FIM) endpoint, powered by the Mercury Edit 2 code model.
Fill-in-the-middle (FIM) generates code that fits between existing code. You send the code **before** the cursor as `prompt` (the prefix) and the code **after** the cursor as `suffix`; Mercury Edit 2 returns the text that belongs in between. This is what powers inline autocomplete in an editor.
## FIM vs. Next Edit
| | Autocomplete (FIM) | [Next Edit](/capabilities/next-edit) |
| ------------------- | ---------------------------------------------- | ------------------------------------------------------------------------- |
| Endpoint | `POST /v1/fim/completions` | `POST /v1/edit/completions` |
| Question it answers | "What belongs between these two known points?" | "What should I change next in this file?" |
| Inputs | `prompt` prefix and optional `suffix` | Current file, editable region, edit history, and recently viewed snippets |
| Best for | Inline completion as the user types | Predictive multi-line edits informed by recent context |
FIM has no separate fields for edit history or other open files. Add any extra context directly to `prompt`, such as imports, related types, or a short description of the intended code. Use [Next Edit](/capabilities/next-edit) when the suggestion should incorporate edit history or recently viewed snippets.
Set your [API key](https://platform.inceptionlabs.ai/dashboard/api-keys) as an environment variable and install the SDK.
```bash macOS / Linux theme={null}
export INCEPTION_API_KEY="your_api_key_here"
```
```cmd Windows (Command Prompt) theme={null}
set INCEPTION_API_KEY=your_api_key_here
```
```powershell Windows (PowerShell) theme={null}
$env:INCEPTION_API_KEY="your_api_key_here"
```
```bash Python theme={null}
pip install inceptionai
```
```bash TypeScript theme={null}
npm install inceptionai
```
In the example below, the prefix defines `factorial` up to `return n * ` and the suffix is the call that uses it. Mercury Edit 2 fills the gap — e.g. `factorial(n - 1)`.
```bash cURL theme={null}
curl https://api.inceptionlabs.ai/v1/fim/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $INCEPTION_API_KEY" \
-d '{
"model": "mercury-edit-2",
"prompt": "def factorial(n):\n if n <= 1:\n return 1\n return n * ",
"suffix": "\n\nprint(factorial(5))",
"max_tokens": 1000
}'
```
```python Python theme={null}
from inceptionai import Inception
client = Inception() # reads INCEPTION_API_KEY from the environment
completion = client.fim.completions.create(
model="mercury-edit-2",
prompt="def factorial(n):\n if n <= 1:\n return 1\n return n * ",
suffix="\n\nprint(factorial(5))",
max_tokens=1000,
)
# The completion is the code that fills the gap, e.g. "factorial(n - 1)"
print(completion.choices[0].text)
```
```typescript TypeScript theme={null}
import Inception from 'inceptionai';
const client = new Inception(); // reads INCEPTION_API_KEY from the environment
const completion = await client.fim.completions.create({
model: 'mercury-edit-2',
prompt: 'def factorial(n):\n if n <= 1:\n return 1\n return n * ',
suffix: '\n\nprint(factorial(5))',
max_tokens: 1000,
});
// The completion is the code that fills the gap, e.g. "factorial(n - 1)"
console.log(completion.choices[0]?.text);
```
# Next Edit
Source: https://docs.inceptionlabs.ai/capabilities/next-edit
Predict a developer's next code edit with the Inception Next Edit endpoint and Mercury Edit 2 using recent snippets, file context, and edit history.
Predict a developer's next code edit with Mercury Edit 2. Provide recent snippets, the current file with an editable region, and edit history — the [request format](#next-edit-request-format) is documented below.
Set your [API key](https://platform.inceptionlabs.ai/dashboard/api-keys) as an environment variable and install the SDK.
```bash macOS / Linux theme={null}
export INCEPTION_API_KEY="your_api_key_here"
```
```cmd Windows (Command Prompt) theme={null}
set INCEPTION_API_KEY=your_api_key_here
```
```powershell Windows (PowerShell) theme={null}
$env:INCEPTION_API_KEY="your_api_key_here"
```
```bash Python theme={null}
pip install inceptionai
```
```bash TypeScript theme={null}
npm install inceptionai
```
```bash cURL theme={null}
curl https://api.inceptionlabs.ai/v1/edit/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $INCEPTION_API_KEY" \
-d "{
\"model\": \"mercury-edit-2\",
\"messages\": [
{
\"role\": \"user\",
\"content\": \"<|recently_viewed_code_snippets|>\\n\\n<|/recently_viewed_code_snippets|>\\n\\n<|current_file_content|>\\ncurrent_file_path: solver.py\\n'''\\nfunction: flagAllNeighbors\\n----------\\nThis function marks each of the covered neighbors of the cell at the given row\\n<|code_to_edit|>\\nand col as flagged.\\n'''\\ndef flagAllNeighbors(board<|cursor|>, row, col): \\n for r, c in b.getNeighbors(row, col):\\n if b.isValid(r, c):\\n b.flag(r, c)\\n\\n<|/code_to_edit|>\\n<|/current_file_content|>\\n\\n<|edit_diff_history|>\\n--- /c:/Users/test/testing/solver.py\\n+++ /c:/Users/test/testing/solver.py\\n@@ -6,1 +6,1 @@\\n-def flagAllNeighbors(b, row, col): \\n+def flagAllNeighbors(board, row, col): \\n\\n<|/edit_diff_history|>\"
}
]
}"
```
```python Python theme={null}
from inceptionai import Inception
client = Inception() # reads INCEPTION_API_KEY from the environment
content = """<|recently_viewed_code_snippets|>
<|/recently_viewed_code_snippets|>
<|current_file_content|>
current_file_path: solver.py
'''
function: flagAllNeighbors
----------
This function marks each of the covered neighbors of the cell at the given row
<|code_to_edit|>
and col as flagged.
'''
def flagAllNeighbors(board<|cursor|>, row, col):
for r, c in b.getNeighbors(row, col):
if b.isValid(r, c):
b.flag(r, c)
<|/code_to_edit|>
<|/current_file_content|>
<|edit_diff_history|>
--- /c:/Users/test/testing/solver.py
+++ /c:/Users/test/testing/solver.py
@@ -6,1 +6,1 @@
-def flagAllNeighbors(b, row, col):
+def flagAllNeighbors(board, row, col):
<|/edit_diff_history|>"""
completion = client.edit.completions.create(
model="mercury-edit-2",
messages=[{"role": "user", "content": content}],
)
print(completion.choices[0].message.content)
```
```typescript TypeScript theme={null}
import Inception from 'inceptionai';
const client = new Inception(); // reads INCEPTION_API_KEY from the environment
const content = `<|recently_viewed_code_snippets|>
<|/recently_viewed_code_snippets|>
<|current_file_content|>
current_file_path: solver.py
'''
function: flagAllNeighbors
----------
This function marks each of the covered neighbors of the cell at the given row
<|code_to_edit|>
and col as flagged.
'''
def flagAllNeighbors(board<|cursor|>, row, col):
for r, c in b.getNeighbors(row, col):
if b.isValid(r, c):
b.flag(r, c)
<|/code_to_edit|>
<|/current_file_content|>
<|edit_diff_history|>
--- /c:/Users/test/testing/solver.py
+++ /c:/Users/test/testing/solver.py
@@ -6,1 +6,1 @@
-def flagAllNeighbors(b, row, col):
+def flagAllNeighbors(board, row, col):
<|/edit_diff_history|>`;
const completion = await client.edit.completions.create({
model: 'mercury-edit-2',
messages: [{ role: 'user', content }],
});
console.log(completion.choices[0]?.message.content);
```
## Next Edit Request Format
Mercury Edit expects next-edit requests to contain 3 sections of code contents: recently viewed snippets, the current file with the editable region, and a time-ordered edit history. The primary purpose of the recently viewed snippets and edit history is to provide context to Mercury Edit so that it can better understand the user's current intent in modifying code.
The recently viewed snippets should be formatted as:
```text theme={null}
<|recently_viewed_code_snippets|>
<|recently_viewed_code_snippet|>
code_snippet_file_path: [SNIPPET FILE PATH 1]
[SNIPPET 1 CODE]
<|/recently_viewed_code_snippet|>
<|recently_viewed_code_snippet|>
code_snippet_file_path: [SNIPPET FILE PATH 2]
[SNIPPET 2 CODE]
<|/recently_viewed_code_snippet|>
<|/recently_viewed_code_snippets|>
```
Each snippet should correspond to a piece of code (or entire code files) that a user has recently viewed. If you do not wish to use recently viewed snippets, please add `<|recently_viewed_code_snippets|>\n\n<|/recently_viewed_code_snippets|>` with no contents inside the tags.
The current file content should be formatted as:
```text theme={null}
<|current_file_content|>
current_file_path: [CURRENT FILE PATH]
[CODE ABOVE EDITABLE REGION]
<|code_to_edit|>
[EDITABLE REGION CODE]
<|/code_to_edit|>
[CODE BELOW EDITABLE REGION]
<|/current_file_content|>
```
Mercury Edit will return an updated version of the editable region in its response (e.g., completing a function) enclosed in triple backticks.
The edit history should be formatted as:
```text theme={null}
<|edit_diff_history|>
--- [EDITED FILE PATH 1]
+++ [EDITED FILE PATH 1]
[DIFF HUNK HEADER 1 WITH @@]
[DIFF LINES 1]
--- [EDITED FILE PATH 2]
+++ [EDITED FILE PATH 2]
[DIFF HUNK HEADER 2 WITH @@]
[DIFF LINES 2]
<|/edit_diff_history|>
```
As seen above, each edit should follow unidiff formatting. It is important to make sure that the bottommost edits in the prompt correspond to the most recent edits made by the user. If you do not wish to use edit history, please add `<|edit_diff_history|>\n\n<|/edit_diff_history|>` with no contents inside the tags.
## Next Edit Prompting Best Practices
The following best practices will help you get the most relevant suggestions out of Mercury Edit while maintaining low latency.
Include **3–5 snippets** of roughly 20 lines each, centered around the user's recent cursor positions. These should be focused code excerpts — not full files. For more advanced implementations, consider using AST nodes at cursor locations or code RAG to surface relevant type definitions and method implementations across the codebase.
Always provide the **latest state** of code at each snippet location, as stale snippets can cause the model to suggest reverting recent changes. Both full declarations and outlines (e.g., type signatures and docstrings) are acceptable snippet formats.
Include at least the **last 3–5 user edits**, ordered chronologically with the most recent edit last. Edits should be **range-based** — if a user made multiple modifications in the same area, combine them into a single unidiff rather than many granular diffs.
The edit history is often the strongest signal for identifying user intent. Ensuring the final entry corresponds to the user's most recent edit can significantly improve suggestion quality.
Pass in the **entire current file** so the model can draw on imports, function signatures, and surrounding code. For extremely large files, trim distant regions while preserving the area around the editable region.
The editable region directly determines output token count and dominates latency. We recommend starting with **10–15 lines** (\~100–150 tokens) and tuning from there. At \~1,000 tokens/second decoding speed, an upper bound of around 25 lines (\~250 tokens) is practical depending on network latency.
For selecting which lines to include, a simple approach is to center the region around the cursor: `[currentLine - 5, currentLine + 10]`.
To suggest edits beyond the cursor vicinity:
* **Parallel requests**: Fire multiple requests with disjoint editable regions and check which ones produce meaningful diffs.
* **Linter-guided selection**: Wrap editable regions around locations where the linter identifies errors.
We recommend making the maximum editable region size configurable so you can tune the tradeoff between suggestion scope and latency.
# Reasoning Efforts
Source: https://docs.inceptionlabs.ai/capabilities/reasoning-efforts
Choose how much Mercury 2 reasons before responding, from near-instant answers to deeper multi-step reasoning.
## Reasoning effort levels
`reasoning_effort` controls how much the model deliberates before answering. It applies to `mercury-2` on the chat completions endpoint and accepts four values:
| Value | Latency | Best for |
| ------------------ | -------- | ---------------------------------------------- |
| `instant` | Lowest | Voice, realtime turns, high-volume automations |
| `low` | Low | Latency-sensitive chat and tool-calling |
| `medium` (default) | Balanced | General-purpose use |
| `high` | Highest | Complex, multi-step reasoning |
If `reasoning_effort` is omitted, the model uses `medium`.
# Streaming & Diffusion
Source: https://docs.inceptionlabs.ai/capabilities/streaming
Stream Mercury 2 chat completions or enable the diffusion mode to visualize how the model iteratively denoises text into the final answer.
Inception API supports streaming output and diffusion effect modes:
* **Streaming:** Get responses block-by-block for instant feedback—ideal for chat and live applications. A "block" is a chunk of refined text the model emits as it decodes, rather than one token at a time.
* **Diffusing:** Optionally visualize how noisy outputs are refined into final text, showcasing the model's iterative denoising process.
Set your [API key](https://platform.inceptionlabs.ai/dashboard/api-keys) as an environment variable and install the SDK.
```bash macOS / Linux theme={null}
export INCEPTION_API_KEY="your_api_key_here"
```
```cmd Windows (Command Prompt) theme={null}
set INCEPTION_API_KEY=your_api_key_here
```
```powershell Windows (PowerShell) theme={null}
$env:INCEPTION_API_KEY="your_api_key_here"
```
```bash Python theme={null}
pip install inceptionai
```
```bash TypeScript theme={null}
npm install inceptionai
```
## Streaming
Stream the response block-by-block as it is generated.
```bash cURL theme={null}
curl https://api.inceptionlabs.ai/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $INCEPTION_API_KEY" \
-d '{
"model": "mercury-2",
"messages": [
{"role": "user", "content": "What is a diffusion model?"}
],
"max_tokens": 1000,
"stream": true
}'
```
```python Python theme={null}
from inceptionai import Inception
client = Inception() # reads INCEPTION_API_KEY from the environment
stream = client.chat.completions.create(
model="mercury-2",
messages=[{"role": "user", "content": "What is a diffusion model?"}],
max_tokens=1000,
stream=True,
)
for chunk in stream:
print(chunk.choices[0].delta.content or "", end="", flush=True)
```
```typescript TypeScript theme={null}
import Inception from 'inceptionai';
const client = new Inception(); // reads INCEPTION_API_KEY from the environment
const stream = await client.chat.completions.create({
model: 'mercury-2',
messages: [{ role: 'user', content: 'What is a diffusion model?' }],
max_tokens: 1000,
stream: true,
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta.content ?? '');
}
```
## Diffusion
Set `diffusing` to `true` to stream the model's intermediate denoising steps. Each chunk contains the full text as it is refined into the final answer.
```bash cURL theme={null}
curl https://api.inceptionlabs.ai/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $INCEPTION_API_KEY" \
-d '{
"model": "mercury-2",
"messages": [
{"role": "user", "content": "What is a diffusion model?"}
],
"max_tokens": 1000,
"stream": true,
"diffusing": true
}'
```
```python Python theme={null}
from inceptionai import Inception
client = Inception() # reads INCEPTION_API_KEY from the environment
stream = client.chat.completions.create(
model="mercury-2",
messages=[{"role": "user", "content": "What is a diffusion model?"}],
max_tokens=1000,
stream=True,
diffusing=True,
)
for chunk in stream:
# In diffusing mode each chunk is the full text as it is denoised
print(chunk.choices[0].delta.content or "", flush=True)
```
```typescript TypeScript theme={null}
import Inception from 'inceptionai';
const client = new Inception(); // reads INCEPTION_API_KEY from the environment
const stream = await client.chat.completions.create({
model: 'mercury-2',
messages: [{ role: 'user', content: 'What is a diffusion model?' }],
max_tokens: 1000,
stream: true,
diffusing: true,
});
for await (const chunk of stream) {
// In diffusing mode each chunk is the full text as it is denoised
console.log(chunk.choices[0]?.delta.content ?? '');
}
```
The diffusing effect is best shown by overwriting the displayed text on each step. Here is an example in a web app using JavaScript:
```javascript theme={null}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let fullContent = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed || !trimmed.startsWith('data: ')) continue;
if (trimmed === 'data: [DONE]') continue;
const jsonStr = trimmed.substring(6);
if (!jsonStr.startsWith('{')) continue;
try {
const data = JSON.parse(jsonStr);
for (const choice of data.choices || []) {
if (choice.delta && choice.delta.content !== null && choice.delta.content !== undefined) {
fullContent = choice.delta.content || '';
contentElement.textContent = fullContent;
}
}
} catch (error) {
console.error('Parsing error:', error);
}
}
}
```
# Structured Outputs
Source: https://docs.inceptionlabs.ai/capabilities/structured-outputs
Constrain Mercury 2 chat completions to a JSON schema using structured outputs, so your application receives strictly typed, machine-parseable responses.
Use JSON schemas to enforce structured data output with specific properties, types, and constraints.
Set your [API key](https://platform.inceptionlabs.ai/dashboard/api-keys) as an environment variable and install the SDK.
```bash macOS / Linux theme={null}
export INCEPTION_API_KEY="your_api_key_here"
```
```cmd Windows (Command Prompt) theme={null}
set INCEPTION_API_KEY=your_api_key_here
```
```powershell Windows (PowerShell) theme={null}
$env:INCEPTION_API_KEY="your_api_key_here"
```
```bash Python theme={null}
pip install inceptionai
```
```bash TypeScript theme={null}
npm install inceptionai
```
```python Python theme={null}
from inceptionai import Inception
client = Inception() # reads INCEPTION_API_KEY from the environment
response_schema = {
"name": "Sentiment",
"strict": True,
"schema": {
"type": "object",
"properties": {
"sentiment": {
"type": "string",
"enum": ["positive", "negative", "neutral"]
},
"confidence": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"key_phrases": {
"type": "array",
"items": {"type": "string"}
}
},
"required": ["sentiment", "confidence", "key_phrases"]
},
}
completion = client.chat.completions.create(
model="mercury-2",
messages=[
{
"role": "user",
"content": "Analyze the sentiment of this text: 'I absolutely love this feature! It works perfectly and saves me so much time.'"
}
],
response_format={"type": "json_schema", "json_schema": response_schema},
)
print(completion.choices[0].message.content)
```
```typescript TypeScript theme={null}
import Inception from 'inceptionai';
const client = new Inception(); // reads INCEPTION_API_KEY from the environment
const responseSchema = {
name: 'Sentiment',
strict: true,
schema: {
type: 'object',
properties: {
sentiment: { type: 'string', enum: ['positive', 'negative', 'neutral'] },
confidence: { type: 'number', minimum: 0, maximum: 1 },
key_phrases: { type: 'array', items: { type: 'string' } },
},
required: ['sentiment', 'confidence', 'key_phrases'],
},
};
const completion = await client.chat.completions.create({
model: 'mercury-2',
messages: [
{
role: 'user',
content:
"Analyze the sentiment of this text: 'I absolutely love this feature! It works perfectly and saves me so much time.'",
},
],
response_format: { type: 'json_schema', json_schema: responseSchema },
});
console.log(completion.choices[0]?.message.content);
```
# Tool Use
Source: https://docs.inceptionlabs.ai/capabilities/tool-use
Use tool calling on the Mercury 2 chat completions endpoint to let the model invoke functions, fetch data, and orchestrate multi-step agent workflows.
Mercury 2 supports OpenAI-compatible tool calling on the chat completions endpoint. You define functions the model may call; when the model decides a tool is needed, it returns a `tool_call` with JSON arguments. You then execute the function and send the result back so the model can produce a final answer.
Set your [API key](https://platform.inceptionlabs.ai/dashboard/api-keys) as an environment variable and install the SDK.
```bash macOS / Linux theme={null}
export INCEPTION_API_KEY="your_api_key_here"
```
```cmd Windows (Command Prompt) theme={null}
set INCEPTION_API_KEY=your_api_key_here
```
```powershell Windows (PowerShell) theme={null}
$env:INCEPTION_API_KEY="your_api_key_here"
```
```bash Python theme={null}
pip install inceptionai
```
```bash TypeScript theme={null}
npm install inceptionai
```
## Request a tool call
Pass your function definitions in `tools`. The model returns a `tool_call` when it needs one.
```bash cURL theme={null}
curl https://api.inceptionlabs.ai/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $INCEPTION_API_KEY" \
-d '{
"model": "mercury-2",
"messages": [{"role": "user", "content": "What'"'"'s the weather like in San Francisco?"}],
"tools": [{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather in a given location", "parameters": {"type": "object", "properties": {"location": {"type": "string", "description": "City and state, e.g., '"'"'San Francisco, CA'"'"'"}, "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}}, "required": ["location", "unit"]}}}]
}'
```
```python Python theme={null}
from inceptionai import Inception
client = Inception() # reads INCEPTION_API_KEY from the environment
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather in a given location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City and state, e.g., 'San Francisco, CA'"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"]
}
},
"required": ["location", "unit"]
}
}
}]
completion = client.chat.completions.create(
model="mercury-2",
messages=[{"role": "user", "content": "What's the weather like in San Francisco?"}],
tools=tools,
)
tool_call = completion.choices[0].message.tool_calls[0].function
print(f"Function called: {tool_call.name}")
print(f"Arguments: {tool_call.arguments}")
```
```typescript TypeScript theme={null}
import Inception from 'inceptionai';
const client = new Inception(); // reads INCEPTION_API_KEY from the environment
const tools = [
{
type: 'function' as const,
function: {
name: 'get_weather',
description: 'Get the current weather in a given location',
parameters: {
type: 'object',
properties: {
location: { type: 'string', description: "City and state, e.g., 'San Francisco, CA'" },
unit: { type: 'string', enum: ['celsius', 'fahrenheit'] },
},
required: ['location', 'unit'],
},
},
},
];
const completion = await client.chat.completions.create({
model: 'mercury-2',
messages: [{ role: 'user', content: "What's the weather like in San Francisco?" }],
tools,
});
const toolCall = completion.choices[0]?.message.tool_calls?.[0]?.function;
console.log(`Function called: ${toolCall?.name}`);
console.log(`Arguments: ${toolCall?.arguments}`);
```
## Return the tool result
Execute the requested function, then append the assistant message and a `tool` message (matched by `tool_call_id`) and call the API again. The model uses the result to produce its final response. Repeat this loop for multi-step agent workflows. The examples below reuse the `tools` definition from above.
```python Python theme={null}
import json
from inceptionai import Inception
client = Inception()
def get_weather(location: str, unit: str) -> str:
# Call your real weather API here.
return json.dumps({"location": location, "unit": unit, "temperature": 18})
messages = [{"role": "user", "content": "What's the weather like in San Francisco?"}]
# First request — the model asks for a tool call.
completion = client.chat.completions.create(
model="mercury-2",
messages=messages,
tools=tools,
)
message = completion.choices[0].message
tool_call = message.tool_calls[0]
args = json.loads(tool_call.function.arguments)
# Execute the function and send the result back.
result = get_weather(**args)
messages.append(message.model_dump(exclude_none=True))
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result,
})
final = client.chat.completions.create(
model="mercury-2",
messages=messages,
tools=tools,
)
print(final.choices[0].message.content)
```
```typescript TypeScript theme={null}
import Inception from 'inceptionai';
const client = new Inception();
function getWeather(location: string, unit: string): string {
// Call your real weather API here.
return JSON.stringify({ location, unit, temperature: 18 });
}
const messages = [
{ role: 'user' as const, content: "What's the weather like in San Francisco?" },
];
// First request — the model asks for a tool call.
const completion = await client.chat.completions.create({
model: 'mercury-2',
messages,
tools,
});
const message = completion.choices[0].message;
const toolCall = message.tool_calls![0];
const args = JSON.parse(toolCall.function.arguments);
// Execute the function and send the result back.
const result = getWeather(args.location, args.unit);
const finalMessages = [
...messages,
message,
{
role: 'tool' as const,
tool_call_id: toolCall.id,
content: result,
},
];
const final = await client.chat.completions.create({
model: 'mercury-2',
messages: finalMessages,
tools,
});
console.log(final.choices[0]?.message.content);
```
## Tool calling parameters
* `tool_choice` Use `tool_choice` to enforce the tool calling behavior that you desire. By default, `tool_choice` is set to `"auto"`. This means that the model will choose to either emit tool calls or output text. If set to `"none"`, tool calls are prevented. If set to `"required"`, the model is forced to emit a tool call in that turn. Use this to enforce the behavior you desire. Just add `tool_choice` to the payload to use it. See below for an example:
```python theme={null}
payload = {
"model": MODEL,
"messages": messages,
"tools": TOOLS,
"tool_choice": "auto", # <- add this line: "auto" or "required" or "none"
}
body = requests.post(API_URL, headers=HEADERS, json=payload).json()
```
* `strict` Use `strict` to force the model to output the correct tool calling schema that you have specified. We recommend setting this field to `True` for best results. To do this, add `"strict": True` to your tool definition. See below for an example.
```python theme={null}
{
"type": "function",
"function": {
"name": "list_directory",
"description": "List the entries (files and subdirectories) inside a directory.",
"strict": True, # <- add this line here
"parameters": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Absolute directory path, e.g. '/repo'.",
},
},
"required": ["path"],
"additionalProperties": False,
},
},
}
```
## Coding agent tool calling demo
See how you can use tool calling with Mercury 2 with the following simple coding agent example. We assume a small codebase of the following structure:
```python theme={null}
### structure ###
/repo
├── main.py
└── stats.py
#################
### stats.py ###
def compute_average(values):
"""Return the arithmetic mean of a list of numbers."""
if not values:
return 0.0
return sum(values) / len(values)
#################
### main.py ###
from stats import compute_average
print(compute_average([1, 2, 3]))
#################
```
### Define tools
First, define your tools. Here, we define two tools for a read-only coding agent: `list_directory` and `read_file`.
```python theme={null}
TOOLS = [
{
"type": "function",
"function": {
"name": "list_directory",
"description": "List the entries (files and subdirectories) inside a directory.",
"parameters": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Absolute directory path, e.g. '/repo'.",
},
},
"required": ["path"],
"additionalProperties": False,
},
},
},
{
"type": "function",
"function": {
"name": "read_file",
"description": "Return the full text contents of a file.",
"parameters": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Absolute file path, e.g. '/repo/stats.py'.",
},
},
"required": ["path"],
"additionalProperties": False,
},
},
},
]
```
### Prompt your model
We prompt the model to search the code base for a specific function and return its definition to the user.
```python theme={null}
SYSTEM_PROMPT = (
"You are a coding assistant with access to a small filesystem via two tools: "
"`list_directory` and `read_file`. Use tools to explore the repo before answering. "
"When you have enough information, reply in plain text with the answer."
)
USER_PROMPT = (
"The repo lives at `/repo`. Find the definition of the function "
"`compute_average` and quote its body back to me."
)
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": USER_PROMPT},
]
```
### Run your agent
```python theme={null}
# setup
API_URL = "https://api.inceptionlabs.ai/v1/chat/completions"
MODEL = "mercury-2"
HEADERS = {
"Authorization": f"Bearer {os.environ['INCEPTION_API_KEY']}",
"Content-Type": "application/json",
}
# agent loop
for turn in range(3):
payload = {
"model": MODEL,
"messages": messages,
"tools": TOOLS,
}
body = requests.post(API_URL, headers=HEADERS, json=payload).json()
# 1. parse body for content and tool_calls.
# 2. execute tools in tool_calls.
# 3. append model output and tool call results to messages.
```
### Results
When we run the above code, we get the following results:
**Step 1:**
Model's tool call:
```python theme={null}
{'name': 'list_directory', 'arguments': '{"path": "/repo"}'}
```
Tool call output:
```python theme={null}
{"path": "/repo", "entries": ["main.py", "stats.py", "README.md"]}
```
**Step 2:**
Model's tool call:
```python theme={null}
{'name': 'read_file', 'arguments': '{"path": "/repo/stats.py"}'}
```
Tool call output:
```python theme={null}
{"path": "/repo/stats.py", "contents": "def compute_average(values):\n \"\"\"Return the arithmetic mean of a list of numbers.\"\"\"\n if not values:\n return 0.0\n return sum(values) / len(values)\n"}
```
**Step 3:**
Model's output:
````text theme={null}
Here is the definition of `compute_average` from **/repo/stats.py**:
```python
def compute_average(values):
"""Return the arithmetic mean of a list of numbers."""
if not values:
return 0.0
return sum(values) / len(values)
```
````
See the full demo's code in [this cookbook](/cookbooks/coding-agent-mercury-tool-calling). Currently, the Inception API only supports sequential tool calling. Parallel tool calling will be supported soon!
# Coding Agent: Mercury + Tool Calling
Source: https://docs.inceptionlabs.ai/cookbooks/coding-agent-mercury-tool-calling
A minimal, runnable coding agent built on Mercury 2 tool calling.
This cookbook is the runnable companion to the [tool use demo](/capabilities/tool-use#coding-agent-tool-calling-demo). You can run it with the following command:
```bash theme={null}
export INCEPTION_API_KEY="your_api_key_here"
python coding-agent.py
```
## Code
```python coding-agent.py theme={null}
"""
Mercury 2 tool-calling example.
Usage:
export INCEPTION_API_KEY=...
python coding-agent.py
"""
import json
import os
import requests
API_URL = "https://api.inceptionlabs.ai/v1/chat/completions"
MODEL = "mercury-2"
HEADERS = {
"Authorization": f"Bearer {os.environ['INCEPTION_API_KEY']}",
"Content-Type": "application/json",
}
# ---------------------------------------------------------------------------
# Mock filesystem the "tools" will pretend to read.
# ---------------------------------------------------------------------------
FAKE_FS = {
"/repo": ["main.py", "stats.py"],
"/repo/stats.py": (
"def compute_average(values):\n"
" \"\"\"Return the arithmetic mean of a list of numbers.\"\"\"\n"
" if not values:\n"
" return 0.0\n"
" return sum(values) / len(values)\n"
),
"/repo/main.py": (
"from stats import compute_average\n"
"\n"
"print(compute_average([1, 2, 3]))\n"
),
}
def run_list_directory(path: str) -> str:
entries = FAKE_FS.get(path)
if entries is None or not isinstance(entries, list):
return json.dumps({"error": f"not a directory: {path}"})
return json.dumps({"path": path, "entries": entries})
def run_read_file(path: str) -> str:
contents = FAKE_FS.get(path)
if contents is None or isinstance(contents, list):
return json.dumps({"error": f"not a file: {path}"})
return json.dumps({"path": path, "contents": contents})
TOOL_RUNNERS = {
"list_directory": lambda args: run_list_directory(args["path"]),
"read_file": lambda args: run_read_file(args["path"]),
}
# ---------------------------------------------------------------------------
# Tool schemas — the OpenAI tools[] format (which Mercury 2 also accepts).
# ---------------------------------------------------------------------------
TOOLS = [
{
"type": "function",
"function": {
"name": "list_directory",
"description": "List the entries (files and subdirectories) inside a directory.",
"parameters": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Absolute directory path, e.g. '/repo'.",
},
},
"required": ["path"],
"additionalProperties": False,
},
},
},
{
"type": "function",
"function": {
"name": "read_file",
"description": "Return the full text contents of a file.",
"parameters": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Absolute file path, e.g. '/repo/stats.py'.",
},
},
"required": ["path"],
"additionalProperties": False,
},
},
},
]
SYSTEM_PROMPT = (
"You are a coding assistant with access to a small filesystem via two tools: "
"`list_directory` and `read_file`. Use tools to explore the repo before answering. "
"When you have enough information, reply in plain text with the answer."
)
USER_PROMPT = (
"The repo lives at `/repo`. Find the definition of the function "
"`compute_average` and quote its body back to me."
)
def chat_completion(messages):
"""POST to the Inception chat/completions endpoint and return the JSON body."""
payload = {
"model": MODEL,
"messages": messages,
"tools": TOOLS,
}
resp = requests.post(API_URL, headers=HEADERS, json=payload)
resp.raise_for_status()
return resp.json()
def main():
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": USER_PROMPT},
]
for turn in range(3):
print(f"\n========== turn {turn} ==========")
body = chat_completion(messages)
choice = body["choices"][0]
msg = choice["message"]
finish = choice.get("finish_reason")
tool_calls = msg.get("tool_calls") or []
print(f"finish_reason: {finish}")
if tool_calls:
# add tool calls to messages history
messages.append({
"role": "assistant",
"content": msg.get("content"),
"tool_calls": tool_calls,
})
for tc in tool_calls:
print(f"tc: {tc}")
fn = tc["function"]
print(f"fn: {fn}")
name = fn["name"]
raw_args = fn["arguments"]
print(f" tool_call id={tc['id']}")
print(f" name: {name}")
print(f" args: {raw_args}")
try:
parsed_args = json.loads(raw_args)
except json.JSONDecodeError:
parsed_args = {}
runner = TOOL_RUNNERS.get(name)
if runner is None:
result = json.dumps({"error": f"unknown tool {name}"})
else:
result = runner(parsed_args)
print(f" result: {result}")
# add tool result to messages history
messages.append({
"role": "tool",
"tool_call_id": tc["id"],
"content": result,
})
else:
# answer — loop is done.
print(f"assistant output:\n{msg.get('content')}")
break
if __name__ == "__main__":
main()
```
# Voice Customer Support Agent: Mercury + Tool Calling + Voice
Source: https://docs.inceptionlabs.ai/cookbooks/customer-support-agent-mercury
Build a voice-enabled dental clinic support agent that uses Mercury 2 tool calling to read and write a live appointment database, with ElevenLabs speech-to-text and text-to-speech.
This cookbook builds a customer support agent for a dental clinic on top of
[Mercury 2](/get-started/models), Inception's
diffusion large language model. The agent understands natural-language requests,
calls tools to read and write a live appointment database, and holds a
real-time voice conversation using [ElevenLabs](https://elevenlabs.io) for
speech-to-text and text-to-speech.
By the end you will have a working agent that can:
* Look up a patient and their upcoming appointments
* Show open appointment slots for a given day or provider
* Book, reschedule, and cancel appointments via real mutations against a database
* Do all of the above out loud, over streamed audio
Because Mercury 2 generates tokens with a diffusion process rather than
autoregressively, it returns tool calls and final answers faster
than comparable models. For a voice customer support agent, where every extra 100 ms is audible,
this difference is critical.
Directly via Google Colab or download the `.ipynb` to run it locally in Jupyter
## Architecture
The clinic database is an in-memory SQLite instance seeded with providers,
patients, and an availability grid. Two API keys (INCEPTION\_API\_KEY, ELEVENLABS\_API\_KEY)
are required to run this demo.
## 1. Setup
Install the Inception SDK, the ElevenLabs SDK, and a few audio helpers.
```python theme={null}
%pip install -q "inceptionai" "elevenlabs>=2.15.0" \
"numpy>=1.26.0" "scipy>=1.16.2" "pydub>=0.25.1"
```
You need two API keys:
* `INCEPTION_API_KEY` — from the [Inception dashboard](https://platform.inceptionlabs.ai)
(new accounts get 100M free tokens)
* `ELEVENLABS_API_KEY` — from the [ElevenLabs dashboard](https://elevenlabs.io/app/settings/api-keys)
The next cell reads them from the environment, and prompts you for any that
aren't already set. Nothing is written to disk.
```python theme={null}
import os
import json
import getpass
import time
import sqlite3
from datetime import datetime, timedelta
from inceptionai import Inception
for var in ("INCEPTION_API_KEY", "ELEVENLABS_API_KEY"):
if not os.environ.get(var):
os.environ[var] = getpass.getpass(f"{var}: ")
client = Inception(api_key=os.environ["INCEPTION_API_KEY"])
MERCURY_MODEL = "mercury-2"
print("Inception client ready. Model:", MERCURY_MODEL)
```
## 2. A mock clinic database
We spin up an in-memory SQLite database for a fabricated dental clinic: **Diffusion Dental**. It contains three tables:
* `providers` — the dentists and hygienists
* `patients` — existing patient records
* `appointments` — a grid of 30-minute slots, each `available`, `booked`, or `cancelled`
The availability grid is generated relative to the moment you run the cell.
It starts with whatever is left of today and rolls forward over the next five
business days, skipping weekends and any hour that has already passed.
A couple of slots start out pre-booked so availability looks realistic.
This is a placeholder for demo purposes; you can plug in your own custom db here.
```python theme={null}
CLINIC_HOURS = (9, 10, 11, 14, 15, 16) # 30-min slots, morning and afternoon
BUSINESS_DAYS = 5 # how many open days to put on the calendar
def build_clinic_db(now=None):
"""Create and seed an in-memory clinic database. Returns a live connection.
The availability grid is built relative to `now` (default: the current date
and time), so the calendar is always current no matter when you run this.
Slots start with whatever is left of today and roll forward over the next
few business days; times that have already passed are never offered.
"""
now = now or datetime.now()
conn = sqlite3.connect(":memory:")
conn.row_factory = sqlite3.Row
cur = conn.cursor()
cur.executescript(
"""
CREATE TABLE providers (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
specialty TEXT NOT NULL
);
CREATE TABLE patients (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
phone TEXT NOT NULL,
email TEXT
);
CREATE TABLE appointments (
id INTEGER PRIMARY KEY,
patient_id INTEGER,
provider_id INTEGER NOT NULL,
start_time TEXT NOT NULL, -- ISO 8601, e.g. 2026-08-17T14:00
duration_min INTEGER NOT NULL DEFAULT 30,
reason TEXT,
status TEXT NOT NULL DEFAULT 'available', -- available | booked | cancelled
FOREIGN KEY (patient_id) REFERENCES patients(id),
FOREIGN KEY (provider_id) REFERENCES providers(id)
);
"""
)
# Deliberately cartoonish names - nobody should mistake this for real PHI.
cur.executemany("INSERT INTO providers VALUES (?,?,?)", [
(1, "Dr. Ada Molar", "General Dentistry"),
(2, "Dr. Reed Wyre", "Orthodontics"),
(3, "Dr. Pearl Flossmore", "Hygiene & Cleaning"),
])
cur.executemany("INSERT INTO patients VALUES (?,?,?,?)", [
(1, "Penny Placeholder", "+1-415-555-0101", "penny@example.com"),
(2, "Sam Sampleton", "+1-415-555-0102", "sam@example.com"),
(3, "Molly Mockford", "+1-415-555-0103", "molly@example.com"),
])
# Walk forward from today, collecting BUSINESS_DAYS weekdays that still have
# open hours left. Weekends are skipped, and so is the rest of today once the
# clinic has closed - that day simply doesn't count toward the total.
rows, appt_id = [], 1
day = now.replace(hour=0, minute=0, second=0, microsecond=0)
days_added = 0
while days_added < BUSINESS_DAYS:
if day.weekday() < 5: # Mon-Fri
starts = [day.replace(hour=h, minute=0) for h in CLINIC_HOURS]
starts = [s for s in starts if s > now] # never offer the past
if starts:
for start in starts:
for provider_id in (1, 2, 3):
rows.append((appt_id, None, provider_id,
start.strftime("%Y-%m-%dT%H:%M"), 30, None, "available"))
appt_id += 1
days_added += 1
day += timedelta(days=1)
cur.executemany("INSERT INTO appointments VALUES (?,?,?,?,?,?,?)", rows)
# Pre-book a couple of slots so the calendar isn't empty. Picked by position
# rather than by hard-coded date, so they land on real slots on any run.
cur.execute("""UPDATE appointments SET patient_id=1, reason='Cleaning', status='booked'
WHERE id = (SELECT id FROM appointments
WHERE provider_id=3 AND status='available'
ORDER BY start_time LIMIT 1)""")
cur.execute("""UPDATE appointments SET patient_id=2, reason='Braces adjustment', status='booked'
WHERE id = (SELECT id FROM appointments
WHERE provider_id=2 AND status='available'
AND start_time LIKE '%T14:00'
ORDER BY start_time LIMIT 1 OFFSET 1)""")
conn.commit()
return conn
conn = build_clinic_db()
# Peek at the seed data.
print("Providers:")
for r in conn.execute("SELECT * FROM providers"):
print(" ", dict(r))
first, last = conn.execute("SELECT MIN(start_time), MAX(start_time) FROM appointments").fetchone()
n_avail = conn.execute("SELECT COUNT(*) FROM appointments WHERE status='available'").fetchone()[0]
n_booked = conn.execute("SELECT COUNT(*) FROM appointments WHERE status='booked'").fetchone()[0]
print(f"\nCalendar runs {first} -> {last}")
print(f"Slots: {n_avail} available, {n_booked} booked")
```
## 3. Define the support tools
Each tool is a plain Python function that reads or writes the database. We keep
the functions small and return dictionaries, which then get
handed back to Mercury 2 as the tool result.
We include a mix of **read** tools (`find_patient`, `list_available_slots`,
`get_appointments_for_patient`) and **write** tools (`book_appointment`,
`reschedule_appointment`, `cancel_appointment`). The write tools validate state
first: you can't book a slot that's already taken, or cancel an appointment
that doesn't exist.
If you have a different database setup, you can customize these functions to match your
database-specific query logic. If you do, make sure you update your tool schemas (next section)
accordingly.
```python theme={null}
_TITLES = {"dr", "doctor", "mr", "mrs", "ms", "prof"}
def _provider_id_from_name(conn, provider_name):
"""Resolve a provider from however the caller referred to them.
Records hold the full name ("Dr. Pearl Flossmore") but a caller on the phone
will just say a surname, so match on the distinctive words and ignore titles.
"""
if not provider_name:
return None
words = [w for w in provider_name.lower().replace(".", " ").split() if w not in _TITLES]
if not words:
return None
q = "SELECT id FROM providers WHERE " + " AND ".join(["LOWER(name) LIKE ?"] * len(words))
row = conn.execute(q, [f"%{w}%" for w in words]).fetchone()
return row["id"] if row else None
def find_patient(conn, name=None, phone=None):
"""Look up a patient by (partial) name and/or phone number."""
q, params = "SELECT * FROM patients WHERE 1=1", []
if name:
q += " AND name LIKE ?"; params.append(f"%{name}%")
if phone:
q += " AND phone LIKE ?"; params.append(f"%{phone}%")
rows = [dict(r) for r in conn.execute(q, params).fetchall()]
return {"patients": rows, "count": len(rows)}
def list_available_slots(conn, date=None, provider_name=None):
"""List open appointment slots, optionally filtered by date (YYYY-MM-DD)
and/or provider surname."""
q = ("SELECT a.id, a.start_time, a.duration_min, p.name AS provider, p.specialty "
"FROM appointments a JOIN providers p ON p.id = a.provider_id "
"WHERE a.status = 'available'")
params = []
if date:
q += " AND a.start_time LIKE ?"; params.append(f"{date}%")
pid = _provider_id_from_name(conn, provider_name)
if provider_name and pid is None:
return {"error": f"No provider matching '{provider_name}'."}
if pid:
q += " AND a.provider_id = ?"; params.append(pid)
q += " ORDER BY a.start_time LIMIT 20"
rows = [dict(r) for r in conn.execute(q, params).fetchall()]
return {"available_slots": rows, "count": len(rows)}
def book_appointment(conn, slot_id, patient_name, patient_phone, reason=None):
"""Book an available slot for a patient, creating the patient if new."""
slot = conn.execute("SELECT * FROM appointments WHERE id=?", (slot_id,)).fetchone()
if slot is None:
return {"error": f"No slot with id {slot_id}."}
if slot["status"] != "available":
return {"error": f"Slot {slot_id} is not available (status: {slot['status']})."}
existing = conn.execute("SELECT * FROM patients WHERE phone=?", (patient_phone,)).fetchone()
if existing:
patient_id = existing["id"]
else:
cur = conn.execute("INSERT INTO patients (name, phone) VALUES (?, ?)",
(patient_name, patient_phone))
patient_id = cur.lastrowid
conn.execute("UPDATE appointments SET patient_id=?, reason=?, status='booked' WHERE id=?",
(patient_id, reason, slot_id))
conn.commit()
confirmed = conn.execute(
"SELECT a.id, a.start_time, p.name AS provider "
"FROM appointments a JOIN providers p ON p.id=a.provider_id WHERE a.id=?",
(slot_id,)).fetchone()
return {"status": "booked", "confirmation": dict(confirmed), "patient_id": patient_id}
def reschedule_appointment(conn, appointment_id, new_slot_id):
"""Move a booking to a new available slot, freeing the old one."""
old = conn.execute("SELECT * FROM appointments WHERE id=?", (appointment_id,)).fetchone()
if old is None or old["status"] != "booked":
return {"error": f"Appointment {appointment_id} is not an active booking."}
new = conn.execute("SELECT * FROM appointments WHERE id=?", (new_slot_id,)).fetchone()
if new is None or new["status"] != "available":
return {"error": f"Slot {new_slot_id} is not available."}
conn.execute("UPDATE appointments SET patient_id=?, reason=?, status='booked' WHERE id=?",
(old["patient_id"], old["reason"], new_slot_id))
conn.execute("UPDATE appointments SET patient_id=NULL, reason=NULL, status='available' WHERE id=?",
(appointment_id,))
conn.commit()
return {"status": "rescheduled", "from_id": appointment_id, "to_id": new_slot_id}
def cancel_appointment(conn, appointment_id):
"""Cancel a booking and return the slot to the available pool."""
appt = conn.execute("SELECT * FROM appointments WHERE id=?", (appointment_id,)).fetchone()
if appt is None or appt["status"] != "booked":
return {"error": f"Appointment {appointment_id} is not an active booking."}
conn.execute("UPDATE appointments SET patient_id=NULL, reason=NULL, status='available' WHERE id=?",
(appointment_id,))
conn.commit()
return {"status": "cancelled", "appointment_id": appointment_id}
def get_appointments_for_patient(conn, name=None, phone=None):
"""List a patient's active (booked) appointments."""
q = ("SELECT a.id, a.start_time, a.reason, a.status, p.name AS provider "
"FROM appointments a "
"JOIN patients pt ON pt.id = a.patient_id "
"JOIN providers p ON p.id = a.provider_id "
"WHERE a.status='booked'")
params = []
if name:
q += " AND pt.name LIKE ?"; params.append(f"%{name}%")
if phone:
q += " AND pt.phone LIKE ?"; params.append(f"%{phone}%")
q += " ORDER BY a.start_time"
rows = [dict(r) for r in conn.execute(q, params).fetchall()]
return {"appointments": rows, "count": len(rows)}
print("Tool functions defined.")
```
### Tool schemas
Mercury 2 uses the OpenAI-compatible function-calling format. Each schema tells
the model the tool's name, what it does, and its parameters. This is how the model decides *which* tool to reach for and *how*
to fill in the arguments. You can also plug in custom tool schemas; just make sure they are compatible with the support tools
you've defined above.
```python theme={null}
TOOLS = [
{
"type": "function",
"function": {
"name": "find_patient",
"description": "Look up an existing patient by name and/or phone number.",
"strict": True,
"parameters": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "Full or partial patient name"},
"phone": {"type": "string", "description": "Full or partial phone number"},
},
},
},
},
{
"type": "function",
"function": {
"name": "list_available_slots",
"description": ("List OPEN appointment slots. Filter by date (format "
"YYYY-MM-DD) and/or provider surname. Call this before booking "
"so you can offer the patient a real, open slot id."),
"strict": True,
"parameters": {
"type": "object",
"properties": {
"date": {"type": "string", "description": "Day to search, YYYY-MM-DD"},
"provider_name": {"type": "string", "description": "Provider surname, e.g. 'Flossmore'"},
},
},
},
},
{
"type": "function",
"function": {
"name": "book_appointment",
"description": ("Book a specific available slot for a patient. Requires the "
"slot_id (from list_available_slots), the patient's name, and "
"their phone number."),
"strict": True,
"parameters": {
"type": "object",
"properties": {
"slot_id": {"type": "integer", "description": "id of an available slot"},
"patient_name": {"type": "string"},
"patient_phone": {"type": "string"},
"reason": {"type": "string", "description": "Reason for the visit"},
},
"required": ["slot_id", "patient_name", "patient_phone"],
},
},
},
{
"type": "function",
"function": {
"name": "reschedule_appointment",
"description": "Move an existing booking to a new available slot.",
"strict": True,
"parameters": {
"type": "object",
"properties": {
"appointment_id": {"type": "integer", "description": "id of the current booking"},
"new_slot_id": {"type": "integer", "description": "id of the new available slot"},
},
"required": ["appointment_id", "new_slot_id"],
},
},
},
{
"type": "function",
"function": {
"name": "cancel_appointment",
"description": "Cancel an existing booking and free the slot.",
"strict": True,
"parameters": {
"type": "object",
"properties": {
"appointment_id": {"type": "integer", "description": "id of the booking to cancel"},
},
"required": ["appointment_id"],
},
},
},
{
"type": "function",
"function": {
"name": "get_appointments_for_patient",
"description": "List a patient's upcoming (booked) appointments by name and/or phone.",
"strict": True,
"parameters": {
"type": "object",
"properties": {
"name": {"type": "string"},
"phone": {"type": "string"},
},
},
},
},
]
# Map tool names to the functions that run them (bound to our connection).
TOOL_RUNNERS = {
"find_patient": lambda **kw: find_patient(conn, **kw),
"list_available_slots": lambda **kw: list_available_slots(conn, **kw),
"book_appointment": lambda **kw: book_appointment(conn, **kw),
"reschedule_appointment": lambda **kw: reschedule_appointment(conn, **kw),
"cancel_appointment": lambda **kw: cancel_appointment(conn, **kw),
"get_appointments_for_patient": lambda **kw: get_appointments_for_patient(conn, **kw),
}
print(f"{len(TOOLS)} tools registered.")
```
## 4. The agent loop
The loop is the heart of a tool-calling agent:
1. Send the conversation **plus the tool schemas** to Mercury 2.
2. If the reply contains `tool_calls`, run each one locally and append the
result as a `role: "tool"` message.
3. Repeat until Mercury returns a normal text answer.
Using this workflow, the loop naturally handles multi-step requests like "reschedule my cleaning
to Thursday" (look up the booking → find a Thursday slot → move it). We query mercury-2 on low reasoning effort: real time customer service is a very latency-sensitive use-case where Mercury's ability to complete complicated tool-calling tasks at ultra-low latencies shines.
```python theme={null}
TODAY = datetime.now()
SYSTEM_PROMPT = f"""You are the friendly virtual receptionist for Diffusion Dental.
You help callers check availability and book, reschedule, or cancel appointments.
Guidelines:
- Today's date is {TODAY.strftime("%A, %B")} {TODAY.day}, {TODAY.year}. The clinic is open Monday-Friday.
- The clinic's providers go by Molar (general dentistry), Wyre (orthodontics),
and Flossmore (hygiene and cleaning).
- Always use the tools to look up real data; never invent slot ids, times, or
patient records.
- Before booking, call list_available_slots and offer the caller a real open slot.
- To book you need the caller's name and phone number - ask if you don't have them.
- Confirm the concrete date, time, and provider back to the caller after any change.
- Keep replies short and conversational - they may be read aloud over the phone."""
def run_agent(user_message, history=None, max_turns=6, verbose=True):
"""Run one user turn through the tool-calling loop. Returns (reply_text, history)."""
messages = history or [{"role": "system", "content": SYSTEM_PROMPT}]
messages.append({"role": "user", "content": user_message})
if verbose:
print(f"👤 USER: {user_message}\n")
for _ in range(max_turns):
t0 = time.time()
response = client.chat.completions.create(
model=MERCURY_MODEL,
reasoning_effort="low",
messages=messages,
tools=TOOLS,
tool_choice="auto",
)
dt = time.time() - t0
msg = response.choices[0].message
# No tool calls => this is the final answer for this turn.
if not msg.tool_calls:
messages.append({"role": "assistant", "content": msg.content})
if verbose:
print(f"🦷 AGENT ({dt:.2f}s): {msg.content}")
return msg.content, messages
# Otherwise, run each requested tool and feed the results back.
messages.append(msg.model_dump(exclude_none=True))
for tool_call in msg.tool_calls:
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
if verbose:
print(f" 🔧 tool_call ({dt:.2f}s): {name}({json.dumps(args)})")
try:
result = TOOL_RUNNERS[name](**args)
except Exception as e: # never crash the loop
result = {"error": f"{type(e).__name__}: {e}"}
if verbose:
print(f" ↳ {json.dumps(result)[:200]}")
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result),
})
return "Sorry, I couldn't complete that. Let me connect you to the front desk.", messages
print(f"Agent loop ready. Today is {TODAY.strftime('%A, %B')} {TODAY.day}, {TODAY.year}.")
```
## 5. Text Customer Service Agent
Let's walk a caller through a full support flow. We keep `history` between calls
so the agent remembers the conversation.
### 5a. Check availability
```python theme={null}
reply, history = run_agent(
"Hi, what times are open with Dr. Flossmore on Wednesday for a cleaning?"
)
```
### 5b. Book a slot (a real database write)
```python theme={null}
reply, history = run_agent(
"Let's do the earliest one. My name is Taylor Testcase, phone 415-555-0199.",
history=history,
)
```
Let's confirm the write actually landed in the database. The slot Taylor took
should now be `booked`, and Taylor should exist as a patient.
```python theme={null}
print("Taylor's bookings:")
print(json.dumps(get_appointments_for_patient(conn, name="Taylor"), indent=2))
```
### 5c. Reschedule
```python theme={null}
reply, history = run_agent(
"Actually something came up - can you move that to Friday instead?",
history=history,
)
```
### 5d. Cancel
```python theme={null}
reply, history = run_agent(
"You know what, I'll just cancel it for now. Thanks!",
history=history,
)
```
Every one of those turns was a real read or write against the SQLite database.
The agent discovered slot ids and appointment ids
purely through tool calls, exactly as it would against a production scheduling
system behind an API.
## 6. Real-time voice with ElevenLabs
Now we give the agent a voice. The pipeline is:
**caller audio → ElevenLabs STT → Mercury 2 (tool loop) → ElevenLabs TTS → spoken reply**
We use ElevenLabs `scribe_v1` for transcription and `eleven_turbo_v2_5` for
low-latency speech. To keep the notebook self-contained (no microphone needed),
we *simulate* the caller by synthesizing their line with a different voice,
then feed that audio through the real STT → agent → TTS path.
```python theme={null}
from elevenlabs import ElevenLabs
from IPython.display import Audio, display
eleven = ElevenLabs(api_key=os.environ["ELEVENLABS_API_KEY"],
base_url="https://api.elevenlabs.io")
CALLER_VOICE = "21m00Tcm4TlvDq8ikWAM" # Rachel - stands in for the caller
AGENT_VOICE = "IKne3meq5aSn9XLyUdCD" # Charlie - the clinic receptionist
TTS_MODEL = "eleven_turbo_v2_5"
STT_MODEL = "scribe_v1"
def synth(text, voice_id, path):
"""Synthesize `text` to an mp3 file and return the raw bytes."""
audio = eleven.text_to_speech.convert(
voice_id=voice_id,
model_id=TTS_MODEL,
text=text,
output_format="mp3_44100_128",
)
data = b"".join(audio)
with open(path, "wb") as f:
f.write(data)
return data
print("ElevenLabs client ready.")
```
### 6a. Simulate the caller
We synthesize the caller's spoken request. In a real deployment this audio would
come from a phone line or the browser's microphone.
```python theme={null}
caller_line = "Hi, I'd like to book a checkup with Dr. Flossmore on Wednesday afternoon."
caller_audio = synth(caller_line, CALLER_VOICE, "caller.mp3")
print(f"Simulated caller said: {caller_line!r}")
display(Audio("caller.mp3"))
```
### 6b. Transcribe with `scribe_v1`
Speech-to-text, timed. This is the first hop of the latency budget.
```python theme={null}
t0 = time.time()
transcript = eleven.speech_to_text.convert(
model_id=STT_MODEL,
file=open("caller.mp3", "rb"),
)
stt_latency = time.time() - t0
caller_text = transcript.text
print(f"📝 Transcript ({stt_latency:.2f}s): {caller_text}")
```
### 6c. Stream the spoken reply, sentence by sentence
The trick for low perceived latency (borrowed from the
[voice assistant cookbook](/cookbooks/realtime-voice-assistant)):
don't wait for the whole answer. Buffer Mercury's streamed text until a sentence
boundary, then synthesize that sentence while the next one is still generating.
Here we run the **tool loop** for the caller's request, then stream the final
answer into ElevenLabs TTS one sentence at a time.
```python theme={null}
import re
def speak_streaming(text_chunks, voice_id, out_path):
"""Consume an iterator of text chunks, synthesize each complete sentence as it
arrives, and concatenate the audio into a single mp3."""
audio_parts, buffer = [], ""
sentence_end = re.compile(r"(.+?[.!?])(\s|$)")
def flush(sentence):
sentence = sentence.strip()
if not sentence:
return
chunk = eleven.text_to_speech.convert(
voice_id=voice_id, model_id=TTS_MODEL,
text=sentence, output_format="mp3_44100_128",
)
audio_parts.append(b"".join(chunk))
for piece in text_chunks:
buffer += piece
while True:
m = sentence_end.match(buffer)
if not m:
break
flush(m.group(1))
buffer = buffer[m.end():]
flush(buffer) # trailing text with no terminal punctuation
data = b"".join(audio_parts)
with open(out_path, "wb") as f:
f.write(data)
return data
```
### 6d. Put it together: one full voice turn
`voice_turn` takes caller audio and returns the agent's spoken reply, printing a
latency breakdown for each stage.
```python theme={null}
def voice_turn(audio_path, history=None, agent_voice=AGENT_VOICE, out_path="agent_reply.mp3"):
timings = {}
# 1. Speech -> text
t0 = time.time()
transcript = eleven.speech_to_text.convert(model_id=STT_MODEL, file=open(audio_path, "rb"))
caller_text = transcript.text
timings["stt"] = time.time() - t0
# 2. Run the tool-calling agent (may make several Mercury calls)
t0 = time.time()
reply_text, history = run_agent(caller_text, history=history, verbose=False)
timings["agent"] = time.time() - t0
# 3. Text -> streamed speech (synthesize sentence by sentence)
t0 = time.time()
speak_streaming(iter([reply_text]), agent_voice, out_path)
timings["tts"] = time.time() - t0
print(f"👤 Caller: {caller_text}")
print(f"🦷 Agent: {reply_text}\n")
print("⏱ latency "
f"STT {timings['stt']:.2f}s | agent {timings['agent']:.2f}s | "
f"TTS {timings['tts']:.2f}s | total {sum(timings.values()):.2f}s")
return history, out_path
history_voice, reply_path = voice_turn("caller.mp3")
display(Audio(reply_path))
```
### 6e. Keep the conversation going
Because `voice_turn` threads `history` back through, the agent remembers context
across spoken turns. Let's confirm the booking out
loud.
```python theme={null}
follow_up = synth(
"Yes please book it. I'm Dana Decoy, my number is 415-555-0288.",
CALLER_VOICE, "caller2.mp3",
)
history_voice, reply_path = voice_turn("caller2.mp3", history=history_voice)
display(Audio(reply_path))
```
## Tips
### Getting tool calls right
* **Put your data conventions where Mercury can read them.** The model can only match your schema if you tell it what your schema looks like, either in tool descriptions or in the system prompt. For example, the line *"The clinic's providers go by Molar (general dentistry), Wyre (orthodontics), and Flossmore (hygiene and cleaning)"* is what makes Mercury pass `Flossmore` (not `Dr. Pearl Flossmore`) when a caller asks for "Doctor Flossmore." The same goes for id formats, enum values, and date formats: if a tool wants `2026-08-19`, say so in the tool's `description`, not just as `{"type": "string"}` in the schema. The `description` fields are prompt text, and they are where the model looks when filling arguments.
* **Then make the tool tolerant anyway.** Prompt guidance reduces malformed arguments; it never eliminates them, and speech-to-text will always run the risk of transcribing speech incorrectly, especially names. That is why `_provider_id_from_name` strips titles and matches on distinctive words. State the contract in the prompt, enforce it in the tool, and return
a structured `{"error": ...}` the model can read and recover from.
### Model sampling
* **Adjust reasoning effort for your use case.** For real-time voice use cases, we recommend *reasoning effort low*, which runs at ultra-low latencies while still delivering a pleasant conversational experience and the complex tool calls needed for a streamlined customer service agent.
### Conversation state
* **Manage context length as history grows.** Every turn appends the full text response and tool call JSON, increasing prompt size and driving up latency for very long-running conversations. However, most of that history goes stale fast. In order to improve latency over very long conversations, consider incorporating a compaction step in your voice loop, using Mercury-2 to summarize your conversation history, maintaining a smooth conversation experience while saving on tokens and latency.
* **Validate every model-supplied id against the database.** The write tools here check
state before mutating: you cannot book a taken slot or cancel an appointment that does
not exist. Keep that discipline in production: treat slot ids and appointment ids from
the model as untrusted input, because a plausible-looking hallucinated id could silently corrupt the loop.
## Resources
* [Tool use with Mercury 2](/capabilities/tool-use)
* [SQL agent cookbook](/cookbooks/natural-language-sql-agent)
* [Voice assistant cookbook](/cookbooks/realtime-voice-assistant)
* [Models & pricing](/get-started/models)
# Natural Language SQL Agent: Mercury + Tool Calling
Source: https://docs.inceptionlabs.ai/cookbooks/natural-language-sql-agent
Build a natural language SQL agent that uses Mercury 2.
This cookbook demonstrates how to build a natural language database query system using Mercury 2's tool calling capabilities. The full loop — natural language question, SQL generation, execution, and formatted answer — completes in under 2 seconds. We walk you through how to:
1. Load a SQLite database (sample or your own)
2. Auto-detect the schema and define a SQL tool for Mercury 2
3. Translate natural language questions into SQL using tool calling
4. Execute queries and return natural language answers
5. Measure end-to-end latency at each step
Open the notebook in Google Colab or download the `.ipynb` to run locally. It builds its sample database in memory — no extra files needed.
***
## Dependencies
Install packages and import the libraries required for the Mercury 2 API and SQLite:
```python theme={null}
%pip install \
"inceptionai" \
"python-dotenv>=1.0.0"
```
```python theme={null}
import os
import json
import time
import sqlite3
from inceptionai import Inception
from dotenv import load_dotenv
```
## API Keys
Set up your Inception Labs API key.
1. Create a `.env` file in this directory
2. Add your API key to the `.env`:
* `INCEPTION_API_KEY`
The key will be automatically loaded from the `.env` file.
```python theme={null}
# Load environment variables from .env file
load_dotenv()
INCEPTION_API_KEY = os.getenv("INCEPTION_API_KEY")
```
## Initialize Client
Initialize the Inception (Mercury 2) client:
```python theme={null}
assert INCEPTION_API_KEY is not None, (
"ERROR: INCEPTION_API_KEY not found. Please create a .env file and add your API key."
)
client = Inception(api_key=INCEPTION_API_KEY)
MERCURY_MODEL = "mercury-2"
```
## Load Database
We create a small in-memory e-commerce database with three tables — `customers`, `products`, and `orders`. If you want to use your own SQLite database instead, replace this cell with `db = sqlite3.connect("your_file.db")`.
```python theme={null}
# To use your own SQLite database instead, replace this cell with:
# db = sqlite3.connect("path/to/your/database.db")
db = sqlite3.connect(":memory:")
cursor = db.cursor()
cursor.executescript("""
CREATE TABLE customers (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL,
signup_date TEXT NOT NULL
);
CREATE TABLE products (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
category TEXT NOT NULL,
price REAL NOT NULL,
stock INTEGER NOT NULL
);
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
quantity INTEGER NOT NULL,
total_amount REAL NOT NULL,
status TEXT NOT NULL,
order_date TEXT NOT NULL,
FOREIGN KEY (customer_id) REFERENCES customers(id),
FOREIGN KEY (product_id) REFERENCES products(id)
);
INSERT INTO customers VALUES (1, 'Alice Johnson', 'alice@example.com', '2024-01-15');
INSERT INTO customers VALUES (2, 'Bob Smith', 'bob@example.com', '2024-02-20');
INSERT INTO customers VALUES (3, 'Sarah Lee', 'sarah@example.com', '2024-03-10');
INSERT INTO customers VALUES (4, 'Mike Chen', 'mike@example.com', '2024-04-05');
INSERT INTO customers VALUES (5, 'Emma Wilson', 'emma@example.com', '2024-05-12');
INSERT INTO products VALUES (1, 'Wireless Headphones', 'Electronics', 79.99, 45);
INSERT INTO products VALUES (2, 'Running Shoes', 'Sports', 129.99, 30);
INSERT INTO products VALUES (3, 'Coffee Maker', 'Home & Kitchen', 49.99, 60);
INSERT INTO products VALUES (4, 'Backpack', 'Accessories', 59.99, 25);
INSERT INTO products VALUES (5, 'Yoga Mat', 'Sports', 29.99, 80);
INSERT INTO products VALUES (6, 'Desk Lamp', 'Home & Kitchen', 34.99, 55);
INSERT INTO products VALUES (7, 'Water Bottle', 'Accessories', 19.99, 100);
INSERT INTO products VALUES (8, 'Bluetooth Speaker', 'Electronics', 99.99, 35);
INSERT INTO orders VALUES (1, 1, 1, 1, 79.99, 'delivered', '2025-01-10');
INSERT INTO orders VALUES (2, 1, 3, 2, 99.98, 'delivered', '2025-01-15');
INSERT INTO orders VALUES (3, 2, 2, 1, 129.99, 'shipped', '2025-02-01');
INSERT INTO orders VALUES (4, 3, 5, 2, 59.98, 'delivered', '2025-02-10');
INSERT INTO orders VALUES (5, 3, 1, 1, 79.99, 'delivered', '2025-02-14');
INSERT INTO orders VALUES (6, 4, 8, 1, 99.99, 'pending', '2025-03-01');
INSERT INTO orders VALUES (7, 4, 4, 1, 59.99, 'pending', '2025-03-05');
INSERT INTO orders VALUES (8, 5, 6, 2, 69.98, 'shipped', '2025-03-08');
INSERT INTO orders VALUES (9, 2, 7, 3, 59.97, 'delivered', '2025-03-10');
INSERT INTO orders VALUES (10, 1, 8, 1, 99.99, 'pending', '2025-03-12');
INSERT INTO orders VALUES (11, 5, 3, 1, 49.99, 'delivered', '2025-01-20');
INSERT INTO orders VALUES (12, 3, 7, 2, 39.98, 'shipped', '2025-03-14');
""")
db.commit()
```
## Inspect Schema
We introspect the schema directly from the database, so the tool calling setup automatically adapts to whatever tables are loaded:
```python theme={null}
cursor.execute("SELECT sql FROM sqlite_master WHERE type='table' AND sql IS NOT NULL")
DB_SCHEMA = "\n\n".join(row[0] for row in cursor.fetchall())
print("Detected schema:")
print(DB_SCHEMA)
```
## Define the SQL Tool
We define a single tool — `run_sql_query` — that Mercury 2 can call to execute SQL against our database. The tool description includes the auto-detected schema so Mercury knows what tables and columns are available:
```python theme={null}
tools = [
{
"type": "function",
"function": {
"name": "run_sql_query",
"description": f"Execute a read-only SQL query against the SQLite database. Schema:\n{DB_SCHEMA}",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The SQL SELECT query to execute",
}
},
"required": ["query"],
},
},
}
]
def run_sql_query(query: str) -> str:
"""Execute a SQL query and return results as a formatted string."""
try:
cursor.execute(query)
columns = [desc[0] for desc in cursor.description] if cursor.description else []
rows = cursor.fetchall()
if not rows:
return "No results found."
result = " | ".join(columns) + "\n"
result += "-" * len(result) + "\n"
for row in rows:
result += " | ".join(str(val) for val in row) + "\n"
return result
except Exception as e:
return f"SQL Error: {e}"
tool_functions = {"run_sql_query": run_sql_query}
```
## Query Helper
This function handles the full tool-calling loop:
1. Send the user's natural language question to Mercury 2 with the tool definition
2. Mercury generates a `run_sql_query` tool call with SQL
3. We execute the SQL and send results back to Mercury
4. Mercury returns a natural language answer
We time each step to show the latency breakdown.
```python theme={null}
def ask_database(question: str) -> dict:
"""
Full tool-calling loop: question -> Mercury tool call -> SQL exec -> Mercury answer.
Returns timing breakdown and the final answer.
"""
messages = [{"role": "user", "content": question}]
# Step 1: Mercury generates a tool call
t0 = time.time()
response = client.chat.completions.create(
model=MERCURY_MODEL,
messages=messages,
tools=tools,
)
t1 = time.time()
assistant_message = response.choices[0].message
tool_call = assistant_message.tool_calls[0]
function_name = tool_call.function.name
function_args = json.loads(tool_call.function.arguments)
print(f"Generated SQL: {function_args['query']}")
# Step 2: Execute the SQL
t2 = time.time()
result = tool_functions[function_name](**function_args)
t3 = time.time()
print(f"Query result:\n{result}")
# Step 3: Send tool result back to Mercury for a natural language answer
messages.append(assistant_message.model_dump(exclude_none=True))
messages.append(
{
"role": "tool",
"tool_call_id": tool_call.id,
"content": result,
}
)
t4 = time.time()
response = client.chat.completions.create(
model=MERCURY_MODEL,
messages=messages,
)
t5 = time.time()
final_answer = response.choices[0].message.content
timing = {
"tool_call_generation": round(t1 - t0, 3),
"sql_execution": round(t3 - t2, 3),
"answer_generation": round(t5 - t4, 3),
"total": round(t5 - t0, 3),
}
return {"answer": final_answer, "sql": function_args["query"], "timing": timing}
```
## Natural Language Database Queries
Let's run several queries and measure the full round-trip latency. Each query goes through: NL question, SQL generation, execution, and NL answer.
### Query 1: Aggregation
*"Who are the top 3 customers by total spending?"*
```python theme={null}
result = ask_database("Who are the top 3 customers by total spending?")
print(f"\nAnswer: {result['answer']}")
print(f"\nLatency breakdown:")
for step, duration in result["timing"].items():
print(f" {step}: {duration}s")
```
### Query 2: Filtering
*"Which orders are still pending?"*
```python theme={null}
result = ask_database("Which orders are still pending?")
print(f"\nAnswer: {result['answer']}")
print(f"\nLatency breakdown:")
for step, duration in result["timing"].items():
print(f" {step}: {duration}s")
```
### Query 3: Join + Lookup
*"Show me all orders placed by Sarah Lee."*
```python theme={null}
result = ask_database("Show me all orders placed by Sarah Lee.")
print(f"\nAnswer: {result['answer']}")
print(f"\nLatency breakdown:")
for step, duration in result["timing"].items():
print(f" {step}: {duration}s")
```
### Query 4: Analytics
*"What is the most popular product category by number of items sold?"*
```python theme={null}
result = ask_database("What is the most popular product category by number of items sold?")
print(f"\nAnswer: {result['answer']}")
print(f"\nLatency breakdown:")
for step, duration in result["timing"].items():
print(f" {step}: {duration}s")
```
### Query 5: Revenue
*"What's our total revenue from delivered orders?"*
```python theme={null}
result = ask_database("What's our total revenue from delivered orders?")
print(f"\nAnswer: {result['answer']}")
print(f"\nLatency breakdown:")
for step, duration in result["timing"].items():
print(f" {step}: {duration}s")
```
## Latency Summary
Mercury 2's diffusion-based architecture generates tokens \~5x faster than traditional autoregressive LLMs. In the queries above, the full tool-calling loop — understanding the question, generating SQL, executing it, and summarizing results in natural language — consistently completes in around 1-2 seconds. SQL execution itself is near-instant; the time is spent on two Mercury inference calls (tool call generation + answer generation), each typically under 1 second.
This makes Mercury 2 fast enough to power **real-time, interactive database assistants** where users expect instant answers to ad-hoc questions — no waiting, no loading spinners.
# Realtime Voice Assistant: Mercury + ElevenLabs
Source: https://docs.inceptionlabs.ai/cookbooks/realtime-voice-assistant
Stream Mercury 2 in ElevenLabs for a low-latency voice assistant.
This notebook demonstrates how to build a low-latency voice assistant using Mercury 2 for real-time intelligence combined with ElevenLabs' speech-to-text and text-to-speech models. We walk you through how to:
1. Convert text to speech using ElevenLabs TTS
2. Convert audio to text using ElevenLabs speech-to-text
3. Generate realtime responses with Mercury 2
4. Optimize latency using Mercury 2 streaming
Directly via Google Colab or download the `.ipynb` to run it locally in Jupyter
***
## Dependencies
Install packages and import the libraries required for the Mercury 2 SDK, ElevenLabs, and audio playback:
```python theme={null}
%pip install \
"inceptionai" \
"elevenlabs>=2.15.0" \
"python-dotenv>=1.0.0"
```
```python theme={null}
import io
import os
import time
import elevenlabs
from dotenv import load_dotenv
from inceptionai import Inception
from IPython.display import Audio
```
## API Keys
Set up your API keys for both ElevenLabs and Inception Labs.
1. Create a `.env` in this directory
2. Add your API keys to the `.env`:
* `INCEPTION_API_KEY`
* `ELEVENLABS_API_KEY`
The keys will be automatically loaded from the `.env` file.
```python theme={null}
# Load environment variables from .env file
load_dotenv()
INCEPTION_API_KEY = os.getenv("INCEPTION_API_KEY")
ELEVENLABS_API_KEY = os.getenv("ELEVENLABS_API_KEY")
```
## Initialize Clients
Create the ElevenLabs and Inception (Mercury 2) clients:
```python theme={null}
assert INCEPTION_API_KEY is not None, (
"ERROR: INCEPTION_API_KEY not found. Please copy .env.example to .env and add your API keys."
)
assert ELEVENLABS_API_KEY is not None, (
"ERROR: ELEVENLABS_API_KEY not found. Please copy .env.example to .env and add your API keys."
)
mercury_client = Inception(api_key=INCEPTION_API_KEY)
elevenlabs_client = elevenlabs.ElevenLabs(
api_key=ELEVENLABS_API_KEY, base_url="https://api.elevenlabs.io"
)
MERCURY_MODEL = "mercury-2"
VOICE_ID = "21m00Tcm4TlvDq8ikWAM" # Rachel
def mercury_completion(user_text, max_tokens=1000, temperature=0.75):
return mercury_client.chat.completions.create(
model=MERCURY_MODEL,
messages=[{"role": "user", "content": user_text}],
max_tokens=max_tokens,
temperature=temperature,
reasoning_effort="low",
)
def mercury_stream(user_text, max_tokens=1000, temperature=0.75):
stream = mercury_client.chat.completions.create(
model=MERCURY_MODEL,
messages=[{"role": "user", "content": user_text}],
max_tokens=max_tokens,
temperature=temperature,
reasoning_effort="low",
stream=True,
)
for chunk in stream:
if not chunk.choices:
continue
text = chunk.choices[0].delta.content
if text:
yield text
```
## Create Input Audio
We can simulate a user audio input using ElevenLabs text-to-speech model:
```python theme={null}
audio = elevenlabs_client.text_to_speech.convert(
voice_id=VOICE_ID,
output_format="mp3_44100_128",
model_id="eleven_v3",
text="Hi, Mercury. ",
)
audio_data = io.BytesIO()
for chunk in audio:
audio_data.write(chunk)
Audio(audio_data.getvalue())
```
## Transcribe Speech into Text
We then transcribe the audio input using ElevenLabs' speech-to-text model. We'll also measure the transcription latency:
```python theme={null}
audio_data.seek(0)
start_time = time.time()
transcription = elevenlabs_client.speech_to_text.convert(file=audio_data, model_id="scribe_v1")
end_time = time.time()
transcription_time = end_time - start_time
print(f"Transcribed text: {transcription.text}")
print(f"Transcription time: {transcription_time:.2f} seconds")
```
## Get a Response from Mercury 2
We send the transcribed text to Mercury 2 and measure the response time. We're using `mercury-2` for fast reasoning responses:
```python theme={null}
start_time = time.time()
completion = mercury_completion(transcription.text, max_tokens=1000, temperature=0.75)
mercury_response = completion.choices[0].message.content
end_time = time.time()
non_streaming_response_time = end_time - start_time
print(mercury_response)
print(f"\nResponse time: {non_streaming_response_time:.2f} seconds")
```
## Real-time with Streaming
Improve response latency by using Mercury 2 streaming. This allows us to receive the first tokens more quickly, reducing user-perceived latency:
```python theme={null}
start_time = time.time()
first_token_time = None
mercury_full_response = ""
for text in mercury_stream(transcription.text, max_tokens=1000, temperature=0.75):
mercury_full_response += text
print(text, end="", flush=True)
if first_token_time is None:
first_token_time = time.time()
streaming_time_to_first_token = first_token_time - start_time
print(
f"\n\nStreaming time to first token: {streaming_time_to_first_token:.2f} seconds - reducing response wait by {(non_streaming_response_time - streaming_time_to_first_token) * 100 / non_streaming_response_time:.2f}%"
)
```
We can also stream the text-to-speech response to reduce silence:
```python theme={null}
start_time = time.time()
first_audio_chunk_time = None
audio_buffer = io.BytesIO()
audio_generator = elevenlabs_client.text_to_speech.stream(
voice_id=VOICE_ID,
output_format="mp3_44100_128",
text=mercury_full_response,
model_id="eleven_turbo_v2_5",
)
for chunk in audio_generator:
if first_audio_chunk_time is None:
first_audio_chunk_time = time.time()
audio_buffer.write(chunk)
streaming_tts_time_to_first_chunk = first_audio_chunk_time - start_time
print(f"Streaming TTS time to first audio chunk: {streaming_tts_time_to_first_chunk:.2f} seconds")
Audio(audio_buffer.getvalue())
```
## Streaming Mercury 2 Directly to TTS
We've optimized Mercury 2 streaming and TTS separately. Now we can stream the model response and convert it to audio as soon as we have complete sentences, demarcated by `!`, `.`, `?`.
```python theme={null}
import re
sentence_pattern = re.compile(r"[.!?]+")
sentence_buffer = ""
audio_chunks = []
start_time = time.time()
first_audio_time = None
for text in mercury_stream(transcription.text, max_tokens=1000, temperature=0.75):
print(text, end="", flush=True)
sentence_buffer += text
if sentence_pattern.search(sentence_buffer):
sentences = sentence_pattern.split(sentence_buffer)
# Process all complete sentences (all but the last element)
for i in range(len(sentences) - 1):
complete_sentence = sentences[i].strip()
if complete_sentence:
audio_gen = elevenlabs_client.text_to_speech.stream(
voice_id=VOICE_ID,
output_format="mp3_44100_128",
text=complete_sentence,
model_id="eleven_turbo_v2_5",
)
sentence_audio = io.BytesIO()
for chunk in audio_gen:
if first_audio_time is None:
first_audio_time = time.time()
sentence_audio.write(chunk)
audio_chunks.append(sentence_audio.getvalue())
sentence_buffer = sentences[-1]
if sentence_buffer.strip():
audio_gen = elevenlabs_client.text_to_speech.stream(
voice_id=VOICE_ID,
output_format="mp3_44100_128",
text=sentence_buffer.strip(),
model_id="eleven_turbo_v2_5",
)
sentence_audio = io.BytesIO()
for chunk in audio_gen:
if first_audio_time is None:
first_audio_time = time.time()
sentence_audio.write(chunk)
audio_chunks.append(sentence_audio.getvalue())
sentence_streaming_time_to_first_audio = first_audio_time - start_time
print(f"\n\nTime to first audio: {sentence_streaming_time_to_first_audio:.2f} seconds")
combined_pcm = b"".join(audio_chunks)
Audio(combined_pcm)
```
In this sentence-by-sentence approach, each chunk is synthesized independently, so the audio can sound less natural than a fully context-aware streaming setup. To offload the voice loop to a managed platform, see how to set up Mercury 2 with the [ElevenLabs Agents platform](/integrations/elevenlabs).
# Search Agent: Mercury + Web Search
Source: https://docs.inceptionlabs.ai/cookbooks/search-agent-mercury
Build an iterative web search agent that plans, searches, and synthesizes cited answers with Mercury.
This cookbook demonstrates how to build an agentic web search loop — `PLAN → SEARCH → ANALYZE → refine or FINISH → ANSWER` with Mercury as the agent. Because each planning turn returns in a few hundred milliseconds, most of the wall clock goes to the search API rather than the model. We walk you through how to build:
1. A pluggable search function and an evidence store with stable citation IDs
2. A JSON action contract and the plan-search-refine loop it drives
3. A fully cited answer with a latency breakdown
Directly via Google Colab or download the `.ipynb` to run it locally in Jupyter
## Dependencies
```python theme={null}
%pip install -q "openai>=1.0" httpx
```
```python theme={null}
import asyncio, getpass, json, os, re, time
import httpx
from openai import AsyncOpenAI
from IPython.display import Markdown, display
```
## API Keys
You need an Inception key plus a key for your search backend. Only the key for the backend you select is prompted for:
| Key | Where |
| ------------------- | -------------------------------------------------------------------------------------------------------- |
| `INCEPTION_API_KEY` | [platform.inceptionlabs.ai](https://platform.inceptionlabs.ai) |
| `EXA_API_KEY` | [dashboard.exa.ai](https://dashboard.exa.ai) — if `SEARCH_BACKEND = "exa"` |
| `PARALLEL_API_KEY` | [docs.parallel.ai](https://docs.parallel.ai/search/search-quickstart) — if `SEARCH_BACKEND = "parallel"` |
```python theme={null}
SEARCH_BACKEND = "exa" # "exa" or "parallel"
BACKEND_KEYS = {"exa": "EXA_API_KEY", "parallel": "PARALLEL_API_KEY"}
for var in ("INCEPTION_API_KEY", BACKEND_KEYS[SEARCH_BACKEND]):
if not os.environ.get(var):
os.environ[var] = getpass.getpass(f"{var}: ")
INCEPTION_BASE_URL = "https://api.inceptionlabs.ai/v1" # OpenAI-compatible
MODEL = "mercury-2"
```
## The Search Backend
The loop only ever sees `{heading: [docs]}`, where a doc is `{url, title, highlights[], publishedDate}`. Keeping that shape fixed means swapping providers — Brave, Tavily, Serper, or your own vector index over internal docs — is one function plus a branch in `search()`.
```python theme={null}
def check(r: httpx.Response) -> httpx.Response:
"""raise_for_status, but keep the server's explanation of what it rejected."""
if r.is_error:
raise RuntimeError(f"{r.request.method} {r.request.url} -> {r.status_code}: {r.text[:400]}")
return r
async def exa_search(http: httpx.AsyncClient, query: str, num_results: int) -> list[dict]:
"""Run one Exa /search with highlights, retrying on rate limits."""
body = {
"query": query,
"type": "auto",
"numResults": num_results,
"contents": {"highlights": {"maxCharacters": 2000}},
}
headers = {"x-api-key": os.environ["EXA_API_KEY"], "Content-Type": "application/json"}
for attempt in range(5):
r = await http.post("https://api.exa.ai/search", json=body, headers=headers)
if (r.status_code == 429 or r.status_code >= 500) and attempt < 4:
await asyncio.sleep(2**attempt) # bursty fanout will hit 429s
continue
return check(r).json().get("results", [])
return []
async def search(http: httpx.AsyncClient, queries: list[str], *, objective: str,
num_results: int, backend: str) -> dict[str, list[dict]]:
"""One round of search -> {transcript heading: [docs]}. Add a provider here."""
if backend == "exa":
per_query = await asyncio.gather(*(exa_search(http, q, num_results) for q in queries))
return dict(zip(queries, per_query))
if backend == "parallel":
return await parallel_search(http, queries, objective=objective, num_results=num_results)
raise ValueError(f"unknown backend: {backend!r} (expected 'exa' or 'parallel')")
```
```python theme={null}
async def parallel_search(http: httpx.AsyncClient, queries: list[str], *,
objective: str, num_results: int) -> dict[str, list[dict]]:
body = {
"objective": objective,
"search_queries": queries,
"mode": "turbo",
"advanced_settings": {"max_results": num_results * len(queries)},
}
headers = {"x-api-key": os.environ["PARALLEL_API_KEY"], "Content-Type": "application/json"}
for attempt in range(5):
r = await http.post("https://api.parallel.ai/v1/search", json=body, headers=headers)
if (r.status_code == 429 or r.status_code >= 500) and attempt < 4:
await asyncio.sleep(2**attempt)
continue
return {" | ".join(queries): [ # one combined transcript heading
{"url": it.get("url"), "title": it.get("title"),
"highlights": it.get("excerpts") or [], "publishedDate": it.get("publish_date")}
for it in check(r).json().get("results", [])
]}
return {}
```
## Evidence Bookkeeping
The agent's memory. Two details are what make multi-round search work:
* **Stable source IDs.** A URL keeps the same `[n]` for the whole run, across rounds. That is what lets the final answer cite `[3]` and have it mean something.
* **Append-only transcript.** Each round is appended, never rewritten, so the prompt prefix stays byte-identical between rounds.
```python theme={null}
class Evidence:
def __init__(self):
self.blocks: list[str] = [] # one formatted block per search round
self.ids: dict[str, int] = {} # url -> stable source id
self.titles: dict[str, str] = {}
def add_round(self, round_no: int, results: dict[str, list[dict]]) -> int:
"""Append one search round. Returns the number of previously unseen sources."""
new, sections = 0, []
for query, docs in results.items():
entries = []
for doc in docs:
url = (doc.get("url") or "").strip()
if not url:
continue
if url not in self.ids: # first sighting -> new id
self.ids[url] = len(self.ids) + 1
self.titles[url] = doc.get("title") or "(untitled)"
new += 1
snippets = "\n".join(f"- {h.strip()}" for h in doc.get("highlights") or []) or "- (none)"
date = f"\nDate: {doc['publishedDate']}" if doc.get("publishedDate") else ""
entries.append(
f"#### Source [{self.ids[url]}]\nURL: {url}\n"
f"Title: {self.titles[url]}{date}\nHighlights:\n{snippets}"
)
sections.append(f"### Query: {query}\n\n" + "\n\n".join(entries))
self.blocks.append(f"## Search round {round_no}\n\n" + "\n\n".join(sections))
return new
def transcript(self) -> str:
return "\n\n".join(self.blocks) if self.blocks else "No searches have been run yet."
def source_list(self) -> str:
by_id = sorted(self.ids.items(), key=lambda kv: kv[1])
return "\n".join(f"[{sid}] {self.titles[url]}: {url}" for url, sid in by_id) or "None."
```
## The Agent Prompt
The prompt the planner sees every round. Three things are doing the work: a strict action contract of exactly one JSON object per turn, an enumerate-then-verify strategy, and hard evidence rules.
```python theme={null}
PLANNER_SYSTEM = "Choose the next search action. Return exactly one JSON object, no prose."
AGENT_PROMPT = """You are a deep search agent. You answer broad, fact-heavy questions by
iteratively searching the web and reasoning over the evidence you retrieve.
## Workflow
1. SEARCH - issue up to {fanout} queries that attack the question from different angles.
2. ANALYZE - decide what is now supported and what is still missing.
3. REFINE - search again only for genuinely missing facts, disambiguation, or verification.
4. FINISH - stop as soon as the evidence can support a complete answer.
## Search strategy
- Enumerate candidates first, then verify each candidate's attributes.
- Prefer official, primary, or list/table sources.
- Never repeat a query string, and avoid synonym-only rewrites.
## Evidence rules
- Answer only from what retrieved sources explicitly state; no outside knowledge.
- If a source does not explicitly support a required fact, treat that fact as missing.
## Action contract - every turn returns exactly one JSON object
- {{"action": "search", "queries": ["...", "..."], "reason": "why these"}}
- {{"action": "finish", "reason": "why the evidence is sufficient"}}
## Question
{query}
## Evidence transcript
{evidence}"""
```
## Model Calls
A plain OpenAI-compatible chat call, with two Mercury-specific notes:
`reasoning_effort` is the speed/quality dial: `instant`, `low`, `medium`, or `high`.
Use `response_format={"type": "json_object"}` for the planner only. The final answer is free-form markdown, and forcing JSON there would coerce a perfectly good table into an empty object.
```python theme={null}
async def call(client, model, system, prompt, *, effort, max_tokens, as_json):
kwargs = {
"model": model,
"messages": [{"role": "system", "content": system},
{"role": "user", "content": prompt}],
"max_tokens": max_tokens,
"temperature": 0.7,
}
if as_json:
kwargs["response_format"] = {"type": "json_object"}
if effort:
kwargs["extra_body"] = {"reasoning_effort": effort}
t0 = time.perf_counter()
resp = await client.chat.completions.create(**kwargs)
return (resp.choices[0].message.content or ""), int((time.perf_counter() - t0) * 1000)
def parse_action(text: str) -> dict:
"""Planner output -> action dict. Tolerates code fences and stray prose."""
try:
return json.loads(text)
except json.JSONDecodeError:
m = re.search(r"\{.*\}", text, re.S)
return json.loads(m.group(0)) if m else {"action": "finish", "reason": "unparseable plan"}
```
```python theme={null}
def normalize_citations(text: str) -> str:
"""Rewrite full-width citation brackets to ASCII: 【1】 -> [1]."""
return re.sub(r"[【\[]\s*(\d{1,3})\s*[】\]]", r"[\1]", text)
def clean_queries(raw: list, seen: list[set[str]], fanout: int) -> list[str]:
out = []
for item in raw:
query = " ".join(str(item).split())
words = set(re.findall(r"[a-z0-9]+", query.lower()))
if len(query) < 12 or len(words) < 3:
continue # fragment, not a query
if any(len(words & prev) / len(words | prev) > 0.8 for prev in seen):
continue # near-dupe of one we already ran
seen.append(words)
out.append(query)
if len(out) >= fanout:
break
return out
```
Finally, a wall-clock ledger, so a run can show how much of its time was the model and how much was waiting on the web.
```python theme={null}
class Timings:
def __init__(self):
self.t0 = time.perf_counter()
self.plan_ms = self.plan_calls = 0
self.search_ms = self.search_rounds = 0
self.synth_ms = 0
def plan(self, ms: int) -> None:
self.plan_ms += ms; self.plan_calls += 1
def searched(self, ms: int) -> None:
self.search_ms += ms; self.search_rounds += 1
def synth(self, ms: int) -> None:
self.synth_ms = ms
def table(self, *, backend: str, model: str) -> str:
total = max(1, int((time.perf_counter() - self.t0) * 1000))
model_ms = self.plan_ms + self.synth_ms
pct = lambda ms: f"{100 * ms / total:.0f}%"
return (
f"### {total / 1000:.1f} s total\n\n"
f"| stage | calls | time | share |\n|---|---|---|---|\n"
f"| {model} planning | {self.plan_calls} | {self.plan_ms:,} ms "
f"| {pct(self.plan_ms)} |\n"
f"| web search ({backend}) | {self.search_rounds} | {self.search_ms:,} ms "
f"| {pct(self.search_ms)} |\n"
f"| {model} synthesis | 1 | {self.synth_ms:,} ms | {pct(self.synth_ms)} |\n\n"
f"**{model} took {model_ms / 1000:.1f} s of the {total / 1000:.1f} s "
f"({pct(model_ms)}) — the other {pct(total - model_ms)} was spent "
f"waiting on the web.**"
)
```
## The Loop
Plan, fan out searches in parallel, append evidence, and repeat until the agent says `finish` or the round cap is hit. Then one grounded synthesis call:
```python theme={null}
async def deep_search(
query: str,
*,
columns: str | None = None, # e.g. "State,Law name,Effective date" -> markdown table
rounds: int = 3, # max search rounds; the agent may stop earlier
fanout: int = 3, # parallel queries per round
results: int = 5, # search results per query
backend: str | None = None, # "exa" | "parallel"; defaults to SEARCH_BACKEND
model: str = MODEL,
effort: str = "instant",
verbose: bool = True,
) -> str:
searcher = AsyncOpenAI(api_key=os.environ["INCEPTION_API_KEY"], base_url=INCEPTION_BASE_URL)
backend = backend or SEARCH_BACKEND
timings = Timings()
evidence = Evidence()
seen_queries: list[set[str]] = [] # token sets of every query already issued
finish_reason = f"reached the {rounds}-round cap"
async with httpx.AsyncClient(timeout=60) as http:
for round_no in range(1, rounds + 1):
prompt = AGENT_PROMPT.format(
fanout=fanout, query=query, evidence=evidence.transcript()
) + (
f"\n\n## Next action\nRounds used: {round_no - 1} "
f"Rounds left: {rounds - round_no + 1} Max queries this round: {fanout}\n"
"Return exactly one JSON action now."
)
raw, ms = await call(searcher, model, PLANNER_SYSTEM, prompt,
effort=effort, max_tokens=2048, as_json=True)
timings.plan(ms)
action = parse_action(raw)
if action.get("action") != "search":
finish_reason = str(action.get("reason") or "planner chose to finish")
print(f" round {round_no}: finish ({ms} ms) — {finish_reason}")
break
queries = clean_queries(action.get("queries") or [], seen_queries, fanout)
if not queries:
finish_reason = "planner produced no new usable queries"
print(f" round {round_no}: finish ({ms} ms) — {finish_reason}")
break
print(f" round {round_no}: planned in {ms} ms — {len(queries)} queries")
if verbose:
for q in queries:
print(f" · {q}")
t0 = time.perf_counter()
found = await search(http, queries, objective=query,
num_results=results, backend=backend)
search_ms = int((time.perf_counter() - t0) * 1000)
timings.searched(search_ms)
new = evidence.add_round(round_no, found)
print(f" searched in {search_ms} ms via {backend} "
f"— {new} new sources ({len(evidence.ids)} total)")
shape = (
f"Return a markdown table only — no prose before or after it.\n"
f"Columns, exactly these and in this order: {' | '.join(columns.split(','))}\n"
f"Emit one row per item the question asks for; leave a cell blank rather than guessing."
if columns else
"Answer in markdown, and cite the sources you used inline with plain square "
"brackets, e.g. [1] or [2][5]."
)
final_prompt = (
f"Question:\n{query}\n\n## Accumulated evidence\n\n{evidence.transcript()}\n\n"
f"## Search phase conclusion\n\n{finish_reason}\n\n"
f"## Sources\n\n{evidence.source_list()}\n\n## Instructions\n\n{shape}\n"
"Use only the accumulated evidence above."
)
print(f"\n synthesizing with {model} …")
answer, ms = await call(searcher, model, "Answer only from the accumulated evidence.",
final_prompt, effort="", max_tokens=8192, as_json=False)
timings.synth(ms)
print(f" answered in {ms} ms")
return (f"{normalize_citations(answer.strip())}\n\n---\n"
f"{timings.table(backend=backend, model=model)}\n\n---\n"
f"**Sources**\n\n{evidence.source_list()}")
```
## Run It
```python theme={null}
QUESTION = ("Which U.S. states have enacted a comprehensive consumer data privacy law? "
"For each state, give the name of the law and the date it takes effect.")
answer = await deep_search(QUESTION, columns="State,Law name,Effective date", rounds=3)
display(Markdown(answer))
```
Three rounds of planning, 32 sources, and a synthesized table in about 7.5 seconds:
```text theme={null}
round 1: planned in 302 ms — 3 queries
· list of US states comprehensive consumer data privacy law effective date
· state consumer privacy law comprehensive name effective date
· comprehensive consumer data privacy statutes US states effective dates
searched in 1279 ms via exa — 8 new sources (8 total)
round 2: planned in 297 ms — 3 queries
· list of U.S. states with comprehensive consumer data privacy laws effective dates 2026
· official state websites consumer privacy law effective date
· comprehensive consumer privacy law name and effective date per state
searched in 1589 ms via exa — 10 new sources (18 total)
round 3: planned in 324 ms — 3 queries
· official state website California Consumer Privacy Act effective date
· official state website Virginia Consumer Data Protection Act effective date
· official state website Colorado Privacy Act effective date
searched in 1679 ms via exa — 14 new sources (32 total)
synthesizing with mercury-2 …
answered in 1925 ms
[total 7.5s]
```
Planning cost roughly 900 ms across all three rounds. The rest was spent waiting on the web, which is the point: at Mercury speeds, the agent's own deliberation stops being the bottleneck, so you can afford more rounds of it.
## Your Own Question
```python theme={null}
answer = await deep_search(
"What are the main obligations for general-purpose AI models under the EU AI Act, "
"and when do they start applying?",
rounds=2,
verbose=False,
)
display(Markdown(answer))
```
# Quick Start
Source: https://docs.inceptionlabs.ai/get-started
Create an account, generate an API key, and send your first request to Mercury 2.
Every new account includes 100 million free tokens — no payment details required.
## Account Setup
1. Create an Inception Platform account or [sign in](https://platform.inceptionlabs.ai/auth/login) directly if you already have one. Each new user is initially assigned **100 million free tokens** to help get started with the API.
2. Go to [API Keys](https://platform.inceptionlabs.ai/dashboard/api-keys) and create a new API key. You can start using the API immediately with your free tokens!
3. When your free tokens are running low, navigate to [Billing](https://platform.inceptionlabs.ai/dashboard/billing) to add your payment information for continued usage beyond the free tier.
## Quick Start
Export your API key as an environment variable in your terminal.
```bash macOS / Linux theme={null}
export INCEPTION_API_KEY="your_api_key_here"
```
```cmd Windows (Command Prompt) theme={null}
set INCEPTION_API_KEY=your_api_key_here
```
```powershell Windows (PowerShell) theme={null}
$env:INCEPTION_API_KEY="your_api_key_here"
```
```bash Python theme={null}
pip install inceptionai
```
```bash TypeScript theme={null}
npm install inceptionai
```
```bash cURL theme={null}
curl https://api.inceptionlabs.ai/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $INCEPTION_API_KEY" \
-d '{
"model": "mercury-2",
"messages": [
{"role": "user", "content": "What is a diffusion model?"}
],
"reasoning_effort": "medium",
"temperature": 0.75,
"max_tokens": 8192
}'
```
```python Python theme={null}
from inceptionai import Inception
client = Inception() # reads INCEPTION_API_KEY from the environment
completion = client.chat.completions.create(
model="mercury-2",
messages=[{"role": "user", "content": "What is a diffusion model?"}],
reasoning_effort="medium",
temperature=0.75,
max_tokens=8192,
)
print(completion.choices[0].message.content)
```
```typescript TypeScript theme={null}
import Inception from 'inceptionai';
const client = new Inception(); // reads INCEPTION_API_KEY from the environment
const completion = await client.chat.completions.create({
model: 'mercury-2',
messages: [{ role: 'user', content: 'What is a diffusion model?' }],
reasoning_effort: 'medium',
temperature: 0.75,
max_tokens: 8192,
});
console.log(completion.choices[0]?.message.content);
```
## Using Third-Party Libraries
Inception API is also fully compatible with popular Python libraries:
```python AISuite theme={null}
import os
import aisuite as ai
client = ai.Client(
{
"inception": {"api_key": os.environ["INCEPTION_API_KEY"], "base_url": "https://api.inceptionlabs.ai/v1"},
}
)
response = client.chat.completions.create(
model="inception:mercury-2",
messages=[{"role": "user", "content": "What is a diffusion model?"}],
max_tokens=8192
)
print(response.choices[0].message.content)
```
```python LiteLLM theme={null}
from litellm import completion
# Inception is a native LiteLLM provider; reads INCEPTION_API_KEY from the environment
response = completion(
model="inception/mercury-2",
messages=[{"role": "user", "content": "What is a diffusion model?"}],
max_tokens=8192,
)
print(response.choices[0].message.content)
```
```python LangChain theme={null}
import os
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
llm = ChatOpenAI(
model="mercury-2",
temperature=0.75,
api_key=os.environ["INCEPTION_API_KEY"],
base_url="https://api.inceptionlabs.ai/v1"
)
llm.invoke([("user", "What is a diffusion model?")])
```
```python OpenAI Client theme={null}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["INCEPTION_API_KEY"],
base_url="https://api.inceptionlabs.ai/v1"
)
response = client.chat.completions.create(
model="mercury-2",
messages=[{"role": "user", "content": "What is a diffusion model?"}],
max_tokens=8192
)
print(response.choices[0].message.content)
```
```javascript VercelAI theme={null}
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import { generateText } from 'ai';
const provider = createOpenAICompatible({
name: 'inception',
apiKey: process.env.INCEPTION_API_KEY,
baseURL: 'https://api.inceptionlabs.ai/v1',
});
const { text } = await generateText({
model: provider('mercury-2'),
prompt: 'What is a diffusion model?',
});
```
# Authentication
Source: https://docs.inceptionlabs.ai/get-started/authentication
Generate API keys in the Inception Platform dashboard.
All API requests require authentication using API keys. Your API keys carry many privileges, so be sure to keep them secure.
## Obtaining API Keys
You can generate API keys from your [Inception Platform Dashboard](https://platform.inceptionlabs.ai/dashboard/api-keys).
## Using API Keys
Export your API key as an environment variable in your terminal.
```bash macOS / Linux theme={null}
export INCEPTION_API_KEY="your_api_key_here"
```
```cmd Windows (Command Prompt) theme={null}
set INCEPTION_API_KEY=your_api_key_here
```
```powershell Windows (PowerShell) theme={null}
$env:INCEPTION_API_KEY="your_api_key_here"
```
Include your API key in the Authorization header:
```
Authorization: Bearer $INCEPTION_API_KEY
```
**Security Note:** Never expose your API keys in client-side code or commit them to version control. Use environment variables or a secure secrets management system.
# Models, Endpoints, and Pricing
Source: https://docs.inceptionlabs.ai/get-started/models
Compare Inception models.
Every new account includes 100 million free tokens.
## Models
| Model | Input Price (1M Tokens) | Cached Input Price (1M Tokens) | Output Price (1M Tokens) | Supported Endpoints | Context Window | Max Output Tokens | Features | Supported Formats |
| ------------------ | ----------------------- | ------------------------------ | :----------------------- | ----------------------------------------------- | --------------------------- | ----------------- | ---------------------------------------- | ----------------- |
| **Mercury 2** | \$0.25 | \$0.025 | \$0.75 | `v1/chat/completions` | Chat: 128K | 50,000 | `Tool Calling`
`Structured Outputs` | `Text` |
| **Mercury Edit 2** | \$0.25 | \$0.025 | \$0.75 | `v1/fim/completions`
`v1/edit/completions` | FIM: 32K
NextEdit: 32K | 8,192 | | `Text` |
The fastest reasoning LLM and our most powerful model.
Export your API key as an environment variable in your terminal.
```bash macOS / Linux theme={null}
export INCEPTION_API_KEY="your_api_key_here"
```
```cmd Windows (Command Prompt) theme={null}
set INCEPTION_API_KEY=your_api_key_here
```
```powershell Windows (PowerShell) theme={null}
$env:INCEPTION_API_KEY="your_api_key_here"
```
## Example Usage
### Chat Completions
`v1/chat/completions`
```bash cURL theme={null}
curl https://api.inceptionlabs.ai/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $INCEPTION_API_KEY" \
-d '{
"model": "mercury-2",
"messages": [
{"role": "user", "content": "What is a diffusion model?"}
],
"max_tokens": 8192
}'
```
```python Python theme={null}
from inceptionai import Inception
client = Inception() # reads INCEPTION_API_KEY from the environment
completion = client.chat.completions.create(
model="mercury-2",
messages=[{"role": "user", "content": "What is a diffusion model?"}],
max_tokens=8192,
)
print(completion.choices[0].message.content)
```
```typescript TypeScript theme={null}
import Inception from 'inceptionai';
const client = new Inception(); // reads INCEPTION_API_KEY from the environment
const completion = await client.chat.completions.create({
model: 'mercury-2',
messages: [{ role: 'user', content: 'What is a diffusion model?' }],
max_tokens: 8192,
});
console.log(completion.choices[0]?.message.content);
```
A code editing LLM for autocomplete (FIM) and next edit suggestions.
Export your API key as an environment variable in your terminal.
```bash macOS / Linux theme={null}
export INCEPTION_API_KEY="your_api_key_here"
```
```cmd Windows (Command Prompt) theme={null}
set INCEPTION_API_KEY=your_api_key_here
```
```powershell Windows (PowerShell) theme={null}
$env:INCEPTION_API_KEY="your_api_key_here"
```
## Example Usage
### Autocomplete (FIM)
`v1/fim/completions`
```bash cURL theme={null}
curl https://api.inceptionlabs.ai/v1/fim/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $INCEPTION_API_KEY" \
-d '{
"model": "mercury-edit-2",
"prompt": "def factorial(n):\n if n <= 1:\n return 1\n return n * ",
"suffix": "\n\nprint(factorial(5))",
"max_tokens": 1000
}'
```
```python Python theme={null}
from inceptionai import Inception
client = Inception() # reads INCEPTION_API_KEY from the environment
completion = client.fim.completions.create(
model="mercury-edit-2",
prompt="def factorial(n):\n if n <= 1:\n return 1\n return n * ",
suffix="\n\nprint(factorial(5))",
max_tokens=1000,
)
print(completion.choices[0].text)
```
```typescript TypeScript theme={null}
import Inception from 'inceptionai';
const client = new Inception(); // reads INCEPTION_API_KEY from the environment
const completion = await client.fim.completions.create({
model: 'mercury-edit-2',
prompt: 'def factorial(n):\n if n <= 1:\n return 1\n return n * ',
suffix: '\n\nprint(factorial(5))',
max_tokens: 1000,
});
console.log(completion.choices[0]?.text);
```
### Next-Edit
`v1/edit/completions`
```bash cURL theme={null}
curl https://api.inceptionlabs.ai/v1/edit/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $INCEPTION_API_KEY" \
--data-binary @- <<'JSON'
{
"model": "mercury-edit-2",
"messages": [
{"role": "user", "content": "<|recently_viewed_code_snippets|>\n\n<|/recently_viewed_code_snippets|>\n\n<|current_file_content|>\ncurrent_file_path: solver.py\n'''\nfunction: flagAllNeighbors\n----------\nThis function marks each of the covered neighbors of the cell at the given row\n<|code_to_edit|>\nand col as flagged.\n'''\ndef flagAllNeighbors(board<|cursor|>, row, col): \n for r, c in b.getNeighbors(row, col):\n if b.isValid(r, c):\n b.flag(r, c)\n\n<|/code_to_edit|>\n<|/current_file_content|>\n\n<|edit_diff_history|>\n--- /c:/Users/test/testing/solver.py\n+++ /c:/Users/test/testing/solver.py\n@@ -6,1 +6,1 @@\n-def flagAllNeighbors(b, row, col): \n+def flagAllNeighbors(board, row, col): \n\n<|/edit_diff_history|>"}
],
"max_tokens": 1000
}
JSON
```
```python Python theme={null}
from inceptionai import Inception
client = Inception() # reads INCEPTION_API_KEY from the environment
completion = client.edit.completions.create(
model="mercury-edit-2",
messages=[
{"role": "user", "content": "<|recently_viewed_code_snippets|>\n\n<|/recently_viewed_code_snippets|>\n\n<|current_file_content|>\ncurrent_file_path: solver.py\n'''\nfunction: flagAllNeighbors\n----------\nThis function marks each of the covered neighbors of the cell at the given row\n<|code_to_edit|>\nand col as flagged.\n'''\ndef flagAllNeighbors(board<|cursor|>, row, col): \n for r, c in b.getNeighbors(row, col):\n if b.isValid(r, c):\n b.flag(r, c)\n\n<|/code_to_edit|>\n<|/current_file_content|>\n\n<|edit_diff_history|>\n--- /c:/Users/test/testing/solver.py\n+++ /c:/Users/test/testing/solver.py\n@@ -6,1 +6,1 @@\n-def flagAllNeighbors(b, row, col): \n+def flagAllNeighbors(board, row, col): \n\n<|/edit_diff_history|>"}
],
max_tokens=1000,
)
print(completion.choices[0].message.content)
```
```typescript TypeScript theme={null}
import Inception from 'inceptionai';
const client = new Inception(); // reads INCEPTION_API_KEY from the environment
const completion = await client.edit.completions.create({
model: 'mercury-edit-2',
messages: [
{ role: 'user', content: '<|recently_viewed_code_snippets|>\n\n<|/recently_viewed_code_snippets|>\n\n<|current_file_content|>\ncurrent_file_path: solver.py\n\'\'\'\nfunction: flagAllNeighbors\n----------\nThis function marks each of the covered neighbors of the cell at the given row\n<|code_to_edit|>\nand col as flagged.\n\'\'\'\ndef flagAllNeighbors(board<|cursor|>, row, col): \n for r, c in b.getNeighbors(row, col):\n if b.isValid(r, c):\n b.flag(r, c)\n\n<|/code_to_edit|>\n<|/current_file_content|>\n\n<|edit_diff_history|>\n--- /c:/Users/test/testing/solver.py\n+++ /c:/Users/test/testing/solver.py\n@@ -6,1 +6,1 @@\n-def flagAllNeighbors(b, row, col): \n+def flagAllNeighbors(board, row, col): \n\n<|/edit_diff_history|>' }
],
max_tokens: 1000,
});
console.log(completion.choices[0]?.message.content);
```
# Rate Limits
Source: https://docs.inceptionlabs.ai/get-started/rate-limits
Per-minute request, input token, and output token rate limits for the Inception API across the Free, Pay As You Go, and Enterprise tiers.
## Limits per Minute
| | **Free Tier** | **Pay As You Go Tier** | **Enterprise Tier** |
| ----------------- | :------------ | ---------------------- | ------------------- |
| **API Requests** | 1,000 | 3,000 | Custom |
| **Input Tokens** | 1,000,000 | 3,000,000 | Custom |
| **Output Tokens** | 100,000 | 300,000 | Custom |
Please [contact support](https://platform.inceptionlabs.ai/contact) if you need increased rate limits.
# SDKs
Source: https://docs.inceptionlabs.ai/get-started/sdks
Official Python and TypeScript client libraries for the Inception API.
Inception provides official client libraries that wrap the REST API with typed methods, streaming helpers, and automatic retries. The API is also OpenAI-compatible, so existing OpenAI SDKs work by pointing them at `https://api.inceptionlabs.ai/v1`.
| Language | Package |
| ----------------------- | ---------------------------------------------------------- |
| Python | [`inceptionai`](https://pypi.org/project/inceptionai/) |
| TypeScript / JavaScript | [`inceptionai`](https://www.npmjs.com/package/inceptionai) |
## Install
```bash Python theme={null}
pip install inceptionai
```
```bash TypeScript theme={null}
npm install inceptionai
```
## Authentication
The client reads your key from the `INCEPTION_API_KEY` environment variable by default. You can also pass it explicitly when constructing the client.
```python Python theme={null}
from inceptionai import Inception
# Reads INCEPTION_API_KEY from the environment
client = Inception()
# Or pass it explicitly:
# client = Inception(api_key="your_api_key_here")
```
```typescript TypeScript theme={null}
import Inception from 'inceptionai';
// Reads INCEPTION_API_KEY from the environment
const client = new Inception();
// Or pass it explicitly:
// const client = new Inception({ apiKey: 'your_api_key_here' });
```
## Make your first request
```python Python theme={null}
from inceptionai import Inception
client = Inception()
completion = client.chat.completions.create(
model="mercury-2",
messages=[{"role": "user", "content": "What is a diffusion model?"}],
)
print(completion.choices[0].message.content)
```
```typescript TypeScript theme={null}
import Inception from 'inceptionai';
const client = new Inception();
const completion = await client.chat.completions.create({
model: 'mercury-2',
messages: [{ role: 'user', content: 'What is a diffusion model?' }],
});
console.log(completion.choices[0].message.content);
```
See the [chat completions API reference](/api-reference/chat/create-a-chat-completion) and [Streaming & Diffusion](/capabilities/streaming) for more usage examples.
# Inception Platform
Source: https://docs.inceptionlabs.ai/index
Build with Mercury — the fastest reasoning and code models, served through an OpenAI-compatible API.
Inception's **Mercury** models are diffusion LLMs that deliver frontier-quality reasoning and code generation at a fraction of the latency. The API is OpenAI-compatible, so you can be up and running in minutes with the tools you already use.
Create an account, grab an API key, and send your first request.
Compare Mercury 2 and Mercury Edit 2 — endpoints, context windows, and pricing.
Streaming, tool use, structured outputs, autocomplete (FIM), and next edit.
Explore the REST endpoints for chat, FIM, edit completions, and models.
Use Mercury in Cursor, Zed, VS Code extensions, LangChain, Vapi, and more.
End-to-end guides for search, customer support, voice, SQL, and coding agents.
# Cursor
Source: https://docs.inceptionlabs.ai/integrations/cursor
Set up Cursor to use Mercury 2.
## Setup Instructions
1. Install Cursor IDE: [https://cursor.com/docs/get-started/quickstart](https://cursor.com/docs/get-started/quickstart)
2. Click the gear icon (⚙️) in the top-right corner → **Settings**
3. Select the **Models** tab from the left sidebar
4. Paste your Inception API key into the **OpenAI API Key** field and enable the setting
5. Enable **Override OpenAI Base URL** and enter: `https://api.inceptionlabs.ai/v1`
6. Scroll to the bottom of the models list, click **+ Add Custom Model**, and enter: `mercury-2`
7. Open a new chat and select `mercury-2` as your model
[Documentation](https://cursor.com/docs)
# ElevenLabs
Source: https://docs.inceptionlabs.ai/integrations/elevenlabs
Wire Mercury 2 into the ElevenLabs Agents platform as a custom LLM.
This tutorial creates a voice assistant, integrating the realtime reasoning intelligence of Mercury 2 with the TTS and STT models from ElevenLabs. For this demo, you will need an Inception [API key](https://platform.inceptionlabs.ai/dashboard/api-keys) and an ElevenLabs [account](https://elevenlabs.io/app/sign-up).
Prefer to build the voice loop yourself in code? See the [Realtime Voice Assistant cookbook](/cookbooks/realtime-voice-assistant).
1. Navigate to the **Agents** [platform](https://elevenlabs.io/app/agents?platform=agents) on the ElevenLabs sidebar.
2. Create a **Blank Agent**. You can name it **Mercury 2**.
3. Click into the model dropdown in the bottom right, under **LLM**.
4. Scroll all the way down and select **Custom LLM**.
5. Fill in Custom LLM details as follows:
* Add your Inception API key by clicking **API Key** —> **Create new secret**
* Set **Server URL** to `https://api.inceptionlabs.ai/v1`
* Set **Temperature** to `0.75`
* Set **Reasoning Effort** as desired (N.B. leaving it unset defaults to `medium` reasoning effort)
6. Add any custom tools under the **Tools** tab.
7. Amend the Mercury 2 system prompt as desired, then hit **Publish** in the top right corner when done!
8. For phone integration, add **Mercury 2** as the assigned agent to pick up your phone calls.
# Kilo Code
Source: https://docs.inceptionlabs.ai/integrations/kilo-code
Set up the Kilo Code VS Code extension to use Mercury 2.
## Setup Instructions
1. Install the Kilo Code extension in VS Code
2. In the Kilo Code Settings, set the following: API Provider="Inception" and Base URL="[https://api.inceptionlabs.ai/v1](https://api.inceptionlabs.ai/v1)"
3. Add your API key and set `Model="mercury-2"`
4. Under Model Configurations, set the Context Window Size to 128,000.
[Documentation](https://kilocode.ai/docs/providers/inception)
# LangChain
Source: https://docs.inceptionlabs.ai/integrations/langchain
Call Mercury 2 from LangChain.
## Setup Instructions
Mercury is OpenAI-compatible, so LangChain talks to it through the `langchain-openai` package.
```bash theme={null}
pip install -qU langchain-openai
```
Export your API key as an environment variable in your terminal.
```bash macOS / Linux theme={null}
export INCEPTION_API_KEY="your_api_key_here"
```
```powershell Windows (PowerShell) theme={null}
$env:INCEPTION_API_KEY = "your_api_key_here"
```
```python theme={null}
import os
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="mercury-2",
temperature=0.75,
api_key=os.environ["INCEPTION_API_KEY"],
base_url="https://api.inceptionlabs.ai/v1",
)
response = llm.invoke([("user", "What is a diffusion model?")]).content
print(response)
```
[LangChain documentation](https://python.langchain.com/docs/introduction/)
# OpenClaw
Source: https://docs.inceptionlabs.ai/integrations/open-claw
Configure OpenClaw to use Mercury 2.
[OpenClaw](https://openclaw.ai/) is an open-source personal AI assistant that runs locally on your machine and connects to messaging platforms like WhatsApp, Telegram, Discord, and more. It supports any OpenAI-compatible provider, including Inception.
## Setup Instructions
1. Set your Inception API key as an environment variable:
```bash theme={null}
export INCEPTION_API_KEY="your-api-key-here"
```
Add this line to your `~/.zshrc` or `~/.bash_profile` so it persists across terminal sessions.
2. Open your OpenClaw config file at `~/.openclaw/openclaw.json` and add Inception as a custom provider:
```json theme={null}
{
"models": {
"mode": "merge",
"providers": {
"inception": {
"baseUrl": "https://api.inceptionlabs.ai/v1",
"apiKey": "${INCEPTION_API_KEY}",
"api": "openai-completions",
"models": [
{
"id": "mercury-2",
"name": "Mercury 2",
"contextWindow": 128000,
"maxTokens": 16384
}
]
}
}
}
}
```
Setting `mode: "merge"` ensures all built-in providers (Anthropic, OpenAI, Gemini, etc.) remain available alongside Inception. Without it, custom providers replace the entire built-in catalog.
3. Set Mercury as your primary model:
```bash theme={null}
openclaw models set inception/mercury-2
```
4. Restart the gateway to pick up the new provider:
```bash theme={null}
openclaw gateway restart
```
OpenClaw's gateway hot-reloads most config changes automatically, but restarting ensures new provider definitions are fully loaded.
5. Verify everything is working:
```bash theme={null}
# Check that the Inception provider and model appear
openclaw models list
# Confirm primary model, auth status, and provider endpoint
openclaw models status
```
You should see `inception/mercury-2` listed as your primary model with valid auth.
## Switching Models
You can switch to Mercury mid-session from any connected chat (Telegram, Discord, WhatsApp, etc.):
```
/model inception/mercury-2
```
Or use Mercury as a fallback instead of the primary model:
```bash theme={null}
openclaw models fallbacks add inception/mercury-2
```
## Troubleshooting
If something isn't working, run the built-in diagnostics:
```bash theme={null}
# General health check — finds and fixes common config issues
openclaw doctor --fix
# Detailed gateway and provider status
openclaw status --all
```
[Documentation](https://docs.openclaw.ai/)
# OpenCode
Source: https://docs.inceptionlabs.ai/integrations/opencode
Connect OpenCode to the Inception API.
## Setup Instructions
1. Install opencode from [https://opencode.ai/](https://opencode.ai/)
2. Run opencode in the terminal window
3. Type `/connect` to select a model
4. Choose the Inception provider (TIP: start typing "Inc" and you'll find Inception in the list)
5. Paste in your Inception API key
6. Select "Mercury 2" from the list of available models (TIP: use `ctrl+t` to toggle between reasoning efforts: low, medium, or high).
[Documentation](https://opencode.ai/docs)
# Vapi
Source: https://docs.inceptionlabs.ai/integrations/vapi
Build a low-latency voice agent in Vapi using Mercury 2.
This tutorial creates a voice agent, integrating the realtime reasoning intelligence of Mercury 2 with Vapi. For this demo, you will need an **Inception** [API key](https://platform.inceptionlabs.ai/dashboard/api-keys) and a [Vapi account](https://vapi.ai).
## Authentication
1. Navigate to **Integrations** in the Vapi sidebar.
2. Under **Model Providers**, click into **Custom LLM** (bottom right).
3. Fill in your Inception API key and click **Save**.
## Setting up your Mercury voice agent
1. Navigate to **Agents** in the Vapi sidebar and select **Create Assistant.**
2. Name your assistant and select your desired assistant template.
3. Select **Custom LLM** from the Model dropdown:
4. Fill in model details as follows:
* Set **Custom LLM URL** as `https://api.inceptionlabs.ai/v1`
* Set **Model** as `mercury-2`
* Set **Temperature** to `0.75`
* Set **Max Tokens** to a slightly higher value, like `4096`. This is because the default reasoning effort is `medium`, and Max Tokens includes reasoning tokens.
* Configure **First Message Mode**, **First Message** and **System Prompt** as desired.
* Scroll down to manage **Tools**, **Structured Outputs** and more.
5. When you're ready, hit **Publish** in the top right corner, and **Talk** to speak to Mercury 2!
# Zed
Source: https://docs.inceptionlabs.ai/integrations/zed
Configure the Zed editor's Edit Predictions feature with your Inception API key to receive next-edit suggestions powered by Mercury Edit 2.
## Setup Instructions
1. After installing Zed, proceed to the Zed settings and navigate to AI -> Edit Predictions -> Configure Providers -> Mercury.
2. Copy paste your Inception API Key and press Enter
3. You're ready to receive next-edit predictions from Mercury Edit! To ensure it is active, confirm that the Inception logo is present in the bottom-right corner of your Zed editor.
[Documentation](https://zed.dev/docs/)
# Inception Chat
Source: https://docs.inceptionlabs.ai/resources/chat
# Error Codes
Source: https://docs.inceptionlabs.ai/resources/error-codes
Reference for HTTP status codes returned by the Inception API.
## Response Format
Responses are returned in JSON. Successful responses have a `2xx` status code and contain the requested data. Error responses have a `4xx` or `5xx` status code and include an `error` object describing what went wrong:
```json theme={null}
{
"error": {
"message": "Incorrect API key provided",
"type": "authentication_error",
"param": null,
"code": "invalid_api_key"
}
}
```
## Error Codes
| Code | Meaning | Solution |
| :---- | :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400` | Invalid request | Check the request parameters. A `context_length_exceeded` error means the request exceeds the model's context window; reduce the messages or completion length. |
| `401` | Invalid authentication | Verify your API key is correct and sent as `Authorization: Bearer $INCEPTION_API_KEY`, or generate a new key. |
| `402` | Payment required | Billing is inactive or your quota is exceeded. If the message is `Account is inactive`, check your account and payment information under [Billing](https://platform.inceptionlabs.ai/dashboard/billing). |
| `404` | Model not found | The requested model is not available for your account. Verify the model name, such as `mercury-2` or `mercury-edit-2`. See [Models, Endpoints & Pricing](/get-started/models). |
| `429` | Rate limit reached | Pace requests and implement exponential backoff. See [Rate Limits](/get-started/rate-limits). |
| `500` | Server error | Retry after a brief wait; contact support if it persists. |
| `503` | Engine overloaded | Retry after a brief wait; contact support if it persists. |
# Frequently Asked Questions
Source: https://docs.inceptionlabs.ai/resources/faq
Answers to common questions about the Inception API.
Reach out to our [support team](/support/support) with details about your use case, expected volume, and any latency or throughput requirements. We’ll review your needs and can raise your limits accordingly.
Implement exponential backoff in your client code. When you receive a rate limit error (429) or a server error (503), wait before retrying.
Tokens are pieces of text that our models process. A token is roughly 4 characters for English text. Both input and output tokens count toward your usage limits.
Responses include a `usage` object with token counts, but not a dollar-cost field:
* `prompt_tokens` — all input tokens, including cached input.
* `prompt_tokens_details.cached_tokens` — the subset of `prompt_tokens` served from the prefix cache.
* `completion_tokens` — all generated tokens, including reasoning tokens.
* `completion_tokens_details.reasoning_tokens` — the subset of `completion_tokens` used for reasoning.
* `total_tokens` — `prompt_tokens + completion_tokens`.
The nested detail counts are already included in their parent totals, so do not add them again. Cached prompt tokens use the cached-input rate; completion tokens, including reasoning tokens, use the output rate.
For streaming (`stream: true`), set `stream_options.include_usage: true` to receive a final usage chunk before `data: [DONE]`. Use the rates on [Models, Endpoints & Pricing](/get-started/models) to calculate cost.
Yes. The full documentation index is published at [https://docs.inceptionlabs.ai/llms.txt](https://docs.inceptionlabs.ai/llms.txt). Point your LLM, IDE assistant, or agent at that URL to discover every page in the docs. The docs site does not block crawlers or agent traffic.
No. Mercury 2 and Mercury Edit 2 accept text input only. Image generation and image input are not supported. See [/get-started/models](/get-started/models) for supported input formats per model.
Every new account includes a one-time credit of 100 million free tokens. The credit is shared across all models rather than granted per model. When your free tokens run low, add payment information under [Billing](https://platform.inceptionlabs.ai/dashboard/billing) to continue using the API. For questions about the free credit that aren't covered here, [contact support](/support/support).
The API does not include a dedicated MCP (Model Context Protocol) server or endpoint. Mercury 2 supports OpenAI-compatible tool calling, so any MCP client or agent framework that lets you configure an OpenAI-compatible backend can use it. Set the base URL to `https://api.inceptionlabs.ai/v1`, provide your API key, and set the model to `mercury-2`. Tool definitions follow the OpenAI function schema shown in the [Tool Use](/capabilities/tool-use) guide.
# Prompt Guide
Source: https://docs.inceptionlabs.ai/resources/prompt-guide
Practical prompting techniques for Mercury 2 across voice, tool-calling, search, and code agents.
This guide walks through prompting techniques for deploying Mercury in your voice agents, tool-calling agents, search agents, and code agents, with examples drawn from production deployments.
Optimizing your prompts for any LLM is primarily a process of trial and error. Prompt changes should always be tested against an evaluation dataset that reflects production usage. Treat the patterns below as starting points, not final answers.
## What's inside
Section ordering, XML tags, and dynamic prompt injection.
Personas, few-shot examples, verbosity, and guardrails.
Markdown suppression, pacing, and pronunciation rules.
Sequential gathering, confirmations, and state machines.
Query construction, recency, grounding, and synthesis.
Style enforcement and agentic coding workflows.
***
## Prompt Structure
### Instruction organization
We recommend structuring prompts in this order:
Who the agent is, how it should sound, and what it's trying to accomplish.
Grounding content, tool definitions, and any reference material.
The active instruction for this turn or workflow.
Positive and negative examples demonstrating desired behavior.
Non-negotiable constraints. Place these last — Mercury weights recent context heavily.
If you have a long knowledge base or policy doc, **sandwich it**: persona and style up top, dynamic content in the middle, current task description and critical rules at the bottom. Static information at the top of a request maximizes cache hit rate; dynamic information goes at the end.
### XML tags for long system prompts
For multi-section prompts, XML tags help Mercury parse which part of the prompt governs which behavior.
```xml theme={null}
You are Richard, the AI Leave of Absence Assistant for ACME Inc.
[KB content]
Authenticate the caller, then answer their leave-of-absence question.
```
| Tag | Purpose |
| ------------------------ | ------------------------------------------------- |
| `` | Who the agent is |
| `