Note: Untuk mempelajari bagaimana "zero data retention" (retensi data nol), atau ZDR, berlaku untuk fitur ini, lihat API dan retensi data.
"Fine-grained tool streaming" (streaming alat fine-grained) mengirimkan input alat ke klien Anda saat Haijun menghasilkannya, tanpa buffering atau validasi JSON di sisi server. Melewati langkah buffering mengurangi waktu hingga fragmen pertama dari parameter besar, seperti dokumen atau blok kode, dan fragmen-fragmen tersebut tiba melalui event Streaming pesan yang sama seperti penggunaan alat standar.
Warning: Karena API tidak melakukan buffering atau memvalidasi input alat sebelum melakukan streaming, Anda mungkin menerima JSON yang parsial atau tidak valid. Respons yang berakhir dengan stop reason
max_tokensjuga dapat memotong parameter di tengah jalan. Akumulasikan fragmen-fragmennya, lindungi proses parse, dan lihat Menangani JSON tidak valid dalam respons alat untuk cara mengembalikan input yang tidak dapat di-parse ke Haijun.
Cara menggunakan streaming alat fine-grained
Semua model mendukung streaming alat fine-grained di Haijun API, Amazon Bedrock, Haijun Platform on AWS, Google Cloud, dan Microsoft Foundry. Untuk menggunakannya, atur eager_input_streaming ke true pada alat buatan pengguna mana pun yang Anda inginkan streaming fine-grained-nya diaktifkan, dan aktifkan streaming pada permintaan Anda.
Field eager_input_streaming bersifat opsional. Mengaturnya ke true mengaktifkan streaming fine-grained untuk alat tersebut, dan menghilangkannya memberi Anda streaming ter-buffer standar, di mana API melakukan buffering dan memvalidasi setiap nilai parameter sebelum melakukan streaming kembali. Pengecualiannya adalah permintaan yang masih mengirim header beta lama fine-grained-tool-streaming-2025-05-14, yang mengaktifkan streaming fine-grained untuk alat yang membiarkan field tersebut tidak diatur. Field per-alat menggantikan header tersebut, dan nilai false eksplisit mempertahankan streaming ter-buffer untuk suatu alat bahkan ketika permintaan masih mengirim header itu. Header lama tidak dapat digabungkan dengan entri toolset computer use atau browser use: API menolak permintaan yang mengirim keduanya, jadi hapus header tersebut dan atur eager_input_streaming pada alat buatan pengguna yang membutuhkannya. Lihat Referensi alat untuk definisi field tersebut.
Contoh berikut mengaktifkan streaming fine-grained untuk alat make_file dan meminta Haijun membuat puisi panjang, sehingga input alat cukup besar untuk diamati saat di-streaming:
curl https://haijun.my.id/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-d '{
"model": "haijun-opus-5-5",
"max_tokens": 65536,
"tools": [
{
"name": "make_file",
"description": "Write text to a file",
"eager_input_streaming": true,
"input_schema": {
"type": "object",
"properties": {
"filename": {
"type": "string",
"description": "The filename to write text to"
},
"lines_of_text": {
"type": "array",
"description": "An array of lines of text to write to the file"
}
},
"required": ["filename", "lines_of_text"]
}
}
],
"messages": [
{
"role": "user",
"content": "Can you write a long poem and make a file called poem.txt?"
}
],
"stream": true
}' ant messages create --stream --format jsonl <<'YAML' |
model: haijun-opus-5-5
max_tokens: 65536
tools:
- name: make_file
description: Write text to a file
eager_input_streaming: true
input_schema:
type: object
properties:
filename:
type: string
description: The filename to write text to
lines_of_text:
type: array
description: An array of lines of text to write to the file
required:
- filename
- lines_of_text
messages:
- role: user
content: Can you write a long poem and make a file called poem.txt?
YAML
jq -rj 'select(.delta.type == "input_json_delta") | .delta.partial_json' client = juglow.Juglow()
with client.messages.stream(
max_tokens=65536,
model="haijun-opus-5-5",
tools=[
{
"name": "make_file",
"description": "Write text to a file",
"eager_input_streaming": True,
"input_schema": {
"type": "object",
"properties": {
"filename": {
"type": "string",
"description": "The filename to write text to",
},
"lines_of_text": {
"type": "array",
"description": "An array of lines of text to write to the file",
},
},
"required": ["filename", "lines_of_text"],
},
}
],
messages=[
{
"role": "user",
"content": "Can you write a long poem and make a file called poem.txt?",
}
],
) as stream:
for event in stream:
if event.type == "input_json":
print(event.partial_json, end="", flush=True)
final_message = stream.get_final_message()
print()
for block in final_message.content:
if block.type == "tool_use":
print(f"Complete tool input: {block.input}") const client = new Juglow();
const stream = client.messages.stream({
model: "haijun-opus-5-5",
max_tokens: 65536,
tools: [
{
name: "make_file",
description: "Write text to a file",
eager_input_streaming: true,
input_schema: {
type: "object",
properties: {
filename: {
type: "string",
description: "The filename to write text to"
},
lines_of_text: {
type: "array",
description: "An array of lines of text to write to the file"
}
},
required: ["filename", "lines_of_text"]
}
}
],
messages: [
{
role: "user",
content: "Can you write a long poem and make a file called poem.txt?"
}
]
});
stream.on("inputJson", (partialJson) => {
process.stdout.write(partialJson);
});
const message = await stream.finalMessage();
console.log();
for (const block of message.content) {
if (block.type === "tool_use") {
console.log("Complete tool input:", block.input);
}
} JuglowClient client = new();
MessageCreateParams parameters = new()
{
Model = Model.HaijunOpus5_5,
MaxTokens = 65536,
Tools =
[
new Tool
{
Name = "make_file",
Description = "Write text to a file",
EagerInputStreaming = true,
InputSchema = new InputSchema
{
Properties = new Dictionary<string, JsonElement>
{
["filename"] = JsonSerializer.SerializeToElement(
new { type = "string", description = "The filename to write text to" }
),
["lines_of_text"] = JsonSerializer.SerializeToElement(
new { type = "array", description = "An array of lines of text to write to the file" }
),
},
Required = ["filename", "lines_of_text"],
},
},
],
Messages =
[
new()
{
Role = Role.User,
Content = "Can you write a long poem and make a file called poem.txt?",
},
],
};
// Contoh C# merakit input sendiri: indeks blok konten -> JSON yang terakumulasi
var toolInputs = new Dictionary<long, StringBuilder>();
await foreach (var streamEvent in client.Messages.CreateStreaming(parameters))
{
if (
streamEvent.TryPickContentBlockStart(out var start)
&& start.ContentBlock.TryPickToolUse(out _)
)
{
toolInputs[start.Index] = new StringBuilder();
}
else if (
streamEvent.TryPickContentBlockDelta(out var delta)
&& delta.Delta.TryPickInputJson(out var inputJson)
)
{
Console.Write(inputJson.PartialJson);
toolInputs[delta.Index].Append(inputJson.PartialJson);
}
}
Console.WriteLine();
foreach (var accumulatedInput in toolInputs.Values)
{
Console.WriteLine($"Complete tool input: {accumulatedInput}");
} client := juglow.NewClient()
makeFileTool := juglow.ToolParam{
Name: "make_file",
Description: juglow.String("Write text to a file"),
EagerInputStreaming: juglow.Bool(true),
InputSchema: juglow.ToolInputSchemaParam{
Properties: map[string]any{
"filename": map[string]any{
"type": "string",
"description": "The filename to write text to",
},
"lines_of_text": map[string]any{
"type": "array",
"description": "An array of lines of text to write to the file",
},
},
Required: []string{"filename", "lines_of_text"},
},
}
stream := client.Messages.NewStreaming(context.Background(), juglow.MessageNewParams{
Model: juglow.ModelHaijunOpus5_5,
MaxTokens: 65536,
Tools: []juglow.ToolUnionParam{{OfTool: &makeFileTool}},
Messages: []juglow.MessageParam{
juglow.NewUserMessage(juglow.NewTextBlock(
"Can you write a long poem and make a file called poem.txt?",
)),
},
})
message := juglow.Message{}
for stream.Next() {
event := stream.Current()
if err := message.Accumulate(event); err != nil {
panic(err)
}
if delta, ok := event.AsAny().(juglow.ContentBlockDeltaEvent); ok {
if inputJSON, ok := delta.Delta.AsAny().(juglow.InputJSONDelta); ok {
fmt.Print(inputJSON.PartialJSON)
}
}
}
if err := stream.Err(); err != nil {
panic(err)
}
fmt.Println()
for _, block := range message.Content {
if toolUse, ok := block.AsAny().(juglow.ToolUseBlock); ok {
fmt.Printf("Complete tool input: %s\n", toolUse.Input)
}
} JuglowClient client = JuglowOkHttpClient.fromEnv();
Tool makeFileTool = Tool.builder()
.name("make_file")
.description("Write text to a file")
.eagerInputStreaming(true)
.inputSchema(Tool.InputSchema.builder()
.properties(Tool.InputSchema.Properties.builder()
.putAdditionalProperty("filename", JsonValue.from(Map.of(
"type", "string",
"description", "The filename to write text to")))
.putAdditionalProperty("lines_of_text", JsonValue.from(Map.of(
"type", "array",
"description", "An array of lines of text to write to the file")))
.build())
.addRequired("filename")
.addRequired("lines_of_text")
.build())
.build();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.HAIJUN_OPUS_5_5)
.maxTokens(65536L)
.addTool(makeFileTool)
.addUserMessage("Can you write a long poem and make a file called poem.txt?")
.build();
MessageAccumulator accumulator = MessageAccumulator.create();
try (StreamResponse<RawMessageStreamEvent> streamResponse =
client.messages().createStreaming(params)) {
streamResponse.stream().forEach(event -> {
accumulator.accumulate(event);
if (event.isContentBlockDelta()) {
var delta = event.asContentBlockDelta().delta();
if (delta.isInputJson()) {
IO.print(delta.asInputJson().partialJson());
}
}
});
}
IO.println("");
accumulator.message().content().forEach(block ->
block.toolUse().ifPresent(toolUse ->
IO.println("Complete tool input: " + toolUse._input()))); use Juglow\Client;
use Juglow\Messages\InputJSONDelta;
use Juglow\Messages\Model;
use Juglow\Messages\RawContentBlockDeltaEvent;
use Juglow\Messages\RawContentBlockStartEvent;
use Juglow\Messages\ToolUseBlock;
$client = new Client();
$stream = $client->messages->createStream(
maxTokens: 65536,
model: Model::HAIJUN_OPUS_5_5,
tools: [
[
'name' => 'make_file',
'description' => 'Write text to a file',
'eager_input_streaming' => true,
'input_schema' => [
'type' => 'object',
'properties' => [
'filename' => [
'type' => 'string',
'description' => 'The filename to write text to',
],
'lines_of_text' => [
'type' => 'array',
'description' => 'An array of lines of text to write to the file',
],
],
'required' => ['filename', 'lines_of_text'],
],
],
],
messages: [
[
'role' => 'user',
'content' => 'Can you write a long poem and make a file called poem.txt?',
],
],
);
// Contoh PHP merakit input sendiri: index => string JSON yang terakumulasi
$toolInputs = [];
foreach ($stream as $event) {
switch (true) {
case $event instanceof RawContentBlockStartEvent:
if ($event->contentBlock instanceof ToolUseBlock) {
$toolInputs[$event->index] = '';
}
break;
case $event instanceof RawContentBlockDeltaEvent:
if ($event->delta instanceof InputJSONDelta) {
echo $event->delta->partialJSON;
$toolInputs[$event->index] .= $event->delta->partialJSON;
}
break;
}
}
echo "\n";
foreach ($toolInputs as $toolInput) {
echo "Complete tool input: {$toolInput}\n";
} client = Juglow::Client.new
stream = client.messages.stream(
model: Juglow::Models::Model::HAIJUN_OPUS_5_5,
max_tokens: 65_536,
tools: [
{
name: "make_file",
description: "Write text to a file",
eager_input_streaming: true,
input_schema: {
type: "object",
properties: {
filename: {
type: "string",
description: "The filename to write text to"
},
lines_of_text: {
type: "array",
description: "An array of lines of text to write to the file"
}
},
required: ["filename", "lines_of_text"]
}
}
],
messages: [
{
role: "user",
content: "Can you write a long poem and make a file called poem.txt?"
}
]
)
stream.each do |event|
print event.partial_json if event.is_a?(Juglow::Streaming::InputJsonEvent)
end
puts
stream.accumulated_message.content.each do |block|
puts "Complete tool input: #{block.input}" if block.type == :tool_use
endSetiap tab mengaktifkan streaming fine-grained untuk alat make_file. Tab SDK mencetak setiap fragmen input begitu fragmen itu tiba, lalu mencetak input terakumulasi lengkap setelah stream berakhir. Tab cURL menampilkan stream event mentah, dan tab CLI menggunakan jq untuk mencetak hanya fragmen-fragmennya. Karena fragmen yang dicetak bergabung menjadi input alat lengkap, puisi tersebut memenuhi terminal Anda saat Haijun menulisnya:
{"filename": "poem.txt", "lines_of_text": ["The Wanderer's Journey", "", "I.", "", "Beneath the vast and star-strewn sky,", "Where silver moonbeams softly lie,", ...
Complete tool input: {"filename": "poem.txt", "lines_of_text": ["The Wanderer's Journey", ...]}Tanpa eager_input_streaming, API melakukan buffering dan memvalidasi setiap nilai parameter sebelum melakukan streaming kembali, sehingga tidak ada yang tercetak untuk parameter besar sampai Haijun selesai menghasilkannya. Dengan field tersebut, fragmen mulai tiba segera setelah Haijun memulai parameter, dan fragmen-fragmen itu biasanya lebih panjang, dengan lebih sedikit pemotongan di tengah kata.
Mengakumulasi delta input alat
Kontrak akumulasinya sama seperti untuk streaming penggunaan alat standar, jadi bagian ini berlaku dengan maupun tanpa eager_input_streaming. Lihat Input JSON delta di Streaming pesan untuk format event-nya. Streaming alat fine-grained mengubah apa yang dapat Anda asumsikan tentang hasilnya: server melakukan streaming fragmen tanpa memvalidasinya, sehingga string terakumulasi mungkin bukan JSON yang valid.
Ketika blok konten tool_use di-streaming, event content_block_start awal berisi input: {} (objek kosong). Ini adalah placeholder. Input sebenarnya tiba sebagai serangkaian event input_json_delta, masing-masing membawa fragmen string partial_json. Untuk menyusun input lengkap, gabungkan fragmen-fragmen ini dan parse hasilnya ketika blok ditutup.
Jika SDK Anda menyediakan helper akumulator (seperti yang dilakukan tab Python, TypeScript, Go, Java, dan Ruby pada contoh sebelumnya), helper tersebut menanganinya untuk Anda. Pola manual ditujukan untuk SDK tanpa helper, atau ketika Anda menginginkan kendali penuh atas cara input disusun.
Kontrak akumulasinya:
- Pada
content_block_startdengantype: "tool_use", inisialisasi string kosong:input_json = ""
- Untuk setiap
content_block_deltadengantype: "input_json_delta", tambahkan:input_json += event.delta.partial_json
- Pada
content_block_stop, parse string terakumulasi
Lindungi proses parse, seperti yang dilakukan contoh SDK berikut. Respons juga dapat berhenti pada max_tokens di tengah parameter. Periksa stop reason dan putuskan apakah akan mencoba ulang permintaan dengan max_tokens yang lebih tinggi atau memperbaiki input parsial tersebut.
Ketidakcocokan tipe antara input: {} awal (objek) dan partial_json (string) memang disengaja. Objek kosong menandai slot dalam array konten. String delta membangun nilai sebenarnya.
# Mengakumulasi delta input per blok memerlukan bahasa pemrograman; tab CLI contoh
# pertama menampilkan fragmen mentah dengan jq. Lihat tab SDK. # Mengakumulasi delta input per blok memerlukan bahasa pemrograman; tab CLI contoh
# pertama menampilkan fragmen mentah dengan jq. Lihat tab SDK. client = juglow.Juglow()
tool_inputs: dict[int, str] = {} # index -> accumulated JSON string
with client.messages.stream(
model="haijun-opus-5-5",
max_tokens=1024,
tools=[
{
"name": "get_weather",
"description": "Get current weather for a city",
"eager_input_streaming": True,
"input_schema": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
}
],
messages=[{"role": "user", "content": "Weather in Paris?"}],
) as stream:
for event in stream:
match event.type:
case "content_block_start" if event.content_block.type == "tool_use":
tool_inputs[event.index] = ""
case "content_block_delta" if event.delta.type == "input_json_delta":
tool_inputs[event.index] += event.delta.partial_json
case "content_block_stop" if event.index in tool_inputs:
raw_input = tool_inputs[event.index]
try:
parsed = json.loads(raw_input)
except json.JSONDecodeError:
# String yang terakumulasi tidak dijamin merupakan JSON yang valid.
# Lihat "Handling invalid JSON in tool responses" di halaman ini.
print(f"Invalid tool input: {raw_input}")
else:
print(f"Tool input: {parsed}") const client = new Juglow();
const toolInputs = new Map<number, string>();
const stream = client.messages.stream({
model: "haijun-opus-5-5",
max_tokens: 1024,
tools: [
{
name: "get_weather",
description: "Get current weather for a city",
eager_input_streaming: true,
input_schema: {
type: "object",
properties: { city: { type: "string" } },
required: ["city"]
}
}
],
messages: [{ role: "user", content: "Weather in Paris?" }]
});
for await (const event of stream) {
switch (event.type) {
case "content_block_start":
if (event.content_block.type === "tool_use") {
toolInputs.set(event.index, "");
}
break;
case "content_block_delta":
if (event.delta.type === "input_json_delta") {
toolInputs.set(
event.index,
(toolInputs.get(event.index) ?? "") + event.delta.partial_json
);
}
break;
case "content_block_stop":
if (toolInputs.has(event.index)) {
const rawInput = toolInputs.get(event.index)!;
try {
console.log("Tool input:", JSON.parse(rawInput));
} catch {
// String yang terakumulasi tidak dijamin merupakan JSON yang valid.
// Lihat "Handling invalid JSON in tool responses" di halaman ini.
console.log("Invalid tool input:", rawInput);
}
}
break;
}
} JuglowClient client = new();
MessageCreateParams parameters = new()
{
Model = Model.HaijunOpus5_5,
MaxTokens = 1024,
Tools =
[
new Tool
{
Name = "get_weather",
Description = "Get current weather for a city",
EagerInputStreaming = true,
InputSchema = new InputSchema
{
Properties = new Dictionary<string, JsonElement>
{
["city"] = JsonSerializer.SerializeToElement(new { type = "string" }),
},
Required = ["city"],
},
},
],
Messages = [new() { Role = Role.User, Content = "Weather in Paris?" }],
};
// Indeks blok -> fragmen JSON yang terakumulasi
// Contoh ini mengakumulasi delta secara manual untuk menunjukkan stream mentah;
// MessageContentAggregator milik SDK juga dapat mengakumulasi input alat secara otomatis.
var toolInputs = new Dictionary<long, StringBuilder>();
await foreach (var streamEvent in client.Messages.CreateStreaming(parameters))
{
if (
streamEvent.TryPickContentBlockStart(out var start)
&& start.ContentBlock.TryPickToolUse(out _)
)
{
toolInputs[start.Index] = new StringBuilder();
}
else if (
streamEvent.TryPickContentBlockDelta(out var delta)
&& delta.Delta.TryPickInputJson(out var inputJson)
)
{
toolInputs[delta.Index].Append(inputJson.PartialJson);
}
else if (
streamEvent.TryPickContentBlockStop(out var stop)
&& toolInputs.TryGetValue(stop.Index, out var accumulated)
)
{
try
{
using var parsed = JsonDocument.Parse(accumulated.ToString());
Console.WriteLine($"Tool input: {parsed.RootElement}");
}
catch (JsonException)
{
// String yang terakumulasi tidak dijamin merupakan JSON yang valid.
// Lihat "Menangani JSON tidak valid dalam respons alat" di halaman ini.
Console.WriteLine($"Invalid tool input: {accumulated}");
}
}
} client := juglow.NewClient()
toolInputs := map[int64]string{} // content block index -> accumulated JSON
stream := client.Messages.NewStreaming(context.Background(), juglow.MessageNewParams{
Model: juglow.ModelHaijunOpus5_5,
MaxTokens: 1024,
Tools: []juglow.ToolUnionParam{{
OfTool: &juglow.ToolParam{
Name: "get_weather",
Description: juglow.String("Get current weather for a city"),
EagerInputStreaming: juglow.Bool(true),
InputSchema: juglow.ToolInputSchemaParam{
Properties: map[string]any{
"city": map[string]any{"type": "string"},
},
Required: []string{"city"},
},
},
}},
Messages: []juglow.MessageParam{
juglow.NewUserMessage(juglow.NewTextBlock("Weather in Paris?")),
},
})
for stream.Next() {
switch event := stream.Current().AsAny().(type) {
case juglow.ContentBlockStartEvent:
if _, ok := event.ContentBlock.AsAny().(juglow.ToolUseBlock); ok {
toolInputs[event.Index] = ""
}
case juglow.ContentBlockDeltaEvent:
if delta, ok := event.Delta.AsAny().(juglow.InputJSONDelta); ok {
toolInputs[event.Index] += delta.PartialJSON
}
case juglow.ContentBlockStopEvent:
if accumulated, ok := toolInputs[event.Index]; ok {
var parsed map[string]any
if err := json.Unmarshal([]byte(accumulated), &parsed); err != nil {
// String yang terakumulasi belum tentu merupakan JSON yang valid.
// Lihat "Handling invalid JSON in tool responses" di halaman ini.
fmt.Println("Invalid tool input:", accumulated)
} else {
fmt.Println("Tool input:", parsed)
}
}
}
}
if err := stream.Err(); err != nil {
panic(err)
} JuglowClient client = JuglowOkHttpClient.fromEnv();
ObjectMapper objectMapper = new ObjectMapper();
Tool weatherTool = Tool.builder()
.name("get_weather")
.description("Get current weather for a city")
.eagerInputStreaming(true)
.inputSchema(Tool.InputSchema.builder()
.properties(Tool.InputSchema.Properties.builder()
.putAdditionalProperty("city", JsonValue.from(Map.of("type", "string")))
.build())
.addRequired("city")
.build())
.build();
MessageCreateParams createParams = MessageCreateParams.builder()
.model(Model.HAIJUN_OPUS_5_5)
.maxTokens(1024)
.addTool(weatherTool)
.addUserMessage("Weather in Paris?")
.build();
// Indeks blok konten -> JSON input alat yang terakumulasi
Map<Long, StringBuilder> toolInputs = new HashMap<>();
try (StreamResponse<RawMessageStreamEvent> streamResponse = client.messages().createStreaming(createParams)) {
var eventIterator = streamResponse.stream().iterator();
while (eventIterator.hasNext()) {
RawMessageStreamEvent event = eventIterator.next();
switch (event.type().value()) {
case CONTENT_BLOCK_START -> {
var blockStart = event.asContentBlockStart();
if (blockStart.contentBlock().isToolUse()) {
toolInputs.put(blockStart.index(), new StringBuilder());
}
}
case CONTENT_BLOCK_DELTA -> {
var blockDelta = event.asContentBlockDelta();
if (blockDelta.delta().isInputJson() && toolInputs.containsKey(blockDelta.index())) {
toolInputs.get(blockDelta.index()).append(blockDelta.delta().asInputJson().partialJson());
}
}
case CONTENT_BLOCK_STOP -> {
var blockStop = event.asContentBlockStop();
if (toolInputs.containsKey(blockStop.index())) {
String accumulated = toolInputs.get(blockStop.index()).toString();
try {
IO.println("Tool input: " + objectMapper.readTree(accumulated));
} catch (JsonProcessingException e) {
// String yang terakumulasi tidak dijamin merupakan JSON yang valid.
// Lihat "Menangani JSON tidak valid dalam respons alat" di halaman ini.
IO.println("Invalid tool input: " + accumulated);
}
}
}
}
}
} use Juglow\Client;
use Juglow\Messages\InputJSONDelta;
use Juglow\Messages\Model;
use Juglow\Messages\RawContentBlockDeltaEvent;
use Juglow\Messages\RawContentBlockStartEvent;
use Juglow\Messages\RawContentBlockStopEvent;
use Juglow\Messages\ToolUseBlock;
$client = new Client();
// SDK PHP tidak menyediakan akumulator stream untuk input alat;
// pola manual yang ditunjukkan di sini adalah pendekatan yang didukung.
$toolInputs = []; // index => accumulated JSON string
$stream = $client->messages->createStream(
maxTokens: 1024,
model: Model::HAIJUN_OPUS_5_5,
tools: [
[
'name' => 'get_weather',
'description' => 'Get current weather for a city',
'eager_input_streaming' => true,
'input_schema' => [
'type' => 'object',
'properties' => ['city' => ['type' => 'string']],
'required' => ['city'],
],
],
],
messages: [['role' => 'user', 'content' => 'Weather in Paris?']],
);
foreach ($stream as $event) {
switch (true) {
case $event instanceof RawContentBlockStartEvent:
if ($event->contentBlock instanceof ToolUseBlock) {
$toolInputs[$event->index] = '';
}
break;
case $event instanceof RawContentBlockDeltaEvent:
if ($event->delta instanceof InputJSONDelta) {
$toolInputs[$event->index] .= $event->delta->partialJSON;
}
break;
case $event instanceof RawContentBlockStopEvent:
if (isset($toolInputs[$event->index])) {
$accumulated = $toolInputs[$event->index];
try {
$parsed = json_decode($accumulated, associative: true, flags: JSON_THROW_ON_ERROR);
echo "Tool input: " . json_encode($parsed) . "\n";
} catch (JsonException $e) {
// String yang terakumulasi tidak dijamin merupakan JSON yang valid.
// Lihat "Menangani JSON tidak valid dalam respons alat" di halaman ini.
echo "Invalid tool input: {$accumulated}\n";
}
}
break;
}
} client = Juglow::Client.new
tool_inputs = {} # index -> accumulated JSON string
stream = client.messages.stream_raw(
model: Juglow::Models::Model::HAIJUN_OPUS_5_5,
max_tokens: 1024,
tools: [
{
name: "get_weather",
description: "Get current weather for a city",
eager_input_streaming: true,
input_schema: {
type: "object",
properties: {city: {type: "string"}},
required: ["city"]
}
}
],
messages: [{role: "user", content: "Weather in Paris?"}]
)
stream.each do |event|
case event
when Juglow::Models::RawContentBlockStartEvent
tool_inputs[event.index] = +"" if event.content_block.type == :tool_use
when Juglow::Models::RawContentBlockDeltaEvent
if event.delta.is_a?(Juglow::Models::InputJSONDelta)
tool_inputs[event.index] << event.delta.partial_json
end
when Juglow::Models::RawContentBlockStopEvent
if tool_inputs.key?(event.index)
accumulated = tool_inputs[event.index]
begin
parsed = JSON.parse(accumulated)
puts "Tool input: #{parsed}"
rescue JSON::ParserError
# String yang terakumulasi tidak dijamin merupakan JSON yang valid.
# Lihat "Menangani JSON tidak valid dalam respons alat" di halaman ini.
puts "Invalid tool input: #{accumulated}"
end
end
end
endTip: Bereaksi terhadap fragmen dan menyusunnya adalah dua hal yang terpisah. Contoh pertama bereaksi terhadap setiap fragmen saat tiba dan tetap menyerahkan penyusunan kepada SDK pada tab yang menggunakan helper akumulator. Gunakan pola manual ketika Anda tidak menggunakan helper akumulator atau ketika Anda menginginkan kendali penuh atas penyusunan.
Menangani JSON tidak valid dalam respons alat
Dengan streaming alat fine-grained, input terakumulasi untuk pemanggilan alat mungkin berupa JSON yang tidak valid atau tidak lengkap. Jika demikian, Anda tidak dapat menjalankan alat tersebut, jadi laporkan kegagalan itu kembali ke Haijun. content dari hasil alat tidak harus berupa JSON, tetapi membungkus string mentah dalam objek JSON di bawah satu kunci membuatnya jelas bagi Haijun bahwa Anda menerima JSON yang tidak valid, dan mempertahankan input asli untuk debugging:
{
"INVALID_JSON": "<the unparseable input you received>"
}Kembalikan pembungkus tersebut, yang diserialisasi menjadi string, sebagai content dari blok konten tool result dengan is_error diatur ke true:
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"is_error": true,
"content": "{\"INVALID_JSON\": \"<the unparseable input you received>\"}"
}Note: Bangun pembungkus tersebut dengan library JSON Anda alih-alih dengan menggabungkan string, sehingga tanda kutip dan karakter khusus lainnya dalam input yang tidak valid di-escape dengan benar.
Langkah selanjutnya
Pahami cara kerja jendela konteks, bagaimana pemikiran diperpanjang dan penggunaan alat diperhitungkan di dalamnya, dan cara mengelola konteks seiring percakapan bertambah panjang.
Lakukan streaming respons Messages API secara bertahap dengan server-sent events, termasuk delta teks, penggunaan alat, dan pemikiran diperpanjang.
Parse blok tool\_use, format respons tool\_result, dan tangani error dengan is\_error.
Direktori alat yang disediakan Juglow dan referensi untuk properti definisi alat opsional.