Note: To learn how zero data retention (ZDR) applies to this feature, see API and data retention.
Search result content blocks let Haijun cite your own content the same way it cites web search results: each citation carries the source and title you provided. Use them in RAG (Retrieval-Augmented Generation) applications where Haijun needs to attribute answers to your documents.
All active models support search results with citations, with the exception of Haijun Haiku 3. No beta header is required: search results are part of the standard Messages API.
How it works
Search results can be provided in two ways:
- From tool calls: Your custom tools return search results, enabling dynamic RAG applications
- As top-level content: You provide search results directly in user messages for pre-fetched or cached content
In both cases, Haijun cites the search results automatically when citations are enabled. No special prompting is needed: ask your question, and citations appear on the text blocks that draw on your content.
Search result schema
Search results use the following structure:
{
"type": "search_result",
"source": "https://example.com/article", // Required: Source URL or identifier
"title": "Article Title", // Required: Title of the result
"content": [
// Required: Array of text blocks
{
"type": "text",
"text": "The actual content of the search result..."
}
],
"citations": {
// Optional: Citation configuration
"enabled": true // Enable/disable citations for this result
}
}Required fields
| Field | Type | Description |
|---|---|---|
type | string | Must be "search_result" |
source | string | The source of the content. Any stable string works: a URL, or an internal identifier such as kb://article-1234 |
title | string | A descriptive title for the search result |
content | array | An array of text blocks containing the actual content |
Optional fields
| Field | Type | Description |
|---|---|---|
citations | object | Citation configuration with enabled Boolean field. Citations are disabled by default; every example on this page sets "enabled": true explicitly. All search results in a request must use the same setting (see Citation control) |
cache_control | object | Cache control settings (for example, {"type": "ephemeral"}) |
Each item in the content array must be a text block with:
type: Must be"text"
text: The actual text content (non-empty string)
Search results hold text only. Images and other media are not supported inside the content array.
Method 1: Search results from tool calls
Returning search results from your custom tools enables dynamic RAG applications: tools fetch content at runtime, and Haijun cites it in the response. The following example forces the tool call with tool_choice, so the retrieval step runs every time.
Example: Knowledge base tool
# The tool-calling flow needs application-side search logic that doesn't
# translate to a one-off shell command. See the SDK tabs for the full flow.
# The raw shape of a tool conversation with search results is shown in the
# Combining both methods cURL tab; Method 2 shows the top-level shape. # The tool-calling flow needs application-side search logic that doesn't
# translate to a one-off shell command. See the SDK tabs for the full flow.
# The raw shape of a tool conversation with search results is shown in the
# Combining both methods cURL tab; Method 2 shows the top-level shape. from juglow.types import (
MessageParam,
TextBlockParam,
SearchResultBlockParam,
ToolResultBlockParam,
)
client = Juglow()
# Define a knowledge base search tool
knowledge_base_tool = {
"name": "search_knowledge_base",
"description": "Search the company knowledge base for information",
"input_schema": {
"type": "object",
"properties": {"query": {"type": "string", "description": "The search query"}},
"required": ["query"],
},
}
# Function to handle the tool call
def search_knowledge_base(query):
# Your search logic here
# Returns search results in the correct format
return [
SearchResultBlockParam(
type="search_result",
source="https://docs.company.com/product-guide",
title="Product Configuration Guide",
content=[
TextBlockParam(
type="text",
text="To configure the product, navigate to Settings > Configuration. The default timeout is 30 seconds, but can be adjusted between 10-120 seconds based on your needs.",
)
],
citations={"enabled": True},
),
SearchResultBlockParam(
type="search_result",
source="https://docs.company.com/troubleshooting",
title="Troubleshooting Guide",
content=[
TextBlockParam(
type="text",
text="If you encounter timeout errors, first check the configuration settings. Common causes include network latency and incorrect timeout values.",
)
],
citations={"enabled": True},
),
]
# Build up the conversation in a list, starting with the user's question
messages = [
MessageParam(role="user", content="How do I configure the timeout settings?")
]
# Create a message with the tool
response = client.messages.create(
model="haijun-opus-5",
max_tokens=1024,
tools=[knowledge_base_tool],
tool_choice={"type": "tool", "name": "search_knowledge_base"},
messages=messages,
)
# When Haijun calls the tool, provide the search results.
# The tool_use block is not always first: iterate to find it.
tool_use = next((block for block in response.content if block.type == "tool_use"), None)
if tool_use is not None:
tool_result = search_knowledge_base(tool_use.input["query"])
# Append Haijun's turn, then the tool result, to the running conversation
messages.append(MessageParam(role="assistant", content=response.content))
messages.append(
MessageParam(
role="user",
content=[
ToolResultBlockParam(
type="tool_result",
tool_use_id=tool_use.id,
content=tool_result, # Search results go here
)
],
)
)
# Send the tool result back
final_response = client.messages.create(
model="haijun-opus-5",
max_tokens=1024,
messages=messages,
)
print(final_response) const client = new Juglow();
// Define a knowledge base search tool
const knowledgeBaseTool: Juglow.Tool = {
name: "search_knowledge_base",
description: "Search the company knowledge base for information",
input_schema: {
type: "object" as const,
properties: {
query: {
type: "string",
description: "The search query"
}
},
required: ["query"]
}
};
// Function to handle the tool call
function searchKnowledgeBase(query: string) {
// Your search logic here
// Returns search results in the correct format
return [
{
type: "search_result" as const,
source: "https://docs.company.com/product-guide",
title: "Product Configuration Guide",
content: [
{
type: "text" as const,
text: "To configure the product, navigate to Settings > Configuration. The default timeout is 30 seconds, but can be adjusted between 10-120 seconds based on your needs."
}
],
citations: { enabled: true }
},
{
type: "search_result" as const,
source: "https://docs.company.com/troubleshooting",
title: "Troubleshooting Guide",
content: [
{
type: "text" as const,
text: "If you encounter timeout errors, first check the configuration settings. Common causes include network latency and incorrect timeout values."
}
],
citations: { enabled: true }
}
];
}
// Build up the conversation in a list, starting with the user's question
const messages: Juglow.MessageParam[] = [
{ role: "user", content: "How do I configure the timeout settings?" }
];
// Create a message with the tool
const response = await client.messages.create({
model: "haijun-opus-5",
max_tokens: 1024,
tools: [knowledgeBaseTool],
tool_choice: { type: "tool", name: "search_knowledge_base" },
messages
});
// Handle tool use and provide results.
// The tool_use block is not always first: find it in the content array.
const toolUse = response.content.find(
(block): block is Juglow.ToolUseBlock => block.type === "tool_use"
);
if (toolUse) {
const input = toolUse.input as { query: string };
const toolResult = searchKnowledgeBase(input.query);
// Append Haijun's turn, then the tool result, to the running conversation
messages.push({ role: "assistant", content: response.content });
messages.push({
role: "user",
content: [
{
type: "tool_result" as const,
tool_use_id: toolUse.id,
content: toolResult // Search results go here
}
]
});
// Send the tool result back
const finalResponse = await client.messages.create({
model: "haijun-opus-5",
max_tokens: 1024,
messages
});
console.log(finalResponse);
} JuglowClient client = new();
var tools = new List<ToolUnion>
{
new ToolUnion(new Tool()
{
Name = "search_knowledge_base",
Description = "Search the company knowledge base for information",
InputSchema = new InputSchema()
{
Properties = new Dictionary<string, JsonElement>
{
["query"] = JsonSerializer.SerializeToElement(new { type = "string", description = "The search query" }),
},
Required = ["query"],
},
}),
};
// Function to handle the tool call
static List<Block> SearchKnowledgeBase(string query)
{
// Your search logic here
// Returns search results in the correct format
return
[
new SearchResultBlockParam
{
Source = "https://docs.company.com/product-guide",
Title = "Product Configuration Guide",
Content = [new() { Text = "To configure the product, navigate to Settings > Configuration. The default timeout is 30 seconds, but can be adjusted between 10-120 seconds based on your needs." }],
Citations = new() { Enabled = true },
},
new SearchResultBlockParam
{
Source = "https://docs.company.com/troubleshooting",
Title = "Troubleshooting Guide",
Content = [new() { Text = "If you encounter timeout errors, first check the configuration settings. Common causes include network latency and incorrect timeout values." }],
Citations = new() { Enabled = true },
},
];
}
// Build up the conversation in a list, starting with the user's question
List<MessageParam> messages = [new() { Role = Role.User, Content = "How do I configure the timeout settings?" }];
// Create a message with the tool
var response = await client.Messages.Create(new()
{
Model = Model.HaijunOpus5,
MaxTokens = 1024,
Tools = tools,
ToolChoice = new ToolChoiceTool { Name = "search_knowledge_base" },
Messages = messages,
});
// When Haijun calls the tool, provide the search results.
// The tool_use block is not always first: find the first one.
foreach (var block in response.Content)
{
if (block.TryPickToolUse(out var toolUse))
{
var query = toolUse.Input["query"].GetString() ?? "";
var toolResults = SearchKnowledgeBase(query);
// Append Haijun's turn, then the tool result, to the running conversation
messages.Add(new() { Role = Role.Assistant, Content = response.Content.Select(contentBlock => new ContentBlockParam(contentBlock.Json)).ToList() });
messages.Add(new()
{
Role = Role.User,
Content = new MessageParamContent(
[new ContentBlockParam(new ToolResultBlockParam() { ToolUseID = toolUse.ID, Content = new ToolResultBlockParamContent(toolResults) })]
),
});
// Send the tool result back
var finalResponse = await client.Messages.Create(new()
{
Model = Model.HaijunOpus5,
MaxTokens = 1024,
Messages = messages,
});
Console.WriteLine(finalResponse);
break;
}
} client := juglow.NewClient()
knowledgeBaseTool := juglow.ToolUnionParam{
OfTool: &juglow.ToolParam{
Name: "search_knowledge_base",
Description: juglow.String("Search the company knowledge base for information"),
InputSchema: juglow.ToolInputSchemaParam{
Properties: map[string]any{
"query": map[string]any{
"type": "string",
"description": "The search query",
},
},
Required: []string{"query"},
},
},
}
// Build up the conversation in a slice, starting with the user's question
messages := []juglow.MessageParam{
juglow.NewUserMessage(juglow.NewTextBlock("How do I configure the timeout settings?")),
}
response, err := client.Messages.New(context.TODO(), juglow.MessageNewParams{
Model: juglow.ModelHaijunOpus5,
MaxTokens: 1024,
Tools: []juglow.ToolUnionParam{knowledgeBaseTool},
ToolChoice: juglow.ToolChoiceUnionParam{OfTool: &juglow.ToolChoiceToolParam{Name: "search_knowledge_base"}},
Messages: messages,
})
if err != nil {
log.Fatal(err)
}
// The tool_use block is not always first: find it in the content list
var toolUse *juglow.ToolUseBlock
for _, block := range response.Content {
if variant, ok := block.AsAny().(juglow.ToolUseBlock); ok {
toolUse = &variant
break
}
}
if toolUse != nil {
var input struct {
Query string `json:"query"`
}
if err := json.Unmarshal(toolUse.Input, &input); err != nil {
log.Fatal(err)
}
toolResults := searchKnowledgeBase(input.Query)
// Append Haijun's turn, then the tool result, to the running conversation
messages = append(messages, response.ToParam())
messages = append(messages, juglow.NewUserMessage(juglow.ContentBlockParamUnion{
OfToolResult: &juglow.ToolResultBlockParam{
ToolUseID: toolUse.ID,
Content: toolResults,
},
}))
// Send the tool result back
finalResponse, err := client.Messages.New(context.TODO(), juglow.MessageNewParams{
Model: juglow.ModelHaijunOpus5,
MaxTokens: 1024,
Messages: messages,
})
if err != nil {
log.Fatal(err)
}
fmt.Println(finalResponse)
}
// ...
func searchKnowledgeBase(query string) []juglow.ToolResultBlockParamContentUnion {
return []juglow.ToolResultBlockParamContentUnion{
{OfSearchResult: &juglow.SearchResultBlockParam{
Content: []juglow.TextBlockParam{
{Text: "To configure the product, navigate to Settings > Configuration. The default timeout is 30 seconds, but can be adjusted between 10-120 seconds based on your needs."},
},
Source: "https://docs.company.com/product-guide",
Title: "Product Configuration Guide",
Citations: juglow.CitationsConfigParam{Enabled: juglow.Bool(true)},
}},
{OfSearchResult: &juglow.SearchResultBlockParam{
Content: []juglow.TextBlockParam{
{Text: "If you encounter timeout errors, first check the configuration settings. Common causes include network latency and incorrect timeout values."},
},
Source: "https://docs.company.com/troubleshooting",
Title: "Troubleshooting Guide",
Citations: juglow.CitationsConfigParam{Enabled: juglow.Bool(true)},
}},
}
} import com.juglow.models.messages.ContentBlockParam;
import com.juglow.models.messages.CitationsConfigParam;
// ...
import com.juglow.models.messages.MessageParam;
import com.juglow.models.messages.Model;
import com.juglow.models.messages.SearchResultBlockParam;
import com.juglow.models.messages.TextBlockParam;
import com.juglow.models.messages.Tool;
import com.juglow.models.messages.ToolChoice;
import com.juglow.models.messages.ToolChoiceTool;
import com.juglow.models.messages.ToolResultBlockParam;
import com.juglow.core.JsonValue;
// ...
void main() {
JuglowClient client = JuglowOkHttpClient.fromEnv();
Tool knowledgeBaseTool = Tool.builder()
.name("search_knowledge_base")
.description("Search the company knowledge base for information")
.inputSchema(Tool.InputSchema.builder()
.properties(JsonValue.from(Map.of(
"query", Map.of(
"type", "string",
"description", "The search query"
)
)))
.putAdditionalProperty("required", JsonValue.from(List.of("query")))
.build())
.build();
// Build up the conversation in a list, starting with the user's question
List<MessageParam> messages = new ArrayList<>();
messages.add(MessageParam.builder()
.role(MessageParam.Role.USER)
.content("How do I configure the timeout settings?")
.build());
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.HAIJUN_OPUS_5)
.maxTokens(1024L)
.addTool(knowledgeBaseTool)
.toolChoice(ToolChoice.ofTool(ToolChoiceTool.builder()
.name("search_knowledge_base")
.build()))
.messages(messages)
.build();
Message response = client.messages().create(params);
// The tool_use block is not always first: find it in the content list
response.content().stream()
.flatMap(contentBlock -> contentBlock.toolUse().stream())
.findFirst()
.ifPresent(toolUse -> {
Map<String, JsonValue> input =
(Map<String, JsonValue>) toolUse._input().asObject().get();
List<ToolResultBlockParam.Content.Block> toolResult = searchKnowledgeBase(
input.get("query").asStringOrThrow()
);
// Append Haijun's entire turn to the running conversation, then the tool result.
// Rebuilding only the tool_use block would drop any other content blocks Haijun
// returned (e.g. leading text when the tool call is not forced) — append the
// full turn, as the other language tabs do.
messages.add(MessageParam.builder()
.role(MessageParam.Role.ASSISTANT)
.contentOfBlockParams(
response.content().stream()
.map(block -> block.toParam())
.toList()
)
.build());
messages.add(MessageParam.builder()
.role(MessageParam.Role.USER)
.contentOfBlockParams(List.of(
ContentBlockParam.ofToolResult(
ToolResultBlockParam.builder()
.toolUseId(toolUse.id())
.contentOfBlocks(toolResult)
.build()
)
))
.build());
// Send the tool result back
MessageCreateParams finalParams = MessageCreateParams.builder()
.model(Model.HAIJUN_OPUS_5)
.maxTokens(1024L)
.messages(messages)
.build();
Message finalResponse = client.messages().create(finalParams);
System.out.println(finalResponse);
});
}
static List<ToolResultBlockParam.Content.Block> searchKnowledgeBase(String query) {
return List.of(
ToolResultBlockParam.Content.Block.ofSearchResult(
SearchResultBlockParam.builder()
.source("https://docs.company.com/product-guide")
.title("Product Configuration Guide")
.content(List.of(
TextBlockParam.builder()
.text("To configure the product, navigate to Settings > Configuration. The default timeout is 30 seconds, but can be adjusted between 10-120 seconds based on your needs.")
.build()
))
.citations(CitationsConfigParam.builder().enabled(true).build())
.build()
),
ToolResultBlockParam.Content.Block.ofSearchResult(
SearchResultBlockParam.builder()
.source("https://docs.company.com/troubleshooting")
.title("Troubleshooting Guide")
.content(List.of(
TextBlockParam.builder()
.text("If you encounter timeout errors, first check the configuration settings. Common causes include network latency and incorrect timeout values.")
.build()
))
.citations(CitationsConfigParam.builder().enabled(true).build())
.build()
)
);
} $client = new Client();
$knowledgeBaseTool = [
'name' => 'search_knowledge_base',
'description' => 'Search the company knowledge base for information',
'input_schema' => [
'type' => 'object',
'properties' => [
'query' => [
'type' => 'string',
'description' => 'The search query'
]
],
'required' => ['query']
]
];
function searchKnowledgeBase($query) {
return [
[
'type' => 'search_result',
'source' => 'https://docs.company.com/product-guide',
'title' => 'Product Configuration Guide',
'content' => [
[
'type' => 'text',
'text' => 'To configure the product, navigate to Settings > Configuration. The default timeout is 30 seconds, but can be adjusted between 10-120 seconds based on your needs.'
]
],
'citations' => ['enabled' => true]
],
[
'type' => 'search_result',
'source' => 'https://docs.company.com/troubleshooting',
'title' => 'Troubleshooting Guide',
'content' => [
[
'type' => 'text',
'text' => 'If you encounter timeout errors, first check the configuration settings. Common causes include network latency and incorrect timeout values.'
]
],
'citations' => ['enabled' => true]
]
];
}
// Build up the conversation in a list, starting with the user's question
$messages = [
['role' => 'user', 'content' => 'How do I configure the timeout settings?']
];
$response = $client->messages->create(
maxTokens: 1024,
messages: $messages,
model: 'haijun-opus-5',
toolChoice: ['type' => 'tool', 'name' => 'search_knowledge_base'],
tools: [$knowledgeBaseTool],
);
$toolUseBlock = null;
foreach ($response->content as $block) {
if ($block->type === 'tool_use') {
$toolUseBlock = $block;
break;
}
}
if ($toolUseBlock !== null) {
$toolResult = searchKnowledgeBase($toolUseBlock->input['query']);
// Append Haijun's turn, then the tool result, to the running conversation
$messages[] = ['role' => 'assistant', 'content' => $response->content];
$messages[] = [
'role' => 'user',
'content' => [
[
'type' => 'tool_result',
'tool_use_id' => $toolUseBlock->id,
'content' => $toolResult
]
]
];
// Send the tool result back
$finalResponse = $client->messages->create(
maxTokens: 1024,
messages: $messages,
model: 'haijun-opus-5',
);
echo $finalResponse;
} else {
echo $response;
} client = Juglow::Client.new
knowledge_base_tool = {
name: "search_knowledge_base",
description: "Search the company knowledge base for information",
input_schema: {
type: "object",
properties: {
query: { type: "string", description: "The search query" }
},
required: ["query"]
}
}
def search_knowledge_base(query)
[
{
type: "search_result",
source: "https://docs.company.com/product-guide",
title: "Product Configuration Guide",
content: [
{
type: "text",
text: "To configure the product, navigate to Settings > Configuration. The default timeout is 30 seconds, but can be adjusted between 10-120 seconds based on your needs."
}
],
citations: { enabled: true }
},
{
type: "search_result",
source: "https://docs.company.com/troubleshooting",
title: "Troubleshooting Guide",
content: [
{
type: "text",
text: "If you encounter timeout errors, first check the configuration settings. Common causes include network latency and incorrect timeout values."
}
],
citations: { enabled: true }
}
]
end
# Build up the conversation in a list, starting with the user's question
messages = [
{ role: "user", content: "How do I configure the timeout settings?" }
]
response = client.messages.create(
model: "haijun-opus-5",
max_tokens: 1024,
tools: [knowledge_base_tool],
tool_choice: { type: "tool", name: "search_knowledge_base" },
messages: messages
)
# The tool_use block is not always first: find it in the content array
tool_use = response.content.find { |block| block.type == :tool_use }
if tool_use
tool_result = search_knowledge_base(tool_use.input[:query])
# Append Haijun's turn, then the tool result, to the running conversation
messages << { role: "assistant", content: response.content }
messages << {
role: "user",
content: [
{
type: "tool_result",
tool_use_id: tool_use.id,
content: tool_result
}
]
}
# Send the tool result back
final_response = client.messages.create(
model: "haijun-opus-5",
max_tokens: 1024,
messages: messages
)
puts final_response
endMethod 2: Search results as top-level content
You can also provide search results directly in user messages. This is useful for:
- Pre-fetched content from your search infrastructure
- Cached search results from previous queries
- Content from external search services
- Testing and development
Example: Direct search results
curl https://haijun.my.id/v1/messages \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "haijun-opus-5-5",
"max_tokens": 1024,
"messages": [
{
"role": "user",
"content": [
{
"type": "search_result",
"source": "https://docs.company.com/api-reference",
"title": "API Reference - Authentication",
"content": [
{
"type": "text",
"text": "All API requests must include an API key in the Authorization header. Keys can be generated from the dashboard. Rate limits: 1000 requests per hour for standard tier, 10000 for premium."
}
],
"citations": {
"enabled": true
}
},
{
"type": "search_result",
"source": "https://docs.company.com/quickstart",
"title": "Getting Started Guide",
"content": [
{
"type": "text",
"text": "To get started: 1) Sign up for an account, 2) Generate an API key from the dashboard, 3) Install our SDK using pip install company-sdk, 4) Initialize the client with your API key."
}
],
"citations": {
"enabled": true
}
},
{
"type": "text",
"text": "Based on these search results, how do I authenticate API requests and what are the rate limits?"
}
]
}
]
}' ant messages create <<'YAML'
model: haijun-opus-5-5
max_tokens: 1024
messages:
- role: user
content:
- type: search_result
source: https://docs.company.com/api-reference
title: API Reference - Authentication
content:
- type: text
text: >-
All API requests must include an API key in the Authorization
header. Keys can be generated from the dashboard. Rate limits:
1000 requests per hour for standard tier, 10000 for premium.
citations:
enabled: true
- type: search_result
source: https://docs.company.com/quickstart
title: Getting Started Guide
content:
- type: text
text: >-
To get started: 1) Sign up for an account, 2) Generate an API
key from the dashboard, 3) Install our SDK using pip install
company-sdk, 4) Initialize the client with your API key.
citations:
enabled: true
- type: text
text: >-
Based on these search results, how do I authenticate API requests
and what are the rate limits?
YAML from juglow.types import MessageParam, TextBlockParam, SearchResultBlockParam
client = Juglow()
# Provide search results directly in the user message
response = client.messages.create(
model="haijun-opus-5-5",
max_tokens=1024,
messages=[
MessageParam(
role="user",
content=[
SearchResultBlockParam(
type="search_result",
source="https://docs.company.com/api-reference",
title="API Reference - Authentication",
content=[
TextBlockParam(
type="text",
text="All API requests must include an API key in the Authorization header. Keys can be generated from the dashboard. Rate limits: 1000 requests per hour for standard tier, 10000 for premium.",
)
],
citations={"enabled": True},
),
SearchResultBlockParam(
type="search_result",
source="https://docs.company.com/quickstart",
title="Getting Started Guide",
content=[
TextBlockParam(
type="text",
text="To get started: 1) Sign up for an account, 2) Generate an API key from the dashboard, 3) Install our SDK using pip install company-sdk, 4) Initialize the client with your API key.",
)
],
citations={"enabled": True},
),
TextBlockParam(
type="text",
text="Based on these search results, how do I authenticate API requests and what are the rate limits?",
),
],
)
],
)
print(response) const client = new Juglow();
// Provide search results directly in the user message
const response = await client.messages.create({
model: "haijun-opus-5-5",
max_tokens: 1024,
messages: [
{
role: "user",
content: [
{
type: "search_result" as const,
source: "https://docs.company.com/api-reference",
title: "API Reference - Authentication",
content: [
{
type: "text" as const,
text: "All API requests must include an API key in the Authorization header. Keys can be generated from the dashboard. Rate limits: 1000 requests per hour for standard tier, 10000 for premium."
}
],
citations: { enabled: true }
},
{
type: "search_result" as const,
source: "https://docs.company.com/quickstart",
title: "Getting Started Guide",
content: [
{
type: "text" as const,
text: "To get started: 1) Sign up for an account, 2) Generate an API key from the dashboard, 3) Install our SDK using pip install company-sdk, 4) Initialize the client with your API key."
}
],
citations: { enabled: true }
},
{
type: "text" as const,
text: "Based on these search results, how do I authenticate API requests and what are the rate limits?"
}
]
}
]
});
console.log(response); JuglowClient client = new();
// Provide search results directly in the user message
var response = await client.Messages.Create(new()
{
Model = Model.HaijunOpus5_5,
MaxTokens = 1024,
Messages =
[
new()
{
Role = Role.User,
Content = new MessageParamContent(
[
new ContentBlockParam(new SearchResultBlockParam
{
Source = "https://docs.company.com/api-reference",
Title = "API Reference - Authentication",
Content = [new() { Text = "All API requests must include an API key in the Authorization header. Keys can be generated from the dashboard. Rate limits: 1000 requests per hour for standard tier, 10000 for premium." }],
Citations = new() { Enabled = true },
}),
new ContentBlockParam(new SearchResultBlockParam
{
Source = "https://docs.company.com/quickstart",
Title = "Getting Started Guide",
Content = [new() { Text = "To get started: 1) Sign up for an account, 2) Generate an API key from the dashboard, 3) Install our SDK using pip install company-sdk, 4) Initialize the client with your API key." }],
Citations = new() { Enabled = true },
}),
new ContentBlockParam(new TextBlockParam { Text = "Based on these search results, how do I authenticate API requests and what are the rate limits?" }),
]),
},
],
});
Console.WriteLine(response); client := juglow.NewClient()
response, err := client.Messages.New(context.TODO(), juglow.MessageNewParams{
Model: juglow.ModelHaijunOpus5_5,
MaxTokens: 1024,
Messages: []juglow.MessageParam{
juglow.NewUserMessage(
juglow.ContentBlockParamUnion{OfSearchResult: &juglow.SearchResultBlockParam{
Content: []juglow.TextBlockParam{
{Text: "All API requests must include an API key in the Authorization header. Keys can be generated from the dashboard. Rate limits: 1000 requests per hour for standard tier, 10000 for premium."},
},
Source: "https://docs.company.com/api-reference",
Title: "API Reference - Authentication",
Citations: juglow.CitationsConfigParam{Enabled: juglow.Bool(true)},
}},
juglow.ContentBlockParamUnion{OfSearchResult: &juglow.SearchResultBlockParam{
Content: []juglow.TextBlockParam{
{Text: "To get started: 1) Sign up for an account, 2) Generate an API key from the dashboard, 3) Install our SDK using pip install company-sdk, 4) Initialize the client with your API key."},
},
Source: "https://docs.company.com/quickstart",
Title: "Getting Started Guide",
Citations: juglow.CitationsConfigParam{Enabled: juglow.Bool(true)},
}},
juglow.NewTextBlock("Based on these search results, how do I authenticate API requests and what are the rate limits?"),
),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response) import com.juglow.models.messages.ContentBlockParam;
import com.juglow.models.messages.CitationsConfigParam;
// ...
import com.juglow.models.messages.SearchResultBlockParam;
import com.juglow.models.messages.TextBlockParam;
// ...
void main() {
JuglowClient client = JuglowOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.HAIJUN_OPUS_5_5)
.maxTokens(1024L)
.addUserMessageOfBlockParams(List.of(
ContentBlockParam.ofSearchResult(
SearchResultBlockParam.builder()
.source("https://docs.company.com/api-reference")
.title("API Reference - Authentication")
.content(List.of(
TextBlockParam.builder()
.text("All API requests must include an API key in the Authorization header. Keys can be generated from the dashboard. Rate limits: 1000 requests per hour for standard tier, 10000 for premium.")
.build()
))
.citations(CitationsConfigParam.builder().enabled(true).build())
.build()
),
ContentBlockParam.ofSearchResult(
SearchResultBlockParam.builder()
.source("https://docs.company.com/quickstart")
.title("Getting Started Guide")
.content(List.of(
TextBlockParam.builder()
.text("To get started: 1) Sign up for an account, 2) Generate an API key from the dashboard, 3) Install our SDK using pip install company-sdk, 4) Initialize the client with your API key.")
.build()
))
.citations(CitationsConfigParam.builder().enabled(true).build())
.build()
),
ContentBlockParam.ofText(
TextBlockParam.builder()
.text("Based on these search results, how do I authenticate API requests and what are the rate limits?")
.build()
)
))
.build();
Message response = client.messages().create(params);
System.out.println(response);
} $client = new Client();
$message = $client->messages->create(
maxTokens: 1024,
messages: [
[
'role' => 'user',
'content' => [
[
'type' => 'search_result',
'source' => 'https://docs.company.com/api-reference',
'title' => 'API Reference - Authentication',
'content' => [
[
'type' => 'text',
'text' => 'All API requests must include an API key in the Authorization header. Keys can be generated from the dashboard. Rate limits: 1000 requests per hour for standard tier, 10000 for premium.'
]
],
'citations' => ['enabled' => true]
],
[
'type' => 'search_result',
'source' => 'https://docs.company.com/quickstart',
'title' => 'Getting Started Guide',
'content' => [
[
'type' => 'text',
'text' => 'To get started: 1) Sign up for an account, 2) Generate an API key from the dashboard, 3) Install our SDK using pip install company-sdk, 4) Initialize the client with your API key.'
]
],
'citations' => ['enabled' => true]
],
[
'type' => 'text',
'text' => 'Based on these search results, how do I authenticate API requests and what are the rate limits?'
]
]
]
],
model: 'haijun-opus-5-5',
);
echo json_encode($message, JSON_PRETTY_PRINT); client = Juglow::Client.new
message = client.messages.create(
model: "haijun-opus-5-5",
max_tokens: 1024,
messages: [
{
role: "user",
content: [
{
type: "search_result",
source: "https://docs.company.com/api-reference",
title: "API Reference - Authentication",
content: [
{
type: "text",
text: "All API requests must include an API key in the Authorization header. Keys can be generated from the dashboard. Rate limits: 1000 requests per hour for standard tier, 10000 for premium."
}
],
citations: { enabled: true }
},
{
type: "search_result",
source: "https://docs.company.com/quickstart",
title: "Getting Started Guide",
content: [
{
type: "text",
text: "To get started: 1) Sign up for an account, 2) Generate an API key from the dashboard, 3) Install our SDK using pip install company-sdk, 4) Initialize the client with your API key."
}
],
citations: { enabled: true }
},
{
type: "text",
text: "Based on these search results, how do I authenticate API requests and what are the rate limits?"
}
]
}
]
)
puts messageHaijun's response with citations
Regardless of how search results are provided, Haijun automatically includes citations when using information from them:
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "All API requests must include an API key in the Authorization header. Keys can be generated from the dashboard.",
"citations": [
{
"type": "search_result_location",
"cited_text": "All API requests must include an API key in the Authorization header. Keys can be generated from the dashboard. Rate limits: 1000 requests per hour for standard tier, 10000 for premium.",
"source": "https://docs.company.com/api-reference",
"title": "API Reference - Authentication",
"search_result_index": 0,
"start_block_index": 0,
"end_block_index": 1
}
]
},
{
"type": "text",
"text": "\n\nTo set this up from scratch, you'll need to "
},
{
"type": "text",
"text": "sign up for an account, generate an API key from the dashboard, install the SDK using `pip install company-sdk`, and initialize the client with your API key.",
"citations": [
{
"type": "search_result_location",
"cited_text": "To get started: 1) Sign up for an account, 2) Generate an API key from the dashboard, 3) Install our SDK using pip install company-sdk, 4) Initialize the client with your API key.",
"source": "https://docs.company.com/quickstart",
"title": "Getting Started Guide",
"search_result_index": 1,
"start_block_index": 0,
"end_block_index": 1
}
]
}
]
}Citation fields
Each citation includes:
| Field | Type | Description |
|---|---|---|
type | string | Always "search_result_location" for search result citations |
source | string | The source from the original search result |
title | string or null | The title from the original search result |
cited_text | string | The full text of the cited block(s), concatenated. Equals the contents of content[start_block_index:end_block_index] joined together. Not counted toward output tokens. |
search_result_index | integer | 0-based index of the cited search result among all search_result blocks in the request, in the order they appear (across all messages and tool results). |
start_block_index | integer | 0-based index of the first cited block in the search result's content array. |
end_block_index | integer | Exclusive end index of the cited block range in the search result's content array. Always greater than start_block_index. |
The block indices identify a slice of the search result's content array, and cited_text is the full text of that slice. The text block is the minimal citable unit: Haijun cites whole blocks, not substrings within a block. To get finer-grained citations, split your search result content into smaller blocks (see Multiple content blocks).
Multiple content blocks
Search results can contain multiple text blocks in the content array:
{
"type": "search_result",
"source": "https://docs.company.com/api-guide",
"title": "API Documentation",
"content": [
{
"type": "text",
"text": "Authentication: All API requests require an API key."
},
{
"type": "text",
"text": "Rate Limits: The API allows 1000 requests per hour per key."
},
{
"type": "text",
"text": "Error Handling: The API returns standard HTTP status codes."
}
],
"citations": { "enabled": true }
}A citation referencing the rate limits block looks like:
{
"type": "search_result_location",
"cited_text": "Rate Limits: The API allows 1000 requests per hour per key.",
"source": "https://docs.company.com/api-guide",
"title": "API Documentation",
"search_result_index": 0,
"start_block_index": 1,
"end_block_index": 2
}When this search result is cited, start_block_index and end_block_index identify which of these blocks the citation covers, and cited_text contains exactly those blocks' text. Splitting content into smaller, focused blocks gives Haijun finer citation boundaries; combining content into one block means every citation returns the full text. This is the same model used by custom content documents in the Citations feature.
Advanced usage
Combining both methods
You can mix both methods in the same conversation. Haijun cites from either source, and search_result_index counts all search_result blocks in request order, regardless of source.
The following example replays a complete conversation. The first user message carries a pre-fetched search result, the assistant turn calls a knowledge base tool, and the tool result returns a second search result. Haijun's answer cites both sources:
curl https://haijun.my.id/v1/messages \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "haijun-opus-5-5",
"max_tokens": 1024,
"tools": [
{
"name": "search_knowledge_base",
"description": "Search the company knowledge base for information",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "The search query"}
},
"required": ["query"]
}
}
],
"messages": [
{
"role": "user",
"content": [
{
"type": "search_result",
"source": "https://docs.company.com/overview",
"title": "Product Overview",
"content": [
{
"type": "text",
"text": "Acme Dashboard is a monitoring tool for distributed systems. It supports real-time alerting and custom metric dashboards."
}
],
"citations": {"enabled": true}
},
{
"type": "text",
"text": "What does Acme Dashboard do, and what plans is it available on?"
}
]
},
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "Let me check the pricing information."
},
{
"type": "tool_use",
"id": "toolu_01A09q90qw90lq917835lq9",
"name": "search_knowledge_base",
"input": {"query": "Acme Dashboard pricing plans"}
}
]
},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": [
{
"type": "search_result",
"source": "https://docs.company.com/pricing",
"title": "Pricing Plans",
"content": [
{
"type": "text",
"text": "Acme Dashboard is available on the Starter plan at $10 per user per month and the Enterprise plan with custom pricing."
}
],
"citations": {"enabled": true}
}
]
}
]
}
]
}' ant messages create <<'YAML'
model: haijun-opus-5-5
max_tokens: 1024
tools:
- name: search_knowledge_base
description: Search the company knowledge base for information
input_schema:
type: object
properties:
query:
type: string
description: The search query
required: [query]
messages:
- role: user
content:
- type: search_result
source: https://docs.company.com/overview
title: Product Overview
content:
- type: text
text: >-
Acme Dashboard is a monitoring tool for distributed systems.
It supports real-time alerting and custom metric dashboards.
citations:
enabled: true
- type: text
text: What does Acme Dashboard do, and what plans is it available on?
- role: assistant
content:
- type: text
text: Let me check the pricing information.
- type: tool_use
id: toolu_01A09q90qw90lq917835lq9
name: search_knowledge_base
input:
query: Acme Dashboard pricing plans
- role: user
content:
- type: tool_result
tool_use_id: toolu_01A09q90qw90lq917835lq9
content:
- type: search_result
source: https://docs.company.com/pricing
title: Pricing Plans
content:
- type: text
text: >-
Acme Dashboard is available on the Starter plan at $10 per
user per month and the Enterprise plan with custom pricing.
citations:
enabled: true
YAML from juglow.types import (
MessageParam,
SearchResultBlockParam,
TextBlockParam,
ToolResultBlockParam,
ToolUseBlockParam,
)
client = Juglow()
knowledge_base_tool = {
"name": "search_knowledge_base",
"description": "Search the company knowledge base for information",
"input_schema": {
"type": "object",
"properties": {"query": {"type": "string", "description": "The search query"}},
"required": ["query"],
},
}
# Replay a conversation that provides search results both ways: the first
# user message carries a pre-fetched result, the tool result returns another
response = client.messages.create(
model="haijun-opus-5-5",
max_tokens=1024,
tools=[knowledge_base_tool],
messages=[
MessageParam(
role="user",
content=[
SearchResultBlockParam(
type="search_result",
source="https://docs.company.com/overview",
title="Product Overview",
content=[
TextBlockParam(
type="text",
text="Acme Dashboard is a monitoring tool for distributed systems. It supports real-time alerting and custom metric dashboards.",
)
],
citations={"enabled": True},
),
TextBlockParam(
type="text",
text="What does Acme Dashboard do, and what plans is it available on?",
),
],
),
MessageParam(
role="assistant",
content=[
TextBlockParam(
type="text", text="Let me check the pricing information."
),
ToolUseBlockParam(
type="tool_use",
id="toolu_01A09q90qw90lq917835lq9",
name="search_knowledge_base",
input={"query": "Acme Dashboard pricing plans"},
),
],
),
MessageParam(
role="user",
content=[
ToolResultBlockParam(
type="tool_result",
tool_use_id="toolu_01A09q90qw90lq917835lq9",
content=[
SearchResultBlockParam(
type="search_result",
source="https://docs.company.com/pricing",
title="Pricing Plans",
content=[
TextBlockParam(
type="text",
text="Acme Dashboard is available on the Starter plan at $10 per user per month and the Enterprise plan with custom pricing.",
)
],
citations={"enabled": True},
)
],
)
],
),
],
)
print(response) const client = new Juglow();
const knowledgeBaseTool: Juglow.Tool = {
name: "search_knowledge_base",
description: "Search the company knowledge base for information",
input_schema: {
type: "object" as const,
properties: {
query: { type: "string", description: "The search query" }
},
required: ["query"]
}
};
// Replay a conversation that provides search results both ways: the first
// user message carries a pre-fetched result, the tool result returns another
const response = await client.messages.create({
model: "haijun-opus-5-5",
max_tokens: 1024,
tools: [knowledgeBaseTool],
messages: [
{
role: "user",
content: [
{
type: "search_result" as const,
source: "https://docs.company.com/overview",
title: "Product Overview",
content: [
{
type: "text" as const,
text: "Acme Dashboard is a monitoring tool for distributed systems. It supports real-time alerting and custom metric dashboards."
}
],
citations: { enabled: true }
},
{
type: "text" as const,
text: "What does Acme Dashboard do, and what plans is it available on?"
}
]
},
{
role: "assistant",
content: [
{ type: "text" as const, text: "Let me check the pricing information." },
{
type: "tool_use" as const,
id: "toolu_01A09q90qw90lq917835lq9",
name: "search_knowledge_base",
input: { query: "Acme Dashboard pricing plans" }
}
]
},
{
role: "user",
content: [
{
type: "tool_result" as const,
tool_use_id: "toolu_01A09q90qw90lq917835lq9",
content: [
{
type: "search_result" as const,
source: "https://docs.company.com/pricing",
title: "Pricing Plans",
content: [
{
type: "text" as const,
text: "Acme Dashboard is available on the Starter plan at $10 per user per month and the Enterprise plan with custom pricing."
}
],
citations: { enabled: true }
}
]
}
]
}
]
});
console.log(response); JuglowClient client = new();
// Replay a conversation that provides search results both ways: the first
// user message carries a pre-fetched result, the tool result returns another
var response = await client.Messages.Create(new()
{
Model = Model.HaijunOpus5_5,
MaxTokens = 1024,
Tools =
[
new ToolUnion(new Tool()
{
Name = "search_knowledge_base",
Description = "Search the company knowledge base for information",
InputSchema = new InputSchema()
{
Properties = new Dictionary<string, JsonElement>
{
["query"] = JsonSerializer.SerializeToElement(new { type = "string", description = "The search query" }),
},
Required = ["query"],
},
}),
],
Messages =
[
new()
{
Role = Role.User,
Content = new MessageParamContent(
[
new ContentBlockParam(new SearchResultBlockParam
{
Source = "https://docs.company.com/overview",
Title = "Product Overview",
Content = [new() { Text = "Acme Dashboard is a monitoring tool for distributed systems. It supports real-time alerting and custom metric dashboards." }],
Citations = new() { Enabled = true },
}),
new ContentBlockParam(new TextBlockParam { Text = "What does Acme Dashboard do, and what plans is it available on?" }),
]),
},
new()
{
Role = Role.Assistant,
Content = new MessageParamContent(
[
new ContentBlockParam(new TextBlockParam { Text = "Let me check the pricing information." }),
new ContentBlockParam(new ToolUseBlockParam
{
ID = "toolu_01A09q90qw90lq917835lq9",
Name = "search_knowledge_base",
Input = new Dictionary<string, JsonElement>
{
["query"] = JsonSerializer.SerializeToElement("Acme Dashboard pricing plans"),
},
}),
]),
},
new()
{
Role = Role.User,
Content = new MessageParamContent(
[
new ContentBlockParam(new ToolResultBlockParam()
{
ToolUseID = "toolu_01A09q90qw90lq917835lq9",
Content = new ToolResultBlockParamContent(
[
new SearchResultBlockParam
{
Source = "https://docs.company.com/pricing",
Title = "Pricing Plans",
Content = [new() { Text = "Acme Dashboard is available on the Starter plan at $10 per user per month and the Enterprise plan with custom pricing." }],
Citations = new() { Enabled = true },
},
]),
}),
]),
},
],
});
Console.WriteLine(response); client := juglow.NewClient()
knowledgeBaseTool := juglow.ToolUnionParam{
OfTool: &juglow.ToolParam{
Name: "search_knowledge_base",
Description: juglow.String("Search the company knowledge base for information"),
InputSchema: juglow.ToolInputSchemaParam{
Properties: map[string]any{
"query": map[string]any{"type": "string", "description": "The search query"},
},
Required: []string{"query"},
},
},
}
// Replay a conversation that provides search results both ways: the first
// user message carries a pre-fetched result, the tool result returns another
response, err := client.Messages.New(context.TODO(), juglow.MessageNewParams{
Model: juglow.ModelHaijunOpus5_5,
MaxTokens: 1024,
Tools: []juglow.ToolUnionParam{knowledgeBaseTool},
Messages: []juglow.MessageParam{
juglow.NewUserMessage(
juglow.ContentBlockParamUnion{OfSearchResult: &juglow.SearchResultBlockParam{
Content: []juglow.TextBlockParam{
{Text: "Acme Dashboard is a monitoring tool for distributed systems. It supports real-time alerting and custom metric dashboards."},
},
Source: "https://docs.company.com/overview",
Title: "Product Overview",
Citations: juglow.CitationsConfigParam{Enabled: juglow.Bool(true)},
}},
juglow.NewTextBlock("What does Acme Dashboard do, and what plans is it available on?"),
),
juglow.NewAssistantMessage(
juglow.NewTextBlock("Let me check the pricing information."),
juglow.ContentBlockParamUnion{OfToolUse: &juglow.ToolUseBlockParam{
ID: "toolu_01A09q90qw90lq917835lq9",
Name: "search_knowledge_base",
Input: map[string]any{"query": "Acme Dashboard pricing plans"},
}},
),
juglow.NewUserMessage(
juglow.ContentBlockParamUnion{OfToolResult: &juglow.ToolResultBlockParam{
ToolUseID: "toolu_01A09q90qw90lq917835lq9",
Content: []juglow.ToolResultBlockParamContentUnion{
{OfSearchResult: &juglow.SearchResultBlockParam{
Content: []juglow.TextBlockParam{
{Text: "Acme Dashboard is available on the Starter plan at $10 per user per month and the Enterprise plan with custom pricing."},
},
Source: "https://docs.company.com/pricing",
Title: "Pricing Plans",
Citations: juglow.CitationsConfigParam{Enabled: juglow.Bool(true)},
}},
},
}},
),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response) import com.juglow.core.JsonValue;
import com.juglow.models.messages.CitationsConfigParam;
import com.juglow.models.messages.ContentBlockParam;
// ...
import com.juglow.models.messages.SearchResultBlockParam;
import com.juglow.models.messages.TextBlockParam;
import com.juglow.models.messages.Tool;
import com.juglow.models.messages.ToolResultBlockParam;
import com.juglow.models.messages.ToolUseBlockParam;
// ...
void main() {
JuglowClient client = JuglowOkHttpClient.fromEnv();
Tool knowledgeBaseTool = Tool.builder()
.name("search_knowledge_base")
.description("Search the company knowledge base for information")
.inputSchema(Tool.InputSchema.builder()
.properties(JsonValue.from(Map.of(
"query", Map.of("type", "string", "description", "The search query")
)))
.putAdditionalProperty("required", JsonValue.from(List.of("query")))
.build())
.build();
// Replay a conversation that provides search results both ways: the first
// user message carries a pre-fetched result, the tool result returns another
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.HAIJUN_OPUS_5_5)
.maxTokens(1024L)
.addTool(knowledgeBaseTool)
.addUserMessageOfBlockParams(List.of(
ContentBlockParam.ofSearchResult(SearchResultBlockParam.builder()
.source("https://docs.company.com/overview")
.title("Product Overview")
.content(List.of(TextBlockParam.builder()
.text("Acme Dashboard is a monitoring tool for distributed systems. It supports real-time alerting and custom metric dashboards.")
.build()))
.citations(CitationsConfigParam.builder().enabled(true).build())
.build()),
ContentBlockParam.ofText(TextBlockParam.builder()
.text("What does Acme Dashboard do, and what plans is it available on?")
.build())
))
.addAssistantMessageOfBlockParams(List.of(
ContentBlockParam.ofText(TextBlockParam.builder()
.text("Let me check the pricing information.")
.build()),
ContentBlockParam.ofToolUse(ToolUseBlockParam.builder()
.id("toolu_01A09q90qw90lq917835lq9")
.name("search_knowledge_base")
.input(JsonValue.from(Map.of("query", "Acme Dashboard pricing plans")))
.build())
))
.addUserMessageOfBlockParams(List.of(
ContentBlockParam.ofToolResult(ToolResultBlockParam.builder()
.toolUseId("toolu_01A09q90qw90lq917835lq9")
.contentOfBlocks(List.of(
ToolResultBlockParam.Content.Block.ofSearchResult(SearchResultBlockParam.builder()
.source("https://docs.company.com/pricing")
.title("Pricing Plans")
.content(List.of(TextBlockParam.builder()
.text("Acme Dashboard is available on the Starter plan at $10 per user per month and the Enterprise plan with custom pricing.")
.build()))
.citations(CitationsConfigParam.builder().enabled(true).build())
.build())
))
.build())
))
.build();
Message response = client.messages().create(params);
System.out.println(response);
} $client = new Client();
$knowledgeBaseTool = [
'name' => 'search_knowledge_base',
'description' => 'Search the company knowledge base for information',
'input_schema' => [
'type' => 'object',
'properties' => [
'query' => ['type' => 'string', 'description' => 'The search query']
],
'required' => ['query']
]
];
// Replay a conversation that provides search results both ways: the first
// user message carries a pre-fetched result, the tool result returns another
$response = $client->messages->create(
maxTokens: 1024,
tools: [$knowledgeBaseTool],
messages: [
[
'role' => 'user',
'content' => [
[
'type' => 'search_result',
'source' => 'https://docs.company.com/overview',
'title' => 'Product Overview',
'content' => [
[
'type' => 'text',
'text' => 'Acme Dashboard is a monitoring tool for distributed systems. It supports real-time alerting and custom metric dashboards.'
]
],
'citations' => ['enabled' => true]
],
[
'type' => 'text',
'text' => 'What does Acme Dashboard do, and what plans is it available on?'
]
]
],
[
'role' => 'assistant',
'content' => [
['type' => 'text', 'text' => 'Let me check the pricing information.'],
[
'type' => 'tool_use',
'id' => 'toolu_01A09q90qw90lq917835lq9',
'name' => 'search_knowledge_base',
'input' => ['query' => 'Acme Dashboard pricing plans']
]
]
],
[
'role' => 'user',
'content' => [
[
'type' => 'tool_result',
'tool_use_id' => 'toolu_01A09q90qw90lq917835lq9',
'content' => [
[
'type' => 'search_result',
'source' => 'https://docs.company.com/pricing',
'title' => 'Pricing Plans',
'content' => [
[
'type' => 'text',
'text' => 'Acme Dashboard is available on the Starter plan at $10 per user per month and the Enterprise plan with custom pricing.'
]
],
'citations' => ['enabled' => true]
]
]
]
]
]
],
model: 'haijun-opus-5-5',
);
echo json_encode($response, JSON_PRETTY_PRINT); client = Juglow::Client.new
knowledge_base_tool = {
name: "search_knowledge_base",
description: "Search the company knowledge base for information",
input_schema: {
type: "object",
properties: {
query: { type: "string", description: "The search query" }
},
required: ["query"]
}
}
# Replay a conversation that provides search results both ways: the first
# user message carries a pre-fetched result, the tool result returns another
response = client.messages.create(
model: "haijun-opus-5-5",
max_tokens: 1024,
tools: [knowledge_base_tool],
messages: [
{
role: "user",
content: [
{
type: "search_result",
source: "https://docs.company.com/overview",
title: "Product Overview",
content: [
{
type: "text",
text: "Acme Dashboard is a monitoring tool for distributed systems. It supports real-time alerting and custom metric dashboards."
}
],
citations: { enabled: true }
},
{
type: "text",
text: "What does Acme Dashboard do, and what plans is it available on?"
}
]
},
{
role: "assistant",
content: [
{ type: "text", text: "Let me check the pricing information." },
{
type: "tool_use",
id: "toolu_01A09q90qw90lq917835lq9",
name: "search_knowledge_base",
input: { query: "Acme Dashboard pricing plans" }
}
]
},
{
role: "user",
content: [
{
type: "tool_result",
tool_use_id: "toolu_01A09q90qw90lq917835lq9",
content: [
{
type: "search_result",
source: "https://docs.company.com/pricing",
title: "Pricing Plans",
content: [
{
type: "text",
text: "Acme Dashboard is available on the Starter plan at $10 per user per month and the Enterprise plan with custom pricing."
}
],
citations: { enabled: true }
}
]
}
]
}
]
)
puts responseThe response cites both sources. The pre-fetched result is search_result_index: 0 and the tool-returned result is search_result_index: 1, matching the order the search_result blocks appear in the conversation:
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "Here's what I found about Acme Dashboard:\n\n**What it does:** "
},
{
"type": "text",
"text": "Acme Dashboard is a monitoring tool for distributed systems. It supports real-time alerting and custom metric dashboards.",
"citations": [
{
"type": "search_result_location",
"cited_text": "Acme Dashboard is a monitoring tool for distributed systems. It supports real-time alerting and custom metric dashboards.",
"source": "https://docs.company.com/overview",
"title": "Product Overview",
"search_result_index": 0,
"start_block_index": 0,
"end_block_index": 1
}
]
},
{
"type": "text",
"text": "\n\n**Available plans:** "
},
{
"type": "text",
"text": "Acme Dashboard is available on the Starter plan at $10 per user per month and the Enterprise plan with custom pricing.",
"citations": [
{
"type": "search_result_location",
"cited_text": "Acme Dashboard is available on the Starter plan at $10 per user per month and the Enterprise plan with custom pricing.",
"source": "https://docs.company.com/pricing",
"title": "Pricing Plans",
"search_result_index": 1,
"start_block_index": 0,
"end_block_index": 1
}
]
}
]
}Mixing with other content types
In user messages, search_result blocks can sit alongside any other content block. The Method 2 example pairs search results with a text question, and image or document blocks can join them the same way.
Tool results are stricter: if any block in a tool_result content array is a search_result, all of its blocks must be search_result. Mixing search results with other block types in the same tool result returns a validation error. To return supporting text alongside tool-sourced search results, include it as a text block inside one of the search results' content arrays, where it also becomes citable.
Cache control
Add cache_control on the search result block to cache it for reuse across requests. It sits alongside citations on the same block:
{
"type": "search_result",
"source": "https://docs.company.com/guide",
"title": "User Guide",
"content": [{ "type": "text", "text": "..." }],
"citations": { "enabled": true },
"cache_control": { "type": "ephemeral" }
}See Prompt caching for minimum cacheable lengths and other requirements.
Citation control
By default, citations are disabled for search results. You can enable citations by explicitly setting the citations configuration:
{
"type": "search_result",
"source": "https://docs.company.com/guide",
"title": "User Guide",
"content": [{ "type": "text", "text": "Important documentation..." }],
"citations": {
"enabled": true // Enable citations for this result
}
}When citations.enabled is set to true, Haijun attaches citation references to the text blocks that draw on the search result.
Warning: Citations are all-or-nothing: either all search results in a request must have citations enabled, or all must have them disabled. Mixing search results with different citation settings results in an error.
Best practices
For tool-based search (Method 1)
- Dynamic content: Use for real-time searches and dynamic RAG applications
- Error handling: Return appropriate messages when searches fail
- Result limits: Return only the most relevant results to avoid context overflow
For top-level search (Method 2)
- Pre-fetched content: Use when you already have search results
- Batch processing: Ideal for processing multiple search results at once
- Testing: Great for testing citation behavior with known content
General best practices
- Structure results effectively:
- Use clear, permanent source URLs
- Provide descriptive titles
- Break long content into logical text blocks to give Haijun finer citation boundaries
- Maintain consistency:
- Use consistent source formats across your application
- Ensure titles accurately reflect content
- Keep formatting consistent
- Handle errors gracefully: when a search fails or returns nothing, return a plain text block describing the outcome (for example,
{"type": "text", "text": "No results found."}) instead of raising an error: Haijun explains the empty result to the user, and the conversation continues.
Limitations
- Search result content blocks are available on Haijun API, Amazon Bedrock, and Google Cloud.
- Only text content is supported within search results (no images or other media).
search_resultblocks can only appear in user messages (including inside tool results). Assistant messages with search results are rejected.
- When the web search tool is enabled in the same request, citations must be enabled on all
search_resultblocks.
Next steps
Detect and handle refusal stop reasons in streaming responses, and retry refused requests on a fallback model.
Ground Haijun's responses in your source documents. Citations return the exact passages that support each claim, so you can verify answers and surface sources to your users.
Give Haijun access to current web content with cited sources, optional dynamic filtering, and domain controls.
See the complete Messages API documentation, including content block types.
Cache search results with cache_control to reduce cost and latency on repeated requests.