skills/compute/streaming-chat/SKILL.md
# Streaming Chat Inference ## Metadata - **Category**: compute - **SDK**: `@0glabs/0g-serving-broker` ^0.6.5, `ethers` ^6.13.0 - **Activation Triggers**: "chatbot", "inference", "LLM", "DeepSeek", "streaming chat", "AI chat" ## Purpose Run conversational AI inference using 0G Compute Network providers. Supports streaming and non-streaming modes with models like DeepSeek V3.1, Qwen, Gemma, and GPT-OSS. ## Prerequisites - Node.js >= 22 - `@0glabs/0g-serving-broker` and `ethers` installed - F
npx skillsauth add 0gfoundation/0g-agent-skills skills/compute/streaming-chatInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
4 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
@0glabs/0g-serving-broker ^0.6.5, ethers ^6.13.0Run conversational AI inference using 0G Compute Network providers. Supports streaming and non-streaming modes with models like DeepSeek V3.1, Qwen, Gemma, and GPT-OSS.
@0glabs/0g-serving-broker and ethers installed.env with PRIVATE_KEY, RPC_URL, PROVIDER_ADDRESSZG-Res-Key header (body fallback)processResponse(providerAddress, chatID, usageData) — CRITICALprocessResponse() after EVERY inference requestprocessResponse(providerAddress, chatID, usageData)ZG-Res-Key header FIRST, use data.id as fallback (chatbot only)processResponse() — causes fee settlement failureprocessResponse()import { ethers } from 'ethers';
import { createZGComputeNetworkBroker } from '@0glabs/0g-serving-broker';
import 'dotenv/config';
async function chat(userMessage: string): Promise<string> {
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);
const broker = await createZGComputeNetworkBroker(wallet);
const providerAddress = process.env.PROVIDER_ADDRESS!;
const { endpoint, model } = await broker.inference.getServiceMetadata(providerAddress);
const headers = await broker.inference.getRequestHeaders(providerAddress);
const messages = [{ role: 'user', content: userMessage }];
const response = await fetch(`${endpoint}/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...headers },
body: JSON.stringify({ messages, model }),
});
const data = await response.json();
const answer = data.choices[0].message.content;
// CRITICAL: Process response for fee settlement
let chatID = response.headers.get('ZG-Res-Key') || response.headers.get('zg-res-key');
if (!chatID) chatID = data.id; // Fallback for chatbot
await broker.inference.processResponse(providerAddress, chatID, JSON.stringify(data.usage));
return answer;
}
// Usage
const reply = await chat('What is 0G?');
console.log(reply);
async function streamingChat(userMessage: string): Promise<string> {
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);
const broker = await createZGComputeNetworkBroker(wallet);
const providerAddress = process.env.PROVIDER_ADDRESS!;
const { endpoint, model } = await broker.inference.getServiceMetadata(providerAddress);
const headers = await broker.inference.getRequestHeaders(providerAddress);
const messages = [{ role: 'user', content: userMessage }];
const response = await fetch(`${endpoint}/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...headers },
body: JSON.stringify({ messages, model, stream: true }),
});
// ChatID from header (primary source)
let chatID = response.headers.get('ZG-Res-Key') || response.headers.get('zg-res-key');
let usage = null;
let streamChatID = null;
let fullResponse = '';
const decoder = new TextDecoder();
const reader = response.body!.getReader();
let rawBody = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
rawBody += chunk;
process.stdout.write(chunk); // Real-time output
}
// Parse stream for fallback chatID and usage data
for (const line of rawBody.split('\n')) {
const trimmed = line.trim();
if (!trimmed || trimmed === 'data: [DONE]') continue;
try {
const jsonStr = trimmed.startsWith('data:') ? trimmed.slice(5).trim() : trimmed;
const message = JSON.parse(jsonStr);
if (!streamChatID && message.id) streamChatID = message.id;
if (message.usage) usage = message.usage;
if (message.choices?.[0]?.delta?.content) {
fullResponse += message.choices[0].delta.content;
}
} catch {}
}
// CRITICAL: processResponse with correct param order
const finalChatID = chatID || streamChatID;
await broker.inference.processResponse(providerAddress, finalChatID, JSON.stringify(usage || {}));
return fullResponse;
}
async function conversation() {
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);
const broker = await createZGComputeNetworkBroker(wallet);
const providerAddress = process.env.PROVIDER_ADDRESS!;
const { endpoint, model } = await broker.inference.getServiceMetadata(providerAddress);
const history: Array<{ role: string; content: string }> = [
{ role: 'system', content: 'You are a helpful assistant.' },
];
async function sendMessage(userMessage: string): Promise<string> {
history.push({ role: 'user', content: userMessage });
const headers = await broker.inference.getRequestHeaders(providerAddress);
const response = await fetch(`${endpoint}/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...headers },
body: JSON.stringify({ messages: history, model }),
});
const data = await response.json();
const answer = data.choices[0].message.content;
history.push({ role: 'assistant', content: answer });
let chatID = response.headers.get('ZG-Res-Key') || response.headers.get('zg-res-key');
if (!chatID) chatID = data.id;
await broker.inference.processResponse(providerAddress, chatID, JSON.stringify(data.usage));
return answer;
}
const reply1 = await sendMessage('What is 0G?');
console.log('Assistant:', reply1);
const reply2 = await sendMessage('Tell me more about its storage layer.');
console.log('Assistant:', reply2);
}
async function resilientChat(userMessage: string, maxRetries = 3): Promise<string> {
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);
const broker = await createZGComputeNetworkBroker(wallet);
const providerAddress = process.env.PROVIDER_ADDRESS!;
const { endpoint, model } = await broker.inference.getServiceMetadata(providerAddress);
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const headers = await broker.inference.getRequestHeaders(providerAddress);
const response = await fetch(`${endpoint}/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...headers },
body: JSON.stringify({ messages: [{ role: 'user', content: userMessage }], model }),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
const data = await response.json();
const answer = data.choices[0].message.content;
let chatID = response.headers.get('ZG-Res-Key') || response.headers.get('zg-res-key');
if (!chatID) chatID = data.id;
await broker.inference.processResponse(providerAddress, chatID, JSON.stringify(data.usage));
return answer;
} catch (error) {
console.error(`Attempt ${attempt} failed:`, error);
if (attempt === maxRetries) throw error;
await new Promise((r) => setTimeout(r, 1000 * attempt));
}
}
throw new Error('All retries exhausted');
}
// BAD: Missing processResponse — fee settlement failure
const data = await response.json();
return data.choices[0].message.content;
// processResponse() never called!
// BAD: Wrong parameter order
await broker.inference.processResponse(
chatID, // WRONG — should be providerAddress
providerAddress, // WRONG — should be chatID
usage,
);
// BAD: Using body ID without checking header first
const chatID = data.id; // Should check ZG-Res-Key header first!
// BAD: ethers v5 syntax
const provider = new ethers.providers.JsonRpcProvider(url); // v5!
// BAD: Hardcoding private keys
const wallet = new ethers.Wallet('0xabc123...', provider); // NEVER do this
| Error | Cause | Fix |
| --------------------------- | ---------------------------- | ----------------------------- |
| Insufficient balance | Sub-account empty | Transfer funds to provider |
| Provider not acknowledged | First-time provider | acknowledgeProviderSigner() |
| Invalid request headers | Stale auth headers | Re-call getRequestHeaders() |
| Fee verification failed | Wrong processResponse params | Check param order and chatID |
| stream error | Network interruption | Implement retry logic |
development
# Upload File to 0G Storage ## Metadata - **Category**: storage - **SDK**: `@0glabs/0g-ts-sdk` ^0.3.3, `ethers` ^6.13.0 - **Activation Triggers**: "upload file", "store on 0G", "ZgFile", "save to storage" ## Purpose Upload files to 0G decentralized storage using the ZgFile API and Indexer. Files are split into chunks, organized as a Merkle tree, and distributed across storage nodes. Returns a root hash for later retrieval. ## Prerequisites - Node.js >= 18 - `@0glabs/0g-ts-sdk` and `ethers`
development
# Merkle Verification ## Metadata - **Category**: storage - **SDK**: `@0glabs/0g-ts-sdk` ^0.3.3 - **Activation Triggers**: "verify file", "merkle proof", "data integrity", "root hash", "check file" ## Purpose Compute root hashes and verify data integrity for files stored on 0G Storage. Uses Merkle tree proofs to cryptographically verify that downloaded data matches what was originally uploaded. ## Prerequisites - Node.js >= 18 - `@0glabs/0g-ts-sdk` installed ## Quick Workflow 1. Create
development
# Download File from 0G Storage ## Metadata - **Category**: storage - **SDK**: `@0glabs/0g-ts-sdk` ^0.3.3, `ethers` ^6.13.0 - **Activation Triggers**: "download file", "retrieve from 0G", "get file", "fetch from storage" ## Purpose Download and verify files from 0G decentralized storage using a root hash. Supports verified downloads with Merkle proof validation to ensure data integrity. ## Prerequisites - Node.js >= 18 - `@0glabs/0g-ts-sdk` installed - Root hash of the file to download - `
development
# Storage + Chain Integration ## Metadata - **Category**: cross-layer - **SDK**: `@0glabs/0g-ts-sdk` ^0.3.3, `ethers` ^6.13.0 - **Activation Triggers**: "on-chain reference", "NFT metadata on 0G", "store hash on-chain", "registry contract", "chain and storage" ## Purpose Combine 0G Storage with 0G Chain smart contracts to create on-chain references to off-chain data. Common patterns include NFT metadata storage, content registries, and verifiable document systems. ## Prerequisites - Node