— No reviews yet
0 installs
2 views
0.0% view→install
Install
$ agentstack add skill-ultroncore-claude-skill-vault-llm-streaming ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Are you the author of Llm Streaming? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claimAbout
LLM Streaming
When to Use
- Streaming Claude or other LLM responses to the client
- Building Next.js App Router API routes that stream
- Creating React components that render streaming text
- Handling
text_deltaevents from the Anthropic SDK - Setting up SSE (Server-Sent Events) endpoints
- Streaming responses that include tool use
Core Rules
- Always set the response
Content-Typetotext/event-streamfor SSE; setCache-Control: no-cacheandConnection: keep-alive. - Each SSE message must end with
\n\n(double newline) — single newline is continuation, not a message boundary. - Use
TransformStreamorReadableStreamin Next.js App Router —res.write()is Pages Router only. - Flush the stream on each chunk — buffering defeats the purpose of streaming.
- Always handle stream errors and send a terminal event (
[DONE]or a typed error event) so clients don't hang. - When streaming with tool use, buffer tool input JSON until the
input_jsondelta is complete — never parse partial JSON. - Use
AbortControlleron the client to cancel in-flight streams when the user navigates away. - Token counts are only available at stream end via
message_stopevent — not during stream. - In React, append chunks to state with a functional updater:
setText(prev => prev + chunk)to avoid stale closure bugs. - Test streams locally with
curl -N(no buffering) before wiring to a frontend.
Anthropic SDK Streaming (Python)
import anthropic
client = anthropic.Anthropic()
# Method 1: stream() context manager (recommended)
with client.messages.stream(
model="claude-opus-4-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Write a short story about a robot."}],
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
# Access final message after stream ends
final_message = stream.get_final_message()
print(f"\n\nInput tokens: {final_message.usage.input_tokens}")
# Method 2: raw event iteration
with client.messages.stream(
model="claude-opus-4-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}],
) as stream:
for event in stream:
if event.type == "content_block_delta":
if event.delta.type == "text_delta":
print(event.delta.text, end="", flush=True)
elif event.type == "message_stop":
print("\n[Stream complete]")
# Method 3: async streaming
import asyncio
import anthropic
async def stream_async():
client = anthropic.AsyncAnthropic()
async with client.messages.stream(
model="claude-opus-4-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Tell me a joke."}],
) as stream:
async for text in stream.text_stream:
print(text, end="", flush=True)
asyncio.run(stream_async())
Anthropic SDK Streaming (TypeScript)
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
// Method 1: stream() with text helper
const stream = client.messages.stream({
model: "claude-opus-4-5",
max_tokens: 1024,
messages: [{ role: "user", content: "Write a haiku." }],
});
stream.on("text", (text) => {
process.stdout.write(text);
});
const finalMessage = await stream.finalMessage();
console.log(`\nTokens used: ${finalMessage.usage.input_tokens}`);
// Method 2: async iteration over events
const stream2 = await client.messages.create({
model: "claude-opus-4-5",
max_tokens: 1024,
stream: true,
messages: [{ role: "user", content: "Hello!" }],
});
for await (const event of stream2) {
if (
event.type === "content_block_delta" &&
event.delta.type === "text_delta"
) {
process.stdout.write(event.delta.text);
}
}
Next.js App Router SSE Route Handler
// app/api/chat/route.ts
import Anthropic from "@anthropic-ai/sdk";
import { NextRequest } from "next/server";
const client = new Anthropic();
export async function POST(req: NextRequest) {
const { messages, system } = await req.json();
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
try {
const anthropicStream = client.messages.stream({
model: "claude-opus-4-5",
max_tokens: 2048,
system: system ?? "You are a helpful assistant.",
messages,
});
anthropicStream.on("text", (text) => {
// SSE format: "data: \n\n"
controller.enqueue(
encoder.encode(`data: ${JSON.stringify({ text })}\n\n`)
);
});
anthropicStream.on("error", (error) => {
controller.enqueue(
encoder.encode(
`data: ${JSON.stringify({ error: error.message })}\n\n`
)
);
controller.close();
});
await anthropicStream.finalMessage();
controller.enqueue(encoder.encode(`data: [DONE]\n\n`));
controller.close();
} catch (error) {
controller.enqueue(
encoder.encode(`data: ${JSON.stringify({ error: String(error) })}\n\n`)
);
controller.close();
}
},
});
return new Response(stream, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
},
});
}
React Streaming UI Hook
// hooks/useStreamingChat.ts
import { useState, useCallback, useRef } from "react";
interface Message {
role: "user" | "assistant";
content: string;
}
export function useStreamingChat() {
const [messages, setMessages] = useState([]);
const [streamingText, setStreamingText] = useState("");
const [isStreaming, setIsStreaming] = useState(false);
const abortRef = useRef(null);
const sendMessage = useCallback(async (userMessage: string) => {
const newMessages: Message[] = [
...messages,
{ role: "user", content: userMessage },
];
setMessages(newMessages);
setStreamingText("");
setIsStreaming(true);
// Cancel any existing stream
abortRef.current?.abort();
abortRef.current = new AbortController();
try {
const response = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ messages: newMessages }),
signal: abortRef.current.signal,
});
if (!response.body) throw new Error("No response body");
const reader = response.body.getReader();
const decoder = new TextDecoder();
let accumulated = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
// Parse SSE lines
for (const line of chunk.split("\n")) {
if (!line.startsWith("data: ")) continue;
const data = line.slice(6);
if (data === "[DONE]") break;
try {
const parsed = JSON.parse(data);
if (parsed.text) {
accumulated += parsed.text;
// Functional updater avoids stale closure
setStreamingText((prev) => prev + parsed.text);
}
} catch {
// Ignore parse errors on incomplete chunks
}
}
}
// Commit streaming text to messages
setMessages((prev) => [
...prev,
{ role: "assistant", content: accumulated },
]);
setStreamingText("");
} catch (error) {
if ((error as Error).name !== "AbortError") {
console.error("Stream error:", error);
}
} finally {
setIsStreaming(false);
}
}, [messages]);
const stop = useCallback(() => {
abortRef.current?.abort();
setIsStreaming(false);
}, []);
return { messages, streamingText, isStreaming, sendMessage, stop };
}
React Streaming Chat Component
// components/StreamingChat.tsx
"use client";
import { useStreamingChat } from "@/hooks/useStreamingChat";
import { useState } from "react";
export function StreamingChat() {
const { messages, streamingText, isStreaming, sendMessage, stop } =
useStreamingChat();
const [input, setInput] = useState("");
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!input.trim() || isStreaming) return;
sendMessage(input);
setInput("");
};
return (
{messages.map((msg, i) => (
{msg.content}
))}
{isStreaming && streamingText && (
{streamingText}
▊
)}
setInput(e.target.value)}
className="flex-1 border rounded-lg px-3 py-2"
placeholder="Type a message..."
disabled={isStreaming}
/>
{isStreaming ? (
Stop
) : (
Send
)}
);
}
Python FastAPI Streaming Endpoint
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import anthropic
import json
app = FastAPI()
client = anthropic.Anthropic()
@app.post("/api/chat")
async def chat_stream(body: dict):
messages = body.get("messages", [])
def generate():
with client.messages.stream(
model="claude-opus-4-5",
max_tokens=2048,
messages=messages,
) as stream:
for text in stream.text_stream:
# SSE format
yield f"data: {json.dumps({'text': text})}\n\n"
yield "data: [DONE]\n\n"
return StreamingResponse(
generate(),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"X-Accel-Buffering": "no", # Disable nginx buffering
},
)
Streaming with Tool Use
# Buffer tool input JSON — never parse partial JSON
tool_input_buffer = {}
with client.messages.stream(
model="claude-opus-4-5",
max_tokens=2048,
tools=tools,
messages=messages,
) as stream:
current_tool_use_id = None
current_tool_name = None
input_json_buffer = ""
for event in stream:
if event.type == "content_block_start":
if event.content_block.type == "tool_use":
current_tool_use_id = event.content_block.id
current_tool_name = event.content_block.name
input_json_buffer = ""
elif event.type == "content_block_delta":
if event.delta.type == "text_delta":
print(event.delta.text, end="", flush=True)
elif event.delta.type == "input_json_delta":
# Buffer — do NOT parse yet
input_json_buffer += event.delta.partial_json
elif event.type == "content_block_stop":
if current_tool_use_id and input_json_buffer:
# Now safe to parse complete JSON
tool_input = json.loads(input_json_buffer)
print(f"\n[Tool call] {current_tool_name}: {tool_input}")
current_tool_use_id = None
input_json_buffer = ""
SSE Event Format Reference
# Standard SSE format:
data: {"text": "Hello"}\n\n
# With event type:
event: message\n
data: {"text": "Hello"}\n\n
# Keepalive (comment):
: ping\n\n
# Terminal signal:
data: [DONE]\n\n
# Multi-line data (rare):
data: line one\n
data: line two\n\n
Token Counting During Stream
# Tokens are only available at stream end
with client.messages.stream(...) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
# Get final usage AFTER stream completes
final = stream.get_final_message()
print(f"\nInput: {final.usage.input_tokens} | Output: {final.usage.output_tokens}")
Buffering Strategy for Smooth UI
// Throttle state updates to avoid re-render thrashing
let buffer = "";
let rafId: number | null = null;
function flushBuffer() {
setStreamingText((prev) => prev + buffer);
buffer = "";
rafId = null;
}
// In your stream reader loop:
buffer += chunk;
if (!rafId) {
rafId = requestAnimationFrame(flushBuffer);
}
Debugging Streams
# Test your SSE endpoint without a browser
curl -N -X POST http://localhost:3000/api/chat \
-H "Content-Type: application/json" \
-d '{"messages":[{"role":"user","content":"Hello"}]}'
# -N disables curl buffering so you see chunks as they arrive
Related Skills
claude-api-skill— streaming with Claudewebsocket-realtime— streaming over WebSocketsstreaming-llm-responses— response streaming patterns
GitNexus Index
This skill is indexed by GitNexus for knowledge graph traversal. Index path: /Users/localuser/.claude/skills/llm-streaming/.gitnexus Last indexed: 2026-05-23
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: UltronCore
- Source: UltronCore/claude-skill-vault
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.