最新资讯
dev.to 如何在专用 GPU 服务器上用 OpenAI 兼容 API 部署 LLM dev.to 审计 153 份 Claude Code 与 Codex 会话记录:User-Agent 与 URL 中的邮箱泄露 dev.to 你的 Agent Harness 应具备的 17 项能力 dev.to 离线 AI 记忆中 0.8 余弦阈值为何失效 dev.to Kimi K3.1 Pro 开放权重:自托管 2.8T 模型前的开发者清单 dev.to 如何通过工具调用引导模型以提升 Harness 质量 dev.to AI Agent 审计轨迹不等于对话记录 dev.to 精简 MCP server 工具数量:更少工具、更小影响面,双重收益 dev.to 为什么 git checkout 无法撤销 AI 编码 agent 的改动? wechat AI短剧门槛降降降!实测即梦短剧工作室:剧本分镜预演一条龙,伙伴计划长期护航 dev.to 让模型改写文档前,先量化事实保真度 dev.to 回放 153 个 Claude Code 与 Codex 会话,审计 Agent 的生产影响范围
多元动态
登录 注册
智能体 dev.to · @Dinesh_gowtham

用 Kinesis Data Streams 流式传输 Claude Agent 事件

原文 我的收藏

教程介绍如何将 Claude 的 token 流接入 Kinesis Data Stream,再由 Node.js 22 Lambda 消费,替代易失且难扩展的 WebSocket 方案。文章对比了 WebSocket 在断连丢 token、单消费者和并发扩展上的局限,但未给出压测或生产验证数据。

AI推荐理由 文章给出把 Claude token 流经 Kinesis Data Stream 和 Node.js 22 Lambda 转发的工程方案,并对比 WebSocket 在连接易失、单消费者和扩展性上的问题。属于架构教程,有具体技术路径,但未提供压测数据或生产验证结果,适合作为 LLM 流式管道的参考实现。

60

正文

Real‑time LLM interactions feel like they belong on websockets, but a Kinesis Data Stream gives you durable, auto‑scaling fan‑out with millisecond latency. In this guide you’ll see how to pipe Claude’s token stream through Kinesis and a Node.js 22 Lambda, turning a flaky WebSocket demo into an enterprise‑grade pipeline. Why Kinesis Beats WebSockets for LLM Streams When you ask Claude for a response, the model sends back tokens (tiny pieces of text) as they are generated. A WebSocket connection can push each token straight to the browser, but it has three hidden costs: Ephemeral connection – if the client disconnects, the in‑flight tokens are lost. Single consumer – only the WebSocket server receives the data; you can’t add a logger, analytics, or a backup consumer without duplicating work. Scaling limits – a single server must handle every concurrent chat, which becomes fragile under load. A Kinesis Data Stream is a managed pipe that breaks the data into shards (independent write/read lanes). Each shard can feed many consumers at the same time (this is called enhanced fan‑out). Think of a water main (the stream) that splits into many smaller hoses (the consumers). Even if one hose is turned off, the water still flows through the main pipe. In plain English – Kinesis lets you treat each token as a durable record that any number of services can read, while still keeping latency low enough for a chat‑like experience. Quick code sketch: putting a token into Kinesis // npm install @aws-sdk/client-kinesis import { KinesisClient, PutRecordCommand } from "@aws-sdk/client-kinesis"; const client = new KinesisClient({ region: "us-east-1" }); async function putToken(streamName, partitionKey, token) { // The token is a short string, we store it as UTF‑8 bytes. const command = new PutRecordCommand({ StreamName: streamName, PartitionKey: partitionKey, // decides which shard receives the record Data: Buffer.from(token), // binary payload }); await client.send(command); } Enter fullscreen mode Exit fullscreen mode Key takeaway – With a few lines you can write a token to a durable, auto‑scaled pipe that any Lambda can later read. Setting Up a Kinesis Data Stream for Claude Tokens What you need to know first Shard – a unit of capacity; each shard can ingest up to 1 MiB / second (≈ 1000 records) and serve up to 2 MiB / second to consumers. Enhanced fan‑out – a feature that gives each consumer its own read throughput, avoiding the 2 MiB shared limit. It only works when the consumer is registered by ARN (Amazon Resource Name). Forgetting this registration falls back to the shared 2 MiB pool and adds ~200 ms latency. Iterator – a pointer that tells Kinesis where to start reading. It expires after 5 minutes, so a consumer must refresh it regularly. Step‑by‑step creation with the SDK import { KinesisClient, CreateStreamCommand, RegisterStreamConsumerCommand, } from "@aws-sdk/client-kinesis"; const kinesis = new KinesisClient({ region: "us-east-1" }); const STREAM_NAME = "claude-token-stream"; const CONSUMER_NAME = "claude-lambda-consumer"; async function createStream() { // Create a stream with 2 shards (adjust based on expected throughput) await kinesis.send( new CreateStreamCommand({ StreamName: STREAM_NAME, ShardCount: 2 }) ); console.log(`Stream ${STREAM_NAME} created`); } async function registerConsumer() { // Registering by ARN enables enhanced fan‑out await kinesis.send( new RegisterStreamConsumerCommand({ StreamARN: `arn:aws:kinesis:us-east-1:123456789012:stream/${STREAM_NAME}`, ConsumerName: CONSUMER_NAME, }) ); console.log(`Consumer ${CONSUMER_NAME} registered for enhanced fan‑out`); } // Run once (or guard with existence checks) await createStream(); await registerConsumer(); Enter fullscreen mode Exit fullscreen mode Tip – After creating the stream, give it a minute to become ACTIVE before you register the consumer; otherwise the registration request will fail. Gotchas to keep in mind Issue Why it hurts How to avoid Shard write limit (1 MiB/s) Burst of tokens can exceed capacity and cause throttling Start with 2‑4 shards; monitor WriteProvisionedThroughputExceeded metrics Iterator expiration (5 min) A stale iterator makes GetRecords return ExpiredIteratorException Refresh iterator on every loop iteration or catch the error and request a new one Empty GetRecords response Kinesis is eventually consistent; records may appear a few milliseconds later Loop with a short sleep (e.g., 200 ms) when no records are returned Lambda Consumer with Node.js 22: Decoding and Forwarding Why a Lambda is a good fit A Lambda is a serverless function that runs only when invoked. By attaching it to the Kinesis stream as an event source, AWS automatically polls the stream, passes batches of records to your code, and handles scaling for you. Node.js 22 brings the native fetch API, so we can forward each token to a browser using Server‑Sent Events (SSE) without adding extra libraries. Minimal Lambda handler // npm install @aws-sdk/client-lambda @aws-sdk/client-kinesis import { KinesisClient, GetRecordsCommand, GetShardIteratorCommand } from "@aws-sdk/client-kinesis"; const kinesis = new KinesisClient({ region: "us-east-1" }); const STREAM_NAME = process.env.STREAM_NAME; const CONSUMER_ARN = process.env.CONSUMER_ARN; // ARN of the enhanced fan‑out consumer // Exponential backoff helper function wait(ms) { return new Promise(res => setTimeout(res, ms)); } export const handler = async (event, context) => { // 1️⃣ Get a shard iterator for each shard (enhanced fan‑out) const shards = await kinesis.send( new DescribeStreamCommand({ StreamName: STREAM_NAME }) ).then(resp => resp.StreamDescription.Shards); for (const shard of shards) { const iteratorCmd = new GetShardIteratorCommand({ StreamName: STREAM_NAME, ShardId: shard.ShardId, ShardIteratorType: "LATEST", // start at newest token ConsumerARN: CONSUMER_ARN, // enables enhanced fan‑out }); let iterator = await kinesis.send(iteratorCmd).then(r => r.ShardIterator); // 2️⃣ Pull records in a loop (Lambda will be terminated after timeout) while (true) { try { const recordsCmd = new GetRecordsCommand({ ShardIterator: iterator, Limit: 100 }); const { Records, NextShardIterator } = await kinesis.send(recordsCmd); iterator = NextShardIterator; if (Records.length === 0) { // No data right now, pause briefly to avoid tight looping await wait(200); continue; } // 3️⃣ Forward each token via SSE to the client for (const rec of Records) { const token = Buffer.from(rec.Data).toString("utf-8"); await forwardTokenSSE(token); } } catch (err) { if (err.name === "ProvisionedThroughputExceededException") { // 4️⃣ Throttling – back off exponentially for (let attempt = 1; attempt { const body = JSON.parse(event.body); const { prompt, userId } = body; // simple payload from the browser // Call Claude with streaming enabled const response = await fetch(CLAUDE_API, { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": process.env.CLAUDE_API_KEY, }, body: JSON.stringify({ model: "claude-3-5-sonnet-20241001", prompt, max_tokens_to_sample: 1024, stream: true, }), }); // Read the raw byte stream; each line is a JSON fragment with a token const reader = response.body.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader.read(); if (done) break; // Split on newline because Claude sends each token on its own line const chunk = decoder.decode(value); const lines = chunk.split("\n").filter(l => l.trim()); for (const line of lines) { const payload = JSON.parse(line); const token = payload.completion; // token string // Write token to Kinesis – partition by userId to keep order per chat await kinesis.send( new PutRecordCommand({ StreamName: STREAM_NAME, PartitionKey: userId, Data: Buffer.from(token), }) ); } } // Respond quickly; the real‑time work continues in the consumer Lambda return { statusCode: 200, body: JSON.stringify({ message: "Chat started" }), }; }; Enter fullscreen mode Exit fullscreen mode In plain English – This Lambda acts like a mailroom clerk: it receives Claude’s letters (tokens), stamps each with the user’s ID (the partition key), and drops them into the Kinesis mailbox for the delivery Lambda to pick up. Common pitfalls for this proxy Missing stream:true – Claude will wait until the whole answer is ready, defeating the real‑time goal. Incorrect partition key – If you use a constant key, all chats share a single shard, quickly hitting the 1 MiB/s write ceiling. Forgot to set CONTENT_TYPE: text/event-stream on the SSE endpoint – browsers will buffer the response, appearing as if nothing is happening. Handling Errors and Observability Why observability matters A streaming pipeline has many moving parts: Claude API latency, Kinesis throttling, Lambda timeouts, and network hiccups. Without visibility you’ll see occasional “missing tokens” but not know why. Simple CloudWatch logging and metrics import { CloudWatchClient, PutMetricDataCommand } from "@aws-sdk/client-cloudwatch"; const cw = new CloudWatchClient({ region: "us-east-1" }); const METRIC_NAMESPACE = "ClaudeKinesisPipeline"; async function recordMetric(name, value = 1) { await cw.send( new PutMetricDataCommand({ Namespace: METRIC_NAMESPACE, MetricData: [ { MetricName: name, Value: value, Unit: "Count", }, ], }) ); } Enter fullscreen mode Exit fullscreen mode Insert recordMetric("TokensProcessed") after each successful token forward, and recordMetric("ThrottleBackoff") inside the backoff catch block. CloudWatch dashboards can then show spikes. Dealing with iterator expiration if (err.name === "ExpiredIteratorException") { // Log for visibility console.warn("Shard iterator expired, requesting a fresh one"); // Re‑fetch the iterator (same code as initial request) const fresh = await kinesis.send(iteratorCmd); iterator = fresh.ShardIterator; } Enter fullscreen mode Exit fullscreen mode Dead‑letter queue (DLQ) for irrecoverable records If a record repeatedly triggers ProvisionedThroughputExceededException after several retries, move it to an SQS queue for later analysis. This prevents the Lambda from getting stuck on a single bad shard. Key tip – Always set a maximum retry count (e.g., 5) before sending to DLQ; otherwise exponential backoff can keep the function running until it times out, inflating costs. The Takeaway You now have a complete, production‑grade pattern for streaming Claude’s token output through Kinesis and a Node.js 22 Lambda. Kinesis provides durability and multi‑consumer fan‑out, eliminating the fragility of a single WebSocket server. Enhanced fan‑out only works when you register the consumer by ARN; forgetting this leaves you with higher latency and extra read costs. Shards are the unit of capacity; start with multiple shards and monitor throughput to avoid throttling. Lambda can read directly from Kinesis, decode token bytes, and forward them with SSE using the built‑in fetch API. Handle ProvisionedThroughputExceededException with exponential backoff and refresh stale shard iterators to keep the pipeline alive. Add lightweight CloudWatch metrics and a DLQ to gain visibility and prevent silent data loss. With these pieces in place, you can replace a brittle WebSocket demo with a scalable, server‑less architecture that works the same way for a handful of users or for thousands of concurrent chats. Happy streaming! Transparency notice This article was written with the help of an AI system — Groq (GPT OSS 120B). Published: 2026-10-23 · Primary focus: Kinesis All code blocks are intended to be correct and runnable, but please verify them against the official docs for the tools mentioned before using in production. Find an error? Drop a comment — corrections are always welcome.

来源报道 · 1 篇 · 以下链接均前往原始媒体

← 返回精选动态 原文

更多内容